summaryrefslogtreecommitdiff
path: root/server/src/workspace
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/workspace')
-rw-r--r--server/src/workspace/init.lua1
-rw-r--r--server/src/workspace/workspace.lua489
2 files changed, 0 insertions, 490 deletions
diff --git a/server/src/workspace/init.lua b/server/src/workspace/init.lua
deleted file mode 100644
index fa8bc6d9..00000000
--- a/server/src/workspace/init.lua
+++ /dev/null
@@ -1 +0,0 @@
-return require 'workspace.workspace'
diff --git a/server/src/workspace/workspace.lua b/server/src/workspace/workspace.lua
deleted file mode 100644
index 6bd5c79e..00000000
--- a/server/src/workspace/workspace.lua
+++ /dev/null
@@ -1,489 +0,0 @@
-local fs = require 'bee.filesystem'
-local async = require 'async'
-local config = require 'config'
-local ll = require 'lpeglabel'
-local platform = require 'bee.platform'
-
-local TrueName = {}
-
-local function getFileName(path)
- local name = path:string()
- if platform.OS == 'Windows' then
- local lname = name:lower()
- TrueName[lname] = name
- return lname
- else
- return name
- end
-end
-
-local function getTrueName(name)
- return TrueName[name] or name
-end
-
-local function fileNameEq(a, b)
- if platform.OS == 'Windows' then
- return a:lower() == b:lower()
- else
- return a == b
- end
-end
-
-local function split(str, sep)
- local t = {}
- for s in str:gmatch('[^' .. sep .. ']+') do
- t[#t+1] = s
- end
- return t
-end
-
-local function similarity(a, b)
- local ta = split(a, '/\\')
- local tb = split(b, '/\\')
- for i = 1, #ta do
- if ta[i] ~= tb[i] then
- return i - 1
- end
- end
- return #ta
-end
-
-local mt = {}
-mt.__index = mt
-
-function mt:uriDecode(uri)
- -- Unix-like系统根是/
- if uri:sub(1, 9) == 'file:////' then
- return fs.path(uri:sub(9))
- end
- if uri:sub(1, 8) ~= 'file:///' then
- log.error('uri decode failed: ', uri)
- return nil
- end
- local names = {}
- for name in uri:sub(9):gmatch '[^%/]+' do
- names[#names+1] = name:gsub('%%([0-9a-fA-F][0-9a-fA-F])', function (hex)
- return string.char(tonumber(hex, 16))
- end)
- end
- if #names == 0 then
- log.error('uri decode failed: ', uri)
- return nil
- end
- -- 盘符后面加个斜杠
- local path = fs.path(names[1] .. '\\')
- for i = 2, #names do
- path = path / names[i]
- end
- return fs.absolute(path)
-end
-
-function mt:uriEncode(path)
- local names = {}
- local cur = fs.absolute(path)
- while true do
- local name = cur:filename():string()
- if name == '' then
- -- 盘符,去掉一个斜杠
- name = cur:string():sub(1, -2)
- end
- name = name:gsub([=[[^%w%-%_%.%~]]=], function (char)
- return ('%%%02X'):format(string.byte(char))
- end)
- table.insert(names, 1, name)
- if cur == cur:parent_path() then
- break
- end
- cur = cur:parent_path()
- end
- return 'file:///' .. table.concat(names, '/')
-end
-
-function mt:listenLoadFile()
- self._loadFileRequest = async.run('loadfile', nil, function (filename, buf)
- local path = fs.path(filename)
- local name = getFileName(path)
- local uri = self:uriEncode(path)
- self.files[name] = uri
- self.lsp:readText(uri, path, buf, self._currentScanCompiled)
- end)
-end
-
-function mt:scanFiles()
- if self._scanRequest then
- log.info('中断上次扫描文件任务')
- self._scanRequest:push('stop')
- self._scanRequest = nil
- self._complete = false
- self:reset()
- end
-
- local ignored = {'.git'}
- for path in pairs(config.config.workspace.ignoreDir) do
- ignored[#ignored+1] = path
- end
- for path, ignore in pairs(config.other.exclude) do
- if ignore then
- ignored[#ignored+1] = path
- end
- end
- if config.config.workspace.ignoreSubmodules then
- local buf = io.load(self.root / '.gitmodules')
- if buf then
- for path in buf:gmatch('path = ([^\r\n]+)') do
- log.info('忽略子模块:', path)
- ignored[#ignored+1] = path
- end
- end
- end
- if config.config.workspace.useGitIgnore then
- local buf = io.load(self.root / '.gitignore')
- if buf then
- for line in buf:gmatch '[^\r\n]+' do
- ignored[#ignored+1] = line
- end
- end
- end
-
- log.info('忽略文件:\r\n' .. table.concat(ignored, '\r\n'))
- log.info('开始扫描文件任务')
- self._currentScanCompiled = {}
- local count = 0
- self._scanRequest = async.run('scanfiles', {
- root = self.root:string(),
- ignored = ignored,
- }, function (mode, ...)
- if mode == 'ok' then
- log.info('扫描文件任务完成,共', count, '个文件。')
- self._complete = true
- self._scanRequest = nil
- self:reset()
- return true
- elseif mode == 'log' then
- log.debug(...)
- elseif mode == 'path' then
- local path = fs.path(...)
- if not self:isLuaFile(path) then
- return
- end
- self._loadFileRequest:push(path:string())
- count = count + 1
- elseif mode == 'stop' then
- log.info('扫描文件任务中断')
- return false
- end
- end)
-end
-
-function mt:init(rootUri)
- self.root = self:uriDecode(rootUri)
- self.uri = rootUri
- if not self.root then
- return
- end
- log.info('Workspace inited, root: ', self.root)
- local logPath = ROOT / 'log' / (rootUri:gsub('[/:]+', '_') .. '.log')
- log.info('Log path: ', logPath)
- log.init(ROOT, logPath)
-
- self:scanFiles()
-end
-
-function mt:isComplete()
- return not not self._complete
-end
-
-function mt:isLuaFile(path)
- local ext = path:extension():string()
- for k, v in pairs(config.other.associations) do
- if fileNameEq(ext, k:match('[^%*]+$')) then
- if v == 'lua' then
- return true
- else
- return false
- end
- end
- end
- if fileNameEq(ext, '.lua') then
- return true
- end
- return false
-end
-
-function mt:addFile(path)
- if not self:isLuaFile(path) then
- return
- end
- local name = getFileName(path)
- local uri = self:uriEncode(path)
- self.files[name] = uri
- self.lsp:readText(uri, path)
-end
-
-function mt:removeFile(path)
- local name = getFileName(path)
- if not self.files[name] then
- return
- end
- self.files[name] = nil
- local uri = self:uriEncode(path)
- self.lsp:removeText(uri)
-end
-
-function mt:findPath(baseUri, searchers)
- local results = {}
- local baseName = getFileName(self:uriDecode(baseUri))
- for filename, uri in pairs(self.files) do
- if filename ~= baseName then
- for _, searcher in ipairs(searchers) do
- if filename:sub(-#searcher) == searcher then
- local sep = filename:sub(-#searcher-1, -#searcher-1)
- if sep == '/' or sep == '\\' then
- results[#results+1] = uri
- end
- end
- end
- end
- end
-
- if #results == 0 then
- return nil
- end
- local uri
- if #results == 1 then
- uri = results[1]
- else
- table.sort(results, function (a, b)
- return similarity(a, baseUri) > similarity(b, baseUri)
- end)
- uri = results[1]
- end
- return uri
-end
-
-function mt:createCompiler(str)
- local state = {
- 'Main',
- }
- local function push(c)
- if state.Main then
- state.Main = state.Main * c
- else
- state.Main = c
- end
- end
- local count = 0
- local function code()
- count = count + 1
- local name = 'C' .. tostring(count)
- local nextName = 'C' .. tostring(count + 1)
- state[name] = ll.P(1) * (#ll.V(nextName) + ll.V(name))
- return ll.V(name)
- end
- local function static(c)
- count = count + 1
- local name = 'C' .. tostring(count)
- local nextName = 'C' .. tostring(count + 1)
- local catch = #ll.V(nextName)
- if platform.OS == 'Windows' then
- for i = #c, 1, -1 do
- local char = c:sub(i, i)
- local u = char:upper()
- local l = char:lower()
- if u == l then
- catch = ll.P(char) * catch
- else
- catch = (ll.P(u) + ll.P(l)) * catch
- end
- end
- else
- catch = ll.P(c) * catch
- end
- state[name] = catch
- return ll.V(name)
- end
- local function eof()
- count = count + 1
- local name = 'C' .. tostring(count)
- state[name] = ll.Cmt(ll.P(1) + ll.Cp(), function (_, _, c)
- return type(c) == 'number'
- end)
- return ll.V(name)
- end
- local isFirstCode = true
- local firstCode
- local compiler = ll.P {
- 'Result',
- Result = (ll.V'Code' + ll.V'Static')^1,
- Code = ll.P'?' / function ()
- if isFirstCode then
- isFirstCode = false
- push(ll.Cmt(ll.C(code()), function (_, pos, code)
- firstCode = code
- return pos, code
- end))
- else
- push(ll.Cmt(
- ll.C(code()),
- function (_, _, me)
- return firstCode == me
- end
- ))
- end
- end,
- Static = (1 - ll.P'?')^1 / function (c)
- push(static(c))
- end,
- }
- compiler:match(str)
- push(eof())
- return ll.P(state)
-end
-
-function mt:compileLuaPath()
- for i, luapath in ipairs(config.config.runtime.path) do
- self.pathMatcher[i] = self:createCompiler(luapath)
- end
-end
-
-function mt:convertPathAsRequire(filename, start)
- local list
- for _, matcher in ipairs(self.pathMatcher) do
- local str = matcher:match(filename:sub(start))
- if str then
- if not list then
- list = {}
- end
- list[#list+1] = str:gsub('/', '.')
- end
- end
- return list
-end
-
-function mt:matchPath(baseUri, input)
- local first = input:match '[^%.]+'
- if not first then
- return nil
- end
- local baseName = getFileName(self:uriDecode(baseUri))
- local rootLen = #self.root:string()
- local map = {}
- for filename in pairs(self.files) do
- if filename ~= baseName then
- local trueFilename = getTrueName(filename)
- local start
- if platform.OS == 'Windows' then
- start = filename:find('[/\\]' .. first:lower(), rootLen + 1)
- else
- start = trueFilename:find('[/\\]' .. first, rootLen + 1)
- end
- if start then
- local list = self:convertPathAsRequire(trueFilename, start + 1)
- if list then
- for _, str in ipairs(list) do
- if #str >= #input and fileNameEq(str:sub(1, #input), input) then
- if not map[str] then
- map[str] = trueFilename
- else
- local s1 = similarity(trueFilename, baseName)
- local s2 = similarity(map[str], baseName)
- if s1 > s2 then
- map[str] = trueFilename
- elseif s1 == s2 then
- if trueFilename < map[str] then
- map[str] = trueFilename
- end
- end
- end
- end
- end
- end
- end
- end
- end
-
- local list = {}
- for str in pairs(map) do
- list[#list+1] = str
- map[str] = map[str]:sub(rootLen + 2)
- end
- if #list == 0 then
- return nil
- end
- table.sort(list, function (a, b)
- local sa = similarity(map[a], baseName)
- local sb = similarity(map[b], baseName)
- if sa == sb then
- return a < b
- else
- return sa > sb
- end
- end)
- return list, map
-end
-
-function mt:searchPath(baseUri, str)
- str = getFileName(fs.path(str))
- if self.searched[baseUri] and self.searched[baseUri][str] then
- return self.searched[baseUri][str]
- end
- str = str:gsub('%.', '/')
- local searchers = {}
- for i, luapath in ipairs(config.config.runtime.path) do
- searchers[i] = luapath:gsub('%?', str)
- end
-
- local uri = self:findPath(baseUri, searchers)
- if uri then
- if not self.searched[baseUri] then
- self.searched[baseUri] = {}
- end
- self.searched[baseUri][str] = uri
- end
- return uri
-end
-
-function mt:loadPath(baseUri, str)
- local ok, relative = pcall(fs.relative, fs.absolute(self.root / str), self.root)
- if not ok then
- return nil
- end
- str = getFileName(relative)
- if self.loaded[str] then
- return self.loaded[str]
- end
-
- local searchers = { str }
-
- local uri = self:findPath(baseUri, searchers)
- if uri then
- self.loaded[str] = uri
- end
- return uri
-end
-
-function mt:reset()
- self.searched = {}
- self.loaded = {}
- self.lsp:reCompile()
-end
-
-function mt:relativePathByUri(uri)
- local path = self:uriDecode(uri)
- local relate = fs.relative(path, self.root)
- return relate
-end
-
-return function (lsp, name)
- local workspace = setmetatable({
- lsp = lsp,
- name = name,
- files = {},
- searched = {},
- loaded = {},
- pathMatcher = {}
- }, mt)
- workspace:compileLuaPath()
- workspace:listenLoadFile()
- return workspace
-end