diff options
-rw-r--r-- | script/config/loader.lua | 3 | ||||
-rw-r--r-- | script/core/collector.lua | 195 | ||||
-rw-r--r-- | script/core/command/autoRequire.lua | 4 | ||||
-rw-r--r-- | script/core/completion/completion.lua | 12 | ||||
-rw-r--r-- | script/core/definition.lua | 2 | ||||
-rw-r--r-- | script/core/diagnostics/different-requires.lua | 2 | ||||
-rw-r--r-- | script/core/hover/description.lua | 2 | ||||
-rw-r--r-- | script/core/type-definition.lua | 2 | ||||
-rw-r--r-- | script/plugin.lua | 4 | ||||
-rw-r--r-- | script/vm/compiler.lua | 4 | ||||
-rw-r--r-- | script/workspace/require-path.lua | 288 | ||||
-rw-r--r-- | script/workspace/workspace.lua | 10 |
12 files changed, 174 insertions, 354 deletions
diff --git a/script/config/loader.lua b/script/config/loader.lua index bf662ef8..5cc7139f 100644 --- a/script/config/loader.lua +++ b/script/config/loader.lua @@ -42,6 +42,9 @@ end ---@return table? function m.loadLocalConfig(uri, filename) + if not filename then + return nil + end local scp = scope.getScope(uri) local path = workspace.getAbsolutePath(uri, filename) if not path then diff --git a/script/core/collector.lua b/script/core/collector.lua deleted file mode 100644 index 368a04ec..00000000 --- a/script/core/collector.lua +++ /dev/null @@ -1,195 +0,0 @@ -local scope = require 'workspace.scope' - ----@class collector ----@field subscribed table<uri, table<string, any>> ----@field collect table<string, table<uri, any>> -local mt = {} -mt.__index = mt - ---- 订阅一个名字 ----@param uri uri ----@param name string ----@param value any -function mt:subscribe(uri, name, value) - uri = uri or '<fallback>' - -- 订阅部分 - local uriSubscribed = self.subscribed[uri] - if not uriSubscribed then - uriSubscribed = {} - self.subscribed[uri] = uriSubscribed - end - uriSubscribed[name] = true - -- 收集部分 - local nameCollect = self.collect[name] - if not nameCollect then - nameCollect = {} - self.collect[name] = nameCollect - end - if value == nil then - value = true - end - nameCollect[uri] = value -end - ---- 丢弃掉某个 uri 中收集的所有信息 ----@param uri uri -function mt:dropUri(uri) - uri = uri or '<fallback>' - local uriSubscribed = self.subscribed[uri] - if not uriSubscribed then - return - end - self.subscribed[uri] = nil - for name in pairs(uriSubscribed) do - self.collect[name][uri] = nil - if not next(self.collect[name]) then - self.collect[name] = nil - end - end -end - -function mt:dropAll() - self.subscribed = {} - self.collect = {} -end - ---- 是否包含某个名字的订阅 ----@param uri uri ----@param name string ----@return boolean -function mt:has(uri, name) - if self:each(uri, name)() then - return true - else - return false - end -end - -local DUMMY_FUNCTION = function () end - ----@param scp scope -local function eachOfFolder(nameCollect, scp) - local curi, value - - ---@return any - ---@return uri - local function getNext() - curi, value = next(nameCollect, curi) - if not curi then - return nil, nil - end - if scp:isChildUri(curi) - or scp:isLinkedUri(curi) then - return value, curi - end - return getNext() - end - - return getNext -end - ----@param scp scope -local function eachOfLinked(nameCollect, scp) - local curi, value - - ---@return any - ---@return uri - local function getNext() - curi, value = next(nameCollect, curi) - if not curi then - return nil, nil - end - if scp:isChildUri(curi) - and scp:isLinkedUri(curi) then - return value, curi - end - - local cscp = scope.getFolder(curi) - or scope.getLinkedScope(curi) - or scope.fallback - - if cscp == scp - or cscp:isChildUri(scp.uri) - or cscp:isLinkedUri(scp.uri) then - return value, curi - end - - return getNext() - end - - return getNext -end - ----@param scp scope -local function eachOfFallback(nameCollect, scp) - local curi, value - - ---@return any - ---@return uri - local function getNext() - curi, value = next(nameCollect, curi) - if not curi then - return nil, nil - end - if scp:isLinkedUri(curi) then - return value, curi - end - - local cscp = scope.getFolder(curi) - or scope.getLinkedScope(curi) - or scope.fallback - - if cscp == scp then - return value, curi - end - - return getNext() - end - - return getNext -end - ---- 迭代某个名字的订阅 ----@param uri uri ----@param name string ----@return fun():any, uri -function mt:each(uri, name) - uri = uri or '<fallback>' - local nameCollect = self.collect[name] - if not nameCollect then - return DUMMY_FUNCTION - end - - local scp = scope.getFolder(uri) - - if scp then - return eachOfFolder(nameCollect, scp) - end - - scp = scope.getLinkedScope(uri) - - if scp then - return eachOfLinked(nameCollect, scp) - end - - return eachOfFallback(nameCollect, scope.fallback) -end - -local collectors = {} - -local function new() - return setmetatable({ - collect = {}, - subscribed = {}, - }, mt) -end - ----@return collector -return function (name) - if name then - collectors[name] = collectors[name] or new() - return collectors[name] - else - return new() - end -end diff --git a/script/core/command/autoRequire.lua b/script/core/command/autoRequire.lua index 020cacae..32911d92 100644 --- a/script/core/command/autoRequire.lua +++ b/script/core/command/autoRequire.lua @@ -71,7 +71,7 @@ local function askAutoRequire(uri, visiblePaths) local selects = {} local nameMap = {} for _, visible in ipairs(visiblePaths) do - local expect = visible.expect + local expect = visible.name local select = lang.script(expect) if not nameMap[select] then nameMap[select] = expect @@ -146,7 +146,7 @@ return function (data) return end table.sort(visiblePaths, function (a, b) - return #a.expect < #b.expect + return #a.name < #b.name end) local result = askAutoRequire(uri, visiblePaths) diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua index 2b806314..b2f74aa8 100644 --- a/script/core/completion/completion.lua +++ b/script/core/completion/completion.lua @@ -916,21 +916,21 @@ local function collectRequireNames(mode, myUri, literal, source, smark, position local infos = rpath.getVisiblePath(uri, path) local relative = workspace.getRelativePath(path) for _, info in ipairs(infos) do - if matchKey(literal, info.expect) then - if not collect[info.expect] then - collect[info.expect] = { + if matchKey(literal, info.name) then + if not collect[info.name] then + collect[info.name] = { textEdit = { start = smark and (source.start + #smark) or position, finish = smark and (source.finish - #smark) or position, - newText = smark and info.expect or util.viewString(info.expect), + newText = smark and info.name or util.viewString(info.name), }, path = relative, } end if vm.isMetaFile(uri) then - collect[info.expect][#collect[info.expect]+1] = ('* [[meta]](%s)'):format(uri) + collect[info.name][#collect[info.name]+1] = ('* [[meta]](%s)'):format(uri) else - collect[info.expect][#collect[info.expect]+1] = ([=[* [%s](%s) %s]=]):format( + collect[info.name][#collect[info.name]+1] = ([=[* [%s](%s) %s]=]):format( relative, uri, lang.script('HOVER_USE_LUA_PATH', info.searcher) diff --git a/script/core/definition.lua b/script/core/definition.lua index 09a5fcf1..866e8f84 100644 --- a/script/core/definition.lua +++ b/script/core/definition.lua @@ -77,7 +77,7 @@ local function checkRequire(source, offset) return nil end if libName == 'require' then - return rpath.findUrisByRequirePath(guide.getUri(source), literal) + return rpath.findUrisByRequireName(guide.getUri(source), literal) elseif libName == 'dofile' or libName == 'loadfile' then return workspace.findUrisByFilePath(literal) diff --git a/script/core/diagnostics/different-requires.lua b/script/core/diagnostics/different-requires.lua index de063c9f..22e3e681 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(uri, literal) + local results = rpath.findUrisByRequireName(uri, literal) if not results or #results ~= 1 then return end diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index 0bfe8cc8..2097e0a3 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -11,7 +11,7 @@ local furi = require 'file-uri' local function collectRequire(mode, literal, uri) local result, searchers if mode == 'require' then - result, searchers = rpath.findUrisByRequirePath(uri, literal) + result, searchers = rpath.findUrisByRequireName(uri, literal) elseif mode == 'dofile' or mode == 'loadfile' then result = ws.findUrisByFilePath(literal) diff --git a/script/core/type-definition.lua b/script/core/type-definition.lua index 791dfa83..a1c2b29f 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(guide.getUri(source), literal) + return rpath.findUrisByRequireName(guide.getUri(source), literal) elseif libName == 'dofile' or libName == 'loadfile' then return workspace.findUrisByFilePath(literal) diff --git a/script/plugin.lua b/script/plugin.lua index bdd02ea8..2a5ee27f 100644 --- a/script/plugin.lua +++ b/script/plugin.lua @@ -77,6 +77,10 @@ local function initPlugin(uri) local interface = {} scp:set('pluginInterface', interface) + if not scp.uri then + return + end + local pluginPath = ws.getAbsolutePath(scp.uri, config.get(scp.uri, 'Lua.runtime.plugin')) log.info('plugin path:', pluginPath) if not pluginPath then diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 46814830..fbc358a6 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -45,7 +45,7 @@ local function bindDocs(source) if not name then return true end - local uri = rpath.findUrisByRequirePath(guide.getUri(source), name)[1] + local uri = rpath.findUrisByRequireName(guide.getUri(source), name)[1] if not uri then return true end @@ -1450,7 +1450,7 @@ local compilerSwitch = util.switch() if not name or type(name) ~= 'string' then return end - local uri = rpath.findUrisByRequirePath(guide.getUri(func), name)[1] + local uri = rpath.findUrisByRequireName(guide.getUri(func), name)[1] if not uri then return end diff --git a/script/workspace/require-path.lua b/script/workspace/require-path.lua index 24a0b025..6c550e12 100644 --- a/script/workspace/require-path.lua +++ b/script/workspace/require-path.lua @@ -3,24 +3,37 @@ local files = require 'files' local furi = require 'file-uri' local workspace = require "workspace" local config = require 'config' -local collector = require 'core.collector' local scope = require 'workspace.scope' +local util = require 'utility' ---@class require-path local m = {} -local function addRequireName(suri, uri, name) - local separator = config.get(uri, 'Lua.completion.requireSeparator') - local fsname = name:gsub('%' .. separator, '/') - local scp = scope.getScope(suri) - ---@type collector - local clt = scp:get('requireName') or scp:set('requireName', collector()) - clt:subscribe(uri, fsname, name) +---@class require-manager +---@field scp scope +---@field nameMap table<string, string> +---@field visibleCache table<string, require-manager.visibleResult[]> +local mt = {} +mt.__index = mt + +---@alias require-manager.visibleResult { searcher: string, name: string } + +---@param scp scope +---@return require-manager +local function createRequireManager(scp) + return setmetatable({ + scp = scp, + nameMap = {}, + visibleCache = {}, + }, mt) end --- `aaa/bbb/ccc.lua` 与 `?.lua` 将返回 `aaa.bbb.cccc` -local function getOnePath(uri, path, searcher) - local separator = config.get(uri, 'Lua.completion.requireSeparator') +---@param path string +---@param searcher string +---@return string? +function mt:getRequireNameByPath(path, searcher) + local separator = config.get(self.scp.uri, 'Lua.completion.requireSeparator') local stemPath = path : gsub('%.[^%.]+$', '') : gsub('[/\\%.]+', separator) @@ -41,89 +54,118 @@ local function getOnePath(uri, path, searcher) return nil end -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) +---@param path string +---@return require-manager.visibleResult[] +function mt:getRequireResultByPath(path) local uri = furi.encode(path) - local scp = scope.getScope(suri) - if not scp:isChildUri(uri) - and not scp:isLinkedUri(uri) then - return {} - end - local libUri = files.getLibraryUri(suri, uri) + local searchers = config.get(self.scp.uri, 'Lua.runtime.path') + local strict = config.get(self.scp.uri, 'Lua.runtime.pathStrict') + local libUri = files.getLibraryUri(self.scp.uri, uri) local libraryPath = libUri and furi.decode(libUri) - local cache = scp:get('visiblePath') or scp:set('visiblePath', {}) - local result = cache[path] - if not result then - result = {} - cache[path] = result - for _, searcher in ipairs(searchers) do - local isAbsolute = searcher:match '^[/\\]' - or searcher:match '^%a+%:' - searcher = workspace.normalize(searcher) - local cutedPath = path - local currentPath = path - local head - local pos = 1 - if not isAbsolute then - if libraryPath then - currentPath = currentPath:sub(#libraryPath + 2) - else - currentPath = workspace.getRelativePath(uri) - end + local result = {} + for _, searcher in ipairs(searchers) do + local isAbsolute = searcher:match '^[/\\]' + or searcher:match '^%a+%:' + searcher = workspace.normalize(searcher) + local cutedPath = path + local currentPath = path + local head + local pos = 1 + if not isAbsolute then + if libraryPath then + currentPath = currentPath:sub(#libraryPath + 2) + else + currentPath = workspace.getRelativePath(uri) end - repeat - cutedPath = currentPath:sub(pos) - head = currentPath:sub(1, pos - 1) - pos = currentPath:match('[/\\]+()', pos) - if platform.OS == 'Windows' then - searcher = searcher :gsub('[/\\]+', '\\') - else - searcher = searcher :gsub('[/\\]+', '/') - end - local expect = getOnePath(suri, cutedPath, searcher) - if expect then - local mySearcher = searcher - if head then - mySearcher = head .. searcher - end - result[#result+1] = { - searcher = mySearcher, - expect = expect, - } - addRequireName(suri, uri, expect) - end - until not pos or strict end + repeat + cutedPath = currentPath:sub(pos) + head = currentPath:sub(1, pos - 1) + pos = currentPath:match('[/\\]+()', pos) + if platform.OS == 'Windows' then + searcher = searcher :gsub('[/\\]+', '\\') + else + searcher = searcher :gsub('[/\\]+', '/') + end + local name = self:getRequireNameByPath(cutedPath, searcher) + if name then + local mySearcher = searcher + if head then + mySearcher = head .. searcher + end + result[#result+1] = { + name = name, + searcher = mySearcher, + } + end + until not pos or strict end return result end ---- 查找符合指定require path的所有uri ----@param path string -function m.findUrisByRequirePath(suri, path) - if type(path) ~= 'string' then +---@param name string +function mt:addName(name) + local separator = config.get(self.scp.uri, 'Lua.completion.requireSeparator') + local fsname = name:gsub('%' .. separator, '/') + self.nameMap[fsname] = name +end + +---@return require-manager.visibleResult[] +function mt:getVisiblePath(path) + local uri = furi.encode(path) + if not self.scp:isChildUri(uri) + and not self.scp:isLinkedUri(uri) then return {} end - local separator = config.get(suri, 'Lua.completion.requireSeparator') - local fspath = path:gsub('%' .. separator, '/') - tracy.ZoneBeginN('findUrisByRequirePath') - local results = {} - local searchers = {} - - ---@type collector - local clt = scope.getScope(suri):get('requireName') - if clt then - for _, uri in clt:each(suri, fspath) do - if uri ~= suri then - local infos = m.getVisiblePath(suri, furi.decode(uri)) - for _, info in ipairs(infos) do - local fsexpect = info.expect:gsub('%' .. separator, '/') - if fsexpect == fspath then - results[#results+1] = uri - searchers[uri] = info.searcher + path = workspace.normalize(path) + local result = self.visibleCache[path] + if not result then + result = self:getRequireResultByPath(path) + self.visibleCache[path] = result + end + return result +end + +--- 查找符合指定require name的所有uri +---@param suri uri +---@param name string +---@return uri[] +---@return table<uri, string>? +function mt:findUrisByRequireName(suri, name) + if type(name) ~= 'string' then + return {} + end + local searchers = config.get(self.scp.uri, 'Lua.runtime.path') + local strict = config.get(self.scp.uri, 'Lua.runtime.pathStrict') + local separator = config.get(self.scp.uri, 'Lua.completion.requireSeparator') + local path = name:gsub('%' .. separator, '/') + local results = {} + local searcherMap = {} + + for _, searcher in ipairs(searchers) do + local fspath = searcher:gsub('%?', path) + if self.scp.uri then + local fullPath = workspace.getAbsolutePath(self.scp.uri, fspath) + local fullUri = furi.encode(fullPath) + if files.exists(fullUri) + and fullUri ~= suri then + results[#results+1] = fullUri + searcherMap[fullUri] = searcher + end + end + if not strict then + local tail = '/' .. furi.encode(fspath):gsub('^file:[/]*', '') + for uri in files.eachFile(self.scp.uri) do + if not searcherMap[uri] + and suri ~= uri + and util.stringEndWith(uri, tail) then + results[#results+1] = uri + local parentUri = files.getLibraryUri(self.scp.uri, uri) or self.scp.uri + if parentUri == nil or parentUri == '' then + parentUri = furi.encode '' end + local relative = uri:sub(#parentUri + 1):sub(1, - #tail) + searcherMap[uri] = workspace.normalize(relative .. searcher) end end end @@ -132,73 +174,42 @@ function m.findUrisByRequirePath(suri, path) for uri in files.eachDll() do local opens = files.getDllOpens(uri) or {} for _, open in ipairs(opens) do - if open == fspath then + if open == path then results[#results+1] = uri end end end - tracy.ZoneEnd() - return results, searchers -end - -local function createVisiblePath(uri) - for _, scp in ipairs(workspace.folders) do - m.getVisiblePath(scp.uri, furi.decode(uri)) - end - m.getVisiblePath(nil, furi.decode(uri)) -end - -local function removeVisiblePath(uri) - local path = furi.decode(uri) - path = workspace.normalize(path) - if not path then - return - end - for _, scp in ipairs(workspace.folders) do - if scp:get('visiblePath') then - scp:get('visiblePath')[path] = nil - end - ---@type collector - local clt = scp:get('requireName') - if clt then - clt:dropUri(uri) - end - end - if scope.fallback:get('visiblePath') then - scope.fallback:get('visiblePath')[path] = nil - end - ---@type collector - local clt = scope.fallback:get('requireName') - if clt then - clt:dropUri(uri) - end + return results, searcherMap end -function m.flush(suri) - local scp = scope.getScope(suri) - scp:set('visiblePath', {}) - ---@type collector - local clt = scp:get('requireName') - if clt then - clt:dropAll() - end - for uri in files.eachFile(suri) do - m.getVisiblePath(scp.uri, furi.decode(uri)) - end +---@param uri uri +---@param path string +---@return require-manager.visibleResult[] +function m.getVisiblePath(uri, path) + local scp = scope.getScope(uri) + ---@type require-manager + local mgr = scp:get 'requireManager' + or scp:set('requireManager', createRequireManager(scp)) + return mgr:getVisiblePath(path) end -for _, scp in ipairs(scope.folders) do - m.flush(scp.uri) +---@param uri uri +---@param name string +function m.findUrisByRequireName(uri, name) + local scp = scope.getScope(uri) + ---@type require-manager + local mgr = scp:get 'requireManager' + or scp:set('requireManager', createRequireManager(scp)) + return mgr:findUrisByRequireName(uri, name) end -m.flush(nil) files.watch(function (ev, uri) - if ev == 'create' then - createVisiblePath(uri) - end - if ev == 'remove' then - removeVisiblePath(uri) + if ev == 'create' or ev == 'delete' then + for _, scp in ipairs(workspace.folders) do + scp:set('requireManager', nil) + end + scope.fallback:set('requireManager', nil) end end) @@ -206,7 +217,8 @@ config.watch(function (uri, key, value, oldValue) if key == 'Lua.completion.requireSeparator' or key == 'Lua.runtime.path' or key == 'Lua.runtime.pathStrict' then - m.flush(uri) + local scp = scope.getScope(uri) + scp:set('requireManager', nil) end end) diff --git a/script/workspace/workspace.lua b/script/workspace/workspace.lua index b719c3e5..c2a5dfc0 100644 --- a/script/workspace/workspace.lua +++ b/script/workspace/workspace.lua @@ -394,16 +394,12 @@ function m.normalize(path) return path end ----@return string? +---@param folderUri uri +---@param path string +---@return string 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 folderUri then - return nil - end local folderPath = furi.decode(folderUri) path = m.normalize(folderPath .. '/' .. path) end |