summaryrefslogtreecommitdiff
path: root/script/core/code_action.lua
diff options
context:
space:
mode:
author最萌小汐 <sumneko@hotmail.com>2019-11-23 00:05:30 +0800
committer最萌小汐 <sumneko@hotmail.com>2019-11-23 00:05:30 +0800
commit6da2b175e20ed3c03b0dfcfc9046de1e0e5d4444 (patch)
treefdc22d78150fd1c5edc46732c8b151ccfefb519f /script/core/code_action.lua
parentd0ff66c9abe9d6abbca12fd811e0c3cb69c1033a (diff)
downloadlua-language-server-6da2b175e20ed3c03b0dfcfc9046de1e0e5d4444.zip
正路目录
Diffstat (limited to 'script/core/code_action.lua')
-rw-r--r--script/core/code_action.lua410
1 files changed, 410 insertions, 0 deletions
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