summaryrefslogtreecommitdiff
path: root/script-beta/provider
diff options
context:
space:
mode:
Diffstat (limited to 'script-beta/provider')
-rw-r--r--script-beta/provider/capability.lua42
-rw-r--r--script-beta/provider/completion.lua53
-rw-r--r--script-beta/provider/diagnostic.lua209
-rw-r--r--script-beta/provider/init.lua298
-rw-r--r--script-beta/provider/markdown.lua22
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