From 6da2b175e20ed3c03b0dfcfc9046de1e0e5d4444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Sat, 23 Nov 2019 00:05:30 +0800 Subject: =?UTF-8?q?=E6=AD=A3=E8=B7=AF=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/core/code_action.lua | 410 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 410 insertions(+) create mode 100644 script/core/code_action.lua (limited to 'script/core/code_action.lua') diff --git a/script/core/code_action.lua b/script/core/code_action.lua new file mode 100644 index 00000000..2c1fb14d --- /dev/null +++ b/script/core/code_action.lua @@ -0,0 +1,410 @@ +local lang = require 'language' +local library = require 'core.library' + +local function disableDiagnostic(lsp, uri, data, callback) + callback { + title = lang.script('ACTION_DISABLE_DIAG', data.code), + kind = 'quickfix', + command = { + title = lang.script.COMMAND_DISABLE_DIAG, + command = 'config', + arguments = { + { + key = {'diagnostics', 'disable'}, + action = 'add', + value = data.code, + } + } + } + } +end + +local function addGlobal(name, callback) + callback { + title = lang.script('ACTION_MARK_GLOBAL', name), + kind = 'quickfix', + command = { + title = lang.script.COMMAND_MARK_GLOBAL, + command = 'config', + arguments = { + { + key = {'diagnostics', 'globals'}, + action = 'add', + value = name, + } + } + }, + } +end + +local function changeVersion(version, callback) + callback { + title = lang.script('ACTION_RUNTIME_VERSION', version), + kind = 'quickfix', + command = { + title = lang.script.COMMAND_RUNTIME_VERSION, + command = 'config', + arguments = { + { + key = {'runtime', 'version'}, + action = 'set', + value = version, + } + } + }, + } +end + +local function openCustomLibrary(libName, callback) + callback { + title = lang.script('ACTION_OPEN_LIBRARY', libName), + kind = 'quickfix', + command = { + title = lang.script.COMMAND_OPEN_LIBRARY, + command = 'config', + arguments = { + { + key = {'runtime', 'library'}, + action = 'add', + value = libName, + } + } + }, + } +end + +local function solveUndefinedGlobal(lsp, uri, data, callback) + local vm, lines, text = lsp:getVM(uri) + if not vm then + return + end + local start = lines:position(data.range.start.line + 1, data.range.start.character + 1) + local finish = lines:position(data.range['end'].line + 1, data.range['end'].character) + local name = text:sub(start, finish) + if #name < 0 or name:find('[^%w_]') then + return + end + addGlobal(name, callback) + local otherVersion = library.other[name] + if otherVersion then + for _, version in ipairs(otherVersion) do + changeVersion(version, callback) + end + end + + local customLibrary = library.custom[name] + if customLibrary then + for _, libName in ipairs(customLibrary) do + openCustomLibrary(libName, callback) + end + end +end + +local function solveLowercaseGlobal(lsp, uri, data, callback) + local vm, lines, text = lsp:getVM(uri) + if not vm then + return + end + local start = lines:position(data.range.start.line + 1, data.range.start.character + 1) + local finish = lines:position(data.range['end'].line + 1, data.range['end'].character) + local name = text:sub(start, finish) + if #name < 0 or name:find('[^%w_]') then + return + end + addGlobal(name, callback) +end + +local function solveTrailingSpace(lsp, uri, data, callback) + callback { + title = lang.script.ACTION_REMOVE_SPACE, + kind = 'quickfix', + command = { + title = lang.script.COMMAND_REMOVE_SPACE, + command = 'removeSpace', + arguments = { + { + uri = uri, + } + } + }, + } +end + +local function solveNewlineCall(lsp, uri, data, callback) + callback { + title = lang.script.ACTION_ADD_SEMICOLON, + kind = 'quickfix', + edit = { + changes = { + [uri] = { + { + range = { + start = data.range.start, + ['end'] = data.range.start, + }, + newText = ';', + } + } + } + } + } +end + +local function solveAmbiguity1(lsp, uri, data, callback) + callback { + title = lang.script.ACTION_ADD_BRACKETS, + kind = 'quickfix', + command = { + title = lang.script.COMMAND_ADD_BRACKETS, + command = 'solve', + arguments = { + { + name = 'ambiguity-1', + uri = uri, + range = data.range, + } + } + }, + } +end + +local function findSyntax(astErr, lines, data) + local start = lines:position(data.range.start.line + 1, data.range.start.character + 1) + local finish = lines:position(data.range['end'].line + 1, data.range['end'].character) + for _, err in ipairs(astErr) do + if err.start == start and err.finish == finish then + return err + end + end + return nil +end + +local function solveSyntaxByChangeVersion(err, callback) + if type(err.version) == 'table' then + for _, version in ipairs(err.version) do + changeVersion(version, callback) + end + else + changeVersion(err.version, callback) + end +end + +local function solveSyntaxByAddDoEnd(uri, data, callback) + callback { + title = lang.script.ACTION_ADD_DO_END, + kind = 'quickfix', + edit = { + changes = { + [uri] = { + { + range = { + start = data.range.start, + ['end'] = data.range.start, + }, + newText = 'do ', + }, + { + range = { + start = data.range['end'], + ['end'] = data.range['end'], + }, + newText = ' end', + } + } + } + } + } +end + +local function solveSyntaxByFix(uri, err, lines, callback) + local changes = {} + for _, e in ipairs(err.fix) do + local start_row, start_col = lines:rowcol(e.start) + local finish_row, finish_col = lines:rowcol(e.finish) + changes[#changes+1] = { + range = { + start = { + line = start_row - 1, + character = start_col - 1, + }, + ['end'] = { + line = finish_row - 1, + character = finish_col, + }, + }, + newText = e.text, + } + end + callback { + title = lang.script['ACTION_' .. err.fix.title], + kind = 'quickfix', + edit = { + changes = { + [uri] = changes, + } + } + } +end + +local function findEndPosition(lines, row, endrow) + if endrow == row then + return { + newText = ' end', + range = { + start = { + line = row - 1, + character = 999999, + }, + ['end'] = { + line = row - 1, + character = 999999, + } + } + } + else + local l = lines[row] + return { + newText = ('\t'):rep(l.tab) .. (' '):rep(l.sp) .. 'end\n', + range = { + start = { + line = endrow, + character = 0, + }, + ['end'] = { + line = endrow, + character = 0, + } + } + } + end +end + +local function isIfPart(id, lines, i) + if id ~= 'if' then + return false + end + local buf = lines:line(i) + local first = buf:match '^[%s\t]*([%w]+)' + if first == 'else' or first == 'elseif' then + return true + end + return false +end + +local function solveSyntaxByAddEnd(uri, start, finish, lines, callback) + local row = lines:rowcol(start) + local line = lines[row] + if not line then + return nil + end + local id = lines.buf:sub(start, finish) + local sp = line.sp + line.tab * 4 + for i = row + 1, #lines do + local nl = lines[i] + local lsp = nl.sp + nl.tab * 4 + if lsp <= sp and not isIfPart(id, lines, i) then + callback { + title = lang.script['ACTION_ADD_END'], + kind = 'quickfix', + edit = { + changes = { + [uri] = { + findEndPosition(lines, row, i - 1) + } + } + } + } + return + end + end + return nil +end + +---@param lsp LSP +---@param uri uri +---@param data table +---@param callback function +local function solveSyntax(lsp, uri, data, callback) + local file = lsp:getFile(uri) + if not file then + return + end + local astErr, lines = file:getAstErr(), file:getLines() + if not astErr or not lines then + return + end + local err = findSyntax(astErr, lines, data) + if not err then + return nil + end + if err.version then + solveSyntaxByChangeVersion(err, callback) + end + if err.type == 'ACTION_AFTER_BREAK' or err.type == 'ACTION_AFTER_RETURN' then + solveSyntaxByAddDoEnd(uri, data, callback) + end + if err.type == 'MISS_END' then + solveSyntaxByAddEnd(uri, err.start, err.finish, lines, callback) + end + if err.type == 'MISS_SYMBOL' and err.info.symbol == 'end' then + solveSyntaxByAddEnd(uri, err.info.related[1], err.info.related[2], lines, callback) + end + if err.fix then + solveSyntaxByFix(uri, err, lines, callback) + end +end + +local function solveDiagnostic(lsp, uri, data, callback) + if data.source == lang.script.DIAG_SYNTAX_CHECK then + solveSyntax(lsp, uri, data, callback) + end + if not data.code then + return + end + if data.code == 'undefined-global' then + solveUndefinedGlobal(lsp, uri, data, callback) + end + if data.code == 'trailing-space' then + solveTrailingSpace(lsp, uri, data, callback) + end + if data.code == 'newline-call' then + solveNewlineCall(lsp, uri, data, callback) + end + if data.code == 'ambiguity-1' then + solveAmbiguity1(lsp, uri, data, callback) + end + if data.code == 'lowercase-global' then + solveLowercaseGlobal(lsp, uri, data, callback) + end + disableDiagnostic(lsp, uri, data, callback) +end + +local function rangeContain(a, b) + if a.start.line > b.start.line then + return false + end + if a.start.character > b.start.character then + return false + end + if a['end'].line < b['end'].line then + return false + end + if a['end'].character < b['end'].character then + return false + end + return true +end + +return function (lsp, uri, diagnostics, range) + local results = {} + + for _, data in ipairs(diagnostics) do + if rangeContain(data.range, range) then + solveDiagnostic(lsp, uri, data, function (result) + results[#results+1] = result + end) + end + end + + return results +end -- cgit v1.2.3