summaryrefslogtreecommitdiff
path: root/server-beta/src
diff options
context:
space:
mode:
Diffstat (limited to 'server-beta/src')
-rw-r--r--server-beta/src/config.lua36
-rw-r--r--server-beta/src/files.lua44
-rw-r--r--server-beta/src/glob/gitignore.lua221
-rw-r--r--server-beta/src/glob/glob.lua122
-rw-r--r--server-beta/src/glob/init.lua4
-rw-r--r--server-beta/src/glob/matcher.lua151
-rw-r--r--server-beta/src/proto/provider.lua2
-rw-r--r--server-beta/src/workspace/workspace.lua15
8 files changed, 570 insertions, 25 deletions
diff --git a/server-beta/src/config.lua b/server-beta/src/config.lua
index 2da1762d..6d2ed47f 100644
--- a/server-beta/src/config.lua
+++ b/server-beta/src/config.lua
@@ -1,6 +1,9 @@
local util = require 'utility'
local DiagnosticDefaultSeverity = require 'define.DiagnosticDefaultSeverity'
+local m = {}
+m.version = 0
+
local function Boolean(v)
if type(v) == 'boolean' then
return true, v
@@ -135,28 +138,27 @@ local OtherTemplate = {
exclude = {{}, Hash(String, Boolean)},
}
-local Config, Other
-
local function init()
- if Config then
+ if m.config then
return
end
- Config = {}
+ m.config = {}
for c, t in pairs(ConfigTemplate) do
- Config[c] = {}
+ m.config[c] = {}
for k, info in pairs(t) do
- Config[c][k] = info[1]
+ m.config[c][k] = info[1]
end
end
- Other = {}
+ m.other = {}
for k, info in pairs(OtherTemplate) do
- Other[k] = info[1]
+ m.other[k] = info[1]
end
end
-local function setConfig(self, config, other)
+function m.setConfig(config, other)
+ m.version = m.version + 1
xpcall(function ()
for c, t in pairs(config) do
for k, v in pairs(t) do
@@ -165,9 +167,9 @@ local function setConfig(self, config, other)
local info = region[k]
local suc, v = info[2](v)
if suc then
- Config[c][k] = v
+ m.config[c][k] = v
else
- Config[c][k] = info[1]
+ m.config[c][k] = info[1]
end
end
end
@@ -176,19 +178,15 @@ local function setConfig(self, config, other)
local info = OtherTemplate[k]
local suc, v = info[2](v)
if suc then
- Other[k] = v
+ m.other[k] = v
else
- Other[k] = info[1]
+ m.other[k] = info[1]
end
end
- log.debug('Config update: ', util.dump(Config), util.dump(Other))
+ log.debug('Config update: ', util.dump(m.config), util.dump(m.other))
end, log.error)
end
init()
-return {
- setConfig = setConfig,
- config = Config,
- other = Other,
-}
+return m
diff --git a/server-beta/src/files.lua b/server-beta/src/files.lua
index 15190630..f0c1c876 100644
--- a/server-beta/src/files.lua
+++ b/server-beta/src/files.lua
@@ -1,11 +1,16 @@
local platform = require 'bee.platform'
local pub = require 'pub'
local task = require 'task'
+local config = require 'config'
+local glob = require 'glob'
+local furi = require 'file-uri'
local m = {}
m.openMap = {}
m.fileMap = {}
+m.assocVersion = -1
+m.assocMatcher = nil
--- 打开文件
function m.open(uri)
@@ -99,4 +104,43 @@ function m.getAst(uri)
return file.ast
end
+--- 判断文件名相等
+function m.eq(a, b)
+ if platform.OS == 'Windows' then
+ return a:lower() == b:lower()
+ else
+ return a == b
+ end
+end
+
+--- 获取文件关联
+function m.getAssoc()
+ if m.assocVersion == config.version then
+ return m.assocMatcher
+ end
+ m.assocVersion = config.version
+ local patt = {}
+ for k, v in pairs(config.other.associations) do
+ if m.eq(v, 'lua') then
+ patt[#patt+1] = k
+ end
+ end
+ m.assocMatcher = glob.glob(patt)
+ if platform.OS == 'Windows' then
+ m.assocMatcher:setOption 'ignoreCase'
+ end
+ return m.assocMatcher
+end
+
+--- 判断是否是Lua文件
+function m.isLua(uri)
+ local ext = uri:match '%.(.-)$'
+ if m.eq(ext, 'lua') then
+ return true
+ end
+ local matcher = m.getAssoc()
+ local path = furi.decode(uri)
+ return matcher(path)
+end
+
return m
diff --git a/server-beta/src/glob/gitignore.lua b/server-beta/src/glob/gitignore.lua
new file mode 100644
index 00000000..f98a2f31
--- /dev/null
+++ b/server-beta/src/glob/gitignore.lua
@@ -0,0 +1,221 @@
+local m = require 'lpeglabel'
+local matcher = require 'glob.matcher'
+
+local function prop(name, pat)
+ return m.Cg(m.Cc(true), name) * pat
+end
+
+local function object(type, pat)
+ return m.Ct(
+ m.Cg(m.Cc(type), 'type') *
+ m.Cg(pat, 'value')
+ )
+end
+
+local function expect(p, err)
+ return p + m.T(err)
+end
+
+local parser = m.P {
+ 'Main',
+ ['Sp'] = m.S(' \t')^0,
+ ['Slash'] = m.S('/\\')^1,
+ ['Main'] = m.Ct(m.V'Sp' * m.P'{' * m.V'Pattern' * (',' * expect(m.V'Pattern', 'Miss exp after ","'))^0 * m.P'}')
+ + m.Ct(m.V'Pattern')
+ + m.T'Main Failed'
+ ,
+ ['Pattern'] = m.Ct(m.V'Sp' * prop('neg', m.P'!') * expect(m.V'Unit', 'Miss exp after "!"'))
+ + m.Ct(m.V'Unit')
+ ,
+ ['NeedRoot'] = prop('root', (m.P'.' * m.V'Slash' + m.V'Slash')),
+ ['Unit'] = m.V'Sp' * m.V'NeedRoot'^-1 * expect(m.V'Exp', 'Miss exp') * m.V'Sp',
+ ['Exp'] = m.V'Sp' * (m.V'FSymbol' + object('/', m.V'Slash') + m.V'Word')^0 * m.V'Sp',
+ ['Word'] = object('word', m.Ct((m.V'CSymbol' + m.V'Char' - m.V'FSymbol')^1)),
+ ['CSymbol'] = object('*', m.P'*')
+ + object('?', m.P'?')
+ + object('[]', m.V'Range')
+ ,
+ ['Char'] = object('char', (1 - m.S',{}[]*?/\\')^1),
+ ['FSymbol'] = object('**', m.P'**'),
+ ['Range'] = m.P'[' * m.Ct(m.V'RangeUnit'^0) * m.P']'^-1,
+ ['RangeUnit'] = m.Ct(- m.P']' * m.C(m.P(1)) * (m.P'-' * - m.P']' * m.C(m.P(1)))^-1),
+}
+
+local mt = {}
+mt.__index = mt
+mt.__name = 'gitignore'
+
+function mt:addPattern(pat)
+ if type(pat) ~= 'string' then
+ return
+ end
+ self.pattern[#self.pattern+1] = pat
+ if self.options.ignoreCase then
+ pat = pat:lower()
+ end
+ local states, err = parser:match(pat)
+ if not states then
+ self.errors[#self.errors+1] = {
+ pattern = pat,
+ message = err
+ }
+ return
+ end
+ for _, state in ipairs(states) do
+ self.matcher[#self.matcher+1] = matcher(state)
+ end
+end
+
+function mt:setOption(op, val)
+ if val == nil then
+ val = true
+ end
+ self.options[op] = val
+end
+
+---@param key string | "'type'" | "'list'"
+---@param func function | "function (path) end"
+function mt:setInterface(key, func)
+ if type(func) ~= 'function' then
+ return
+ end
+ self.interface[key] = func
+end
+
+function mt:callInterface(name, ...)
+ local func = self.interface[name]
+ return func(...)
+end
+
+function mt:hasInterface(name)
+ return self.interface[name] ~= nil
+end
+
+function mt:checkDirectory(catch, path, matcher)
+ if not self:hasInterface 'type' then
+ return true
+ end
+ if not matcher:isNeedDirectory() then
+ return true
+ end
+ if #catch < #path then
+ -- if path is 'a/b/c' and catch is 'a/b'
+ -- then the catch must be a directory
+ return true
+ else
+ return self:callInterface('type', path) == 'directory'
+ end
+end
+
+function mt:simpleMatch(path)
+ for i = #self.matcher, 1, -1 do
+ local matcher = self.matcher[i]
+ local catch = matcher(path)
+ if catch and self:checkDirectory(catch, path, matcher) then
+ if matcher:isNegative() then
+ return false
+ else
+ return true
+ end
+ end
+ end
+ return nil
+end
+
+function mt:finishMatch(path)
+ local paths = {}
+ for filename in path:gmatch '[^/\\]+' do
+ paths[#paths+1] = filename
+ end
+ for i = 1, #paths do
+ local newPath = table.concat(paths, '/', 1, i)
+ local passed = self:simpleMatch(newPath)
+ if passed == true then
+ return true
+ elseif passed == false then
+ return false
+ end
+ end
+ return false
+end
+
+function mt:scan(callback)
+ local files = {}
+ if type(callback) ~= 'function' then
+ callback = nil
+ end
+ local list = {}
+ local result = self:callInterface('list', '')
+ if type(result) ~= 'table' then
+ return files
+ end
+ for _, path in ipairs(result) do
+ list[#list+1] = path:match '([^/\\]+)[/\\]*$'
+ end
+ while #list > 0 do
+ local current = list[#list]
+ if not current then
+ break
+ end
+ list[#list] = nil
+ if not self:simpleMatch(current) then
+ local fileType = self:callInterface('type', current)
+ if fileType == 'file' then
+ if callback then
+ callback(current)
+ end
+ files[#files+1] = current
+ elseif fileType == 'directory' then
+ local result = self:callInterface('list', current)
+ if type(result) == 'table' then
+ for _, path in ipairs(result) do
+ local filename = path:match '([^/\\]+)[/\\]*$'
+ if filename then
+ list[#list+1] = current .. '/' .. filename
+ end
+ end
+ end
+ end
+ end
+ end
+ return files
+end
+
+function mt:__call(path)
+ if self.options.ignoreCase then
+ path = path:lower()
+ end
+ return self:finishMatch(path)
+end
+
+return function (pattern, options, interface)
+ local self = setmetatable({
+ pattern = {},
+ options = {},
+ matcher = {},
+ errors = {},
+ interface = {},
+ }, mt)
+
+ if type(pattern) == 'table' then
+ for _, pat in ipairs(pattern) do
+ self:addPattern(pat)
+ end
+ else
+ self:addPattern(pattern)
+ end
+
+ if type(options) == 'table' then
+ for op, val in pairs(options) do
+ self:setOption(op, val)
+ end
+ end
+
+ if type(interface) == 'table' then
+ for key, func in pairs(interface) do
+ self:setInterface(key, func)
+ end
+ end
+
+ return self
+end
diff --git a/server-beta/src/glob/glob.lua b/server-beta/src/glob/glob.lua
new file mode 100644
index 00000000..aa8923f3
--- /dev/null
+++ b/server-beta/src/glob/glob.lua
@@ -0,0 +1,122 @@
+local m = require 'lpeglabel'
+local matcher = require 'glob.matcher'
+
+local function prop(name, pat)
+ return m.Cg(m.Cc(true), name) * pat
+end
+
+local function object(type, pat)
+ return m.Ct(
+ m.Cg(m.Cc(type), 'type') *
+ m.Cg(pat, 'value')
+ )
+end
+
+local function expect(p, err)
+ return p + m.T(err)
+end
+
+local parser = m.P {
+ 'Main',
+ ['Sp'] = m.S(' \t')^0,
+ ['Slash'] = m.S('/\\')^1,
+ ['Main'] = m.Ct(m.V'Sp' * m.P'{' * m.V'Pattern' * (',' * expect(m.V'Pattern', 'Miss exp after ","'))^0 * m.P'}')
+ + m.Ct(m.V'Pattern')
+ + m.T'Main Failed'
+ ,
+ ['Pattern'] = m.Ct(m.V'Sp' * prop('neg', m.P'!') * expect(m.V'Unit', 'Miss exp after "!"'))
+ + m.Ct(m.V'Unit')
+ ,
+ ['NeedRoot'] = prop('root', (m.P'.' * m.V'Slash' + m.V'Slash')),
+ ['Unit'] = m.V'Sp' * m.V'NeedRoot'^-1 * expect(m.V'Exp', 'Miss exp') * m.V'Sp',
+ ['Exp'] = m.V'Sp' * (m.V'FSymbol' + object('/', m.V'Slash') + m.V'Word')^0 * m.V'Sp',
+ ['Word'] = object('word', m.Ct((m.V'CSymbol' + m.V'Char' - m.V'FSymbol')^1)),
+ ['CSymbol'] = object('*', m.P'*')
+ + object('?', m.P'?')
+ + object('[]', m.V'Range')
+ ,
+ ['Char'] = object('char', (1 - m.S',{}[]*?/\\')^1),
+ ['FSymbol'] = object('**', m.P'**'),
+ ['RangeWord'] = 1 - m.P']',
+ ['Range'] = m.P'[' * m.Ct(m.V'RangeUnit'^0) * m.P']'^-1,
+ ['RangeUnit'] = m.Ct(m.C(m.V'RangeWord') * m.P'-' * m.C(m.V'RangeWord'))
+ + m.V'RangeWord',
+}
+
+local mt = {}
+mt.__index = mt
+mt.__name = 'glob'
+
+function mt:addPattern(pat)
+ if type(pat) ~= 'string' then
+ return
+ end
+ self.pattern[#self.pattern+1] = pat
+ if self.options.ignoreCase then
+ pat = pat:lower()
+ end
+ local states, err = parser:match(pat)
+ if not states then
+ self.errors[#self.errors+1] = {
+ pattern = pat,
+ message = err
+ }
+ return
+ end
+ for _, state in ipairs(states) do
+ if state.neg then
+ self.refused[#self.refused+1] = matcher(state)
+ else
+ self.passed[#self.passed+1] = matcher(state)
+ end
+ end
+end
+
+function mt:setOption(op, val)
+ if val == nil then
+ val = true
+ end
+ self.options[op] = val
+end
+
+function mt:__call(path)
+ if self.options.ignoreCase then
+ path = path:lower()
+ end
+ for _, refused in ipairs(self.refused) do
+ if refused(path) then
+ return false
+ end
+ end
+ for _, passed in ipairs(self.passed) do
+ if passed(path) then
+ return true
+ end
+ end
+ return false
+end
+
+return function (pattern, options)
+ local self = setmetatable({
+ pattern = {},
+ options = {},
+ passed = {},
+ refused = {},
+ errors = {},
+ }, mt)
+
+ if type(pattern) == 'table' then
+ for _, pat in ipairs(pattern) do
+ self:addPattern(pat)
+ end
+ else
+ self:addPattern(pattern)
+ end
+
+ if type(options) == 'table' then
+ for op, val in pairs(options) do
+ self:setOption(op, val)
+ end
+ end
+ return self
+end
diff --git a/server-beta/src/glob/init.lua b/server-beta/src/glob/init.lua
new file mode 100644
index 00000000..6578a0d4
--- /dev/null
+++ b/server-beta/src/glob/init.lua
@@ -0,0 +1,4 @@
+return {
+ glob = require 'glob.glob',
+ gitignore = require 'glob.gitignore',
+}
diff --git a/server-beta/src/glob/matcher.lua b/server-beta/src/glob/matcher.lua
new file mode 100644
index 00000000..f4c2b12c
--- /dev/null
+++ b/server-beta/src/glob/matcher.lua
@@ -0,0 +1,151 @@
+local m = require 'lpeglabel'
+
+local Slash = m.S('/\\')^1
+local Symbol = m.S',{}[]*?/\\'
+local Char = 1 - Symbol
+local Path = Char^1 * Slash
+local NoWord = #(m.P(-1) + Symbol)
+local function whatHappened()
+ return m.Cmt(m.P(1)^1, function (...)
+ print(...)
+ end)
+end
+
+local mt = {}
+mt.__index = mt
+mt.__name = 'matcher'
+
+function mt:exp(state, index)
+ local exp = state[index]
+ if not exp then
+ return
+ end
+ if exp.type == 'word' then
+ return self:word(exp, state, index + 1)
+ elseif exp.type == 'char' then
+ return self:char(exp, state, index + 1)
+ elseif exp.type == '**' then
+ return self:anyPath(exp, state, index + 1)
+ elseif exp.type == '*' then
+ return self:anyChar(exp, state, index + 1)
+ elseif exp.type == '?' then
+ return self:oneChar(exp, state, index + 1)
+ elseif exp.type == '[]' then
+ return self:range(exp, state, index + 1)
+ elseif exp.type == '/' then
+ return self:slash(exp, state, index + 1)
+ end
+end
+
+function mt:word(exp, state, index)
+ local current = self:exp(exp.value, 1)
+ local after = self:exp(state, index)
+ if after then
+ return current * Slash * after
+ else
+ return current
+ end
+end
+
+function mt:char(exp, state, index)
+ local current = m.P(exp.value)
+ local after = self:exp(state, index)
+ if after then
+ return current * after * NoWord
+ else
+ return current * NoWord
+ end
+end
+
+function mt:anyPath(_, state, index)
+ local after = self:exp(state, index)
+ if after then
+ return m.P {
+ 'Main',
+ Main = after
+ + Path * m.V'Main'
+ }
+ else
+ return Path^0
+ end
+end
+
+function mt:anyChar(_, state, index)
+ local after = self:exp(state, index)
+ if after then
+ return m.P {
+ 'Main',
+ Main = after
+ + Char * m.V'Main'
+ }
+ else
+ return Char^0
+ end
+end
+
+function mt:oneChar(_, state, index)
+ local after = self:exp(state, index)
+ if after then
+ return Char * after
+ else
+ return Char
+ end
+end
+
+function mt:range(exp, state, index)
+ local after = self:exp(state, index)
+ local ranges = {}
+ local selects = {}
+ for _, range in ipairs(exp.value) do
+ if #range == 1 then
+ selects[#selects+1] = range[1]
+ elseif #range == 2 then
+ ranges[#ranges+1] = range[1] .. range[2]
+ end
+ end
+ local current = m.S(table.concat(selects)) + m.R(table.unpack(ranges))
+ if after then
+ return current * after
+ else
+ return current
+ end
+end
+
+function mt:slash(_, state, index)
+ local after = self:exp(state, index)
+ if after then
+ return after
+ else
+ self.needDirectory = true
+ return nil
+ end
+end
+
+function mt:pattern(state)
+ if state.root then
+ return m.C(self:exp(state, 1))
+ else
+ return m.C(self:anyPath(nil, state, 1))
+ end
+end
+
+function mt:isNeedDirectory()
+ return self.needDirectory == true
+end
+
+function mt:isNegative()
+ return self.state.neg == true
+end
+
+function mt:__call(path)
+ return self.matcher:match(path)
+end
+
+return function (state, options)
+ local self = setmetatable({
+ options = options,
+ state = state,
+ }, mt)
+ self.matcher = self:pattern(state)
+ return self
+end
diff --git a/server-beta/src/proto/provider.lua b/server-beta/src/proto/provider.lua
index fbaba8ca..3fdbd34c 100644
--- a/server-beta/src/proto/provider.lua
+++ b/server-beta/src/proto/provider.lua
@@ -34,7 +34,7 @@ local function updateConfig()
local oldConfig = util.deepCopy(config.config)
local oldOther = util.deepCopy(config.other)
- config:setConfig(updated, other)
+ config.setConfig(updated, other)
local newConfig = config.config
local newOther = config.other
if not util.equal(oldConfig.runtime, newConfig.runtime) then
diff --git a/server-beta/src/workspace/workspace.lua b/server-beta/src/workspace/workspace.lua
index 1684f224..8368df69 100644
--- a/server-beta/src/workspace/workspace.lua
+++ b/server-beta/src/workspace/workspace.lua
@@ -1,6 +1,7 @@
-local pub = require 'pub'
-local fs = require 'bee.filesystem'
-local furi = require 'file-uri'
+local pub = require 'pub'
+local fs = require 'bee.filesystem'
+local furi = require 'file-uri'
+local files = require 'files'
local m = {}
m.type = 'workspace'
@@ -32,8 +33,12 @@ function m.preload()
end
end
scan(m.uri, function (uri)
- local text = pub.task('loadFile', uri)
- log.debug('Preload file at: ' .. uri, #text)
+ if files.isLua(uri) then
+ pub.syncTask('loadFile', uri, function (text)
+ log.debug('Preload file at: ' .. uri, #text)
+ files.setText(uri, text)
+ end)
+ end
end)
log.info('Preload finish.')
end