dotfiles from arch

This commit is contained in:
2025-09-28 11:39:12 +02:00
parent 75885729cd
commit d1c6923bbb
1358 changed files with 575835 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
local workspace = require 'workspace.workspace'
return workspace

View File

@@ -0,0 +1,228 @@
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 furi = require 'file-uri'
local pub = require 'pub'
---@class workspace.loading
---@field scp scope
---@field _bar progress
---@field _stash function[]
---@field _refs uri[]
---@field _cache table<uri, boolean>
---@field _sets function[]
---@field _removed boolean
local mt = {}
mt.__index = mt
mt._loadLock = false
mt.read = 0
mt.max = 0
mt.preload = 0
function mt:__close()
self:remove()
end
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('WINDOW_INCREASE_UPPER_LIMIT'),
}
, 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? uri
---@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()
---@async
self._stash[#self._stash+1] = function ()
if files.getFile(uri) then
self.read = self.read + 1
self:update()
if not self._cache[uri] then
files.addRef(uri)
end
self._cache[uri] = true
log.info(('Skip loaded file: %s'):format(uri))
else
local content = pub.awaitTask('loadFile', furi.decode(uri))
self.read = self.read + 1
self:update()
if not content then
return
end
if files.getFile(uri) then
log.info(('Skip loaded file: %s'):format(uri))
return
end
log.info(('Preload file at: %s , size = %.3f KB'):format(uri, #content / 1024.0))
--await.wait(function (waker)
-- self._sets[#self._sets+1] = waker
--end)
files.setText(uri, content, false)
files.compileState(uri)
if not self._cache[uri] then
files.addRef(uri)
end
self._cache[uri] = true
end
if libraryUri then
log.info('++++As library of:', libraryUri)
end
end
elseif files.isDll(uri) then
self.max = self.max + 1
self:update()
---@async
self._stash[#self._stash+1] = function ()
if files.getFile(uri) then
self.read = self.read + 1
self:update()
if not self._cache[uri] then
files.addRef(uri)
end
self._cache[uri] = true
log.info(('Skip loaded file: %s'):format(uri))
else
local content = pub.awaitTask('loadFile', furi.decode(uri))
self.read = self.read + 1
self:update()
if not content then
return
end
if files.getFile(uri) then
log.info(('Skip loaded file: %s'):format(uri))
return
end
log.info(('Preload dll at: %s , size = %.3f KB'):format(uri, #content / 1024.0))
--await.wait(function (waker)
-- self._sets[#self._sets+1] = waker
--end)
files.saveDll(uri, content)
if not self._cache[uri] then
files.addRef(uri)
end
self._cache[uri] = true
end
if libraryUri then
log.info('++++As library of:', libraryUri)
end
end
end
await.delay()
end
---@async
function mt:loadAll(fileName)
local startClock = os.clock()
log.info('Load files from disk:', fileName)
while self.read < self.max do
self:update()
local loader = table.remove(self._stash)
if loader then
await.call(loader)
await.delay()
else
await.sleep(0.1)
end
end
local loadedClock = os.clock()
log.info(('Loaded files takes [%.3f] sec: %s'):format(loadedClock - startClock, fileName))
self._bar:remove()
self._bar = progress.create(self.scp.uri, lang.script('WORKSPACE_LOADING', self.scp.uri), 0)
for i, set in ipairs(self._sets) do
await.delay()
set()
self.read = i
self:update()
end
log.info(('Compile files takes [%.3f] sec: %s'):format(os.clock() - loadedClock, fileName))
log.info('Loaded finish:', fileName)
end
function mt:remove()
if self._removed then
return
end
self._removed = true
self._bar:remove()
end
function mt:isRemoved()
return self._removed == true
end
---@class workspace.loading.manager
local m = {}
---@type table<workspace.loading, boolean>
m._loadings = setmetatable({}, { __mode = 'k' })
---@return workspace.loading
function m.create(scp)
local loading = setmetatable({
scp = scp,
_bar = progress.create(scp.uri, lang.script('WORKSPACE_LOADING', scp.uri), 0.5),
_stash = {},
_cache = {},
_sets = {},
}, mt)
m._loadings[loading] = true
return loading
end
function m.count()
local num = 0
for ld in pairs(m._loadings) do
if ld:isRemoved() then
m._loadings[ld] = nil
else
num = num + 1
end
end
return num
end
return m

View File

@@ -0,0 +1,315 @@
local platform = require 'bee.platform'
local files = require 'files'
local furi = require 'file-uri'
local workspace = require "workspace"
local config = require 'config'
local scope = require 'workspace.scope'
local util = require 'utility'
local plugin = require 'plugin'
---@class require-path
local m = {}
---@class require-manager
---@field scp scope
---@field nameMap table<string, string>
---@field visibleCache table<string, require-manager.visibleResult[]>
---@field requireCache table<string, table>
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 = {},
requireCache = {},
}, mt)
end
--- `aaa/bbb/ccc.lua` 与 `?.lua` 将返回 `aaa.bbb.cccc`
---@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)
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
---@param path string
---@return require-manager.visibleResult[]
function mt:getRequireResultByPath(path)
local vm = require 'vm'
local uri = furi.encode(path)
local result = {}
if vm.isMetaFile(uri) then
local metaName = vm.getMetaName(uri)
if metaName then
if vm.isMetaFileRequireable(uri) then
result[#result+1] = {
name = metaName,
searcher = '[[meta]]',
}
end
return result
end
end
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)
for _, searcher in ipairs(searchers) do
local isAbsolute = searcher:match '^[/\\]'
or searcher:match '^%a+%:'
searcher = files.normalize(searcher)
if searcher:sub(1, 1) == '.' then
strict = true
end
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 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
---@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
path = files.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 name string
---@return uri[]
---@return table<uri, string>?
function mt:searchUrisByRequireName(name)
local vm = require 'vm'
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 = {}
local excludes = {}
local pluginSuccess, pluginResults = plugin.dispatch('ResolveRequire', self.scp.uri, name)
if pluginSuccess and pluginResults ~= nil then
return pluginResults
end
for uri in files.eachFile(self.scp.uri) do
if vm.isMetaFileRequireable(uri) then
local metaName = vm.getMetaName(uri)
if metaName == name then
results[#results+1] = uri
return results
end
if metaName then
excludes[uri] = true
end
end
end
for _, searcher in ipairs(searchers) do
local fspath = searcher:gsub('%?', (path:gsub('%%', '%%%%')))
fspath = files.normalize(fspath)
local tail = '/' .. furi.encode(fspath):gsub('^file:[/]*', '')
for uri in files.eachFile(self.scp.uri) do
if not searcherMap[uri]
and not excludes[uri]
and util.stringEndWith(uri, tail)
and (not vm.isMetaFile(uri) or vm.isMetaFileRequireable(uri)) then
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)
if not strict
or relative == '/'
or relative == '' then
results[#results+1] = uri
searcherMap[uri] = files.normalize(relative .. searcher)
end
end
end
end
for uri in files.eachDll() do
local opens = files.getDllOpens(uri) or {}
for _, open in ipairs(opens) do
if open == path then
results[#results+1] = uri
end
end
end
return results, searcherMap
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 cache = self.requireCache[name]
if not cache then
local results, searcherMap = self:searchUrisByRequireName(name)
cache = {
results = results,
searcherMap = searcherMap,
}
self.requireCache[name] = cache
end
local results = {}
local searcherMap = {}
for _, uri in ipairs(cache.results) do
if uri ~= suri then
results[#results+1] = uri
searcherMap[uri] = cache.searcherMap and cache.searcherMap[uri]
end
end
return results, searcherMap
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
---@param uri uri
---@param name string
---@return uri[]
---@return table<uri, 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
---@param suri uri
---@param uri uri
---@param name string
---@return boolean
function m.isMatchedUri(suri, uri, name)
local searchers = config.get(suri, 'Lua.runtime.path')
local strict = config.get(suri, 'Lua.runtime.pathStrict')
local separator = config.get(suri, 'Lua.completion.requireSeparator')
local path = name:gsub('%' .. separator, '/')
for _, searcher in ipairs(searchers) do
local fspath = searcher:gsub('%?', (path:gsub('%%', '%%%%')))
fspath = files.normalize(fspath)
local tail = '/' .. furi.encode(fspath):gsub('^file:[/]*', '')
if util.stringEndWith(uri, tail) then
local parentUri = files.getLibraryUri(suri, uri) or uri
if parentUri == nil or parentUri == '' then
parentUri = furi.encode '/'
end
local relative = uri:sub(#parentUri + 1):sub(1, - #tail)
if not strict
or relative == '/'
or relative == '' then
return true
end
end
end
return false
end
files.watch(function (_ev, _uri)
for _, scp in ipairs(workspace.folders) do
scp:set('requireManager', nil)
end
scope.fallback:set('requireManager', nil)
end)
config.watch(function (uri, key, _value, _oldValue)
if key == 'Lua.completion.requireSeparator'
or key == 'Lua.runtime.path'
or key == 'Lua.runtime.pathStrict' then
local scp = scope.getScope(uri)
scp:set('requireManager', nil)
end
end)
return m

View File

@@ -0,0 +1,254 @@
local gc = require 'gc'
---@class scope.manager
local m = {}
---@alias scope.type '"override"'|'"folder"'|'"fallback"'
---@class scope
---@field type scope.type
---@field uri? uri
---@field folderName? string
---@field _links table<uri, boolean>
---@field _data table<string, any>
---@field _gc gc
---@field _removed? true
local mt = {}
mt.__index = mt
function mt:__tostring()
if self.uri then
return ('{scope|%s|%s}'):format(self.type, self.uri)
else
return ('{scope|%s}'):format(self.type)
end
end
---@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
---@return fun(): uri
---@return table<uri, true>
function mt:eachLink()
return next, self._links
end
---@param uri uri
---@return boolean
function mt:isChildUri(uri)
if not uri then
return false
end
if not self.uri then
return false
end
if self.uri == uri then
return true
end
if uri:sub(1, #self.uri) ~= self.uri then
return false
end
if uri:sub(#self.uri, #self.uri) == '/'
or uri:sub(#self.uri + 1, #self.uri + 1) == '/' then
return true
end
return false
end
---@param uri uri
---@return boolean
function mt:isLinkedUri(uri)
if not uri then
return false
end
for linkUri in pairs(self._links) do
if uri == linkUri then
return true
end
if uri:sub(1, #linkUri) ~= linkUri then
goto CONTINUE
end
if uri:sub(#linkUri, #linkUri) == '/'
or uri:sub(#linkUri + 1, #linkUri + 1) == '/' then
return true
end
::CONTINUE::
end
return false
end
---@param uri uri
---@return boolean
function mt:isVisible(uri)
return self:isChildUri(uri)
or self:isLinkedUri(uri)
or self == m.getScope(uri)
end
---@param uri uri
---@return uri?
function mt:getLinkedUri(uri)
if not uri then
return nil
end
for linkUri in pairs(self._links) do
if uri:sub(1, #linkUri) == linkUri then
return linkUri
end
end
return nil
end
---@param uri uri
---@return uri?
function mt:getRootUri(uri)
if self:isChildUri(uri) then
return self.uri
end
return self:getLinkedUri(uri)
end
---@param k string
---@param v any
function mt:set(k, v)
self._data[k] = v
return v
end
function mt:get(k)
return self._data[k]
end
---@return string
function mt:getName()
return self.uri or ('<' .. self.type .. '>')
end
---@return string?
function mt:getFolderName()
return self.folderName
end
function mt:gc(obj)
self._gc:add(obj)
end
function mt:flushGC()
self._gc:remove()
if self._removed then
return
end
self._gc = gc()
end
function mt:remove()
if self._removed then
return
end
self._removed = true
for i, scp in ipairs(m.folders) do
if scp == self then
table.remove(m.folders, i)
break
end
end
self:flushGC()
end
function mt:isRemoved()
return self._removed == true
end
---@param scopeType scope.type
---@return scope
local function createScope(scopeType)
local scope = setmetatable({
type = scopeType,
_links = {},
_data = {},
_gc = gc(),
}, mt)
return scope
end
function m.reset()
---@type scope[]
m.folders = {}
m.override = createScope 'override'
m.fallback = createScope 'fallback'
end
m.reset()
---@param uri uri
---@param folderName? string
---@return scope
function m.createFolder(uri, folderName)
local scope = createScope 'folder'
scope.uri = uri
scope.folderName = folderName
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 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
return nil
end
---@param uri? uri
---@return scope
function m.getScope(uri)
return uri and (m.getFolder(uri)
or m.getLinkedScope(uri))
or m.fallback
end
return m

View File

@@ -0,0 +1,615 @@
local pub = require 'pub'
local fs = require 'bee.filesystem'
local furi = require 'file-uri'
local files = require 'files'
local config = require 'config'
local glob = require 'glob'
local platform = require 'bee.platform'
local await = require 'await'
local client = require 'client'
local util = require 'utility'
local fw = require 'filewatch'
local scope = require 'workspace.scope'
local loading = require 'workspace.loading'
local inspect = require 'inspect'
local lang = require 'language'
---@class workspace
local m = {}
m.type = 'workspace'
m.watchList = {}
--- 注册事件
---@param callback async fun(ev: string, uri: uri)
function m.watch(callback)
m.watchList[#m.watchList+1] = callback
end
function m.onWatch(ev, uri)
for _, callback in ipairs(m.watchList) do
await.call(function ()
callback(ev, uri)
end)
end
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, folderName)
log.info('Workspace create: ', uri)
local scp = scope.createFolder(uri, folderName)
m.folders[#m.folders+1] = scp
if uri == furi.encode '/'
or uri == furi.encode(os.getenv 'HOME' or '') then
if not FORCE_ACCEPT_WORKSPACE then
client.showMessage('Error', lang.script('WORKSPACE_NOT_ALLOWED', furi.decode(uri)))
scp:set('bad root', true)
end
end
end
function m.remove(uri)
log.info('Workspace remove: ', uri)
for i, scp in ipairs(m.folders) do
if scp.uri == uri then
scp:remove()
table.remove(m.folders, i)
scp:set('ready', false)
scp:set('nativeMatcher', nil)
scp:set('libraryMatcher', nil)
scp:removeAllLinks()
m.flushFiles(scp)
return
end
end
end
function m.reset()
---@type scope[]
m.folders = {}
m.rootUri = nil
end
m.reset()
function m.getRootUri(uri)
local scp = scope.getScope(uri)
return scp.uri
end
local globInteferFace = {
type = function (path, data)
if data[path] then
return data[path]
end
local result
pcall(function ()
if fs.is_directory(fs.path(path)) then
result = 'directory'
data[path] = 'directory'
else
result = 'file'
data[path] = 'file'
end
end)
return result
end,
list = function (path, data)
if data[path] == 'file' then
return nil
end
local fullPath = fs.path(path)
if not fs.is_directory(fullPath) then
data[path] = 'file'
return nil
end
data[path] = true
local paths = {}
pcall(function ()
for fullpath, status in fs.pairs(fullPath) do
local pathString = fullpath:string()
paths[#paths+1] = pathString
local st = status:type()
if st == 'directory'
or st == 'symlink'
or st == 'junction' then
data[pathString] = 'directory'
else
data[pathString] = 'file'
end
end
end)
return paths
end
}
--- 创建排除文件匹配器
---@param scp scope
function m.getNativeMatcher(scp)
if scp:get 'nativeMatcher' then
return scp:get 'nativeMatcher'
end
local pattern = {}
for path, ignore in pairs(config.get(scp.uri, 'files.exclude')) do
if ignore then
log.debug('Ignore by exclude:', path)
pattern[#pattern+1] = path
end
end
if scp.uri and config.get(scp.uri, 'Lua.workspace.useGitIgnore') then
local buf = util.loadFile(furi.decode(scp.uri) .. '/.gitignore')
if buf then
for line in buf:gmatch '[^\r\n]+' do
if line:sub(1, 1) ~= '#' then
log.debug('Ignore by .gitignore:', line)
pattern[#pattern+1] = line
end
end
end
buf = util.loadFile(furi.decode(scp.uri).. '/.git/info/exclude')
if buf then
for line in buf:gmatch '[^\r\n]+' do
if line:sub(1, 1) ~= '#' then
log.debug('Ignore by .git/info/exclude:', line)
pattern[#pattern+1] = line
end
end
end
end
if scp.uri and config.get(scp.uri, 'Lua.workspace.ignoreSubmodules') then
local buf = util.loadFile(furi.decode(scp.uri) .. '/.gitmodules')
if buf then
for path in buf:gmatch('path = ([^\r\n]+)') do
log.debug('Ignore by .gitmodules:', path)
pattern[#pattern+1] = path
end
end
end
for _, path in ipairs(config.get(scp.uri, 'Lua.workspace.library')) do
path = m.getAbsolutePath(scp.uri, path)
if path then
log.debug('Ignore by library:', path)
debug[#pattern+1] = path
end
end
for _, path in ipairs(config.get(scp.uri, 'Lua.workspace.ignoreDir')) do
log.debug('Ignore directory:', path)
pattern[#pattern+1] = path
end
local matcher = glob.gitignore(pattern, {
root = scp.uri and furi.decode(scp.uri),
ignoreCase = platform.os == 'windows',
}, globInteferFace)
scp:set('nativeMatcher', matcher)
return matcher
end
--- 创建代码库筛选器
---@param scp scope
function m.getLibraryMatchers(scp)
if scp:get 'libraryMatcher' then
return scp:get 'libraryMatcher'
end
log.debug('Build library matchers:', scp)
local pattern = {}
for path, ignore in pairs(config.get(scp.uri, 'files.exclude')) do
if ignore then
log.debug('Ignore by exclude:', path)
pattern[#pattern+1] = path
end
end
for _, path in ipairs(config.get(scp.uri, 'Lua.workspace.ignoreDir')) do
log.debug('Ignore directory:', path)
pattern[#pattern+1] = path
end
local librarys = {}
for _, path in ipairs(config.get(scp.uri, 'Lua.workspace.library')) do
path = m.getAbsolutePath(scp.uri, path)
if path then
librarys[files.normalize(path)] = true
end
end
local metaPaths = scp:get 'metaPaths'
log.debug('meta path:', inspect(metaPaths))
if metaPaths then
for _, metaPath in ipairs(metaPaths) do
librarys[files.normalize(metaPath)] = true
end
end
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(pattern, {
root = path,
ignoreCase = platform.os == 'windows',
}, globInteferFace)
matchers[#matchers+1] = {
uri = furi.encode(nPath),
matcher = matcher
}
end
end
scp:set('libraryMatcher', matchers)
--log.debug('library matcher:', inspect(matchers))
return matchers
end
--- 文件是否被忽略
---@param uri uri
function m.isIgnored(uri)
local scp = scope.getScope(uri)
local path = m.getRelativePath(uri)
local ignore = m.getNativeMatcher(scp)
if not ignore then
return false
end
return ignore(path)
end
---@async
function m.isValidLuaUri(uri)
if not files.isLua(uri) then
return false
end
if m.isIgnored(uri)
and not files.isLibrary(uri) then
return false
end
return true
end
---@async
function m.awaitLoadFile(uri)
m.awaitReady(uri)
local scp = scope.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)
local uri = files.getRealUri(furi.encode(path))
scp:get('cachedUris')[uri] = true
ld:loadFile(uri)
end)
ld:loadAll(uri)
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 and cachedUris[uri] then
cachedUris[uri] = nil
files.delRef(uri)
end
end
end
end
--- 预读工作区内所有文件
---@async
---@param scp scope
function m.awaitPreload(scp)
await.close('preload:' .. scp:getName())
await.setID('preload:' .. scp:getName())
await.sleep(0.1)
scp:flushGC()
if scp:isRemoved() then
return
end
local ld <close> = loading.create(scp)
scp:set('loading', ld)
log.info('Preload start:', scp:getName())
local native = m.getNativeMatcher(scp)
local librarys = m.getLibraryMatchers(scp)
if scp.uri and not scp:get('bad root') then
log.info('Scan files at:', scp:getName())
scp:gc(fw.watch(files.normalize(furi.decode(scp.uri)), true, function (path)
local rpath = m.getRelativePath(path)
if native(rpath) then
return false
end
return true
end))
local count = 0
---@async
native:scan(furi.decode(scp.uri), function (path)
local uri = files.getRealUri(furi.encode(path))
scp:get('cachedUris')[uri] = true
ld:loadFile(uri)
end, function (_) ---@async
count = count + 1
if count == 100000 then
client.showMessage('Warning', lang.script('WORKSPACE_SCAN_TOO_MUCH', count, furi.decode(scp.uri)))
end
end)
end
for _, libMatcher in ipairs(librarys) do
log.info('Scan library at:', libMatcher.uri)
local count = 0
scp:gc(fw.watch(furi.decode(libMatcher.uri), true, function (path)
local rpath = m.getRelativePath(path)
if libMatcher.matcher(rpath) then
return false
end
return true
end))
scp:addLink(libMatcher.uri)
---@async
libMatcher.matcher:scan(furi.decode(libMatcher.uri), function (path)
local uri = files.getRealUri(furi.encode(path))
scp:get('cachedUris')[uri] = true
ld:loadFile(uri, libMatcher.uri)
end, function () ---@async
count = count + 1
if count == 100000 then
client.showMessage('Warning', lang.script('WORKSPACE_SCAN_TOO_MUCH', count, furi.decode(libMatcher.uri)))
end
end)
end
-- must wait for other scopes to add library
await.sleep(0.1)
log.info(('Found %d files at:'):format(ld.max), scp:getName())
ld:loadAll(scp:getName())
log.info('Preload finish at:', scp:getName())
end
--- 查找符合指定file path的所有uri
---@param path string
function m.findUrisByFilePath(path)
if type(path) ~= 'string' then
return {}
end
local myUri = furi.encode(path)
local vm = require 'vm'
local resultCache = vm.getCache 'findUrisByFilePath.result'
if resultCache[path] then
return resultCache[path]
end
local results = {}
for uri in files.eachFile() do
if uri == myUri then
results[#results+1] = uri
end
end
resultCache[path] = results
return results
end
---@param folderUri? uri
---@param path string
---@return string?
function m.getAbsolutePath(folderUri, path)
path = files.normalize(path)
if fs.path(path):is_relative() then
if not folderUri then
return nil
end
local folderPath = furi.decode(folderUri)
path = files.normalize(folderPath .. '/' .. path)
end
return path
end
---@param uriOrPath uri|string
---@return string
---@return boolean suc
function m.getRelativePath(uriOrPath)
local path, uri
if uriOrPath:sub(1, 5) == 'file:' then
path = furi.decode(uriOrPath)
uri = uriOrPath
else
path = uriOrPath
uri = furi.encode(uriOrPath)
end
local scp = scope.getScope(uri)
if not scp.uri then
local relative = files.normalize(path)
return relative:gsub('^[/\\]+', ''), false
end
local _, pos = files.normalize(path):find(furi.decode(scp.uri), 1, true)
if pos then
return files.normalize(path:sub(pos + 1)):gsub('^[/\\]+', ''), true
else
return files.normalize(path):gsub('^[/\\]+', ''), false
end
end
---@param scp scope
function m.reload(scp)
---@async
await.call(function ()
m.awaitReload(scp)
end)
end
function m.init()
if m.rootUri then
for _, folder in ipairs(scope.folders) do
m.reload(folder)
end
end
m.reload(scope.fallback)
end
---@param scp scope
function m.flushFiles(scp)
local cachedUris = scp:get 'cachedUris'
scp:set('cachedUris', {})
if not cachedUris then
return
end
for uri in pairs(cachedUris) do
files.delRef(uri)
end
end
---@param scp scope
function m.resetFiles(scp)
local cachedUris = scp:get 'cachedUris'
if cachedUris then
for uri in pairs(cachedUris) do
files.resetText(uri)
end
end
for uri in pairs(files.openMap) do
if scope.getScope(uri) == scp then
files.resetText(uri)
end
end
end
---@async
---@param scp scope
function m.awaitReload(scp)
await.unique('workspace reload:' .. scp:getName())
await.sleep(0.1)
scp:set('ready', false)
scp:set('nativeMatcher', nil)
scp:set('libraryMatcher', nil)
scp:removeAllLinks()
m.flushFiles(scp)
m.onWatch('startReload', scp.uri)
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
---@return scope
function m.getFirstScope()
return m.folders[1] or scope.fallback
end
---等待工作目录加载完成
---@async
function m.awaitReady(uri)
if m.isReady(uri) then
return
end
local scp = scope.getScope(uri)
local waitingReady = scp:get('waitingReady')
or scp:set('waitingReady', {})
await.wait(function (waker)
waitingReady[#waitingReady+1] = waker
end)
end
---@param uri uri
---@return boolean
function m.isReady(uri)
local scp = scope.getScope(uri)
return scp:get('ready') == true
end
---@return boolean
function m.isAllReady()
local scp = scope.fallback
if not scp:get 'ready' then
return false
end
for _, folder in ipairs(scope.folders) do
if not folder:get 'ready' then
return false
end
end
return true
end
function m.getLoadingProcess(uri)
local scp = scope.getScope(uri)
---@type workspace.loading
local ld = scp:get 'loading'
if ld then
return ld.read, ld.max
else
return 0, 0
end
end
config.watch(function (uri, key, value, oldValue)
if key:find '^Lua.runtime'
or key:find '^Lua.workspace'
or key:find '^Lua.type'
or key:find '^files' then
if value ~= oldValue then
local scp = scope.getScope(uri)
m.reload(scp)
m.resetFiles(scp)
end
end
end)
fw.event(function (ev, path) ---@async
local uri = furi.encode(path)
if ev == 'create' then
log.debug('FileChangeType.Created', uri)
m.awaitLoadFile(uri)
elseif ev == '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 ev == 'change' then
if m.isValidLuaUri(uri) then
-- 如果文件处于关闭状态则立即更新否则等待didChange协议来更新
if not files.isOpen(uri) then
files.setText(uri, pub.awaitTask('loadFile', furi.decode(uri)), false)
end
end
end
local filename = fs.path(path):filename():string()
-- 排除类文件发生更改需要重新扫描
if filename == '.gitignore'
or filename == '.gitmodules' then
local scp = scope.getScope(uri)
if scp.type ~= 'fallback' then
m.reload(scp)
end
end
end)
return m