From 737e7e86ddaac60278b936785c1472317544eb9e Mon Sep 17 00:00:00 2001 From: Tim Starling Date: Thu, 5 Jul 2012 15:20:37 +1000 Subject: [PATCH] Improved Lua indenting * Support single-line blocks: don't indent the following line. * Indent the line following a line with unclosed parentheses, such as a function call where some parameters are specified on the same line as the name. * Use a negative next-line indent to restore the correct indenting level after the end of a multi-line parenthesized block where the ending parenthesis occurs with other text on the same line as it. * Implement outdenting. Outdenting is triggered after "enter" is pressed, similar to the Python mode. This avoids problems with incomplete token information, e.g. outdenting when the user is halfway through typing a quoted string "the end is nigh", where "end" is recognised as a keyword until the right quote is present. Of course it is still flawed, anything less than a complete Lua parser will be flawed, but I'm pretty confident that the code here is better than what came before. --- lib/ace/mode/lua.js | 108 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/lib/ace/mode/lua.js b/lib/ace/mode/lua.js index af61d48e..8280f6c2 100644 --- a/lib/ace/mode/lua.js +++ b/lib/ace/mode/lua.js @@ -22,6 +22,7 @@ * Fabian Jakobs * Colin Gourlay * Lee Gao +* Tim Starling * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -44,6 +45,7 @@ var oop = require("../lib/oop"); var TextMode = require("./text").Mode; var Tokenizer = require("../tokenizer").Tokenizer; var LuaHighlightRules = require("./lua_highlight_rules").LuaHighlightRules; +var Range = require("../range").Range; var Mode = function() { this.$tokenizer = new Tokenizer(new LuaHighlightRules().getRules()); @@ -51,34 +53,100 @@ var Mode = function() { oop.inherits(Mode, TextMode); (function() { + var indentKeywords = { + "function": 1, + "then": 1, + "do": 1, + "else": 1, + "elseif": 1, + "repeat": 1, + "end": -1, + "until": -1, + }; + var outdentKeywords = [ + "else", + "elseif", + "end", + "until" + ]; + + function getNetIndentLevel(tokens) { + var level = 0; + // Support single-line blocks by decrementing the indent level if + // an ending token is found + for (var i in tokens){ + var token = tokens[i]; + if (token.type == "keyword") { + if (token.value in indentKeywords) { + level += indentKeywords[token.value]; + } + } else if (token.type == "paren.lparen") { + level ++; + } else if (token.type == "paren.rparen") { + level --; + } + } + // Limit the level to +/- 1 since usually users only indent one level + // at a time regardless of the logical nesting level + if (level < 0) { + return -1; + } else if (level > 0) { + return 1; + } else { + return 0; + } + } + this.getNextLineIndent = function(state, line, tab) { var indent = this.$getIndent(line); + var level = 0; var tokenizedLine = this.$tokenizer.getLineTokens(line, state); var tokens = tokenizedLine.tokens; - - var chunks = ["function", "then", "do", "repeat"]; - - if (state == "start") { - var match = line.match(/^.*[\{\(\[]\s*$/); - if (match) { - indent += tab; - } else { - for (var i in tokens){ - var token = tokens[i]; - if (token.type != "keyword") continue; - var chunk_i = chunks.indexOf(token.value); - if (chunk_i != -1){ - indent += tab; - break; - } - } - } - } + if (state == "start") { + level = getNetIndentLevel(tokens); + } + if (level > 0) { + return indent + tab; + } else if (level < 0 && indent.substr(indent.length - tab.length) == tab) { + // Don't do a next-line outdent if we're going to do a real outdent of this line + if (!this.checkOutdent(state, line, "\n")) { + return indent.substr(0, indent.length - tab.length); + } + } return indent; }; - + + this.checkOutdent = function(state, line, input) { + if (input != "\n" && input != "\r" && input != "\r\n") + return false; + + if (line.match(/^\s*[\)\}\]]$/)) + return true; + + var tokens = this.$tokenizer.getLineTokens(line.trim(), state).tokens; + + if (!tokens || !tokens.length) + return false; + + return (tokens[0].type == "keyword" && outdentKeywords.indexOf(tokens[0].value) != -1); + }; + + this.autoOutdent = function(state, session, row) { + var prevLine = session.getLine(row - 1); + var prevIndent = this.$getIndent(prevLine).length; + var prevTokens = this.$tokenizer.getLineTokens(prevLine, "start").tokens; + var tabLength = session.getTabString().length; + var expectedIndent = prevIndent + tabLength * getNetIndentLevel(prevTokens); + var curIndent = this.$getIndent(session.getLine(row)).length; + if (curIndent < expectedIndent) { + // User already outdented // + return; + } + session.outdentRows(new Range(row, 0, row + 2, 0)); + }; + }).call(Mode.prototype); exports.Mode = Mode;