diff options
Diffstat (limited to 'script')
49 files changed, 1276 insertions, 935 deletions
diff --git a/script/client.lua b/script/client.lua index c7e12fbf..d223715d 100644 --- a/script/client.lua +++ b/script/client.lua @@ -6,6 +6,7 @@ local define = require 'proto.define' local config = require 'config' local converter = require 'proto.converter' local json = require 'json-beautify' +local await = require 'await' local m = {} @@ -108,10 +109,8 @@ end ---@param type message.type ---@param message string ---@param titles string[] ----@return string action ----@return integer index ----@async -function m.awaitRequestMessage(type, message, titles) +---@param callback fun(action: string, index: integer) +function m.requestMessage(type, message, titles, callback) proto.notify('window/logMessage', { type = define.MessageType[type] or 3, message = message, @@ -124,15 +123,29 @@ function m.awaitRequestMessage(type, message, titles) } map[title] = i end - local item = proto.awaitRequest('window/showMessageRequest', { + proto.request('window/showMessageRequest', { type = define.MessageType[type] or 3, message = message, actions = actions, - }) - if not item then - return nil - end - return item.title, map[item.title] + }, function (item) + if item then + callback(item.title, map[item.title]) + else + callback(nil, nil) + end + end) +end + +---@param type message.type +---@param message string +---@param titles string[] +---@return string action +---@return integer index +---@async +function m.awaitRequestMessage(type, message, titles) + return await.wait(function (waker) + m.requestMessage(type, message, titles, waker) + end) end ---@param type message.type @@ -187,35 +200,49 @@ end ---@field uri? uri ---@param cfg table +---@param uri uri ---@param changes config.change[] -local function applyConfig(cfg, changes) +---@return boolean +local function applyConfig(cfg, uri, changes) + local ws = require 'workspace' + local scp = ws.getScope(uri) + local ok = false for _, change in ipairs(changes) do - cfg[change.key] = config.getRaw(change.key) + if scp:isChildUri(change.uri) + or scp:isLinkedUri(change.uri) then + cfg[change.key] = config.getRaw(change.uri, change.key) + ok = true + end end + return ok end -local function tryModifySpecifiedConfig(finalChanges) +local function tryModifySpecifiedConfig(uri, finalChanges) if #finalChanges == 0 then return false end local workspace = require 'workspace' local loader = require 'config.loader' - if loader.lastLocalType ~= 'json' then + local scp = workspace.getScope(uri) + if scp:get('lastLocalType') ~= 'json' then + return false + end + local suc = applyConfig(scp:get('lastLocalConfig'), uri, finalChanges) + if not suc then return false end - applyConfig(loader.lastLocalConfig, finalChanges) - local path = workspace.getAbsolutePath(CONFIGPATH) - util.saveFile(path, json.beautify(loader.lastLocalConfig, { indent = ' ' })) + local path = workspace.getAbsolutePath(uri, CONFIGPATH) + util.saveFile(path, json.beautify(scp:get('lastLocalConfig'), { indent = ' ' })) return true end -local function tryModifyRC(finalChanges, create) +local function tryModifyRC(uri, finalChanges, create) if #finalChanges == 0 then return false end local workspace = require 'workspace' local loader = require 'config.loader' - local path = workspace.getAbsolutePath '.luarc.json' + local path = workspace.getAbsolutePath(uri, '.luarc.json') if not path then return false end @@ -223,24 +250,40 @@ local function tryModifyRC(finalChanges, create) if not buf and not create then return false end - local rc = loader.lastRCConfig or { + local scp = workspace.getScope(uri) + local rc = scp:get('lastRCConfig') or { ['$schema'] = lang.id == 'zh-cn' and [[https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema-zh-cn.json]] or [[https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json]] } - applyConfig(rc, finalChanges) + local suc = applyConfig(rc, uri, finalChanges) + if not suc then + return false + end util.saveFile(path, json.beautify(rc, { indent = ' ' })) return true end -local function tryModifyClient(finalChanges) +local function tryModifyClient(uri, finalChanges) if #finalChanges == 0 then return false end if not m.getOption 'changeConfiguration' then return false end + local ws = require 'workspace' + local scp = ws.getScope(uri) + local scpChanges = {} + for _, change in ipairs(finalChanges) do + if change.uri + and (scp:isChildUri(change.uri) or scp:isLinkedUri(change.uri)) then + scpChanges[#scpChanges+1] = change + end + end + if #scpChanges == 0 then + return false + end proto.notify('$/command', { command = 'lua.config', - data = finalChanges, + data = scpChanges, }) return true end @@ -274,22 +317,21 @@ function m.setConfig(changes, onlyMemory) local finalChanges = {} for _, change in ipairs(changes) do if change.action == 'add' then - local suc = config.add(change.key, change.value) + local suc = config.add(change.uri, change.key, change.value) if suc then finalChanges[#finalChanges+1] = change end elseif change.action == 'set' then - local suc = config.set(change.key, change.value) + local suc = config.set(change.uri, change.key, change.value) if suc then finalChanges[#finalChanges+1] = change end elseif change.action == 'prop' then - local suc = config.prop(change.key, change.prop, change.value) + local suc = config.prop(change.uri, change.key, change.prop, change.value) if suc then finalChanges[#finalChanges+1] = change end end - change.uri = m.info.rootUri end if onlyMemory then return @@ -298,17 +340,25 @@ function m.setConfig(changes, onlyMemory) return end xpcall(function () - tryModifyClientGlobal(finalChanges) - if tryModifySpecifiedConfig(finalChanges) then - return - end - if tryModifyRC(finalChanges) then + local ws = require 'workspace' + if #ws.folders == 0 then + tryModifyClient(finalChanges) return end - if tryModifyClient(finalChanges) then - return + tryModifyClientGlobal(finalChanges) + for _, scp in ipairs(ws.folders) do + if tryModifySpecifiedConfig(scp.uri, finalChanges) then + goto CONTINUE + end + if tryModifyRC(scp.uri, finalChanges, false) then + goto CONTINUE + end + if tryModifyClient(scp.uri, finalChanges) then + goto CONTINUE + end + tryModifyRC(scp.uri, finalChanges, true) + ::CONTINUE:: end - tryModifyRC(finalChanges, true) end, log.error) end diff --git a/script/config/config.lua b/script/config/config.lua index 0f74828d..81ab4b81 100644 --- a/script/config/config.lua +++ b/script/config/config.lua @@ -1,6 +1,7 @@ local util = require 'utility' local define = require 'proto.define' local timer = require 'timer' +local scope = require 'workspace.scope' ---@alias config.source '"client"'|'"path"'|'"local"' @@ -214,42 +215,85 @@ local Template = { ['editor.acceptSuggestionOnEnter'] = Type.String >> 'on', } -local config = {} -local rawConfig = {} - ---@class config.api local m = {} m.watchList = {} -local function update(key, value, raw) - local oldValue = config[key] - config[key] = value - rawConfig[key] = raw - m.event(key, value, oldValue) +m.NULL = {} + +---@param scp scope +---@param key string +---@param nowValue any +---@param rawValue any +local function update(scp, key, nowValue, rawValue) + local now = scp:get 'config.now' + local raw = scp:get 'config.raw' + + local oldValue = now[key] + + now[key] = nowValue + raw[key] = rawValue + + m.event(scp.uri, key, nowValue, oldValue) +end + +---@param uri uri +---@param key? string +---@return scope +local function getScope(uri, key) + local raw = scope.override:get 'config.raw' + if raw then + if not key or raw[key] ~= nil then + return scope.override + end + end + if uri then + ---@type scope + local scp = scope.getFolder(uri) or scope.getLinkedScope(uri) + if scp then + if not key + or (scp:get 'config.raw' and scp:get 'config.raw' [key] ~= nil) then + return scp + end + end + end + return scope.fallback end -function m.set(key, value) +---@param scp scope +---@param key string +---@param value any +function m.setByScope(scp, key, value) local unit = Template[key] if not unit then return false end - if util.equal(rawConfig[key], value) then + local raw = scp:get 'config.raw' + if util.equal(raw[key], value) then return false end if unit:checker(value) then - update(key, unit:loader(value), value) + update(scp, key, unit:loader(value), value) else - update(key, unit.default, unit.default) + update(scp, key, unit.default, unit.default) end return true end -function m.add(key, value) +---@param uri uri +---@param key string +---@param value any +function m.set(uri, key, value) + local scp = getScope(uri) + return m.setByScope(scp, key, value) +end + +function m.add(uri, key, value) local unit = Template[key] if not unit then return false end - local list = rawConfig[key] + local list = m.getRaw(uri, key) if type(list) ~= 'table' then return false end @@ -261,18 +305,15 @@ function m.add(key, value) copyed[i] = v end copyed[#copyed+1] = value - if unit:checker(copyed) then - update(key, unit:loader(copyed), copyed) - end - return true + return m.set(uri, key, copyed) end -function m.prop(key, prop, value) +function m.prop(uri, key, prop, value) local unit = Template[key] if not unit then return false end - local map = rawConfig[key] + local map = m.getRaw(uri, key) if type(map) ~= 'table' then return false end @@ -284,43 +325,49 @@ function m.prop(key, prop, value) copyed[k] = v end copyed[prop] = value - if unit:checker(copyed) then - update(key, unit:loader(copyed), copyed) - end - return true + return m.set(uri, key, copyed) end -function m.get(key) - return config[key] -end - -function m.getRaw(key) - return rawConfig[key] +---@param uri uri +---@param key string +---@return any +function m.get(uri, key) + local scp = getScope(uri, key) + local value = scp:get 'config.now' [key] + if value == nil then + value = Template[key].default + end + if value == m.NULL then + value = nil + end + return value end -function m.dump() - local dump = {} - - local function expand(parent, key, value) - local left, right = key:match '([^%.]+)%.(.+)' - if left then - if not parent[left] then - parent[left] = {} - end - expand(parent[left], right, value) - else - parent[key] = value - end +---@param uri uri +---@param key string +---@return any +function m.getRaw(uri, key) + local scp = getScope(uri, key) + local value = scp:get 'config.raw' [key] + if value == nil then + value = Template[key].default end - - for key, value in pairs(config) do - expand(dump, key, value) + if value == m.NULL then + value = nil end - - return dump + return value end -function m.update(new, null) +---@param scp scope +---@param new table +---@param null any +function m.update(scp, new, null) + local oldConfig = scp:get 'config.now' + local newConfig = {} + + scp:set('config.now', newConfig) + scp:set('config.raw', {}) + local function expand(t, left) for key, value in pairs(t) do local fullKey = key @@ -328,12 +375,12 @@ function m.update(new, null) fullKey = left .. '.' .. key end if value == null then - value = nil + value = m.NULL end if Template[fullKey] then - m.set(fullKey, value) + m.setByScope(scp, fullKey, value) elseif Template['Lua.' .. fullKey] then - m.set('Lua.' .. fullKey, value) + m.setByScope(scp, 'Lua.' .. fullKey, value) elseif type(value) == 'table' then expand(value, fullKey) end @@ -341,14 +388,21 @@ function m.update(new, null) end expand(new) + + -- compare then fire event + if oldConfig then + for key, oldValue in pairs(oldConfig) do + m.event(scp.uri, key, oldValue, newConfig[key]) + end + end end ----@param callback fun(key: string, value: any, oldValue: any) +---@param callback fun(uri: uri, key: string, value: any, oldValue: any) function m.watch(callback) m.watchList[#m.watchList+1] = callback end -function m.event(key, value, oldValue) +function m.event(uri, key, value, oldValue) if not m.delay then m.delay = {} timer.wait(0, function () @@ -356,26 +410,19 @@ function m.event(key, value, oldValue) m.delay = nil for _, info in ipairs(delay) do for _, callback in ipairs(m.watchList) do - callback(info.key, info.value, info.oldValue) + callback(info.uri, info.key, info.value, info.oldValue) end end end) end m.delay[#m.delay+1] = { + uri = uri, key = key, value = value, oldValue = oldValue, } end -function m.init() - if m.inited then - return - end - m.inited = true - for key, unit in pairs(Template) do - m.set(key, unit.default) - end -end +m.update(scope.fallback, {}) return m diff --git a/script/config/loader.lua b/script/config/loader.lua index daa370cd..b2cf6fd3 100644 --- a/script/config/loader.lua +++ b/script/config/loader.lua @@ -1,5 +1,3 @@ -local fs = require 'bee.filesystem' -local fsu = require 'fs-utility' local json = require 'json' local proto = require 'proto' local lang = require 'language' @@ -14,40 +12,43 @@ local function errorMessage(msg) log.error(msg) end +---@class config.loader local m = {} -function m.loadRCConfig(filename) - local path = workspace.getAbsolutePath(filename) +function m.loadRCConfig(uri, filename) + local scp = workspace.getScope(uri) + local path = workspace.getAbsolutePath(uri, filename) if not path then - m.lastRCConfig = nil + scp:set('lastRCConfig', nil) return nil end local buf = util.loadFile(path) if not buf then - m.lastRCConfig = nil + scp:set('lastRCConfig', nil) return nil end local suc, res = pcall(json.decode, buf) if not suc then errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) - return m.lastRCConfig + return scp:get('lastRCConfig') end - m.lastRCConfig = res + scp:set('lastRCConfig', res) return res end -function m.loadLocalConfig(filename) - local path = workspace.getAbsolutePath(filename) +function m.loadLocalConfig(uri, filename) + local scp = workspace.getScope(uri) + local path = workspace.getAbsolutePath(uri, filename) if not path then - m.lastLocalConfig = nil - m.lastLocalType = nil + scp:set('lastLocalConfig', nil) + scp:set('lastLocalType', nil) return nil end local buf = util.loadFile(path) if not buf then errorMessage(lang.script('CONFIG_LOAD_FAILED', path)) - m.lastLocalConfig = nil - m.lastLocalType = nil + scp:set('lastLocalConfig', nil) + scp:set('lastLocalType', nil) return nil end local firstChar = buf:match '%S' @@ -55,10 +56,10 @@ function m.loadLocalConfig(filename) local suc, res = pcall(json.decode, buf) if not suc then errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) - return m.lastLocalConfig + return scp:get('lastLocalConfig') end - m.lastLocalConfig = res - m.lastLocalType = 'json' + scp:set('lastLocalConfig', res) + scp:set('lastLocalType', 'json') return res else local suc, res = pcall(function () @@ -66,36 +67,38 @@ function m.loadLocalConfig(filename) end) if not suc then errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) - return m.lastLocalConfig + scp:set('lastLocalConfig', res) end - m.lastLocalConfig = res - m.lastLocalType = 'lua' + scp:set('lastLocalConfig', res) + scp:set('lastLocalType', 'lua') return res end end ---@async -function m.loadClientConfig() +---@param uri? uri +---@return table +function m.loadClientConfig(uri) local configs = proto.awaitRequest('workspace/configuration', { items = { { - scopeUri = workspace.uri, + scopeUri = uri, section = 'Lua', }, { - scopeUri = workspace.uri, + scopeUri = uri, section = 'files.associations', }, { - scopeUri = workspace.uri, + scopeUri = uri, section = 'files.exclude', }, { - scopeUri = workspace.uri, + scopeUri = uri, section = 'editor.semanticHighlighting.enabled', }, { - scopeUri = workspace.uri, + scopeUri = uri, section = 'editor.acceptSuggestionOnEnter', }, }, diff --git a/script/core/code-action.lua b/script/core/code-action.lua index ad048c48..b2a6fac9 100644 --- a/script/core/code-action.lua +++ b/script/core/code-action.lua @@ -49,7 +49,7 @@ local function disableDiagnostic(uri, code, start, results) kind = 'quickfix', command = { title = lang.script.COMMAND_DISABLE_DIAG, - command = 'lua.setConfig:' .. sp:get_id(), + command = 'lua.setConfig', arguments = { { key = 'Lua.diagnostics.disable', @@ -86,7 +86,7 @@ local function markGlobal(uri, name, results) kind = 'quickfix', command = { title = lang.script.COMMAND_MARK_GLOBAL, - command = 'lua.setConfig:' .. sp:get_id(), + command = 'lua.setConfig', arguments = { { key = 'Lua.diagnostics.globals', @@ -105,7 +105,7 @@ local function changeVersion(uri, version, results) kind = 'quickfix', command = { title = lang.script.COMMAND_RUNTIME_VERSION, - command = 'lua.setConfig:' .. sp:get_id(), + command = 'lua.setConfig', arguments = { { key = 'Lua.runtime.version', @@ -223,7 +223,7 @@ local function solveSyntaxUnicodeName(uri, err, results) kind = 'quickfix', command = { title = lang.script.COMMAND_UNICODE_NAME, - command = 'lua.setConfig:' .. sp:get_id(), + command = 'lua.setConfig', arguments = { { key = 'Lua.runtime.unicodeName', @@ -280,7 +280,7 @@ local function solveAmbiguity1(uri, diag, results) kind = 'quickfix', command = { title = lang.script.COMMAND_ADD_BRACKETS, - command = 'lua.solve:' .. sp:get_id(), + command = 'lua.solve', arguments = { { name = 'ambiguity-1', @@ -298,7 +298,7 @@ local function solveTrailingSpace(uri, diag, results) kind = 'quickfix', command = { title = lang.script.COMMAND_REMOVE_SPACE, - command = 'lua.removeSpace:' .. sp:get_id(), + command = 'lua.removeSpace', arguments = { { uri = uri, @@ -570,7 +570,7 @@ local function checkJsonToLua(results, uri, start, finish) kind = 'refactor.rewrite', command = { title = lang.script.COMMAND_JSON_TO_LUA, - command = 'lua.jsonToLua:' .. sp:get_id(), + command = 'lua.jsonToLua', arguments = { { uri = uri, diff --git a/script/core/collector.lua b/script/core/collector.lua index 3293c9fe..2f29fc37 100644 --- a/script/core/collector.lua +++ b/script/core/collector.lua @@ -1,3 +1,6 @@ +local scope = require 'workspace.scope' +local ws = require 'workspace' + local collect = {} local subscribed = {} @@ -58,17 +61,56 @@ end local DUMMY_FUNCTION = function () end --- 迭代某个名字的订阅 +---@param uri uri ---@param name string -function m.each(name) +function m.each(uri, name) local nameCollect = collect[name] if not nameCollect then return DUMMY_FUNCTION end - local uri, value - return function () - uri, value = next(nameCollect, uri) - return value, uri + ---@type scope + local scp = scope.getFolder(uri) + or scope.getLinkedScope(uri) + or scope.fallback + + local curi, value + local function getNext() + curi, value = next(nameCollect, curi) + if not curi then + return nil, nil + end + if not scp:isChildUri(curi) + and not scp:isLinkedUri(curi) then + return getNext() + end + + return value, curi end + return getNext end +--- 迭代某个名字的引用订阅 +---@param uri uri +---@param name string +function m.eachRef(uri, name) + local nameCollect = collect[name] + if not nameCollect then + return DUMMY_FUNCTION + end + ---@type scope + if scope.getFolder(uri) then + return m.each(uri, name) + end + + local curi, value + local function getNext() + curi, value = next(nameCollect, curi) + if not curi then + return nil, nil + end + + return value, curi + end + return getNext +end return m diff --git a/script/core/command/autoRequire.lua b/script/core/command/autoRequire.lua index 30bd13a1..c0deecfc 100644 --- a/script/core/command/autoRequire.lua +++ b/script/core/command/autoRequire.lua @@ -64,7 +64,7 @@ local function findInsertRow(uri) end ---@async -local function askAutoRequire(visiblePaths) +local function askAutoRequire(uri, visiblePaths) local selects = {} local nameMap = {} for _, visible in ipairs(visiblePaths) do @@ -91,6 +91,7 @@ local function askAutoRequire(visiblePaths) key = 'Lua.completion.autoRequire', action = 'set', value = false, + uri = uri, } } return @@ -137,7 +138,7 @@ return function (data) end local path = furi.decode(target) - local visiblePaths = rpath.getVisiblePath(path) + local visiblePaths = rpath.getVisiblePath(uri, path) if not visiblePaths or #visiblePaths == 0 then return end @@ -145,7 +146,7 @@ return function (data) return #a.expect < #b.expect end) - local result = askAutoRequire(visiblePaths) + local result = askAutoRequire(uri, visiblePaths) if not result then return end diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua index 425c8026..28e9d108 100644 --- a/script/core/completion/completion.lua +++ b/script/core/completion/completion.lua @@ -180,7 +180,7 @@ local function buildDetail(source) end local function getSnip(source) - local context = config.get 'Lua.completion.displayContext' + local context = config.get(guide.getUri(source), 'Lua.completion.displayContext') if context <= 0 then return nil end @@ -222,7 +222,7 @@ local function buildDesc(source) end local function buildFunction(results, source, value, oop, data) - local snipType = config.get 'Lua.completion.callSnippet' + local snipType = config.get(guide.getUri(source), 'Lua.completion.callSnippet') if snipType == 'Disable' or snipType == 'Both' then results[#results+1] = data end @@ -324,7 +324,7 @@ local function checkLocal(state, word, position, results) end local function checkModule(state, word, position, results) - if not config.get 'Lua.completion.autoRequire' then + if not config.get(state.uri, 'Lua.completion.autoRequire') then return end local locals = guide.getVisibleLocals(state.ast, position) @@ -337,7 +337,7 @@ local function checkModule(state, word, position, results) local stemName = fileName:gsub('%..+', '') if not locals[stemName] and not vm.hasGlobalSets(stemName) - and not config.get 'Lua.diagnostics.globals'[stemName] + and not config.get(state.uri, 'Lua.diagnostics.globals')[stemName] and stemName:match '^[%a_][%w_]*$' and matchKey(word, stemName) then local targetState = files.getState(uri) @@ -367,7 +367,7 @@ local function checkModule(state, word, position, results) commitCharacters = {'.'}, command = { title = 'autoRequire', - command = 'lua.autoRequire:' .. sp:get_id(), + command = 'lua.autoRequire', arguments = { { uri = guide.getUri(state.ast), @@ -448,8 +448,8 @@ local function checkFieldFromFieldToIndex(state, name, src, parent, word, startP } end else - if config.get 'Lua.runtime.version' == 'lua 5.1' - or config.get 'Lua.runtime.version' == 'luaJIT' then + if config.get(state.uri, 'Lua.runtime.version') == 'lua 5.1' + or config.get(state.uri, 'Lua.runtime.version') == 'luaJIT' then textEdit.newText = '_G' .. textEdit.newText else textEdit.newText = '_ENV' .. textEdit.newText @@ -536,7 +536,7 @@ local function checkFieldOfRefs(refs, state, word, startPos, position, parent, o goto CONTINUE end local funcLabel - if config.get 'Lua.completion.showParams' then + if config.get(state.uri, 'Lua.completion.showParams') then local value = searcher.getObjectValue(src) or src if value.type == 'function' or value.type == 'doc.type.function' then @@ -584,14 +584,14 @@ end ---@async local function checkGlobal(state, word, startPos, position, parent, oop, results) local locals = guide.getVisibleLocals(state.ast, position) - local globals = vm.getGlobalSets '*' + local globals = vm.getGlobalSets(state.uri, '*') checkFieldOfRefs(globals, state, word, startPos, position, parent, oop, results, locals, 'global') end ---@async local function checkField(state, word, start, position, parent, oop, results) if parent.tag == '_ENV' or parent.special == '_G' then - local globals = vm.getGlobalSets '*' + local globals = vm.getGlobalSets(state.uri, '*') checkFieldOfRefs(globals, state, word, start, position, parent, oop, results) else local refs = vm.getRefs(parent, '*') @@ -630,7 +630,7 @@ end local function checkCommon(state, word, position, results) local myUri = state.uri - local showWord = config.get 'Lua.completion.showWord' + local showWord = config.get(state.uri, 'Lua.completion.showWord') if showWord == 'Disable' then return end @@ -645,7 +645,7 @@ local function checkCommon(state, word, position, results) for _, data in ipairs(keyWordMap) do used[data[1]] = true end - if config.get 'Lua.completion.workspaceWord' and #word >= 2 then + if config.get(state.uri, 'Lua.completion.workspaceWord') and #word >= 2 then results.complete = true local myHead = word:sub(1, 2) for uri in files.eachFile() do @@ -720,7 +720,7 @@ end local function checkKeyWord(state, start, position, word, hasSpace, afterLocal, results) local text = state.lua - local snipType = config.get 'Lua.completion.keywordSnippet' + local snipType = config.get(state.uri, 'Lua.completion.keywordSnippet') local symbol = lookBackward.findSymbol(text, guide.positionToOffset(state, start)) local isExp = symbol == '(' or symbol == ',' or symbol == '=' local info = { @@ -887,7 +887,7 @@ local function collectRequireNames(mode, myUri, literal, source, smark, position goto CONTINUE end local path = workspace.getRelativePath(uri) - local infos = rpath.getVisiblePath(path) + local infos = rpath.getVisiblePath(uri, path) for _, info in ipairs(infos) do if matchKey(literal, info.expect) then if not collect[info.expect] then @@ -1628,7 +1628,7 @@ local function tryluaDocBySource(state, position, source, results) if source.type == 'doc.extends.name' then if source.parent.type == 'doc.class' then local used = {} - for _, doc in ipairs(vm.getDocDefines '*') do + for _, doc in ipairs(vm.getDocDefines(state.uri, '*')) do if doc.type == 'doc.class.name' and doc.parent ~= source.parent and not used[doc[1]] @@ -1649,7 +1649,7 @@ local function tryluaDocBySource(state, position, source, results) return true elseif source.type == 'doc.type.name' then local used = {} - for _, doc in ipairs(vm.getDocDefines '*') do + for _, doc in ipairs(vm.getDocDefines(state.uri, '*')) do if (doc.type == 'doc.class.name' or doc.type == 'doc.alias.name') and doc.parent ~= source.parent and not used[doc[1]] @@ -1727,7 +1727,7 @@ end local function tryluaDocByErr(state, position, err, docState, results) if err.type == 'LUADOC_MISS_CLASS_EXTENDS_NAME' then - for _, doc in ipairs(vm.getDocDefines '*') do + for _, doc in ipairs(vm.getDocDefines(state.uri, '*')) do if doc.type == 'doc.class.name' and doc.parent ~= docState then results[#results+1] = { @@ -1737,7 +1737,7 @@ local function tryluaDocByErr(state, position, err, docState, results) end end elseif err.type == 'LUADOC_MISS_TYPE_NAME' then - for _, doc in ipairs(vm.getDocDefines '*') do + for _, doc in ipairs(vm.getDocDefines(state.uri, '*')) do if (doc.type == 'doc.class.name' or doc.type == 'doc.alias.name') then results[#results+1] = { label = doc[1], @@ -1940,89 +1940,6 @@ local function tryComment(state, position, results) checkCommon(state, word, position, results) end -local function makeCache(uri, position, results) - local cache = workspace.getCache 'completion' - if not uri then - cache.results = nil - return - end - local text = files.getText(uri) - local state = files.getState(uri) - local word = lookBackward.findWord(text, guide.positionToOffset(state, position)) - if not word or #word < 2 then - cache.results = nil - return - end - cache.results = results - cache.position= position - cache.word = word:lower() - cache.length = #word - cache.uri = uri -end - -local function isValidCache(word, result) - if result.kind == define.CompletionItemKind.Text then - return false - end - local match = result.match or result.label - if matchKey(word, match) then - return true - end - if result.textEdit then - match = result.textEdit.newText:match '[%w_]+' - if match and matchKey(word, match) then - return true - end - end - return false -end - -local function getCache(uri, position) - do return nil end - local cache = workspace.getCache 'completion' - if not cache.results then - return nil - end - if cache.uri ~= uri then - return nil - end - local text = files.getText(uri) - local state = files.getState(uri) - local word = lookBackward.findWord(text, guide.positionToOffset(state, position)) - if not word then - return nil - end - if word:sub(1, #cache.word):lower() ~= cache.word then - return nil - end - - local ext = #word - cache.length - cache.length = #word - local results = cache.results - for i = #results, 1, -1 do - local result = results[i] - if isValidCache(word, result) then - if result.textEdit then - result.textEdit.finish = result.textEdit.finish + ext - end - else - results[i] = results[#results] - results[#results] = nil - end - end - - if results.enableCommon then - checkCommon(state, word, position, results) - end - - return cache.results -end - -local function clearCache() - local cache = workspace.getCache 'completion' - cache.results = nil -end - ---@async local function tryCompletions(state, position, triggerCharacter, results) local text = state.lua @@ -2052,56 +1969,30 @@ end ---@async local function completion(uri, position, triggerCharacter) - tracy.ZoneBeginN 'completion cache' - local results = getCache(uri, position) - tracy.ZoneEnd() - if results then - return results - end - await.delay() - tracy.ZoneBeginN 'completion #1' local state = files.getState(uri) if not state then return nil end - results = {} clearStack() - tracy.ZoneEnd() + local results = {} tracy.ZoneBeginN 'completion #2' tryCompletions(state, position, triggerCharacter, results) tracy.ZoneEnd() if #results == 0 then - clearCache() return nil end - --tracy.ZoneBeginN 'completion #3' - --makeCache(uri, position, results) - --tracy.ZoneEnd() return results end ---@async local function resolve(id) local item = resolveStack(id) - local cache = workspace.getCache 'completion' - if item and cache.results then - for _, res in ipairs(cache.results) do - if res and res.id == id then - for k, v in pairs(item) do - res[k] = v - end - res.id = nil - break - end - end - end return item end return { completion = completion, resolve = resolve, - clearCache = clearCache, } diff --git a/script/core/completion/postfix.lua b/script/core/completion/postfix.lua index 0395e2a5..2ac24933 100644 --- a/script/core/completion/postfix.lua +++ b/script/core/completion/postfix.lua @@ -278,7 +278,7 @@ return function (state, position, results) offset = newOffset - 1 end local symbol = text:sub(offset, offset) - if symbol == config.get 'Lua.completion.postfix' then + if symbol == config.get(state.uri, 'Lua.completion.postfix') then local wordPosition = guide.offsetToPosition(state, offset - 1) checkPostFix(state, word or '', wordPosition, position, symbol, results) return symbol ~= '.' and symbol ~= ':' diff --git a/script/core/definition.lua b/script/core/definition.lua index eadae30f..6c6b3df3 100644 --- a/script/core/definition.lua +++ b/script/core/definition.lua @@ -75,7 +75,7 @@ local function checkRequire(source, offset) return nil end if libName == 'require' then - return rpath.findUrisByRequirePath(literal) + return rpath.findUrisByRequirePath(guide.getUri(source), literal) elseif libName == 'dofile' or libName == 'loadfile' then return workspace.findUrisByFilePath(literal) diff --git a/script/core/diagnostics/circle-doc-class.lua b/script/core/diagnostics/circle-doc-class.lua index ae6d4d3b..61dc46b4 100644 --- a/script/core/diagnostics/circle-doc-class.lua +++ b/script/core/diagnostics/circle-doc-class.lua @@ -40,7 +40,7 @@ return function (uri, callback) end if not mark[newName] then mark[newName] = true - local docs = vm.getDocDefines(newName) + local docs = vm.getDocDefines(uri, newName) for _, otherDoc in ipairs(docs) do if otherDoc.type == 'doc.class.name' then list[#list+1] = otherDoc.parent diff --git a/script/core/diagnostics/deprecated.lua b/script/core/diagnostics/deprecated.lua index e9a1fef7..1c248646 100644 --- a/script/core/diagnostics/deprecated.lua +++ b/script/core/diagnostics/deprecated.lua @@ -23,10 +23,10 @@ return function (uri, callback) if not key then return end - if config.get 'Lua.diagnostics.globals'[key] then + if config.get(uri, 'Lua.diagnostics.globals')[key] then return end - if config.get 'Lua.runtime.special'[key] then + if config.get(uri, 'Lua.runtime.special')[key] then return end end @@ -83,7 +83,7 @@ return function (uri, callback) end table.sort(versions) if #versions > 0 then - message = ('%s(%s)'):format(message, lang.script('DIAG_DEFINED_VERSION', table.concat(versions, '/'), config.get 'Lua.runtime.version')) + message = ('%s(%s)'):format(message, lang.script('DIAG_DEFINED_VERSION', table.concat(versions, '/'), config.get(uri, 'Lua.runtime.version'))) end end cache[id] = { diff --git a/script/core/diagnostics/different-requires.lua b/script/core/diagnostics/different-requires.lua index 3a49ceef..de063c9f 100644 --- a/script/core/diagnostics/different-requires.lua +++ b/script/core/diagnostics/different-requires.lua @@ -21,7 +21,7 @@ return function (uri, callback) return end local literal = arg1[1] - local results = rpath.findUrisByRequirePath(literal) + local results = rpath.findUrisByRequirePath(uri, literal) if not results or #results ~= 1 then return end diff --git a/script/core/diagnostics/duplicate-doc-class.lua b/script/core/diagnostics/duplicate-doc-class.lua index 97e2089a..5114a54f 100644 --- a/script/core/diagnostics/duplicate-doc-class.lua +++ b/script/core/diagnostics/duplicate-doc-class.lua @@ -19,7 +19,7 @@ return function (uri, callback) if doc.type == 'doc.alias' then local name = guide.getKeyName(doc) if not cache[name] then - local docs = vm.getDocDefines(name) + local docs = vm.getDocDefines(uri, name) cache[name] = {} for _, otherDoc in ipairs(docs) do if otherDoc.type == 'doc.class.name' diff --git a/script/core/diagnostics/init.lua b/script/core/diagnostics/init.lua index 4950900b..4e2a4976 100644 --- a/script/core/diagnostics/init.lua +++ b/script/core/diagnostics/init.lua @@ -20,13 +20,13 @@ table.sort(diagList, function (a, b) end) local function check(uri, name, results) - if config.get 'Lua.diagnostics.disable'[name] then + if config.get(uri, 'Lua.diagnostics.disable')[name] then return end - local level = config.get 'Lua.diagnostics.severity'[name] + local level = config.get(uri, 'Lua.diagnostics.severity')[name] or define.DiagnosticDefaultSeverity[name] - local neededFileStatus = config.get 'Lua.diagnostics.neededFileStatus'[name] + local neededFileStatus = config.get(uri, 'Lua.diagnostics.neededFileStatus')[name] or define.DiagnosticDefaultNeededFileStatus[name] if neededFileStatus == 'None' then diff --git a/script/core/diagnostics/lowercase-global.lua b/script/core/diagnostics/lowercase-global.lua index 299ac110..d7032c13 100644 --- a/script/core/diagnostics/lowercase-global.lua +++ b/script/core/diagnostics/lowercase-global.lua @@ -24,7 +24,7 @@ return function (uri, callback) end local definedGlobal = {} - for name in pairs(config.get 'Lua.diagnostics.globals') do + for name in pairs(config.get(uri, 'Lua.diagnostics.globals')) do definedGlobal[name] = true end diff --git a/script/core/diagnostics/type-check.lua b/script/core/diagnostics/type-check.lua index 8728b169..5f37312e 100644 --- a/script/core/diagnostics/type-check.lua +++ b/script/core/diagnostics/type-check.lua @@ -36,11 +36,11 @@ local function isTable(name) return false end -local function isUserDefineClass(name) +local function isUserDefineClass(uri, name) if vm.isBuiltinType(name) then return false else - local defs = vm.getDocDefines(name) + local defs = vm.getDocDefines(uri, name) for _, v in ipairs(defs) do if v.type == 'doc.class.name' then return true @@ -116,10 +116,10 @@ end -- end -- end -local function addFatherClass(infers) +local function addFatherClass(uri, infers) for k in pairs(infers) do if type(k) == 'string' then - local docDefs = vm.getDocDefines(k) + local docDefs = vm.getDocDefines(uri, k) for _, doc in ipairs(docDefs) do if doc.parent and doc.parent.type == 'doc.class' @@ -264,7 +264,7 @@ local function getInfoFromDefs(defs) return paramsTypes end -local function getArgsInfo(callArgs) +local function getArgsInfo(uri, callArgs) local callArgsType = {} for _, arg in ipairs(callArgs) do -- local defs = vm.getDefs(arg) @@ -276,7 +276,7 @@ local function getArgsInfo(callArgs) end local hasAny = infers['any'] ---处理继承 - addFatherClass(infers) + addFatherClass(uri, infers) if not hasAny then infers['any'] = nil infers['unknown'] = nil @@ -285,7 +285,7 @@ local function getArgsInfo(callArgs) if not infers['table'] then for k in pairs(infers) do if not vm.isBuiltinType(k) - and isUserDefineClass(k) then + and isUserDefineClass(uri, k) then infers['table'] = true break end @@ -424,7 +424,7 @@ return function (uri, callback) end await.delay() local callArgs = source.args - local suc, callArgsType = getArgsInfo(callArgs) + local suc, callArgsType = getArgsInfo(uri, callArgs) if not suc then return end diff --git a/script/core/diagnostics/undefined-doc-class.lua b/script/core/diagnostics/undefined-doc-class.lua index e7133ab9..5f3902a2 100644 --- a/script/core/diagnostics/undefined-doc-class.lua +++ b/script/core/diagnostics/undefined-doc-class.lua @@ -25,7 +25,7 @@ return function (uri, callback) end for _, ext in ipairs(doc.extends) do local name = ext[1] - local docs = vm.getDocDefines(name) + local docs = vm.getDocDefines(uri, name) if cache[name] == nil then cache[name] = false for _, otherDoc in ipairs(docs) do diff --git a/script/core/diagnostics/undefined-global.lua b/script/core/diagnostics/undefined-global.lua index 48c8a226..45fc390b 100644 --- a/script/core/diagnostics/undefined-global.lua +++ b/script/core/diagnostics/undefined-global.lua @@ -27,10 +27,10 @@ return function (uri, callback) if not key then return end - if config.get 'Lua.diagnostics.globals'[key] then + if config.get(uri, 'Lua.diagnostics.globals')[key] then return end - if config.get 'Lua.runtime.special'[key] then + if config.get(uri, 'Lua.runtime.special')[key] then return end local node = src.node diff --git a/script/core/hint.lua b/script/core/hint.lua index 06bbf421..eebadb05 100644 --- a/script/core/hint.lua +++ b/script/core/hint.lua @@ -32,11 +32,11 @@ local function typeHint(uri, results, start, finish) return end if source.parent.type == 'funcargs' then - if not config.get 'Lua.hint.paramType' then + if not config.get(uri, 'Lua.hint.paramType') then return end else - if not config.get 'Lua.hint.setType' then + if not config.get(uri, 'Lua.hint.setType') then return end end @@ -99,7 +99,7 @@ end ---@async local function paramName(uri, results, start, finish) - local paramConfig = config.get 'Lua.hint.paramName' + local paramConfig = config.get(uri, 'Lua.hint.paramName') if not paramConfig or paramConfig == 'Disable' then return end @@ -162,7 +162,7 @@ end ---@async local function awaitHint(uri, results, start, finish) - local awaitConfig = config.get 'Lua.hint.await' + local awaitConfig = config.get(uri, 'Lua.hint.await') if not awaitConfig then return end diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index df9fb6e9..c46b239d 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -11,11 +11,11 @@ local guide = require 'parser.guide' local noder = require 'core.noder' local rpath = require 'workspace.require-path' -local function collectRequire(mode, literal) - local rootPath = ws.path or '' +local function collectRequire(mode, literal, uri) + local rootPath = ws.getRootUri(uri) or '' local result, searchers if mode == 'require' then - result, searchers = rpath.findUrisByRequirePath(literal) + result, searchers = rpath.findUrisByRequirePath(uri, literal) elseif mode == 'dofile' or mode == 'loadfile' then result = ws.findUrisByFilePath(literal) @@ -57,7 +57,7 @@ local function asStringInRequire(source, literal) if libName == 'require' or libName == 'dofile' or libName == 'loadfile' then - return collectRequire(libName, literal) + return collectRequire(libName, literal, guide.getUri(source)) end end end @@ -65,11 +65,11 @@ end local function asStringView(source, literal) -- 内部包含转义符? local rawLen = source.finish - source.start - 2 * #source[2] - if config.get 'Lua.hover.viewString' + if config.get(guide.getUri(source), 'Lua.hover.viewString') and (source[2] == '"' or source[2] == "'") and rawLen > #literal then local view = literal - local max = config.get 'Lua.hover.viewStringMax' + local max = config.get(guide.getUri(source), 'Lua.hover.viewStringMax') if #view > max then view = view:sub(1, max) .. '...' end @@ -157,7 +157,7 @@ local function tryDocModule(source) if not source.module then return end - return collectRequire('require', source.module) + return collectRequire('require', source.module, guide.getUri(source)) end local function buildEnumChunk(docType, name) diff --git a/script/core/hover/label.lua b/script/core/hover/label.lua index 78756ea8..a39f0eac 100644 --- a/script/core/hover/label.lua +++ b/script/core/hover/label.lua @@ -157,7 +157,7 @@ local function formatNumber(n) end local function asNumber(source) - if not config.get 'Lua.hover.viewNumber' then + if not config.get(guide.getUri(source), 'Lua.hover.viewNumber') then return nil end local num = source[1] diff --git a/script/core/hover/return.lua b/script/core/hover/return.lua index 49f9536a..681e9747 100644 --- a/script/core/hover/return.lua +++ b/script/core/hover/return.lua @@ -1,4 +1,5 @@ local infer = require 'core.infer' +local guide = require 'parser.guide' local function getReturnDualByDoc(source) local docs = source.bindDocs @@ -67,7 +68,7 @@ local function asFunction(source) end end if next(infers) or rtn[1] then - local tp = infer.viewInfers(infers) + local tp = infer.viewInfers(guide.getUri(source), infers) if rtn[1].name then line[#line+1] = ('%s%s: %s'):format( rtn[1].name[1], diff --git a/script/core/hover/table.lua b/script/core/hover/table.lua index 285d5c02..bd2f81ad 100644 --- a/script/core/hover/table.lua +++ b/script/core/hover/table.lua @@ -3,6 +3,7 @@ local util = require 'utility' local config = require 'config' local infer = require 'core.infer' local await = require 'await' +local guide = require 'parser.guide' local function formatKey(key) if type(key) == 'string' then @@ -136,7 +137,7 @@ end ---@async return function (source) - local maxFields = config.get 'Lua.hover.previewFields' + local maxFields = config.get(guide.getUri(source), 'Lua.hover.previewFields') if maxFields <= 0 then return 'table' end diff --git a/script/core/infer.lua b/script/core/infer.lua index d204fff1..982b8ef3 100644 --- a/script/core/infer.lua +++ b/script/core/infer.lua @@ -3,6 +3,7 @@ local config = require 'config' local noder = require 'core.noder' local util = require 'utility' local vm = require "vm.vm" +local guide = require "parser.guide" local CLASS = {'CLASS'} local TABLE = {'TABLE'} @@ -232,8 +233,8 @@ local function bindClassOrType(source) return false end -local function cleanInfers(infers) - local version = config.get 'Lua.runtime.version' +local function cleanInfers(uri, infers) + local version = config.get(uri, 'Lua.runtime.version') local enableInteger = version == 'Lua 5.3' or version == 'Lua 5.4' infers['unknown'] = nil if infers['number'] then @@ -269,7 +270,7 @@ end ---合并对象的推断类型 ---@param infers string[] ---@return string -function m.viewInfers(infers) +function m.viewInfers(uri, infers) if infers[CACHE] then return infers[CACHE] end @@ -298,7 +299,7 @@ function m.viewInfers(infers) return sa < sb end end) - local limit = config.get 'Lua.hover.enumsLimit' + local limit = config.get(uri, 'Lua.hover.enumsLimit') if limit < 0 then limit = 0 end @@ -510,7 +511,7 @@ function m.searchInfers(source, field, mark) end end end - cleanInfers(infers) + cleanInfers(guide.getUri(source), infers) return infers end @@ -606,7 +607,7 @@ function m.searchAndViewInfers(source, field, mark) return 'any' end local infers = m.searchInfers(source, field, mark) - local view = m.viewInfers(infers) + local view = m.viewInfers(guide.getUri(source), infers) if type(view) == 'boolean' then log.error('Why view is boolean?', util.dump(infers)) return 'any' @@ -630,8 +631,8 @@ function m.getClass(source) end end end - cleanInfers(infers) - local view = m.viewInfers(infers) + cleanInfers(guide.getUri(source), infers) + local view = m.viewInfers(guide.getUri(source), infers) if view == 'any' then return nil end diff --git a/script/core/noder.lua b/script/core/noder.lua index dc846cf3..6681fa7c 100644 --- a/script/core/noder.lua +++ b/script/core/noder.lua @@ -497,7 +497,7 @@ local function getNodeKey(source) if methodNode then return getNodeKey(methodNode) end - if config.get 'Lua.IntelliSense.traceFieldInject' then + if config.get(guide.getUri(source), 'Lua.IntelliSense.traceFieldInject') then local localValueID = getLocalValueID(source) if localValueID then return localValueID @@ -822,7 +822,7 @@ local function bindValue(noders, source, id) local bindDocs = source.bindDocs if source.type == 'getlocal' or source.type == 'setlocal' then - if not config.get 'Lua.IntelliSense.traceLocalSet' then + if not config.get(guide.getUri(source), 'Lua.IntelliSense.traceLocalSet') then return end bindDocs = source.node.bindDocs @@ -837,7 +837,7 @@ local function bindValue(noders, source, id) end -- x = y : x -> y pushForward(noders, id, valueID, INFO_REJECT_SET) - if not config.get 'Lua.IntelliSense.traceBeSetted' + if not config.get(guide.getUri(source), 'Lua.IntelliSense.traceBeSetted') and source.type ~= 'local' then return end @@ -1329,7 +1329,7 @@ compileNodeMap = util.switch() , index ) pushForward(noders, returnID, getID(rtnObj)) - if config.get 'Lua.IntelliSense.traceReturn' then + if config.get(guide.getUri(source), 'Lua.IntelliSense.traceReturn') then pushBackward(noders, getID(rtnObj), returnID, INFO_DEEP_AND_DONT_CROSS) end end diff --git a/script/core/searcher.lua b/script/core/searcher.lua index 72dda55f..558dc96b 100644 --- a/script/core/searcher.lua +++ b/script/core/searcher.lua @@ -27,6 +27,7 @@ local getUri = guide.getUri local getRoot = guide.getRoot local ceach = collector.each +local ceachref = collector.eachRef local getState = files.getState @@ -796,7 +797,7 @@ function m.searchRefsByID(status, suri, expect, mode) if not requireName then return end - local uris = rpath.findUrisByRequirePath(requireName) + local uris = rpath.findUrisByRequirePath(suri, requireName) footprint(status, 'require:', requireName) for i = 1, #uris do local ruri = uris[i] @@ -820,7 +821,7 @@ function m.searchRefsByID(status, suri, expect, mode) or mode == 'alldef' or field or hasCall(field) then - for _, guri in ceach('def:' .. id) do + for _, guri in ceach(suri, 'def:' .. id) do if uri == guri then goto CONTINUE end @@ -829,14 +830,14 @@ function m.searchRefsByID(status, suri, expect, mode) end elseif mode == 'field' or mode == 'allfield' then - for _, guri in ceach('def:' .. id) do + for _, guri in ceach(suri, 'def:' .. id) do if uri == guri then goto CONTINUE end searchID(guri, id, field, uri) ::CONTINUE:: end - for _, guri in ceach('field:' .. id) do + for _, guri in ceach(suri, 'field:' .. id) do if uri == guri then goto CONTINUE end @@ -844,7 +845,7 @@ function m.searchRefsByID(status, suri, expect, mode) ::CONTINUE:: end else - for _, guri in ceach(id) do + for _, guri in ceachref(suri, id) do if crossed[guri] then goto CONTINUE end @@ -872,7 +873,7 @@ function m.searchRefsByID(status, suri, expect, mode) or ignoredIDs[id] or id == 'dn:string' or hasCall(field) then - for _, guri in ceach('def:' .. id) do + for _, guri in ceach(suri, 'def:' .. id) do if uri == guri then goto CONTINUE end @@ -880,7 +881,7 @@ function m.searchRefsByID(status, suri, expect, mode) ::CONTINUE:: end else - for _, guri in ceach(id) do + for _, guri in ceachref(suri, id) do if crossed[guri] then goto CONTINUE end diff --git a/script/core/semantic-tokens.lua b/script/core/semantic-tokens.lua index 8389cbb4..12848d2b 100644 --- a/script/core/semantic-tokens.lua +++ b/script/core/semantic-tokens.lua @@ -9,10 +9,8 @@ local converter = require 'proto.converter' local infer = require 'core.infer' local config = require 'config' -local isEnhanced = config.get 'Lua.color.mode' == 'SemanticEnhanced' - local Care = {} -Care['getglobal'] = function (source, results) +Care['getglobal'] = function (source, options, results) local isLib = vm.isGlobalLibraryName(source[1]) local isFunc = false local value = source.value @@ -21,7 +19,7 @@ Care['getglobal'] = function (source, results) isFunc = true elseif source.parent.type == 'call' then isFunc = true - elseif isEnhanced then + elseif options.isEnhanced then isFunc = infer.hasType(source, 'function') end @@ -36,7 +34,7 @@ Care['getglobal'] = function (source, results) } end Care['setglobal'] = Care['getglobal'] -Care['getmethod'] = function (source, results) +Care['getmethod'] = function (source, options, results) local method = source.method if method and method.type == 'method' then results[#results+1] = { @@ -48,7 +46,7 @@ Care['getmethod'] = function (source, results) end end Care['setmethod'] = Care['getmethod'] -Care['field'] = function (source, results) +Care['field'] = function (source, options, results) local modifiers = 0 if source.parent and source.parent.type == 'tablefield' then modifiers = define.TokenModifiers.declaration @@ -65,7 +63,7 @@ Care['field'] = function (source, results) return end end - if isEnhanced and infer.hasType(source, 'function') then + if options.isEnhanced and infer.hasType(source, 'function') then results[#results+1] = { start = source.start, finish = source.finish, @@ -90,7 +88,7 @@ Care['field'] = function (source, results) modifieres = modifiers, } end -Care['getlocal'] = function (source, results) +Care['getlocal'] = function (source, options, results) local loc = source.node -- 1. 值为函数的局部变量 | Local variable whose value is a function if loc.refs then @@ -127,7 +125,7 @@ Care['getlocal'] = function (source, results) return end -- 5. References to other functions - if isEnhanced and infer.hasType(loc, 'function') then + if options.isEnhanced and infer.hasType(loc, 'function') then results[#results+1] = { start = source.start, finish = source.finish, @@ -137,7 +135,7 @@ Care['getlocal'] = function (source, results) return end -- 6. Class declaration - if isEnhanced then + if options.isEnhanced then -- search all defs for _, def in ipairs(vm.getDefs(source)) do if def.bindDocs then @@ -214,7 +212,7 @@ Care['getlocal'] = function (source, results) } end Care['setlocal'] = Care['getlocal'] -Care['local'] = function (source, results) -- Local declaration, i.e. "local x", "local y = z", or "local function() end" +Care['local'] = function (source, options, results) -- Local declaration, i.e. "local x", "local y = z", or "local function() end" if source[1] == '_ENV' or source[1] == 'self' then return @@ -231,7 +229,7 @@ Care['local'] = function (source, results) -- Local declaration, i.e. "local x", if source.value then local isFunction = false - if isEnhanced then + if options.isEnhanced then isFunction = source.value.type == 'function' or infer.hasType(source.value, 'function') else isFunction = source.value.type == 'function' @@ -299,21 +297,21 @@ Care['local'] = function (source, results) -- Local declaration, i.e. "local x", modifieres = modifiers, } end -Care['doc.return.name'] = function (source, results) +Care['doc.return.name'] = function (source, options, results) results[#results+1] = { start = source.start, finish = source.finish, type = define.TokenTypes.parameter, } end -Care['doc.tailcomment'] = function (source, results) +Care['doc.tailcomment'] = function (source, options, results) results[#results+1] = { start = source.start, finish = source.finish, type = define.TokenTypes.comment, } end -Care['doc.type.name'] = function (source, results) +Care['doc.type.name'] = function (source, options, results) if source.typeGeneric then results[#results+1] = { start = source.start, @@ -323,14 +321,14 @@ Care['doc.type.name'] = function (source, results) end end -Care['nonstandardSymbol.comment'] = function (source, results) +Care['nonstandardSymbol.comment'] = function (source, options, results) results[#results+1] = { start = source.start, finish = source.finish, type = define.TokenTypes.comment, } end -Care['nonstandardSymbol.continue'] = function (source, results) +Care['nonstandardSymbol.continue'] = function (source, options, results) results[#results+1] = { start = source.start, finish = source.finish, @@ -367,27 +365,24 @@ local function buildTokens(uri, results) return tokens end -config.watch(function (key, value) - if key == 'Lua.color.mode' then - isEnhanced = value == 'SemanticEnhanced' - end -end) - ---@async return function (uri, start, finish) local state = files.getState(uri) - local text = files.getText(uri) if not state then return nil end + local options = { + isEnhanced = config.get(uri, 'Lua.color.mode') == 'SemanticEnhanced', + } + local results = {} guide.eachSourceBetween(state.ast, start, finish, function (source) ---@async local method = Care[source.type] if not method then return end - method(source, results) + method(source, options, results) await.delay() end) diff --git a/script/core/type-definition.lua b/script/core/type-definition.lua index e9cf3e47..1f021fb3 100644 --- a/script/core/type-definition.lua +++ b/script/core/type-definition.lua @@ -76,7 +76,7 @@ local function checkRequire(source, offset) return nil end if libName == 'require' then - return rpath.findUrisByRequirePath(literal) + return rpath.findUrisByRequirePath(guide.getUri(source), literal) elseif libName == 'dofile' or libName == 'loadfile' then return workspace.findUrisByFilePath(literal) diff --git a/script/file-uri.lua b/script/file-uri.lua index d849eac1..7f3e36a8 100644 --- a/script/file-uri.lua +++ b/script/file-uri.lua @@ -24,6 +24,9 @@ local m = {} ---@param path string ---@return uri uri function m.encode(path) + if not path then + return nil + end local authority = '' if platform.OS == 'Windows' then path = path:gsub('\\', '/') @@ -67,6 +70,9 @@ end ---@param uri uri ---@return string path function m.decode(uri) + if not uri then + return nil + end local scheme, authority, path = uri:match('([^:]*):?/?/?([^/]*)(.*)') if not scheme then return '' diff --git a/script/files.lua b/script/files.lua index 074a262e..d18fade3 100644 --- a/script/files.lua +++ b/script/files.lua @@ -64,6 +64,7 @@ function m.open(uri) cache = {}, } m.onWatch('open', uri) + m.addRef(uri) end --- 关闭文件 @@ -75,6 +76,7 @@ function m.close(uri) file.trusted = false end m.onWatch('close', uri) + m.delRef(uri) end --- 是否打开 @@ -93,8 +95,12 @@ function m.getOpenedCache(uri) end --- 标记为库文件 -function m.setLibraryPath(uri, libraryPath) - m.libraryMap[uri] = libraryPath +---@param scp scope +---@param uri uri +---@param libraryUri uri +function m.setLibraryUri(scp, uri, libraryUri) + scp:get 'libraryMap' [uri] = libraryUri + scp:addLink(uri) end --- 是否是库文件 @@ -107,8 +113,9 @@ function m.getLibraryPath(uri) return m.libraryMap[uri] end -function m.flushAllLibrary() - m.libraryMap = {} +---@param scp scope +function m.flushAllLibrary(scp) + scp:set('libraryMap', {}) end --- 是否存在 @@ -174,7 +181,7 @@ function m.setText(uri, text, isTrust, version) return end if not isTrust then - local encoding = config.get 'Lua.runtime.fileEncoding' + local encoding = config.get(uri, 'Lua.runtime.fileEncoding') text = encoder.decode(encoding, text) end file.version = version @@ -344,6 +351,28 @@ function m.getChildFiles(uri) return results end +function m.addRef(uri) + local file = m.fileMap[uri] + if not file then + return nil + end + file._ref = (file._ref or 0) + 1 + return function () + m.delRef(uri) + end +end + +function m.delRef(uri) + local file = m.fileMap[uri] + if not file then + return + end + file._ref = (file._ref or 0) - 1 + if file._ref <= 0 then + m.remove(uri) + end +end + --- 移除文件 ---@param uri uri function m.remove(uri) @@ -355,7 +384,6 @@ function m.remove(uri) m.fileMap[uri] = nil m.astMap[uri] = nil m._pairsCache = nil - m.flushFileCache(uri) m.fileCount = m.fileCount - 1 m.globalVersion = m.globalVersion + 1 @@ -365,43 +393,6 @@ function m.remove(uri) m.onWatch('remove', originUri) end ---- 移除所有文件 -function m.removeAll() - local ws = require 'workspace.workspace' - m.globalVersion = m.globalVersion + 1 - await.close('files.version') - m.onWatch('version') - m._pairsCache = nil - for uri in pairs(m.fileMap) do - if not m.libraryMap[uri] then - m.fileCount = m.fileCount - 1 - m.fileMap[uri] = nil - m.astMap[uri] = nil - m.onWatch('remove', uri) - end - end - ws.flushCache() - --m.notifyCache = {} -end - ---- 移除所有关闭的文件 -function m.removeAllClosed() - m.globalVersion = m.globalVersion + 1 - await.close('files.version') - m.onWatch('version') - m._pairsCache = nil - for uri in pairs(m.fileMap) do - if not m.openMap[uri] - and not m.libraryMap[uri] then - m.fileCount = m.fileCount - 1 - m.fileMap[uri] = nil - m.astMap[uri] = nil - m.onWatch('remove', uri) - end - end - --m.notifyCache = {} -end - --- 获取一个包含所有文件uri的数组 ---@return uri[] function m.getAllUris() @@ -452,7 +443,7 @@ function m.compileState(uri, text) local client = require 'client' if not m.isOpen(uri) and not m.isLibrary(uri) - and #text >= config.get 'Lua.workspace.preloadFileSize' * 1000 then + and #text >= config.get(uri, 'Lua.workspace.preloadFileSize') * 1000 then if not m.notifyCache['preloadFileSize'] then m.notifyCache['preloadFileSize'] = {} m.notifyCache['skipLargeFileCount'] = 0 @@ -462,7 +453,7 @@ function m.compileState(uri, text) m.notifyCache['skipLargeFileCount'] = m.notifyCache['skipLargeFileCount'] + 1 local message = lang.script('WORKSPACE_SKIP_LARGE_FILE' , ws.getRelativePath(uri) - , config.get 'Lua.workspace.preloadFileSize' + , config.get(uri, 'Lua.workspace.preloadFileSize') , #text / 1000 ) if m.notifyCache['skipLargeFileCount'] <= 1 then @@ -478,11 +469,11 @@ function m.compileState(uri, text) local clock = os.clock() local state, err = parser.compile(text , 'Lua' - , config.get 'Lua.runtime.version' + , config.get(uri, 'Lua.runtime.version') , { - special = config.get 'Lua.runtime.special', - unicodeName = config.get 'Lua.runtime.unicodeName', - nonstandardSymbol = config.get 'Lua.runtime.nonstandardSymbol', + special = config.get(uri, 'Lua.runtime.special'), + unicodeName = config.get(uri, 'Lua.runtime.unicodeName'), + nonstandardSymbol = config.get(uri, 'Lua.runtime.nonstandardSymbol'), } ) local passed = os.clock() - clock @@ -633,13 +624,9 @@ function m.getCache(uri) end --- 获取文件关联 -function m.getAssoc() - if m.assocVersion == config.get 'version' then - return m.assocMatcher - end - m.assocVersion = config.get 'version' +function m.getAssoc(uri) local patt = {} - for k, v in pairs(config.get 'files.associations') do + for k, v in pairs(config.get(uri, 'files.associations')) do if v == 'lua' then patt[#patt+1] = k end @@ -659,7 +646,7 @@ function m.isLua(uri) if ext == 'lua' then return true end - local matcher = m.getAssoc() + local matcher = m.getAssoc(uri) local path = furi.decode(uri) return matcher(path) end @@ -752,24 +739,6 @@ function m.onWatch(ev, uri) end end -function m.flushCache() - for uri, file in pairs(m.fileMap) do - file.cacheActiveTime = math.huge - m.astMap[uri] = nil - file.cache = {} - end -end - -function m.flushFileCache(uri) - local file = m.fileMap[uri] - if not file then - return - end - file.cacheActiveTime = math.huge - m.astMap[uri] = nil - file.cache = {} -end - function m.init() --TODO 可以清空文件缓存,之后看要不要启用吧 --timer.loop(10, function () diff --git a/script/library.lua b/script/library.lua index daf91a38..3fa202b6 100644 --- a/script/library.lua +++ b/script/library.lua @@ -11,11 +11,14 @@ local files = require 'files' local await = require 'await' local timer = require 'timer' local encoder = require 'encoder' +local ws = require 'workspace.workspace' local m = {} -local function getDocFormater() - local version = config.get 'Lua.runtime.version' +m.metaPaths = {} + +local function getDocFormater(uri) + local version = config.get(uri, 'Lua.runtime.version') if client.isVSCode() then if version == 'Lua 5.1' then return 'HOVER_NATIVE_DOCUMENT_LUA51' @@ -43,8 +46,8 @@ local function getDocFormater() end end -local function convertLink(text) - local fmt = getDocFormater() +local function convertLink(uri, text) + local fmt = getDocFormater(uri) return text:gsub('%$([%.%w]+)', function (name) local lastDot = '' if name:sub(-1) == '.' then @@ -82,7 +85,7 @@ local function createViewDocument(name) return ('[%s](%s)'):format(lang.script.HOVER_VIEW_DOCUMENTS, lang.script(fmt, 'pdf-' .. name)) end -local function compileSingleMetaDoc(script, metaLang, status) +local function compileSingleMetaDoc(uri, script, metaLang, status) if not script then return nil end @@ -99,11 +102,11 @@ local function compileSingleMetaDoc(script, metaLang, status) middleBuf[#middleBuf+1] = ('PUSH [===[%s]===]'):format(script:sub(last)) local middleScript = table.concat(middleBuf, '\n') local version, jit - if config.get 'Lua.runtime.version' == 'LuaJIT' then + if config.get(uri, 'Lua.runtime.version') == 'LuaJIT' then version = 5.1 jit = true else - version = tonumber(config.get 'Lua.runtime.version':sub(-3)) + version = tonumber(config.get(uri, 'Lua.runtime.version'):sub(-3)) jit = false end @@ -122,7 +125,7 @@ local function compileSingleMetaDoc(script, metaLang, status) compileBuf[#compileBuf+1] = '---\n' for line in util.eachLine(des) do compileBuf[#compileBuf+1] = '---' - compileBuf[#compileBuf+1] = convertLink(line) + compileBuf[#compileBuf+1] = convertLink(uri, line) compileBuf[#compileBuf+1] = '\n' end local viewDocument = createViewDocument(name) @@ -138,7 +141,7 @@ local function compileSingleMetaDoc(script, metaLang, status) if not des then des = ('Miss locale <%s>'):format(name) end - compileBuf[#compileBuf+1] = convertLink(des) + compileBuf[#compileBuf+1] = convertLink(uri, des) compileBuf[#compileBuf+1] = '\n' end, ALIVE = function (str) @@ -197,14 +200,12 @@ local function loadMetaLocale(langID, result) return result end -local function initBuiltIn() - if not m.inited then - return - end +local function initBuiltIn(uri) + local scp = ws.getScope(uri) local langID = lang.id - local version = config.get 'Lua.runtime.version' - local encoding = config.get 'Lua.runtime.fileEncoding' - local metaPath = fs.path(METAPATH) / config.get 'Lua.runtime.meta':gsub('%$%{(.-)%}', { + local version = config.get(uri, 'Lua.runtime.version') + local encoding = config.get(uri, 'Lua.runtime.fileEncoding') + local metaPath = fs.path(METAPATH) / config.get(uri, 'Lua.runtime.meta'):gsub('%$%{(.-)%}', { version = version, language = langID, encoding = encoding, @@ -214,13 +215,11 @@ local function initBuiltIn() if langID ~= 'en-US' then loadMetaLocale(langID, metaLang) end - --log.debug('metaLang:', util.dump(metaLang)) - if m.metaPath == metaPath:string() then + if scp:get('metaPath') == metaPath:string() then return end - m.metaPath = metaPath:string() - m.metaPaths = {} + scp:set('metaPath', metaPath:string()) local suc = xpcall(function () if not fs.exists(metaPath) then fs.create_directories(metaPath) @@ -232,18 +231,18 @@ local function initBuiltIn() local out = fsu.dummyFS() local templateDir = ROOT / 'meta' / 'template' for libName, status in pairs(define.BuiltIn) do - status = config.get 'Lua.runtime.builtin'[libName] or status + status = config.get(uri, 'Lua.runtime.builtin')[libName] or status if status == 'disable' then goto CONTINUE end libName = libName .. '.lua' local libPath = templateDir / libName - local metaDoc = compileSingleMetaDoc(fsu.loadFile(libPath), metaLang, status) + local metaDoc = compileSingleMetaDoc(uri, fsu.loadFile(libPath), metaLang, status) if metaDoc then - local outPath = metaPath / libName metaDoc = encoder.encode(encoding, metaDoc, 'auto') out:saveFile(libName, metaDoc) - m.metaPaths[#m.metaPaths+1] = outPath:string() + local outputPath = metaPath / libName + m.metaPaths[outputPath:string()] = true end ::CONTINUE:: end @@ -310,17 +309,17 @@ local function load3rdConfigInDir(dir, configs, inner) end end -local function load3rdConfig() +local function load3rdConfig(uri) local configs = {} load3rdConfigInDir(innerThirdDir, configs, true) - local thirdDirs = config.get 'Lua.workspace.userThirdParty' + local thirdDirs = config.get(uri, 'Lua.workspace.userThirdParty') for _, thirdDir in ipairs(thirdDirs) do load3rdConfigInDir(fs.path(thirdDir), configs) end return configs end -local function apply3rd(cfg, onlyMemory) +local function apply3rd(uri, cfg, onlyMemory) local changes = {} if cfg.configs then for _, change in ipairs(cfg.configs) do @@ -333,6 +332,7 @@ local function apply3rd(cfg, onlyMemory) key = 'Lua.runtime.plugin', action = 'set', value = ('%s/plugin.lua'):format(cfg.dirname), + uri = uri, } end @@ -340,6 +340,7 @@ local function apply3rd(cfg, onlyMemory) key = 'Lua.workspace.library', action = 'add', value = ('%s/library'):format(cfg.dirname), + uri = uri, } client.setConfig(changes, onlyMemory) @@ -347,7 +348,7 @@ end local hasAsked ---@async -local function askFor3rd(cfg) +local function askFor3rd(uri, cfg) hasAsked = true local yes1 = lang.script.WINDOW_APPLY_WHIT_SETTING local yes2 = lang.script.WINDOW_APPLY_WHITOUT_SETTING @@ -360,21 +361,23 @@ local function askFor3rd(cfg) return nil end if result == yes1 then - apply3rd(cfg, false) + apply3rd(uri, cfg, false) client.setConfig({ { key = 'Lua.workspace.checkThirdParty', action = 'set', value = false, + uri = uri, }, }, false) elseif result == yes2 then - apply3rd(cfg, true) + apply3rd(uri, cfg, true) client.setConfig({ { key = 'Lua.workspace.checkThirdParty', action = 'set', value = false, + uri = uri, }, }, true) end @@ -397,7 +400,7 @@ local function wholeMatch(a, b) return true end -local function check3rdByWords(text, configs) +local function check3rdByWords(uri, text, configs) if hasAsked then return end @@ -407,7 +410,7 @@ local function check3rdByWords(text, configs) for _, word in ipairs(cfg.words) do await.delay() if wholeMatch(text, word) then - askFor3rd(cfg) + askFor3rd(uri, cfg) return end end @@ -420,7 +423,6 @@ local function check3rdByFileName(uri, configs) if hasAsked then return end - local ws = require 'workspace' local path = ws.getRelativePath(uri) if not path then return @@ -431,7 +433,7 @@ local function check3rdByFileName(uri, configs) for _, filename in ipairs(cfg.files) do await.delay() if wholeMatch(path, filename) then - askFor3rd(cfg) + askFor3rd(uri, cfg) return end end @@ -455,11 +457,11 @@ local function check3rd(uri) if hasAsked then return end - if not config.get 'Lua.workspace.checkThirdParty' then + if not config.get(uri, 'Lua.workspace.checkThirdParty') then return end if thirdConfigs == nil then - thirdConfigs = load3rdConfig() or false + thirdConfigs = load3rdConfig(uri) or false end if not thirdConfigs then return @@ -468,16 +470,16 @@ local function check3rd(uri) if files.isLua(uri) then local text = files.getText(uri) if text then - check3rdByWords(text, thirdConfigs) + check3rdByWords(uri, text, thirdConfigs) end end check3rdByFileName(uri, thirdConfigs) end end -config.watch(function (key, value, oldValue) +config.watch(function (uri, key, value, oldValue) if key:find '^Lua.runtime' then - initBuiltIn() + initBuiltIn(uri) end end) @@ -493,7 +495,13 @@ function m.init() return end m.inited = true - initBuiltIn() + if #ws.folders == 0 then + initBuiltIn(nil) + else + for _, scp in ipairs(ws.folders) do + initBuiltIn(scp.uri) + end + end end return m diff --git a/script/plugin.lua b/script/plugin.lua index f56dc9f9..0914c0c0 100644 --- a/script/plugin.lua +++ b/script/plugin.lua @@ -43,13 +43,6 @@ function m.isReady() return m.interface ~= nil end -local function resetFiles() - local files = require 'files' - for uri in files.eachFile() do - files.resetText(uri) - end -end - ---@async local function checkTrustLoad() local filePath = LOGPATH .. '/trusted' @@ -75,7 +68,8 @@ local function checkTrustLoad() return true end -function m.init() +---@param scp scope +function m.init(scp) if m.hasInited then return end @@ -84,7 +78,7 @@ function m.init() local ws = require 'workspace' m.interface = {} - local pluginPath = ws.getAbsolutePath(config.get 'Lua.runtime.plugin') + local pluginPath = ws.getAbsolutePath(scp.uri, config.get(scp.uri, 'Lua.runtime.plugin')) log.info('plugin path:', pluginPath) if not pluginPath then return @@ -110,7 +104,7 @@ function m.init() return end - resetFiles() + ws.resetFiles(scp) end) end diff --git a/script/progress.lua b/script/progress.lua index 8ae5e9e0..d5c174ce 100644 --- a/script/progress.lua +++ b/script/progress.lua @@ -10,6 +10,7 @@ local m = {} m.map = {} ---@class progress +---@field _scp scope local mt = {} mt.__index = mt mt._token = nil @@ -83,7 +84,7 @@ function mt:_update() and self._clock + self._delay <= os.clock() then self._updated = os.clock() self._dirty = false - if not config.get 'Lua.window.progressBar' then + if not config.get(self._scp.uri, 'Lua.window.progressBar') then return end proto.request('window/workDoneProgress/create', { @@ -106,7 +107,7 @@ function mt:_update() if not self._showed then return end - if not config.get 'Lua.window.progressBar' then + if not config.get(self._scp.uri, 'Lua.window.progressBar') then self:remove() return end @@ -143,14 +144,16 @@ function m.update() end ---创建一个进度条 +---@param scp scope ---@param title string # 标题 ---@param delay number # 至少经过这么久之后才会显示出来 -function m.create(title, delay) +function m.create(scp, title, delay) local prog = setmetatable({ _token = nextToken(), _title = title, _clock = os.clock(), _delay = delay, + _scp = scp, }, mt) m.map[prog._token] = prog diff --git a/script/provider/capability.lua b/script/provider/capability.lua index b712defc..439978d6 100644 --- a/script/provider/capability.lua +++ b/script/provider/capability.lua @@ -81,11 +81,11 @@ function m.getIniter() }, executeCommandProvider = { commands = { - 'lua.removeSpace:' .. sp:get_id(), - 'lua.solve:' .. sp:get_id(), - 'lua.jsonToLua:' .. sp:get_id(), - 'lua.setConfig:' .. sp:get_id(), - 'lua.autoRequire:' .. sp:get_id(), + 'lua.removeSpace', + 'lua.solve', + 'lua.jsonToLua', + 'lua.setConfig', + 'lua.autoRequire', }, }, foldingRangeProvider = true, diff --git a/script/provider/completion.lua b/script/provider/completion.lua index ec31858a..ec2ac6c7 100644 --- a/script/provider/completion.lua +++ b/script/provider/completion.lua @@ -2,6 +2,7 @@ local proto = require 'proto' local nonil = require 'without-check-nil' local client = require 'client' local config = require 'config' +local ws = require 'workspace' local isEnable = false @@ -13,15 +14,17 @@ local function allWords() list[#list+1] = c mark[c] = true end - local postfix = config.get 'Lua.completion.postfix' - if postfix ~= '' and not mark[postfix] then - list[#list+1] = postfix - mark[postfix] = true + for _, scp in ipairs(ws.folders) do + local postfix = config.get(scp.uri, 'Lua.completion.postfix') + if postfix ~= '' and not mark[postfix] then + list[#list+1] = postfix + mark[postfix] = true + end end return list end -local function enable() +local function enable(uri) if isEnable then return end @@ -46,7 +49,7 @@ local function enable() }) end -local function disable() +local function disable(uri) if not isEnable then return end @@ -67,18 +70,18 @@ local function disable() }) end -config.watch(function (key, value) +config.watch(function (uri, key, value) if key == 'Lua.completion.enable' then if value == true then - enable() + enable(uri) else - disable() + disable(uri) end end if key == 'Lua.completion.postfix' then - if config.get 'Lua.completion.enable' then - disable() - enable() + if config.get(uri, 'Lua.completion.enable') then + disable(uri) + enable(uri) end end end) diff --git a/script/provider/diagnostic.lua b/script/provider/diagnostic.lua index 03b5e179..4a6706bb 100644 --- a/script/provider/diagnostic.lua +++ b/script/provider/diagnostic.lua @@ -13,9 +13,9 @@ local converter = require 'proto.converter' ---@class diagnosticProvider local m = {} -m._start = false m.cache = {} m.sleepRest = 0.0 +m.coroutineUri = setmetatable({}, { __mode = 'k' }) local function concat(t, sep) if type(t) ~= 'table' then @@ -29,7 +29,7 @@ local function buildSyntaxError(uri, err) local message = lang.script('PARSER_'..err.type, err.info) if err.version then - local version = err.info and err.info.version or config.get 'Lua.runtime.version' + local version = err.info and err.info.version or config.get(uri, 'Lua.runtime.version') message = message .. ('(%s)'):format(lang.script('DIAG_NEED_VERSION' , concat(err.version, '/') , version @@ -159,7 +159,7 @@ function m.syntaxErrors(uri, ast) pcall(function () for _, err in ipairs(ast.errs) do - if not config.get 'Lua.diagnostics.disable'[err.type:lower():gsub('_', '-')] then + if not config.get(uri, 'Lua.diagnostics.disable')[err.type:lower():gsub('_', '-')] then results[#results+1] = buildSyntaxError(uri, err) end end @@ -169,11 +169,7 @@ function m.syntaxErrors(uri, ast) end function m.diagnostics(uri, diags) - if not m._start then - return - end - - if not ws.isReady() then + if not ws.isReady(uri) then return end @@ -189,11 +185,11 @@ end ---@async function m.doDiagnostic(uri) - if not config.get 'Lua.diagnostics.enable' then + if not config.get(uri, 'Lua.diagnostics.enable') then return end if files.isLibrary(uri) then - local status = config.get 'Lua.diagnostics.libraryFiles' + local status = config.get(uri, 'Lua.diagnostics.libraryFiles') if status == 'Disable' then return elseif status == 'Opened' then @@ -203,7 +199,7 @@ function m.doDiagnostic(uri) end end if ws.isIgnored(uri) then - local status = config.get 'Lua.diagnostics.ignoredFiles' + local status = config.get(uri, 'Lua.diagnostics.ignoredFiles') if status == 'Disable' then return elseif status == 'Opened' then @@ -215,18 +211,19 @@ function m.doDiagnostic(uri) await.delay() - local ast = files.getState(uri) - if not ast then + local state = files.getState(uri) + if not state then m.clear(uri) return end local version = files.getVersion(uri) + local scp = ws.getScope(uri) local prog <close> = progress.create(lang.script.WINDOW_DIAGNOSING, 0.5) prog:setMessage(ws.getRelativePath(uri)) - local syntax = m.syntaxErrors(uri, ast) + local syntax = m.syntaxErrors(uri, state) local diags = {} local function pushResult() tracy.ZoneBeginN 'mergeSyntaxAndDiags' @@ -252,25 +249,25 @@ function m.doDiagnostic(uri) end end - if await.hasID 'diagnosticsAll' then - m.checkStepResult = nil + if await.hasID('diagnosticsScope:' .. uri) then + scp:set('diagStepPush', nil) else local clock = os.clock() - m.checkStepResult = function () + scp:set('diagStepPush', function () if os.clock() - clock >= 0.2 then pushResult() clock = os.clock() end - end + end) end m.diagnostics(uri, diags) pushResult() - m.checkStepResult = nil + scp:set('diagStepPush', nil) end function m.refresh(uri) - if not m._start then + if not ws.isReady(uri) then return end await.close('diag:' .. uri) @@ -281,12 +278,12 @@ function m.refresh(uri) m.clearCache(uri) xpcall(m.doDiagnostic, log.error, uri) end - m.diagnosticsAll() + m.diagnosticsScope(uri) end, 'files.version') end ---@async -local function askForDisable() +local function askForDisable(uri) if m.dontAskedForDisable then return end @@ -318,6 +315,7 @@ local function askForDisable() key = 'Lua.diagnostics.workspaceDelay', action = 'set', value = delay * 1000, + uri = uri, } } elseif item.title == lang.script.WINDOW_DISABLE_DIAGNOSTIC then @@ -326,25 +324,27 @@ local function askForDisable() key = 'Lua.diagnostics.workspaceDelay', action = 'set', value = -1, + uri = uri, } } end end -function m.diagnosticsAll(force) - if not force and not config.get 'Lua.diagnostics.enable' then - m.clearAll() +function m.diagnosticsScope(uri, force) + if not ws.isReady(uri) then return end - if not m._start then + if not force and not config.get(uri, 'Lua.diagnostics.enable') then + m.clearAll() return end - local delay = config.get 'Lua.diagnostics.workspaceDelay' / 1000 + local delay = config.get(uri, 'Lua.diagnostics.workspaceDelay') / 1000 if not force and delay < 0 then return end - await.close 'diagnosticsAll' + await.close ('diagnosticsScope:' .. uri) await.call(function () ---@async + m.coroutineUri[coroutine.running()] = uri await.sleep(delay) m.diagnosticsAllClock = os.clock() local clock = os.clock() @@ -353,7 +353,10 @@ function m.diagnosticsAll(force) bar:onCancel(function () log.debug('Cancel workspace diagnostics') cancelled = true - await.call(askForDisable) + ---@async + await.call(function () + askForDisable(uri) + end) end) local uris = files.getAllUris() for i, uri in ipairs(uris) do @@ -368,31 +371,26 @@ function m.diagnosticsAll(force) end bar:remove() log.debug('全文诊断耗时:', os.clock() - clock) - end, 'files.version', 'diagnosticsAll') -end - -function m.start() - m._start = true - m.diagnosticsAll() -end - -function m.pause() - m._start = false - await.close 'diagnosticsAll' + end, 'files.version', ('diagnosticsScope:' .. uri)) end -function m.checkStepResult() - if await.hasID 'diagnosticsAll' then +function m.checkStepResult(uri) + if await.hasID('diagnosticsScope:' .. uri) then return end + local scp = ws.getScope(uri) + local stepPush = scp:get 'diagStepPush' + if stepPush then + stepPush() + end end ---@async -function m.checkWorkspaceDiag() - if not await.hasID 'diagnosticsAll' then +function m.checkWorkspaceDiag(uri) + if not await.hasID('diagnosticsScope:' .. uri) then return end - local speedRate = config.get 'Lua.diagnostics.workspaceRate' + local speedRate = config.get(uri, 'Lua.diagnostics.workspaceRate') if speedRate <= 0 or speedRate >= 100 then return end @@ -413,6 +411,12 @@ function m.checkWorkspaceDiag() return false end +ws.watch(function (ev, uri) + if ev == 'reload' then + m.diagnosticsScope(uri) + end +end) + files.watch(function (ev, uri) ---@async if ev == 'remove' then m.clear(uri) @@ -435,17 +439,19 @@ end) await.watch(function (ev, co) ---@async if ev == 'delay' then - if m.checkStepResult then - m.checkStepResult() + local uri = m.coroutineUri[co] + if not uri then + return end - return m.checkWorkspaceDiag() + m.checkStepResult(uri) + return m.checkWorkspaceDiag(uri) end end) -config.watch(function (key, value, oldValue) +config.watch(function (uri, key, value, oldValue) if key:find 'Lua.diagnostics' then if value ~= oldValue then - m.diagnosticsAll() + m.diagnosticsScope(uri) end end end) diff --git a/script/provider/init.lua b/script/provider/init.lua index a69fbab3..dfba351a 100644 --- a/script/provider/init.lua +++ b/script/provider/init.lua @@ -1 +1,2 @@ +require 'provider.diagnostic' return require 'provider.provider' diff --git a/script/provider/provider.lua b/script/provider/provider.lua index cf06090b..77c45778 100644 --- a/script/provider/provider.lua +++ b/script/provider/provider.lua @@ -16,30 +16,41 @@ local cfgLoader = require 'config.loader' local converter = require 'proto.converter' local filewatch = require 'filewatch' local json = require 'json' +local scope = require 'workspace.scope' ---@async -local function updateConfig() - local clientConfig = cfgLoader.loadClientConfig() - if clientConfig then - log.debug('load config from client') - config.update(clientConfig, json.null) +local function updateConfig(uri) + local specified = cfgLoader.loadLocalConfig(uri, CONFIGPATH) + if specified then + log.debug('Load config from specified', CONFIGPATH) + log.debug(util.dump(specified)) + -- watch directory + filewatch.watch(workspace.getAbsolutePath(uri, CONFIGPATH):gsub('[^/\\]+$', '')) + config.update(scope.override, specified, json.null) end - local rc = cfgLoader.loadRCConfig('.luarc.json') - if rc then - log.debug('load config from luarc') - config.update(rc, json.null) - end + for _, folder in ipairs(scope.folders) do + local uri = folder.uri - local cfg = cfgLoader.loadLocalConfig(CONFIGPATH) - if cfg then - log.debug('load config from local', CONFIGPATH) - -- watch directory - filewatch.watch(workspace.getAbsolutePath(CONFIGPATH):gsub('[^/\\]+$', '')) - config.update(cfg, json.null) + local clientConfig = cfgLoader.loadClientConfig(uri) + if clientConfig then + log.debug('Load config from client', uri) + log.debug(util.dump(clientConfig)) + config.update(folder, clientConfig, json.null) + end + + local rc = cfgLoader.loadRCConfig(uri, '.luarc.json') + if rc then + log.debug('Load config from luarc.json', uri) + log.debug(util.dump(rc)) + config.update(folder, rc, json.null) + end end - log.debug('loaded config dump:', util.dump(config.dump())) + local global = cfgLoader.loadClientConfig() + log.debug('Load config from client', 'fallback') + log.debug(util.dump(global)) + config.update(scope.fallback, global, json.null) end ---@class provider @@ -55,13 +66,22 @@ function m.register(method) end filewatch.event(function (changes) ---@async - local configPath = CONFIGPATH and workspace.getAbsolutePath(CONFIGPATH) - local rcPath = workspace.getAbsolutePath('.luarc.json') for _, change in ipairs(changes) do - if change.path == configPath - or change.path == rcPath then - updateConfig() - return + if (CONFIGPATH and util.stringEndWith(change.path, CONFIGPATH)) then + for _, scp in ipairs(workspace.folders) do + local configPath = workspace.getAbsolutePath(scp.uri, CONFIGPATH) + if change.path == configPath then + updateConfig(scp.uri) + end + end + end + if util.stringEndWith(change.path, '.luarc.json') then + for _, scp in ipairs(workspace.folders) do + local rcPath = workspace.getAbsolutePath(scp.uri, '.luarc.json') + if change.path == rcPath then + updateConfig(scp.uri) + end + end end end end) @@ -69,8 +89,19 @@ end) m.register 'initialize' { function (params) client.init(params) - config.init() - workspace.initPath(params.rootUri) + + if params.rootUri then + workspace.initRoot(params.rootUri) + end + + if params.workspaceFolders then + for _, folder in ipairs(params.workspaceFolders) do + workspace.create(folder.uri) + end + elseif params.rootUri then + workspace.create(params.rootUri) + end + return { capabilities = cap.getIniter(), serverInfo = { @@ -189,9 +220,9 @@ m.register 'workspace/didRenameFiles' { m.register 'textDocument/didOpen' { ---@async function (params) - workspace.awaitReady() local doc = params.textDocument local uri = files.getRealUri(doc.uri) + workspace.awaitReady(uri) local text = doc.text files.setText(uri, text, true, doc.version) files.open(uri) @@ -213,10 +244,10 @@ m.register 'textDocument/didClose' { m.register 'textDocument/didChange' { ---@async function (params) - workspace.awaitReady() local doc = params.textDocument local changes = params.contentChanges local uri = files.getRealUri(doc.uri) + workspace.awaitReady(uri) --log.debug('changes', util.dump(changes)) local text = files.getOriginText(uri) or '' local rows = files.getCachedRows(uri) @@ -230,13 +261,13 @@ m.register 'textDocument/hover' { abortByFileUpdate = true, ---@async function (params) - if not config.get('Lua.hover.enable') then - return - end local doc = params.textDocument local uri = files.getRealUri(doc.uri) + if not config.get(uri, 'Lua.hover.enable') then + return + end if not workspace.isReady() then - local count, max = workspace.getLoadProcess() + local count, max = workspace.getLoadingProcess(uri) return { contents = { value = lang.script('HOVER_WS_LOADING', count, max), @@ -268,13 +299,13 @@ m.register 'textDocument/definition' { abortByFileUpdate = true, ---@async function (params) - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_DEFINITION, 0.5) - local core = require 'core.definition' local uri = files.getRealUri(params.textDocument.uri) + workspace.awaitReady(uri) if not files.exists(uri) then return nil end + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_DEFINITION, 0.5) + local core = require 'core.definition' local pos = converter.unpackPosition(uri, params.position) local result = core(uri, pos) if not result then @@ -307,13 +338,13 @@ m.register 'textDocument/typeDefinition' { abortByFileUpdate = true, ---@async function (params) - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_TYPE_DEFINITION, 0.5) - local core = require 'core.type-definition' local uri = files.getRealUri(params.textDocument.uri) + workspace.awaitReady(uri) if not files.exists(uri) then return nil end + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_TYPE_DEFINITION, 0.5) + local core = require 'core.type-definition' local pos = converter.unpackPosition(uri, params.position) local result = core(uri, pos) if not result then @@ -346,13 +377,13 @@ m.register 'textDocument/references' { abortByFileUpdate = true, ---@async function (params) - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_REFERENCE, 0.5) - local core = require 'core.reference' local uri = files.getRealUri(params.textDocument.uri) + workspace.awaitReady(uri) if not files.exists(uri) then return nil end + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_REFERENCE, 0.5) + local core = require 'core.reference' local pos = converter.unpackPosition(uri, params.position) local result = core(uri, pos) if not result then @@ -397,13 +428,13 @@ m.register 'textDocument/rename' { abortByFileUpdate = true, ---@async function (params) - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_RENAME, 0.5) - local core = require 'core.rename' local uri = files.getRealUri(params.textDocument.uri) + workspace.awaitReady(uri) if not files.exists(uri) then return nil end + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_RENAME, 0.5) + local core = require 'core.rename' local pos = converter.unpackPosition(uri, params.position) local result = core.rename(uri, pos, params.newName) if not result then @@ -449,7 +480,7 @@ m.register 'textDocument/completion' { function (params) local uri = files.getRealUri(params.textDocument.uri) if not workspace.isReady() then - local count, max = workspace.getLoadProcess() + local count, max = workspace.getLoadingProcess(uri) return { { label = lang.script('HOVER_WS_LOADING', count, max),textEdit = { @@ -471,7 +502,7 @@ m.register 'textDocument/completion' { return nil end local triggerCharacter = params.context and params.context.triggerCharacter - if config.get 'editor.acceptSuggestionOnEnter' ~= 'off' then + if config.get(uri, 'editor.acceptSuggestionOnEnter') ~= 'off' then if triggerCharacter == '\n' or triggerCharacter == '{' or triggerCharacter == ',' then @@ -600,15 +631,15 @@ m.register 'textDocument/signatureHelp' { abortByFileUpdate = true, ---@async function (params) - if not config.get 'Lua.signatureHelp.enable' then + local uri = files.getRealUri(params.textDocument.uri) + if not config.get(uri, 'Lua.signatureHelp.enable') then return nil end - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SIGNATURE, 0.5) - local uri = files.getRealUri(params.textDocument.uri) + workspace.awaitReady(uri) if not files.exists(uri) then return nil end + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SIGNATURE, 0.5) local pos = converter.unpackPosition(uri, params.position) local core = require 'core.signature' local results = core(uri, pos) @@ -646,11 +677,11 @@ m.register 'textDocument/documentSymbol' { abortByFileUpdate = true, ---@async function (params) - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SYMBOL, 0.5) - local core = require 'core.document-symbol' local uri = files.getRealUri(params.textDocument.uri) + workspace.awaitReady(uri) + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SYMBOL, 0.5) + local core = require 'core.document-symbol' local symbols = core(uri) if not symbols then return nil @@ -749,7 +780,6 @@ m.register 'workspace/symbol' { abortByFileUpdate = true, ---@async function (params) - workspace.awaitReady() local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_WS_SYMBOL, 0.5) local core = require 'core.workspace-symbol' @@ -783,7 +813,7 @@ m.register 'textDocument/semanticTokens/full' { ---@async function (params) local uri = files.getRealUri(params.textDocument.uri) - workspace.awaitReady() + workspace.awaitReady(uri) local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SEMANTIC_FULL, 0.5) local core = require 'core.semantic-tokens' local results = core(uri, 0, math.huge) @@ -798,7 +828,7 @@ m.register 'textDocument/semanticTokens/range' { ---@async function (params) local uri = files.getRealUri(params.textDocument.uri) - workspace.awaitReady() + workspace.awaitReady(uri) local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SEMANTIC_RANGE, 0.5) local core = require 'core.semantic-tokens' local cache = files.getOpenedCache(uri) @@ -882,7 +912,9 @@ m.register '$/status/click' { end if result == titleDiagnostic then local diagnostic = require 'provider.diagnostic' - diagnostic.diagnosticsAll(true) + for _, scp in ipairs(workspace.folders) do + diagnostic.diagnosticsScope(scp.uri, true) + end end end } @@ -891,10 +923,10 @@ m.register 'textDocument/onTypeFormatting' { abortByFileUpdate = true, ---@async function (params) - workspace.awaitReady() + local uri = files.getRealUri(params.textDocument.uri) + workspace.awaitReady(uri) local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_TYPE_FORMATTING, 0.5) local ch = params.ch - local uri = files.getRealUri(params.textDocument.uri) if not files.exists(uri) then return nil end @@ -928,12 +960,12 @@ m.register '$/cancelRequest' { m.register '$/requestHint' { ---@async function (params) - local core = require 'core.hint' - if not config.get 'Lua.hint.enable' then + local uri = files.getRealUri(params.textDocument.uri) + if not config.get(uri, 'Lua.hint.enable') then return end - workspace.awaitReady() - local uri = files.getRealUri(params.textDocument.uri) + workspace.awaitReady(uri) + local core = require 'core.hint' local start, finish = converter.unpackRange(uri, params.range) local results = core(uri, start, finish) local hintResults = {} @@ -952,13 +984,13 @@ m.register '$/requestHint' { do ---@async local function updateHint(uri) - if not config.get 'Lua.hint.enable' then + if not config.get(uri, 'Lua.hint.enable') then return end local id = 'updateHint' .. uri await.close(id) await.setID(id) - workspace.awaitReady() + workspace.awaitReady(uri) local visibles = files.getVisibles(uri) if not visibles then return @@ -966,7 +998,7 @@ do await.close(id) await.setID(id) await.delay() - workspace.awaitReady() + workspace.awaitReady(uri) local edits = {} local hint = require 'core.hint' local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_HINT, 0.5) @@ -999,15 +1031,21 @@ do end local function refreshStatusBar() - local value = config.get 'Lua.window.statusBar' - if value then + local valid = true + for _, scp in ipairs(workspace.folders) do + if not config.get(scp.uri, 'Lua.window.statusBar') then + valid = false + break + end + end + if valid then proto.notify('$/status/show') else proto.notify('$/status/hide') end end -config.watch(function (key, value) +config.watch(function (uri, key, value) if key == 'Lua.window.statusBar' then refreshStatusBar() end @@ -1016,7 +1054,7 @@ end) m.register '$/status/refresh' { refreshStatusBar } files.watch(function (ev, uri) - if not workspace.isReady() then + if not workspace.isReady(uri) then return end if ev == 'update' diff --git a/script/provider/semantic-tokens.lua b/script/provider/semantic-tokens.lua index 68db9b7f..f6acb280 100644 --- a/script/provider/semantic-tokens.lua +++ b/script/provider/semantic-tokens.lua @@ -20,7 +20,7 @@ local function toArray(map) end local dontShowAgain = false -local function enable() +local function enable(uri) if isEnable then return end @@ -47,7 +47,7 @@ local function enable() }, } }) - if config.get 'editor.semanticHighlighting.enabled' == 'configuredByTheme' and not dontShowAgain then + if config.get(uri, 'editor.semanticHighlighting.enabled') == 'configuredByTheme' and not dontShowAgain then proto.request('window/showMessageRequest', { type = define.MessageType.Info, message = lang.script.WINDOW_CHECK_SEMANTIC, @@ -80,7 +80,7 @@ local function enable() end end -local function disable() +local function disable(uri) if not isEnable then return end @@ -109,16 +109,16 @@ local function refresh() proto.request('workspace/semanticTokens/refresh', json.null) end -config.watch(function (key, value, oldValue) +config.watch(function (uri, key, value, oldValue) if key == 'Lua.color.mode' then if value == 'Semantic' or value == 'SemanticEnhanced' then if isEnable and value ~= oldValue then refresh() else - enable() + enable(uri) end else - disable() + disable(uri) end end if key:find '^Lua.runtime' diff --git a/script/service/service.lua b/script/service/service.lua index 825ebfba..087ee85e 100644 --- a/script/service/service.lua +++ b/script/service/service.lua @@ -11,6 +11,7 @@ local lang = require 'language' local ws = require 'workspace' local time = require 'bee.time' local fw = require 'filewatch' +local furi = require 'file-uri' local m = {} m.type = 'service' @@ -189,8 +190,14 @@ function m.reportStatus() else info.text = '😺Lua' end + + local roots = {} + for i, scp in ipairs(ws.folders) do + roots[i] = furi.decode(scp.uri) + end + info.tooltip = lang.script('WINDOW_LUA_STATUS', { - ws = ws.path or '', + ws = table.concat(roots, ';'), ast = files.astCount, max = files.fileCount, mem = collectgarbage('count') / 1000, @@ -226,8 +233,6 @@ function m.start() require 'provider' m.startTimer() - - ws.reload() end return m diff --git a/script/service/telemetry.lua b/script/service/telemetry.lua index dac72f3f..d20719b0 100644 --- a/script/service/telemetry.lua +++ b/script/service/telemetry.lua @@ -72,9 +72,12 @@ local function pushErrorLog(link) )) end +local validMap = {} +local isValid = false + timer.wait(5, function () timer.loop(300, function () - if not config.get 'Lua.telemetry.enable' then + if not isValid then return end local suc, link = pcall(net.connect, 'tcp', 'moe-moe.love', 11577) @@ -93,7 +96,7 @@ timer.wait(5, function () end end)() timer.loop(1, function () - if not config.get 'Lua.telemetry.enable' then + if not isValid then return end net.update() @@ -102,8 +105,26 @@ end) local m = {} -function m.updateConfig() - if config.get 'Lua.telemetry.enable' ~= nil then +function m.updateConfig(uri, value) + validMap[uri or ''] = value + isValid = config.get(nil, 'Lua.telemetry.enable') + if isValid == false then + return + end + -- one false, all false + for _, v in pairs(validMap) do + if v == false then + isValid = false + return + end + end + for _, v in pairs(validMap) do + if v == true then + isValid = true + break + end + end + if isValid ~= nil then return end if m.hasShowedMessage then @@ -151,9 +172,9 @@ function m.updateConfig() end) end -config.watch(function (key) +config.watch(function (uri, key, value) if key == 'Lua.telemetry.enable' then - m.updateConfig() + m.updateConfig(uri, value) end end) diff --git a/script/utility.lua b/script/utility.lua index f06dd21e..004e8066 100644 --- a/script/utility.lua +++ b/script/utility.lua @@ -708,4 +708,12 @@ function m.getUpvalue(f, name) return nil, false end +function m.stringStartWith(str, head) + return str:sub(1, #head) == head +end + +function m.stringEndWith(str, tail) + return str:sub(-#tail) == tail +end + return m diff --git a/script/vm/getDocs.lua b/script/vm/getDocs.lua index 3a0765bf..c635b2a9 100644 --- a/script/vm/getDocs.lua +++ b/script/vm/getDocs.lua @@ -10,14 +10,14 @@ local noder = require 'core.noder' ---获取class与alias ---@param name? string ---@return parser.guide.object[] -function vm.getDocDefines(name) +function vm.getDocDefines(uri, name) local cache = vm.getCache 'getDocDefines' if cache[name] then return cache[name] end local results = {} if name == '*' then - for noders in collector.each('def:dn:') do + for noders in collector.each(uri, 'def:dn:') do for id in noder.eachID(noders) do if id:sub(1, 3) == 'dn:' and not id:find(noder.SPLIT_CHAR) then @@ -31,7 +31,7 @@ function vm.getDocDefines(name) end else local id = 'dn:' .. name - for noders in collector.each('def:' .. id) do + for noders in collector.each(uri, 'def:' .. id) do for source in noder.eachSource(noders, id) do if source.type == 'doc.class.name' or source.type == 'doc.alias.name' then @@ -153,7 +153,7 @@ local function isDeprecated(value) return true elseif doc.type == 'doc.version' then local valids = vm.getValidVersions(doc) - if not valids[config.get 'Lua.runtime.version'] then + if not valids[config.get(guide.getUri(value), 'Lua.runtime.version')] then value._deprecated = true return true end diff --git a/script/vm/getGlobals.lua b/script/vm/getGlobals.lua index 92fd1c8e..f6646559 100644 --- a/script/vm/getGlobals.lua +++ b/script/vm/getGlobals.lua @@ -14,7 +14,7 @@ function vm.hasGlobalSets(name) return collector.has(id) end -function vm.getGlobalSets(name) +function vm.getGlobalSets(uri, name) local cache = vm.getCache 'getGlobalSets' if cache[name] then return cache[name] @@ -22,7 +22,7 @@ function vm.getGlobalSets(name) local results = {} cache[name] = results if name == '*' then - for noders in collector.each('def:g:') do + for noders in collector.each(uri, 'def:g:') do for id in noder.eachID(noders) do if id:sub(1, 2) == 'g:' and not id:find(noder.SPLIT_CHAR) then @@ -41,7 +41,7 @@ function vm.getGlobalSets(name) else id = ('g:%s'):format(noder.STRING_CHAR, name) end - for noders in collector.each('def:' .. id) do + for noders in collector.each(uri, 'def:' .. id) do for source in noder.eachSource(noders, id) do if guide.isSet(source) then results[#results+1] = source diff --git a/script/vm/getLinks.lua b/script/vm/getLinks.lua index 77d869f8..7fe31e5e 100644 --- a/script/vm/getLinks.lua +++ b/script/vm/getLinks.lua @@ -20,7 +20,7 @@ local function getFileLinks(uri) if not args or not args[1] or args[1].type ~= 'string' then return end - local uris = rpath.findUrisByRequirePath(args[1][1]) + local uris = rpath.findUrisByRequirePath(uri, args[1][1]) for _, u in ipairs(uris) do if not links[u] then links[u] = {} diff --git a/script/workspace/loading.lua b/script/workspace/loading.lua new file mode 100644 index 00000000..cbcfa567 --- /dev/null +++ b/script/workspace/loading.lua @@ -0,0 +1,170 @@ +local progress = require 'progress' +local lang = require 'language' +local await = require 'await' +local files = require 'files' +local config = require 'config.config' +local client = require 'client' +local pub = require 'pub.pub' + +---@class workspace.loading +---@field scp scope +---@field _bar progress +---@field _stash function[] +---@field _refs uri[] +---@field _cache table<uri, boolean> +local mt = {} +mt.__index = mt + +mt._loadLock = false +mt.read = 0 +mt.max = 0 +mt.preload = 0 + +function mt:update() + self._bar:setMessage(('%d/%d'):format(self.read, self.max)) + self._bar:setPercentage(self.read / self.max * 100.0) +end + +---@param uri uri +function mt:checkMaxPreload(uri) + local max = config.get(uri, 'Lua.workspace.maxPreload') + if self.preload <= max then + return true + end + if self.scp:get 'hasHintedMaxPreload' then + return false + end + self.scp:set('hasHintedMaxPreload', true) + client.requestMessage('Info' + , lang.script('MWS_MAX_PRELOAD', max) + , { + lang.script + } + , function (_, index) + if index == 1 then + client.setConfig { + { + key = 'Lua.workspace.maxPreload', + uri = self.scp.uri, + action = 'set', + value = max + math.max(1000, max), + } + } + end + end + ) + return false +end + +---@param uri uri +---@param libraryUri boolean +---@async +function mt:loadFile(uri, libraryUri) + if files.isLua(uri) then + if not libraryUri then + self.preload = self.preload + 1 + if not self:checkMaxPreload(uri) then + return + end + end + self.max = self.max + 1 + self:update() + pub.task('loadFile', uri, function (content) + self._stash[#self._stash+1] = function () + self.read = self.read + 1 + self:update() + if not content then + return + end + if self._cache[uri] then + return + end + log.info(('Preload file at: %s , size = %.3f KB'):format(uri, #content / 1024.0)) + self._cache[uri] = true + files.setText(uri, content, false) + files.addRef(uri) + if libraryUri then + log.info('++++As library of:', libraryUri) + files.setLibraryUri(self.scp, uri, libraryUri) + end + end + end) + elseif files.isDll(uri) then + self.max = self.max + 1 + self:update() + pub.task('loadFile', uri, function (content) + self._stash[#self._stash+1] = function () + self.read = self.read + 1 + self:update() + if not content then + return + end + if self._cache[uri] then + return + end + log.info(('Preload dll at: %s , size = %.3f KB'):format(uri, #content / 1024.0)) + self._cache[uri] = true + files.saveDll(uri, content) + files.addRef(uri) + if libraryUri then + log.info('++++As library of:', libraryUri) + end + end + end) + await.delay() + end +end + +function mt:loadStashed() + self:update() + if self._loadLock then + return + end + self._loadLock = true + ---@async + await.call(function () + while true do + local loader = table.remove(self._stash) + if not loader then + break + end + loader() + await.delay() + end + self._loadLock = false + end) +end + +---@async +function mt:loadAll() + while self.read < self.max do + log.info(('Loaded %d/%d files'):format(self.read, self.max)) + self:loadStashed() + await.sleep(0.1) + end +end + +function mt:remove() + self._bar:remove() +end + +function mt:__close() + self:remove() +end + +---@class workspace.loading.manager +local m = {} + +---@return workspace.loading +function m.create(scp) + local loading = setmetatable({ + scp = scp, + _bar = progress.create(lang.script('WORKSPACE_LOADING', scp.uri)), + _stash = {}, + _cache = {}, + }, mt) + scp:set('cachedUris', loading._cache) + return loading +end + +return m diff --git a/script/workspace/require-path.lua b/script/workspace/require-path.lua index 0fec6cc9..84541808 100644 --- a/script/workspace/require-path.lua +++ b/script/workspace/require-path.lua @@ -8,8 +8,8 @@ local m = {} m.cache = {} --- `aaa/bbb/ccc.lua` 与 `?.lua` 将返回 `aaa.bbb.cccc` -local function getOnePath(path, searcher) - local separator = config.get 'Lua.completion.requireSeparator' +local function getOnePath(uri, path, searcher) + local separator = config.get(uri, 'Lua.completion.requireSeparator') local stemPath = path : gsub('%.[^%.]+$', '') : gsub('[/\\%.]+', separator) @@ -27,9 +27,9 @@ local function getOnePath(path, searcher) return nil end -function m.getVisiblePath(path) - local searchers = config.get 'Lua.runtime.path' - local strict = config.get 'Lua.runtime.pathStrict' +function m.getVisiblePath(suri, path) + local searchers = config.get(suri, 'Lua.runtime.path') + local strict = config.get(suri, 'Lua.runtime.pathStrict') path = workspace.normalize(path) local uri = furi.encode(path) local libraryPath = files.getLibraryPath(uri) @@ -63,7 +63,7 @@ function m.getVisiblePath(path) else searcher = searcher :gsub('[/\\]+', '/') end - local expect = getOnePath(cutedPath, searcher) + local expect = getOnePath(suri, cutedPath, searcher) if expect then local mySearcher = searcher if head then @@ -82,11 +82,11 @@ end --- 查找符合指定require path的所有uri ---@param path string -function m.findUrisByRequirePath(path) +function m.findUrisByRequirePath(suri, path) if type(path) ~= 'string' then return {} end - local separator = config.get 'Lua.completion.requireSeparator' + local separator = config.get(suri, 'Lua.completion.requireSeparator') local fspath = path:gsub('%' .. separator, '/') local vm = require 'vm' local cache = vm.getCache 'findUrisByRequirePath' @@ -106,7 +106,7 @@ function m.findUrisByRequirePath(path) end for uri in files.eachFile() do - local infos = m.getVisiblePath(furi.decode(uri)) + local infos = m.getVisiblePath(suri, furi.decode(uri)) for _, info in ipairs(infos) do local fsexpect = info.expect:gsub('%' .. separator, '/') if fsexpect == fspath then @@ -135,7 +135,7 @@ files.watch(function (ev) end end) -config.watch(function (key, value, oldValue) +config.watch(function (uri, key, value, oldValue) if key == 'Lua.completion.requireSeparator' or key == 'Lua.runtime.path' or key == 'Lua.runtime.pathStrict' then diff --git a/script/workspace/scope.lua b/script/workspace/scope.lua new file mode 100644 index 00000000..fc71e60c --- /dev/null +++ b/script/workspace/scope.lua @@ -0,0 +1,126 @@ +---@alias scope.type '"override"'|'"folder"'|'"fallback"' + +---@class scope +---@field type scope.type +---@field uri? uri +---@field _links table<uri, boolean> +---@field _data table<string, any> +local mt = {} +mt.__index = mt + +---@param uri uri +function mt:addLink(uri) + self._links[uri] = true +end + +---@param uri uri +function mt:removeLink(uri) + self._links[uri] = nil +end + +function mt:removeAllLinks() + self._links = {} +end + +---@param uri uri +---@return boolean +function mt:isChildUri(uri) + if not self.uri then + return true + end + return uri:sub(1, #self.uri) == self.uri +end + +---@param uri uri +---@return boolean +function mt:isLinkedUri(uri) + for linkUri in pairs(self._links) do + if uri:sub(1, #linkUri) == linkUri then + return true + end + end + return false +end + +---@param k string +---@param v any +function mt:set(k, v) + self._data[k] = v + return v +end + +---@param k string +---@return any +function mt:get(k) + return self._data[k] +end + +---@param scopeType scope.type +---@return scope +local function createScope(scopeType) + local scope = setmetatable({ + type = scopeType, + _links = {}, + _data = {}, + }, mt) + + return scope +end + +---@class scope.manager +local m = {} + +---@type scope[] +m.folders = {} +m.override = createScope 'override' +m.fallback = createScope 'fallback' + +---@param uri uri +---@return scope +function m.createFolder(uri) + local scope = createScope 'folder' + scope.uri = uri + + local inserted = false + for i, otherScope in ipairs(m.folders) do + if #uri > #otherScope.uri then + table.insert(m.folders, i, scope) + inserted = true + break + end + end + if not inserted then + table.insert(m.folders, scope) + end + + return scope +end + +---@param uri uri +---@return scope +function m.getFolder(uri) + for _, scope in ipairs(m.folders) do + if not uri or scope:isChildUri(uri) then + return scope + end + end + return nil +end + +---@param uri uri +---@return scope +function m.getLinkedScope(uri) + if m.override and m.override:isLinkedUri(uri) then + return m.override + end + for _, scope in ipairs(m.folders) do + if scope:isLinkedUri(uri) then + return scope + end + end + if m.fallback:isLinkedUri(uri) then + return m.fallback + end +end + +return m diff --git a/script/workspace/workspace.lua b/script/workspace/workspace.lua index 49d158c1..b8ddedb5 100644 --- a/script/workspace/workspace.lua +++ b/script/workspace/workspace.lua @@ -6,44 +6,56 @@ local config = require 'config' local glob = require 'glob' local platform = require 'bee.platform' local await = require 'await' -local proto = require 'proto.proto' -local lang = require 'language' -local library = require 'library' -local progress = require 'progress' -local define = require "proto.define" local client = require 'client' local plugin = require 'plugin' local util = require 'utility' local fw = require 'filewatch' +local scope = require 'workspace.scope' +local loading = require 'workspace.loading' +---@class workspace local m = {} m.type = 'workspace' -m.nativeVersion = -1 -m.libraryVersion = -1 -m.nativeMatcher = nil -m.fileLoaded = 0 -m.fileFound = 0 -m.waitingReady = {} -m.requireCache = {} -m.cache = {} -m.watchers = {} -m.matchOption = {} +---@type scope[] +m.folders = {} +m.watchList = {} + +--- 注册事件 +---@param callback async fun(ev: string, uri: uri) +function m.watch(callback) + m.watchList[#m.watchList+1] = callback +end ---- 初始化工作区 -function m.initPath(uri) - log.info('Workspace inited: ', uri) - if not uri then - return +function m.onWatch(ev, uri) + for _, callback in ipairs(m.watchList) do + await.call(function () + callback(ev, uri) + end) end - m.uri = uri - m.path = m.normalize(furi.decode(uri)) - plugin.workspace = m.path +end + +function m.initRoot(uri) + m.rootUri = uri + log.info('Workspace init root: ', uri) + local logPath = fs.path(LOGPATH) / (uri:gsub('[/:]+', '_') .. '.log') client.logMessage('Log', 'Log path: ' .. furi.encode(logPath:string())) log.info('Log path: ', logPath) log.init(ROOT, logPath) +end + +--- 初始化工作区 +function m.create(uri) + log.info('Workspace create: ', uri) + local path = m.normalize(furi.decode(uri)) + fw.watch(path) + local scp = scope.createFolder(uri) + m.folders[#m.folders+1] = scp +end - fw.watch(m.path) +function m.getRootUri(uri) + local scp = m.getScope(uri) + return scp.uri end local globInteferFace = { @@ -75,25 +87,21 @@ local globInteferFace = { --- 创建排除文件匹配器 ---@async -function m.getNativeMatcher() - if not m.path then - return nil - end - if m.nativeMatcher then - return m.nativeMatcher +---@param scp scope +function m.getNativeMatcher(scp) + if scp:get 'nativeMatcher' then + return scp:get 'nativeMatcher' end local pattern = {} - -- config.get 'files.exclude' - for path, ignore in pairs(config.get 'files.exclude') do + for path, ignore in pairs(config.get(scp.uri, 'files.exclude')) do if ignore then log.info('Ignore by exclude:', path) pattern[#pattern+1] = path end end - -- config.get 'workspace.useGitIgnore' - if config.get 'Lua.workspace.useGitIgnore' then - local buf = pub.awaitTask('loadFile', furi.encode(m.path .. '/.gitignore')) + if config.get(scp.uri, 'Lua.workspace.useGitIgnore') then + local buf = pub.awaitTask('loadFile', m.rootUri .. '/.gitignore') if buf then for line in buf:gmatch '[^\r\n]+' do if line:sub(1, 1) ~= '#' then @@ -102,7 +110,7 @@ function m.getNativeMatcher() end end end - buf = pub.awaitTask('loadFile', furi.encode(m.path .. '/.git/info/exclude')) + buf = pub.awaitTask('loadFile', m.rootUri .. '/.git/info/exclude') if buf then for line in buf:gmatch '[^\r\n]+' do if line:sub(1, 1) ~= '#' then @@ -112,9 +120,8 @@ function m.getNativeMatcher() end end end - -- config.get 'workspace.ignoreSubmodules' - if config.get 'Lua.workspace.ignoreSubmodules' then - local buf = pub.awaitTask('loadFile', furi.encode(m.path .. '/.gitmodules')) + if config.get(scp.uri, 'Lua.workspace.ignoreSubmodules') then + local buf = pub.awaitTask('loadFile', m.rootUri .. '/.gitmodules') if buf then for path in buf:gmatch('path = ([^\r\n]+)') do log.info('Ignore by .gitmodules:', path) @@ -122,66 +129,73 @@ function m.getNativeMatcher() end end end - -- config.get 'workspace.library' - for path in pairs(config.get 'Lua.workspace.library') do - path = m.getAbsolutePath(path) + for path in pairs(config.get(scp.uri, 'Lua.workspace.library')) do + path = m.getAbsolutePath(scp.uri, path) if path then log.info('Ignore by library:', path) pattern[#pattern+1] = path end end - -- config.get 'workspace.ignoreDir' - for _, path in ipairs(config.get 'Lua.workspace.ignoreDir') do + for _, path in ipairs(config.get(scp.uri, 'Lua.workspace.ignoreDir')) do log.info('Ignore directory:', path) pattern[#pattern+1] = path end - m.nativeMatcher = glob.gitignore(pattern, m.matchOption, globInteferFace) - m.nativeMatcher:setOption('root', m.path) + local matcher = glob.gitignore(pattern, { + root = furi.decode(scp.uri), + ignoreCase = platform.OS == 'Windows', + }, globInteferFace) - m.nativeVersion = config.get 'version' - return m.nativeMatcher + scp:set('nativeMatcher', matcher) + return matcher end --- 创建代码库筛选器 -function m.getLibraryMatchers() - if m.libraryMatchers then - return m.libraryMatchers +---@param scp scope +function m.getLibraryMatchers(scp) + if scp:get 'libraryMatcher' then + return scp:get 'libraryMatcher' end local librarys = {} - for path in pairs(config.get 'Lua.workspace.library') do - path = m.getAbsolutePath(path) + for path in pairs(config.get(scp.uri, 'Lua.workspace.library')) do + path = m.getAbsolutePath(scp.uri, path) if path then librarys[m.normalize(path)] = true end end - if library.metaPath then - librarys[m.normalize(library.metaPath)] = true + -- TODO + if scp:get 'metaPath' then + librarys[m.normalize(scp:get 'metaPath')] = true end - m.libraryMatchers = {} + + local matchers = {} for path in pairs(librarys) do if fs.exists(fs.path(path)) then local nPath = fs.absolute(fs.path(path)):string() - local matcher = glob.gitignore(true, m.matchOption, globInteferFace) - matcher:setOption('root', path) - log.debug('getLibraryMatchers', path, nPath) - m.libraryMatchers[#m.libraryMatchers+1] = { - path = nPath, + local matcher = glob.gitignore(true, { + root = path, + ignoreCase = platform.OS == 'Windows', + }, globInteferFace) + matchers[#matchers+1] = { + uri = furi.encode(nPath), matcher = matcher } end end - m.libraryVersion = config.get 'version' - return m.libraryMatchers + scp:set('libraryMatcher', matchers) + + return matchers end --- 文件是否被忽略 ---@async +---@param uri uri function m.isIgnored(uri) - local path = m.getRelativePath(uri) - local ignore = m.getNativeMatcher() + local scp = m.getScope(uri) + local path = m.getRelativePath(uri) + local ignore = m.getNativeMatcher(scp) if not ignore then return false end @@ -200,187 +214,77 @@ function m.isValidLuaUri(uri) return true end -local function loadFileFactory(root, progressData, isLibrary) - return function (path) ---@async - local uri = furi.encode(path) - if files.isLua(uri) then - if not isLibrary and progressData.preload >= config.get 'Lua.workspace.maxPreload' then - if not m.hasHitMaxPreload then - m.hasHitMaxPreload = true - proto.request('window/showMessageRequest', { - type = define.MessageType.Info, - message = lang.script('MWS_MAX_PRELOAD', config.get 'Lua.workspace.maxPreload'), - actions = { - { - title = lang.script.WINDOW_INCREASE_UPPER_LIMIT, - }, - { - title = lang.script.WINDOW_CLOSE, - } - } - }, function (item) - if not item then - return - end - if item.title == lang.script.WINDOW_INCREASE_UPPER_LIMIT then - client.setConfig { - { - key = 'Lua.workspace.maxPreload', - action = 'set', - value = config.get 'Lua.workspace.maxPreload' - + math.max(1000, config.get 'Lua.workspace.maxPreload'), - } - } - end - end) - end - return - end - if not isLibrary then - progressData.preload = progressData.preload + 1 - end - progressData.max = progressData.max + 1 - progressData:update() - pub.task('loadFile', uri, function (text) - local loader = function () - if text then - log.info(('Preload file at: %s , size = %.3f KB'):format(uri, #text / 1024.0)) - if isLibrary then - log.info('++++As library of:', root) - files.setLibraryPath(uri, root) - end - files.setText(uri, text, false) - else - files.remove(uri) - end - progressData.read = progressData.read + 1 - progressData:update() - end - if progressData.loaders then - progressData.loaders[#progressData.loaders+1] = loader - else - loader() - end - end) - end - if files.isDll(uri) then - progressData.max = progressData.max + 1 - progressData:update() - pub.task('loadFile', uri, function (content) - if content then - log.info(('Preload file at: %s , size = %.3f KB'):format(uri, #content / 1024.0)) - if isLibrary then - log.info('++++As library of:', root) - end - files.saveDll(uri, content) - end - progressData.read = progressData.read + 1 - progressData:update() - end) - end - await.delay() - end -end - ---@async function m.awaitLoadFile(uri) - local progressBar <close> = progress.create(lang.script.WORKSPACE_LOADING) - local progressData = { - max = 0, - read = 0, - preload = 0, - update = function (self) - progressBar:setMessage(('%d/%d'):format(self.read, self.max)) - progressBar:setPercentage(self.read / self.max * 100) + local scp = m.getScope(uri) + local ld <close> = loading.create(scp) + local native = m.getNativeMatcher(scp) + log.info('Scan files at:', uri) + ---@async + native:scan(furi.decode(uri), function (path) + ld:loadFile(furi.encode(path)) + end) + ld:loadAll() +end + +function m.removeFile(uri) + for _, scp in ipairs(m.folders) do + if scp:isChildUri(uri) + or scp:isLinkedUri(uri) then + local cachedUris = scp:get 'cachedUris' + if cachedUris[uri] then + cachedUris[uri] = nil + files.delRef(uri) + end end - } - local nativeLoader = loadFileFactory(m.path, progressData) - local native = m.getNativeMatcher() - if native then - log.info('Scan files at:', furi.decode(uri)) - native:scan(furi.decode(uri), nativeLoader) end end --- 预读工作区内所有文件 ---@async -function m.awaitPreload() - local diagnostic = require 'provider.diagnostic' - await.close 'preload' - await.setID 'preload' +---@param scp scope +function m.awaitPreload(scp) + await.close('preload:' .. scp.uri) + await.setID('preload:' .. scp.uri) await.sleep(0.1) - diagnostic.pause() - m.libraryMatchers = nil - m.nativeMatcher = nil - m.fileLoaded = 0 - m.fileFound = 0 - m.cache = {} - for i, watchers in ipairs(m.watchers) do - watchers() - m.watchers[i] = nil - end - local progressBar <close> = progress.create(lang.script.WORKSPACE_LOADING) - local progressData = { - max = 0, - read = 0, - preload = 0, - loaders = {}, - update = function (self) - progressBar:setMessage(('%d/%d'):format(self.read, self.max)) - progressBar:setPercentage(self.read / self.max * 100) - m.fileLoaded = self.read - m.fileFound = self.max - end - } - log.info('Preload start.') - local nativeLoader = loadFileFactory(m.path, progressData) - local native = m.getNativeMatcher() - local librarys = m.getLibraryMatchers() - if native then - log.info('Scan files at:', m.path) - native:scan(m.path, nativeLoader) - end - for _, library in ipairs(librarys) do - local libraryLoader = loadFileFactory(library.path, progressData, true) - log.info('Scan library at:', library.path) - library.matcher:scan(library.path, libraryLoader) - m.watchers[#m.watchers+1] = fw.watch(library.path) - end - - local isLoadingFiles = false - local function loadSomeFiles() - if isLoadingFiles then - return + + local watchers = scp:get 'watchers' + if watchers then + for _, dispose in ipairs(watchers) do + dispose() end - await.call(function () ---@async - isLoadingFiles = true - while true do - local loader = table.remove(progressData.loaders) - if not loader then - break - end - loader() - await.delay() - end - isLoadingFiles = false - end) end + watchers = {} + scp:set('watchers', watchers) - log.info(('Found %d files.'):format(progressData.max)) - while true do - loadSomeFiles() - log.info(('Loaded %d/%d files'):format(progressData.read, progressData.max)) - progressData:update() - if progressData.read >= progressData.max then - break - end - await.sleep(0.1) + local ld <close> = loading.create(scp) + scp:set('loading', ld) + + log.info('Preload start:', scp.uri) + + local native = m.getNativeMatcher(scp) + local librarys = m.getLibraryMatchers(scp) + + do + log.info('Scan files at:', m.rootUri) + ---@async + native:scan(furi.decode(scp.uri), function (path) + ld:loadFile(furi.encode(path)) + end) end - progressBar:remove() - log.info('Preload finish.') + for _, libMatcher in ipairs(librarys) do + log.info('Scan library at:', libMatcher.uri) + ---@async + libMatcher.matcher:scan(furi.decode(libMatcher.uri), function (path) + ld:loadFile(furi.encode(path), libMatcher.uri) + end) + watchers[#watchers+1] = fw.watch(furi.decode(libMatcher.uri)) + end - diagnostic.start() + log.info(('Found %d files at:'):format(ld.max), scp.uri) + ld:loadAll() + log.info('Preload finish at:', scp.uri) end --- 查找符合指定file path的所有uri @@ -391,7 +295,7 @@ function m.findUrisByFilePath(path) end local myUri = furi.encode(path) local vm = require 'vm' - local resultCache = vm.getCache 'findUrisByRequirePath.result' + local resultCache = vm.getCache 'findUrisByFilePath.result' if resultCache[path] then return resultCache[path] end @@ -431,16 +335,17 @@ function m.normalize(path) end ---@return string -function m.getAbsolutePath(path) +function m.getAbsolutePath(folderUri, path) if not path or path == '' then return nil end path = m.normalize(path) if fs.path(path):is_relative() then - if not m.path then + if not folderUri then return nil end - path = m.normalize(m.path .. '/' .. path) + local folderPath = furi.decode(folderUri) + path = m.normalize(folderPath .. '/' .. path) end return path end @@ -448,17 +353,20 @@ end ---@param uriOrPath uri|string ---@return string function m.getRelativePath(uriOrPath) - local path + local path, uri if uriOrPath:sub(1, 5) == 'file:' then path = furi.decode(uriOrPath) + uri = uriOrPath else path = uriOrPath + uri = furi.encode(uriOrPath) end - if not m.path then + local scp = m.getScope(uri) + if not scp.uri then local relative = m.normalize(path) return relative:gsub('^[/\\]+', '') end - local _, pos = m.normalize(path):find(m.path, 1, true) + local _, pos = m.normalize(path):find(furi.decode(scp.uri), 1, true) if pos then return m.normalize(path:sub(pos + 1)):gsub('^[/\\]+', '') else @@ -466,34 +374,18 @@ function m.getRelativePath(uriOrPath) end end -function m.isWorkspaceUri(uri) - if not m.uri then - return false - end - local ruri = m.uri - return uri:sub(1, #ruri) == ruri -end - ---- 获取工作区等级的缓存 -function m.getCache(name) - if not m.cache[name] then - m.cache[name] = {} - end - return m.cache[name] -end - -function m.flushCache() - m.cache = {} -end - -function m.reload() +---@param scp scope +function m.reload(scp) if not m.inited then return end if TEST then return end - await.call(m.awaitReload) + ---@async + await.call(function () + m.awaitReload(scp) + end) end function m.init() @@ -501,43 +393,100 @@ function m.init() return end m.inited = true - m.reload() + if m.rootUri then + for _, folder in ipairs(scope.folders) do + m.reload(folder) + end + else + m.reload(scope.fallback) + end +end + +---@param scp scope +function m.flushFiles(scp) + local cachedUris = scp:get 'cachedUris' + if not cachedUris then + return + end + scp:set('cachedUris', nil) + for uri in pairs(cachedUris) do + files.delRef(uri) + end +end + +---@param scp scope +function m.resetFiles(scp) + local cachedUris = scp:get 'cachedUris' + if not cachedUris then + return + end + for uri in pairs(cachedUris) do + files.resetText(uri) + end end ---@async -function m.awaitReload() - m.ready = false - m.hasHitMaxPreload = false - files.flushAllLibrary() - files.removeAllClosed() - files.flushCache() - plugin.init() - m.awaitPreload() - m.ready = true - local waiting = m.waitingReady - m.waitingReady = {} - for _, waker in ipairs(waiting) do - waker() +---@param scp scope +function m.awaitReload(scp) + scp:set('ready', false) + scp:set('nativeMatcher', nil) + scp:set('libraryMatcher', nil) + m.flushFiles(scp) + files.flushAllLibrary(scp) + plugin.init(scp) + m.awaitPreload(scp) + scp:set('ready', true) + local waiting = scp:get('waitingReady') + if waiting then + scp:set('waitingReady', nil) + for _, waker in ipairs(waiting) do + waker() + end end + + m.onWatch('reload', scp.uri) +end + +---@param uri uri +---@return scope +function m.getScope(uri) + return scope.getFolder(uri) + or scope.getLinkedScope(uri) + or scope.fallback end ---等待工作目录加载完成 ---@async -function m.awaitReady() - if m.isReady() then +function m.awaitReady(uri) + if m.isReady(uri) then return end + local scp = m.getScope(uri) + local waitingReady = scp:get('waitingReady') + or scp:set('waitingReady', {}) await.wait(function (waker) - m.waitingReady[#m.waitingReady+1] = waker + waitingReady[#waitingReady+1] = waker end) end -function m.isReady() - return m.ready == true +---@param uri uri +function m.isReady(uri) + local scp = m.getScope(uri) + if scp.type == 'fallback' then + return true + end + return scp:get('ready') == true end -function m.getLoadProcess() - return m.fileLoaded, m.fileFound +function m.getLoadingProcess(uri) + local scp = m.getScope(uri) + ---@type workspace.loading + local ld = scp:get 'loading' + if ld then + return ld.read, ld.max + else + return 0, 0 + end end files.watch(function (ev, uri) ---@async @@ -548,35 +497,33 @@ files.watch(function (ev, uri) ---@async end end) -config.watch(function (key, value, oldValue) +config.watch(function (uri, key, value, oldValue) if key:find '^Lua.runtime' or key:find '^Lua.workspace' or key:find '^files' then if value ~= oldValue then - m.reload() + m.reload(m.getScope(uri)) end end end) fw.event(function (changes) ---@async - m.awaitReady() for _, change in ipairs(changes) do local path = change.path local uri = furi.encode(path) - if not m.isWorkspaceUri(uri) - and not files.isLibrary(uri) then - goto CONTINUE - end + m.awaitReady(uri) if change.type == 'create' then log.debug('FileChangeType.Created', uri) m.awaitLoadFile(uri) elseif change.type == 'delete' then log.debug('FileChangeType.Deleted', uri) files.remove(uri) + m.removeFile(uri) local childs = files.getChildFiles(uri) for _, curi in ipairs(childs) do log.debug('FileChangeType.Deleted.Child', curi) files.remove(curi) + m.removeFile(uri) end elseif change.type == 'change' then if m.isValidLuaUri(uri) then @@ -589,7 +536,10 @@ fw.event(function (changes) ---@async -- 排除类文件发生更改需要重新扫描 if filename == '.gitignore' or filename == '.gitmodules' then - m.reload() + local scp = m.getScope(uri) + if scp.type ~= 'fallback' then + m.reload(scp) + end break end end |