diff options
Diffstat (limited to 'script-beta/provider')
-rw-r--r-- | script-beta/provider/capability.lua | 42 | ||||
-rw-r--r-- | script-beta/provider/completion.lua | 53 | ||||
-rw-r--r-- | script-beta/provider/diagnostic.lua | 209 | ||||
-rw-r--r-- | script-beta/provider/init.lua | 298 | ||||
-rw-r--r-- | script-beta/provider/markdown.lua | 22 |
5 files changed, 624 insertions, 0 deletions
diff --git a/script-beta/provider/capability.lua b/script-beta/provider/capability.lua new file mode 100644 index 00000000..aa95c758 --- /dev/null +++ b/script-beta/provider/capability.lua @@ -0,0 +1,42 @@ +local m = {} + +m.initer = { + -- 文本同步方式 + textDocumentSync = { + -- 打开关闭文本时通知 + openClose = true, + -- 文本改变时完全通知 TODO 支持差量更新(2) + change = 1, + }, + + hoverProvider = true, + definitionProvider = true, + referencesProvider = true, + renameProvider = { + prepareProvider = true, + }, + --documentSymbolProvider = true, + documentHighlightProvider = true, + --codeActionProvider = true, + --signatureHelpProvider = { + -- triggerCharacters = { '(', ',' }, + --}, + --workspace = { + -- workspaceFolders = { + -- supported = true, + -- changeNotifications = true, + -- } + --}, + --documentOnTypeFormattingProvider = { + -- firstTriggerCharacter = '}', + --}, + --executeCommandProvider = { + -- commands = { + -- 'config', + -- 'removeSpace', + -- 'solve', + -- }, + --}, +} + +return m diff --git a/script-beta/provider/completion.lua b/script-beta/provider/completion.lua new file mode 100644 index 00000000..d2df44d2 --- /dev/null +++ b/script-beta/provider/completion.lua @@ -0,0 +1,53 @@ +local proto = require 'proto' + +local isEnable = false + +local function allWords() + local str = [[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:('"[,#*@| ]] + local list = {} + for c in str:gmatch '.' do + list[#list+1] = c + end + return list +end + +local function enable() + if isEnable then + return + end + isEnable = true + log.debug('Enable completion.') + proto.awaitRequest('client/registerCapability', { + registrations = { + { + id = 'completion', + method = 'textDocument/completion', + registerOptions = { + resolveProvider = false, + triggerCharacters = allWords(), + }, + }, + } + }) +end + +local function disable() + if not isEnable then + return + end + isEnable = false + log.debug('Disable completion.') + proto.awaitRequest('client/unregisterCapability', { + unregisterations = { + { + id = 'completion', + method = 'textDocument/completion', + }, + } + }) +end + +return { + enable = enable, + disable = disable, +} diff --git a/script-beta/provider/diagnostic.lua b/script-beta/provider/diagnostic.lua new file mode 100644 index 00000000..ba95f2bf --- /dev/null +++ b/script-beta/provider/diagnostic.lua @@ -0,0 +1,209 @@ +local await = require 'await' +local proto = require 'proto.proto' +local define = require 'proto.define' +local lang = require 'language' +local files = require 'files' +local config = require 'config' +local core = require 'core.diagnostics' +local util = require 'utility' + +local m = {} +m._start = false +m.cache = {} + +local function concat(t, sep) + if type(t) ~= 'table' then + return t + end + return table.concat(t, sep) +end + +local function buildSyntaxError(uri, err) + local lines = files.getLines(uri) + local text = files.getText(uri) + local message = lang.script('PARSER_'..err.type, err.info) + + if err.version then + local version = err.info and err.info.version or config.config.runtime.version + message = message .. ('(%s)'):format(lang.script('DIAG_NEED_VERSION' + , concat(err.version, '/') + , version + )) + end + + local related = err.info and err.info.related + local relatedInformation + if related then + relatedInformation = {} + for _, rel in ipairs(related) do + local rmessage + if rel.message then + rmessage = lang.script('PARSER_'..rel.message) + else + rmessage = text:sub(rel.start, rel.finish) + end + relatedInformation[#relatedInformation+1] = { + message = rmessage, + location = define.location(uri, define.range(lines, text, rel.start, rel.finish)), + } + end + end + + return { + range = define.range(lines, text, err.start, err.finish), + severity = define.DiagnosticSeverity.Error, + source = lang.script.DIAG_SYNTAX_CHECK, + message = message, + relatedInformation = relatedInformation, + } +end + +local function buildDiagnostic(uri, diag) + local lines = files.getLines(uri) + local text = files.getText(uri) + + local relatedInformation + if diag.related then + relatedInformation = {} + for _, rel in ipairs(diag.related) do + local rtext = files.getText(rel.uri) + local rlines = files.getLines(rel.uri) + relatedInformation[#relatedInformation+1] = { + message = rel.message or rtext:sub(rel.start, rel.finish), + location = define.location(rel.uri, define.range(rlines, rtext, rel.start, rel.finish)) + } + end + end + + return { + range = define.range(lines, text, diag.start, diag.finish), + source = lang.script.DIAG_DIAGNOSTICS, + severity = diag.level, + message = diag.message, + code = diag.code, + tags = diag.tags, + relatedInformation = relatedInformation, + } +end + +local function merge(a, b) + if not a and not b then + return nil + end + local t = {} + if a then + for i = 1, #a do + t[#t+1] = a[i] + end + end + if b then + for i = 1, #b do + t[#t+1] = b[i] + end + end + return t +end + +function m.clear(uri) + if not m.cache[uri] then + return + end + m.cache[uri] = nil + proto.notify('textDocument/publishDiagnostics', { + uri = uri, + diagnostics = {}, + }) +end + +function m.syntaxErrors(uri, ast) + if #ast.errs == 0 then + return nil + end + + local results = {} + + for _, err in ipairs(ast.errs) do + results[#results+1] = buildSyntaxError(uri, err) + end + + return results +end + +function m.diagnostics(uri, syntaxOnly) + if syntaxOnly or not m._start then + return m.cache[uri] + end + + local diags = core(uri) + if not diags then + return nil + end + + local results = {} + for _, diag in ipairs(diags) do + results[#results+1] = buildDiagnostic(uri, diag) + end + + return results +end + +function m.doDiagnostic(uri, syntaxOnly) + local ast = files.getAst(uri) + if not ast then + m.clear(uri) + return + end + + local syntax = m.syntaxErrors(uri, ast) + local diagnostics = m.diagnostics(uri, syntaxOnly) + local full = merge(syntax, diagnostics) + if not full then + m.clear(uri) + return + end + + if util.equal(m.cache[uri], full) then + return + end + m.cache[uri] = full + + proto.notify('textDocument/publishDiagnostics', { + uri = uri, + diagnostics = full, + }) +end + +function m.refresh(uri) + await.create(function () + await.delay(function () + return files.globalVersion + end) + if uri then + m.doDiagnostic(uri, true) + end + if not m._start then + return + end + local clock = os.clock() + if uri then + m.doDiagnostic(uri) + end + for destUri in files.eachFile() do + if destUri ~= uri then + m.doDiagnostic(files.getOriginUri(destUri)) + await.delay(function () + return files.globalVersion + end) + end + end + local passed = os.clock() - clock + log.info(('Finish diagnostics, takes [%.3f] sec.'):format(passed)) + end) +end + +function m.start() + m._start = true + m.refresh() +end + +return m diff --git a/script-beta/provider/init.lua b/script-beta/provider/init.lua new file mode 100644 index 00000000..95f4b3d1 --- /dev/null +++ b/script-beta/provider/init.lua @@ -0,0 +1,298 @@ +local util = require 'utility' +local cap = require 'provider.capability' +local completion= require 'provider.completion' +local await = require 'await' +local files = require 'files' +local proto = require 'proto.proto' +local define = require 'proto.define' +local workspace = require 'workspace' +local config = require 'config' +local library = require 'library' +local markdown = require 'provider.markdown' + +local function updateConfig() + local configs = proto.awaitRequest('workspace/configuration', { + items = { + { + scopeUri = workspace.uri, + section = 'Lua', + }, + { + scopeUri = workspace.uri, + section = 'files.associations', + }, + { + scopeUri = workspace.uri, + section = 'files.exclude', + } + }, + }) + + local updated = configs[1] + local other = { + associations = configs[2], + exclude = configs[3], + } + + local oldConfig = util.deepCopy(config.config) + local oldOther = util.deepCopy(config.other) + config.setConfig(updated, other) + local newConfig = config.config + local newOther = config.other + if not util.equal(oldConfig.runtime, newConfig.runtime) then + library.reload() + end + if not util.equal(oldConfig.diagnostics, newConfig.diagnostics) then + end + if not util.equal(oldConfig.plugin, newConfig.plugin) then + end + if not util.equal(oldConfig.workspace, newConfig.workspace) + or not util.equal(oldConfig.plugin, newConfig.plugin) + or not util.equal(oldOther.associations, newOther.associations) + or not util.equal(oldOther.exclude, newOther.exclude) + then + end + + if newConfig.completion.enable then + --completion.enable() + else + completion.disable() + end +end + +proto.on('initialize', function (params) + --log.debug(util.dump(params)) + if params.workspaceFolders then + local name = params.workspaceFolders[1].name + local uri = params.workspaceFolders[1].uri + workspace.init(name, uri) + end + return { + capabilities = cap.initer, + } +end) + +proto.on('initialized', function (params) + updateConfig() + proto.awaitRequest('client/registerCapability', { + registrations = { + -- 监视文件变化 + { + id = '0', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/', + kind = 1 | 2 | 4, + } + }, + }, + }, + -- 配置变化 + { + id = '1', + method = 'workspace/didChangeConfiguration', + } + } + }) + await.create(workspace.awaitPreload) + return true +end) + +proto.on('exit', function () + log.info('Server exited.') + os.exit(true) +end) + +proto.on('shutdown', function () + log.info('Server shutdown.') + return true +end) + +proto.on('workspace/configuration', function () + updateConfig() +end) + +proto.on('workspace/didChangeWatchedFiles', function (params) +end) + +proto.on('textDocument/didOpen', function (params) + local doc = params.textDocument + local uri = doc.uri + local text = doc.text + files.open(uri) + files.setText(uri, text) +end) + +proto.on('textDocument/didClose', function (params) + local doc = params.textDocument + local uri = doc.uri + files.close(uri) + if not files.isLua(uri) then + files.remove(uri) + end +end) + +proto.on('textDocument/didChange', function (params) + local doc = params.textDocument + local change = params.contentChanges + local uri = doc.uri + local text = change[1].text + if files.isLua(uri) or files.isOpen(uri) then + files.setText(uri, text) + end +end) + +proto.on('textDocument/hover', function (params) + local core = require 'core.hover' + local doc = params.textDocument + local uri = doc.uri + if not files.exists(uri) then + return nil + end + local lines = files.getLines(uri) + local text = files.getText(uri) + local offset = define.offset(lines, text, params.position) + local hover = core(uri, offset) + if not hover then + return nil + end + local md = markdown() + md:add('lua', hover.label) + return { + contents = { + value = md:string(), + kind = 'markdown', + }, + range = define.range(lines, text, hover.source.start, hover.source.finish), + } +end) + +proto.on('textDocument/definition', function (params) + local core = require 'core.definition' + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local lines = files.getLines(uri) + local text = files.getText(uri) + local offset = define.offset(lines, text, params.position) + local result = core(uri, offset) + if not result then + return nil + end + local response = {} + for i, info in ipairs(result) do + local targetUri = info.uri + local targetLines = files.getLines(targetUri) + local targetText = files.getText(targetUri) + response[i] = define.locationLink(targetUri + , define.range(targetLines, targetText, info.target.start, info.target.finish) + , define.range(targetLines, targetText, info.target.start, info.target.finish) + , define.range(lines, text, info.source.start, info.source.finish) + ) + end + return response +end) + +proto.on('textDocument/references', function (params) + local core = require 'core.reference' + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local lines = files.getLines(uri) + local text = files.getText(uri) + local offset = define.offset(lines, text, params.position) + local result = core(uri, offset) + if not result then + return nil + end + local response = {} + for i, info in ipairs(result) do + local targetUri = info.uri + local targetLines = files.getLines(targetUri) + local targetText = files.getText(targetUri) + response[i] = define.location(targetUri + , define.range(targetLines, targetText, info.target.start, info.target.finish) + ) + end + return response +end) + +proto.on('textDocument/documentHighlight', function (params) + local core = require 'core.highlight' + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local lines = files.getLines(uri) + local text = files.getText(uri) + local offset = define.offset(lines, text, params.position) + local result = core(uri, offset) + if not result then + return nil + end + local response = {} + for _, info in ipairs(result) do + response[#response+1] = { + range = define.range(lines, text, info.start, info.finish), + kind = info.kind, + } + end + return response +end) + +proto.on('textDocument/rename', function (params) + local core = require 'core.rename' + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local lines = files.getLines(uri) + local text = files.getText(uri) + local offset = define.offset(lines, text, params.position) + local result = core.rename(uri, offset, params.newName) + if not result then + return nil + end + local workspaceEdit = { + changes = {}, + } + for _, info in ipairs(result) do + local ruri = info.uri + local rlines = files.getLines(ruri) + local rtext = files.getText(ruri) + if not workspaceEdit.changes[ruri] then + workspaceEdit.changes[ruri] = {} + end + local textEdit = define.textEdit(define.range(rlines, rtext, info.start, info.finish), info.text) + workspaceEdit.changes[ruri][#workspaceEdit.changes[ruri]+1] = textEdit + end + return workspaceEdit +end) + +proto.on('textDocument/prepareRename', function (params) + local core = require 'core.rename' + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local lines = files.getLines(uri) + local text = files.getText(uri) + local offset = define.offset(lines, text, params.position) + local result = core.prepareRename(uri, offset) + if not result then + return nil + end + return { + range = define.range(lines, text, result.start, result.finish), + placeholder = result.text, + } +end) + +proto.on('textDocument/completion', function (params) + --log.info(util.dump(params)) + return nil +end) diff --git a/script-beta/provider/markdown.lua b/script-beta/provider/markdown.lua new file mode 100644 index 00000000..0f69ad87 --- /dev/null +++ b/script-beta/provider/markdown.lua @@ -0,0 +1,22 @@ +local mt = {} +mt.__index = mt +mt.__name = 'markdown' + +function mt:add(language, text) + if not text then + return + end + if language == 'lua' then + self[#self+1] = ('```lua\n%s\n```'):format(text) + else + self[#self+1] = text:gsub('\n', '\n\n') + end +end + +function mt:string() + return table.concat(self, '\n') +end + +return function () + return setmetatable({}, mt) +end |