local platform = require 'bee.platform' 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' ---@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) end --- `aaa/bbb/ccc.lua` 与 `?.lua` 将返回 `aaa.bbb.cccc` local function getOnePath(uri, path, searcher) local separator = config.get(uri, 'Lua.completion.requireSeparator') local stemPath = path : gsub('%.[^%.]+$', '') : gsub('[/\\%.]+', separator) local stemSearcher = searcher : gsub('%.[^%.]+$', '') : gsub('[/\\%.]+', separator) local start = stemSearcher:match '()%?' or 1 if stemPath:sub(1, start - 1) ~= stemSearcher:sub(1, start - 1) then return nil end for pos = #stemPath, start, -1 do local word = stemPath:sub(start, pos) local newSearcher = stemSearcher:gsub('%?', (word:gsub('%%', '%%%%'))) if newSearcher == stemPath then return word end end 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) local uri = furi.encode(path) local scp = scope.getScope(suri) if not scp:isChildUri(uri) and not scp:isLinkedUri(uri) then return {} end local libraryPath = furi.decode(files.getLibraryUri(suri, uri)) 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 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 end return result end --- 查找符合指定require path的所有uri ---@param path string function m.findUrisByRequirePath(suri, path) if type(path) ~= 'string' then return {} end local separator = config.get(suri, 'Lua.completion.requireSeparator') local fspath = path:gsub('%' .. separator, '/') tracy.ZoneBeginN('findUrisByRequirePath') local results = {} local searchers = {} for uri in files.eachDll() do local opens = files.getDllOpens(uri) or {} for _, open in ipairs(opens) do if open == fspath then results[#results+1] = uri end end end ---@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 end end 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 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 end for _, scp in ipairs(scope.folders) do m.flush(scp.uri) end m.flush(nil) files.watch(function (ev, uri) if ev == 'create' then createVisiblePath(uri) end if ev == 'remove' then removeVisiblePath(uri) end end) 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) end end) return m