diff options
83 files changed, 5603 insertions, 2034 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json index 73516e26..c4194825 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -24,7 +24,7 @@ "type": "lua", "request": "attach", "stopOnEntry": false, - "address": "127.0.0.1:11427", + "address": "127.0.0.1:11413", "outputCapture": [ ], "sourceMaps": [ diff --git a/3rd/bee.lua b/3rd/bee.lua -Subproject b025c851433cc698b6e6f6af2043f54cd085f0c +Subproject ba8291f17c44c1f934a261535e818364a582857 diff --git a/3rd/love-api b/3rd/love-api -Subproject b913d6e804402d39401b343e3032b30eef0c83b +Subproject 4fa1de69cb04af2e405b3ac400d4e0cec0e0753 diff --git a/3rd/luamake b/3rd/luamake -Subproject 077e399e7e04def80ee59f686a4a88bfee0cf3a +Subproject 59940d9a2c7de34d4c6bfd575dac654719bd144 diff --git a/changelog.md b/changelog.md index 7d46a108..73fb8878 100644 --- a/changelog.md +++ b/changelog.md @@ -8,6 +8,8 @@ + `Lua.completion.requireSeparator` * `NEW` diagnostics: + `different-requires` +* `NEW` supports `---@CustomClass<string, number>` +* `NEW` supports `$/cancelRequest` * `CHG` hover: improve showing multi defines * `CHG` hover: improve showing multi comments at enums * `CHG` hint: `Lua.hint.paramName` now supports `Disable`, `Literal` and `All` @@ -15,10 +17,18 @@ * `CHG` no longer ignore file names case in Windows * `CHG` watching library changes * `CHG` completion: improve misspelling results +* `CHG` completion: `Lua.completion.displayContext` default to `0` +* `CHG` completion: `autoRequire` has better inserting position +* `CHG` diagnostics: + + `redundant-parameter` default severity to `Warning` + + `redundant-value` default severity to `Warning` +* `CHG` [#663](https://github.com/sumneko/lua-language-server/issues/663) * `FIX` runtime errors * `FIX` hint: may show param-2 as `self` * `FIX` semantic: may fail when scrolling +* `FIX` [#647](https://github.com/sumneko/lua-language-server/issues/647) * `FIX` [#660](https://github.com/sumneko/lua-language-server/issues/660) +* `FIX` [#673](https://github.com/sumneko/lua-language-server/issues/673) ## 2.3.7 `2021-8-17` diff --git a/meta/3rd/love2d/library/love.graphics.lua b/meta/3rd/love2d/library/love.graphics.lua index 9f453304..f70efefd 100644 --- a/meta/3rd/love2d/library/love.graphics.lua +++ b/meta/3rd/love2d/library/love.graphics.lua @@ -530,12 +530,13 @@ function love.graphics.newFont(filename) end --- ---Creates a new Image from a filepath, FileData, an ImageData, or a CompressedImageData, and optionally generates or specifies mipmaps for the image. --- ----@overload fun(imageData: love.ImageData):love.Image ----@overload fun(compressedImageData: love.CompressedImageData):love.Image ----@overload fun(filename: string, flags: table):love.Image +---@overload fun(fileData: love.FileData, flags: table):love.Image +---@overload fun(imageData: love.ImageData, flags: table):love.Image +---@overload fun(compressedImageData: love.CompressedImageData, flags: table):love.Image ---@param filename string # The filepath to the image file. ----@return love.Image image # An Image object which can be drawn on screen. -function love.graphics.newImage(filename) end +---@param flags {dpiscale: number, linear: boolean, mipmaps: boolean} # A table containing the following fields: +---@return love.Image image # A new Image object which can be drawn on screen. +function love.graphics.newImage(filename, flags) end --- ---Creates a new specifically formatted image. diff --git a/script/await.lua b/script/await.lua index 2d27ae2c..495f381c 100644 --- a/script/await.lua +++ b/script/await.lua @@ -1,11 +1,12 @@ local timer = require 'timer' -local util = require 'utility' + +local wkmt = { __mode = 'k' } ---@class await local m = {} m.type = 'await' -m.coMap = setmetatable({}, { __mode = 'k' }) +m.coMap = setmetatable({}, wkmt) m.idMap = {} m.delayQueue = {} m.delayQueueIndex = 1 @@ -74,7 +75,7 @@ function m.setID(id, co) return end if not m.idMap[id] then - m.idMap[id] = setmetatable({}, { __mode = 'k' }) + m.idMap[id] = setmetatable({}, wkmt) end m.idMap[id][co] = true end @@ -85,6 +86,7 @@ function m.close(id) if not map then return end + m.idMap[id] = nil for co in pairs(map) do if coroutine.status(co) == 'suspended' then map[co] = nil diff --git a/script/brave/work.lua b/script/brave/work.lua index 907f78cf..7af36fda 100644 --- a/script/brave/work.lua +++ b/script/brave/work.lua @@ -26,12 +26,12 @@ brave.on('timer', function (time) end) brave.on('compile', function (text) - local state, err = parser:compile(text, 'lua', 'Lua 5.4') + local state, err = parser.compile(text, 'Lua', 'Lua 5.4') if not state then log.error(err) return end - local lines = parser:lines(text) + local lines = parser.lines(text) return { root = state.root, value = state.value, diff --git a/script/client.lua b/script/client.lua index c22b59ab..e77eaaae 100644 --- a/script/client.lua +++ b/script/client.lua @@ -4,6 +4,7 @@ local lang = require 'language' local proto = require 'proto' local define = require 'proto.define' local config = require 'config' +local converter = require 'proto.converter' local m = {} @@ -215,15 +216,15 @@ function m.setConfig(changes, onlyMemory) end end ----@alias textEdit {start: integer, finish: integer, text: string} +---@alias textEditor {start: integer, finish: integer, text: string} ---@param uri uri ----@param edits textEdit[] +---@param edits textEditor[] function m.editText(uri, edits) local files = require 'files' local textEdits = {} for i, edit in ipairs(edits) do - textEdits[i] = define.textEdit(files.range(uri, edit.start, edit.finish), edit.text) + textEdits[i] = converter.textEdit(converter.packRange(uri, edit.start, edit.finish), edit.text) end proto.request('workspace/applyEdit', { edit = { diff --git a/script/config/config.lua b/script/config/config.lua index b44b48dc..52848a44 100644 --- a/script/config/config.lua +++ b/script/config/config.lua @@ -180,7 +180,7 @@ local Template = { ['Lua.completion.enable'] = Type.Boolean >> true, ['Lua.completion.callSnippet'] = Type.String >> 'Disable', ['Lua.completion.keywordSnippet'] = Type.String >> 'Replace', - ['Lua.completion.displayContext'] = Type.Integer >> 6, + ['Lua.completion.displayContext'] = Type.Integer >> 0, ['Lua.completion.workspaceWord'] = Type.Boolean >> true, ['Lua.completion.showWord'] = Type.String >> 'Fallback', ['Lua.completion.autoRequire'] = Type.Boolean >> true, diff --git a/script/core/code-action.lua b/script/core/code-action.lua index 64f862f9..8256107e 100644 --- a/script/core/code-action.lua +++ b/script/core/code-action.lua @@ -1,63 +1,49 @@ -local files = require 'files' -local lang = require 'language' -local util = require 'utility' -local sp = require 'bee.subprocess' -local guide = require "parser.guide" +local files = require 'files' +local lang = require 'language' +local util = require 'utility' +local sp = require 'bee.subprocess' +local guide = require "parser.guide" +local converter = require 'proto.converter' local function checkDisableByLuaDocExits(uri, row, mode, code) - local lines = files.getLines(uri) - local ast = files.getState(uri) - local text = files.getOriginText(uri) - local line = lines[row] - if ast.ast.docs and line then - for _, doc in ipairs(ast.ast.docs) do - if doc.start >= line.start - and doc.finish <= line.finish then - if doc.type == 'doc.diagnostic' then - if doc.mode == mode then - if doc.names then - return { - start = doc.finish, - finish = doc.finish, - newText = text:sub(doc.finish, doc.finish) - .. ', ' - .. code - } - else - return { - start = doc.finish, - finish = doc.finish, - newText = text:sub(doc.finish, doc.finish) - .. ': ' - .. code - } - end - end + if row < 0 then + return nil + end + local state = files.getState(uri) + local lines = state.lines + if state.ast.docs and lines then + return guide.eachSourceBetween(state.ast.docs, guide.positionOf(row, 0), guide.positionOf(row + 1, 0), function (doc) + if doc.type == 'doc.diagnostic' + and doc.mode == mode then + if doc.names then + return { + start = doc.finish, + finish = doc.finish, + newText = ', ' .. code, + } + else + return { + start = doc.finish, + finish = doc.finish, + newText = ': ' .. code, + } end end - end + end) end return nil end local function checkDisableByLuaDocInsert(uri, row, mode, code) - local lines = files.getLines(uri) - local ast = files.getState(uri) - local text = files.getOriginText(uri) - -- 先看看上一行是不是已经有了 - -- 没有的话就插入一行 - local line = lines[row] return { - start = line.start, - finish = line.start, - newText = '---@diagnostic ' .. mode .. ': ' .. code .. '\n' - .. text:sub(line.start, line.start) + start = guide.positionOf(row, 0), + finish = guide.positionOf(row, 0), + newText = '---@diagnostic ' .. mode .. ': ' .. code .. '\n', } end local function disableDiagnostic(uri, code, start, results) - local lines = files.getLines(uri) - local row = guide.positionOf(lines, start) + local row = guide.rowColOf(start) results[#results+1] = { title = lang.script('ACTION_DISABLE_DIAG', code), kind = 'quickfix', @@ -90,8 +76,8 @@ local function disableDiagnostic(uri, code, start, results) checkDisableByLuaDocExits (uri, row - 1, 'disable-next-line', code) or checkDisableByLuaDocInsert(uri, row, 'disable-next-line', code)) pushEdit(lang.script('ACTION_DISABLE_DIAG_FILE', code), - checkDisableByLuaDocExits (uri, 1, 'disable', code) - or checkDisableByLuaDocInsert(uri, 1, 'disable', code)) + checkDisableByLuaDocExits (uri, 0, 'disable', code) + or checkDisableByLuaDocInsert(uri, 0, 'disable', code)) end local function markGlobal(uri, name, results) @@ -134,8 +120,8 @@ end local function solveUndefinedGlobal(uri, diag, results) local ast = files.getState(uri) - local offset = files.offsetOfWord(uri, diag.range.start) - guide.eachSourceContain(ast.ast, offset, function (source) + local start = converter.unpackRange(uri, diag.range) + guide.eachSourceContain(ast.ast, start, function (source) if source.type ~= 'getglobal' then return end @@ -153,8 +139,8 @@ end local function solveLowercaseGlobal(uri, diag, results) local ast = files.getState(uri) - local offset = files.offsetOfWord(uri, diag.range.start) - guide.eachSourceContain(ast.ast, offset, function (source) + local start = converter.unpackRange(uri, diag.range) + guide.eachSourceContain(ast.ast, start, function (source) if source.type ~= 'setglobal' then return end @@ -168,7 +154,7 @@ local function findSyntax(uri, diag) local ast = files.getState(uri) for _, err in ipairs(ast.errs) do if err.type:lower():gsub('_', '-') == diag.code then - local range = files.range(uri, err.start, err.finish) + local range = converter.packRange(uri, err.start, err.finish) if util.equal(range, diag.range) then return err end @@ -197,8 +183,13 @@ local function solveSyntaxByAddDoEnd(uri, err, results) [uri] = { { start = err.start, + finish = err.start, + newText = 'do ', + }, + { + start = err.finish, finish = err.finish, - newText = ('do %s end'):format(text:sub(err.start, err.finish)), + newText = ' end', }, } } @@ -265,7 +256,7 @@ local function solveSyntax(uri, diag, results) end local function solveNewlineCall(uri, diag, results) - local start = files.unrange(uri, diag.range) + local start = converter.unpackRange(uri, diag.range) results[#results+1] = { title = lang.script.ACTION_ADD_SEMICOLON, kind = 'quickfix', @@ -349,18 +340,18 @@ local function checkQuickFix(results, uri, start, diagnostics) end local function checkSwapParams(results, uri, start, finish) - local ast = files.getState(uri) - local text = files.getText(uri) - if not ast then + local state = files.getState(uri) + local text = files.getText(uri) + if not state then return end local args = {} - guide.eachSourceBetween(ast.ast, start, finish, function (source) + guide.eachSourceBetween(state.ast, start, finish, function (source) if source.type == 'callargs' or source.type == 'funcargs' then local targetIndex for index, arg in ipairs(source) do - if arg.start - 1 <= finish and arg.finish >= start then + if arg.start <= finish and arg.finish >= start then -- should select only one param if targetIndex then return @@ -373,11 +364,17 @@ local function checkSwapParams(results, uri, start, finish) end local node if source.type == 'callargs' then - node = text:sub(source.parent.node.start, source.parent.node.finish) + node = text:sub( + guide.positionToOffset(state, source.parent.node.start) + 1, + guide.positionToOffset(state, source.parent.node.finish) + ) elseif source.type == 'funcargs' then local var = source.parent.parent if guide.isSet(var) then - node = text:sub(var.start, var.finish) + node = text:sub( + guide.positionToOffset(state, var.start) + 1, + guide.positionToOffset(state, var.finish) + ) else node = lang.script.SYMBOL_ANONYMOUS end @@ -411,12 +408,18 @@ local function checkSwapParams(results, uri, start, finish) { start = myArg.start, finish = myArg.finish, - newText = text:sub(targetArg.start, targetArg.finish), + newText = text:sub( + guide.positionToOffset(state, targetArg.start) + 1, + guide.positionToOffset(state, targetArg.finish) + ), }, { start = targetArg.start, finish = targetArg.finish, - newText = text:sub(myArg.start, myArg.finish), + newText = text:sub( + guide.positionToOffset(state, myArg.start) + 1, + guide.positionToOffset(state, myArg.finish) + ), }, } } @@ -499,13 +502,16 @@ end --end local function checkJsonToLua(results, uri, start, finish) - local text = files.getText(uri) - local jsonStart = text:match ('()[%{%[]', start) + local text = files.getText(uri) + local state = files.getState(uri) + local startOffset = guide.positionToOffset(state, start) + local finishOffset = guide.positionToOffset(state, finish) + local jsonStart = text:match ('()[%{%[]', startOffset + 1) if not jsonStart then return end local jsonFinish - for i = math.min(finish, #text), jsonStart + 1, -1 do + for i = math.min(finishOffset, #text), jsonStart + 1, -1 do local char = text:sub(i, i) if char == ']' or char == '}' then @@ -528,8 +534,8 @@ local function checkJsonToLua(results, uri, start, finish) arguments = { { uri = uri, - start = jsonStart, - finish = jsonFinish, + start = guide.offsetToPosition(state, jsonStart) - 1, + finish = guide.offsetToPosition(state, jsonFinish), } } }, diff --git a/script/core/command/autoRequire.lua b/script/core/command/autoRequire.lua index 2cb6a8f8..b485fcb1 100644 --- a/script/core/command/autoRequire.lua +++ b/script/core/command/autoRequire.lua @@ -4,21 +4,32 @@ local config = require 'config' local rpath = require 'workspace.require-path' local client = require 'client' local lang = require 'language' +local guide = require 'parser.guide' -local function findInsertOffset(uri) - local lines = files.getLines(uri) +local function findInsertRow(uri) local text = files.getText(uri) + local state = files.getState(uri) + local lines = state.lines local fmt = { pair = false, quot = '"', col = nil, } - for i = 1, #lines do + local hasFounded + for i = 0, #lines do local ln = lines[i] - local lnText = text:sub(ln.start, ln.finish) + local lnText = text:match('[^\r\n]*', ln) if not lnText:find('require', 1, true) then - return ln.start, fmt + if hasFounded then + return i, fmt + end + if not lnText:match '^local%s' + and not lnText:match '^%s*$' + and not lnText:match '^%-%-' then + return i, fmt + end else + hasFounded = true local lpPos = lnText:find '%(' if lpPos then fmt.pair = true @@ -33,7 +44,7 @@ local function findInsertOffset(uri) end end end - return 1, fmt + return 0, fmt end local function askAutoRequire(visiblePaths) @@ -70,7 +81,7 @@ local function askAutoRequire(visiblePaths) return nameMap[result] end -local function applyAutoRequire(uri, offset, name, result, fmt) +local function applyAutoRequire(uri, row, name, result, fmt) local quotedResult = ('%q'):format(result) if fmt.quot == "'" then quotedResult = ([['%s']]):format(quotedResult:sub(2, -2) @@ -88,11 +99,11 @@ local function applyAutoRequire(uri, offset, name, result, fmt) if fmt.col and fmt.col > #text then sp = (' '):rep(fmt.col - #text - 1) end - text = ('\nlocal %s%s= require%s\n'):format(name, sp, quotedResult) + text = ('local %s%s= require%s\n'):format(name, sp, quotedResult) client.editText(uri, { { - start = offset, - finish = offset - 1, + start = guide.positionOf(row, 0), + finish = guide.positionOf(row, 0), text = text, } }) @@ -121,6 +132,6 @@ return function (data) return end - local offset, fmt = findInsertOffset(uri) + local offset, fmt = findInsertRow(uri) applyAutoRequire(uri, offset, name, result, fmt) end diff --git a/script/core/command/jsonToLua.lua b/script/core/command/jsonToLua.lua index c4f001ff..8a493b5e 100644 --- a/script/core/command/jsonToLua.lua +++ b/script/core/command/jsonToLua.lua @@ -1,9 +1,10 @@ -local files = require 'files' -local json = require 'json' -local util = require 'utility' -local proto = require 'proto' -local define = require 'proto.define' -local lang = require 'language' +local files = require 'files' +local json = require 'json' +local util = require 'utility' +local proto = require 'proto' +local define = require 'proto.define' +local lang = require 'language' +local converter = require 'proto.converter' return function (data) local text = files.getText(data.uri) @@ -26,7 +27,7 @@ return function (data) changes = { [data.uri] = { { - range = files.range(data.uri, data.start, data.finish), + range = converter.packRange(data.uri, data.start, data.finish), newText = luaStr, } } diff --git a/script/core/command/removeSpace.lua b/script/core/command/removeSpace.lua index b94f9788..3021d4a4 100644 --- a/script/core/command/removeSpace.lua +++ b/script/core/command/removeSpace.lua @@ -1,7 +1,8 @@ -local files = require 'files' -local guide = require 'parser.guide' -local proto = require 'proto' -local lang = require 'language' +local files = require 'files' +local guide = require 'parser.guide' +local proto = require 'proto' +local lang = require 'language' +local converter = require 'proto.converter' local function isInString(ast, offset) return guide.eachSourceContain(ast.ast, offset, function (source) @@ -13,29 +14,39 @@ end return function (data) local uri = data.uri - local lines = files.getLines(uri) local text = files.getText(uri) - local ast = files.getState(uri) - if not lines then + local state = files.getState(uri) + if not state then return end + local lines = state.lines local textEdit = {} - for i = 1, #lines do - local line = guide.lineContent(lines, text, i, true) - local pos = line:find '[ \t]+$' - if pos then - local start, finish = guide.lineRange(lines, i, true) - start = start + pos - if isInString(ast, start) then - goto NEXT_LINE - end - textEdit[#textEdit+1] = { - range = files.range(uri, start, finish), - newText = '', - } + for i = 0, #lines do + local startOffset = lines[i] + local finishOffset = text:find('[\r\n]', startOffset) or (#text + 1) + local lastOffset = finishOffset - 1 + local lastChar = text:sub(lastOffset, lastOffset) + if lastChar ~= ' ' and lastChar ~= '\t' then + goto NEXT_LINE + end + local lastPos = guide.offsetToPosition(state, lastOffset) + if isInString(state.ast, lastPos) then goto NEXT_LINE end + local firstOffset = startOffset + for n = lastOffset - 1, startOffset, -1 do + local char = text:sub(n, n) + if char ~= ' ' and char ~= '\t' then + firstOffset = n + 1 + break + end + end + local firstPos = guide.offsetToPosition(state, firstOffset) - 1 + textEdit[#textEdit+1] = { + range = converter.packRange(uri, firstPos, lastPos), + newText = '', + } ::NEXT_LINE:: end diff --git a/script/core/command/solve.lua b/script/core/command/solve.lua index a493de24..9428d065 100644 --- a/script/core/command/solve.lua +++ b/script/core/command/solve.lua @@ -1,7 +1,8 @@ -local files = require 'files' -local guide = require 'parser.guide' -local proto = require 'proto' -local lang = require 'language' +local files = require 'files' +local guide = require 'parser.guide' +local proto = require 'proto' +local lang = require 'language' +local converter = require 'proto.converter' local opMap = { ['+'] = true, @@ -34,8 +35,7 @@ return function (data) return end - local start = files.offsetOfWord(uri, data.range.start) - local finish = files.offsetOfWord(uri, data.range['end']) + local start, finish = converter.unpackRange(uri, data.range) local result = guide.eachSourceContain(ast.ast, start, function (source) if source.start ~= start @@ -85,7 +85,7 @@ return function (data) changes = { [uri] = { { - range = files.range(uri, result.start, result.finish), + range = converter.packRange(uri, result.start, result.finish), newText = ('(%s)'):format(text:sub(result.start, result.finish)), } }, diff --git a/script/core/completion.lua b/script/core/completion.lua index fb7b2eb4..bb55506a 100644 --- a/script/core/completion.lua +++ b/script/core/completion.lua @@ -53,17 +53,17 @@ local function trim(str) return str:match '^%s*(%S+)%s*$' end -local function findNearestSource(ast, offset) +local function findNearestSource(state, position) local source - guide.eachSourceContain(ast.ast, offset, function (src) + guide.eachSourceContain(state.ast, position, function (src) source = src end) return source end -local function findNearestTableField(ast, offset) +local function findNearestTableField(state, position) local source - guide.eachSourceContain(ast.ast, offset, function (src) + guide.eachSourceContain(state.ast, position, function (src) if src.type == 'table' or src.type == 'tablefield' or src.type == 'tableindex' @@ -74,7 +74,8 @@ local function findNearestTableField(ast, offset) return source end -local function findParent(ast, text, offset) +local function findParent(state, text, position) + local offset = guide.positionToOffset(state, position) for i = offset, 1, -1 do local char = text:sub(i, i) if lookBackward.isSpace(char) then @@ -92,11 +93,12 @@ local function findParent(ast, text, offset) else return nil, nil end - local anyPos = lookBackward.findAnyPos(text, i-1) - if not anyPos then + local anyOffset = lookBackward.findAnyOffset(text, i-1) + if not anyOffset then return nil, nil end - local parent = guide.eachSourceContain(ast.ast, anyPos, function (source) + local anyPos = guide.offsetToPosition(state, anyOffset) + local parent = guide.eachSourceContain(state.ast, anyPos, function (source) if source.finish == anyPos then return source end @@ -109,9 +111,9 @@ local function findParent(ast, text, offset) return nil, nil end -local function findParentInStringIndex(ast, text, offset) +local function findParentInStringIndex(state, text, position) local near, nearStart - guide.eachSourceContain(ast.ast, offset, function (source) + guide.eachSourceContain(state.ast, position, function (source) local start = guide.getStartFinish(source) if not start then return @@ -169,17 +171,18 @@ local function getSnip(source) if def ~= source and def.type == 'function' then local uri = guide.getUri(def) local text = files.getText(uri) - local lines = files.getLines(uri) + local state = files.getState(uri) + local lines = state.lines if not text then goto CONTINUE end if vm.isMetaFile(uri) then goto CONTINUE end - local row = guide.positionOf(lines, def.start) - local firstRow = lines[row] - local lastRow = lines[math.min(row + context - 1, #lines)] - local snip = text:sub(firstRow.start, lastRow.finish) + local firstRow = guide.rowColOf(def.start) + local lastRow = firstRow + context + local lastOffset = lines[lastRow] and (lines[lastRow] - 1) or #text + local snip = text:sub(lines[firstRow], lastOffset) return snip end ::CONTINUE:: @@ -222,38 +225,8 @@ local function buildFunction(results, source, value, oop, data) end end -local function buildInsertRequire(ast, targetUri, stemName) - local uri = guide.getUri(ast.ast) - local lines = files.getLines(uri) - local text = files.getText(uri) - local start = 1 - for i = 1, #lines do - local ln = lines[i] - local lnText = text:sub(ln.start, ln.finish) - if not lnText:find('require', 1, true) then - start = ln.start - break - end - end - local path = furi.decode(targetUri) - local visiblePaths = rpath.getVisiblePath(path, config.get 'Lua.runtime.path', true) - if not visiblePaths or #visiblePaths == 0 then - return nil - end - table.sort(visiblePaths, function (a, b) - return #a.expect < #b.expect - end) - return { - { - start = start, - finish = start - 1, - newText = ('local %s = require %q\n'):format(stemName, visiblePaths[1].expect) - } - } -end - -local function isSameSource(ast, source, pos) - if guide.getUri(source) ~= guide.getUri(ast.ast) then +local function isSameSource(state, source, pos) + if guide.getUri(source) ~= guide.getUri(state.ast) then return false end if source.type == 'field' @@ -283,10 +256,10 @@ local function getParams(func, oop) return '(' .. table.concat(args, ', ') .. ')' end -local function checkLocal(ast, word, offset, results) - local locals = guide.getVisibleLocals(ast.ast, offset) +local function checkLocal(state, word, position, results) + local locals = guide.getVisibleLocals(state.ast, position) for name, source in util.sortPairs(locals) do - if isSameSource(ast, source, offset) then + if isSameSource(state, source, position) then goto CONTINUE end if not matchKey(word, name) then @@ -330,13 +303,13 @@ local function checkLocal(ast, word, offset, results) end end -local function checkModule(ast, word, offset, results) +local function checkModule(state, word, position, results) if not config.get 'Lua.completion.autoRequire' then return end - local locals = guide.getVisibleLocals(ast.ast, offset) + local locals = guide.getVisibleLocals(state.ast, position) for uri in files.eachFile() do - if uri == guide.getUri(ast.ast) then + if uri == guide.getUri(state.ast) then goto CONTINUE end local path = furi.decode(uri) @@ -347,11 +320,11 @@ local function checkModule(ast, word, offset, results) and not config.get 'Lua.diagnostics.globals'[stemName] and stemName:match '^[%a_][%w_]*$' and matchKey(word, stemName) then - local targetAst = files.getState(uri) - if not targetAst then + local targetState = files.getState(uri) + if not targetState then goto CONTINUE end - local targetReturns = targetAst.ast.returns + local targetReturns = targetState.ast.returns if not targetReturns then goto CONTINUE end @@ -377,7 +350,7 @@ local function checkModule(ast, word, offset, results) command = 'lua.autoRequire:' .. sp:get_id(), arguments = { { - uri = guide.getUri(ast.ast), + uri = guide.getUri(state.ast), target = uri, name = stemName, }, @@ -393,7 +366,7 @@ local function checkModule(ast, word, offset, results) return { detail = buildDetail(targetSource), description = md, - --additionalTextEdits = buildInsertRequire(ast, originUri, stemName), + --additionalTextEdits = buildInsertRequire(state, originUri, stemName), } end) } @@ -402,19 +375,28 @@ local function checkModule(ast, word, offset, results) end end -local function checkFieldFromFieldToIndex(name, src, parent, word, start, offset) +local function checkFieldFromFieldToIndex(name, src, parent, word, startPos, position) if name:match '^[%a_][%w_]*$' then return nil end local textEdit, additionalTextEdits - local uri = guide.getUri(parent) - local text = files.getText(uri) - local wordStart + local uri = guide.getUri(parent) + local text = files.getText(uri) + local state = files.getState(uri) + local startOffset = guide.positionToOffset(state, startPos) + local offset = guide.positionToOffset(state, position) + local wordStartOffset if word == '' then - wordStart = text:match('()%S', start + 1) or (offset + 1) + wordStartOffset = text:match('()%S', startOffset + 1) + if wordStartOffset then + wordStartOffset = wordStartOffset - 1 + else + wordStartOffset = offset + end else - wordStart = offset - #word + 1 + wordStartOffset = offset - #word end + local wordStartPos = guide.offsetToPosition(state, wordStartOffset) local newText if vm.getKeyType(src) == 'string' then newText = ('[%q]'):format(name) @@ -422,26 +404,28 @@ local function checkFieldFromFieldToIndex(name, src, parent, word, start, offset newText = ('[%s]'):format(name) end textEdit = { - start = wordStart, - finish = offset, + start = wordStartPos, + finish = position, newText = newText, } local nxt = parent.next if nxt then - local dotStart + local dotStart, dotFinish if nxt.type == 'setfield' or nxt.type == 'getfield' or nxt.type == 'tablefield' then dotStart = nxt.dot.start + dotFinish = nxt.dot.finish elseif nxt.type == 'setmethod' or nxt.type == 'getmethod' then dotStart = nxt.colon.start + dotFinish = nxt.colon.finish end if dotStart then additionalTextEdits = { { start = dotStart, - finish = dotStart, + finish = dotFinish, newText = '', } } @@ -457,7 +441,7 @@ local function checkFieldFromFieldToIndex(name, src, parent, word, start, offset return textEdit, additionalTextEdits end -local function checkFieldThen(name, src, word, start, offset, parent, oop, results) +local function checkFieldThen(name, src, word, startPos, position, parent, oop, results) local value = searcher.getObjectValue(src) or src local kind = define.CompletionItemKind.Field if value.type == 'function' @@ -494,11 +478,11 @@ local function checkFieldThen(name, src, word, start, offset, parent, oop, resul local str = parent.next.index textEdit = { start = str.start + #str[2], - finish = offset, + finish = position, newText = name, } else - textEdit, additionalTextEdits = checkFieldFromFieldToIndex(name, src, parent, word, start, offset) + textEdit, additionalTextEdits = checkFieldFromFieldToIndex(name, src, parent, word, startPos, position) end results[#results+1] = { label = name, @@ -515,7 +499,7 @@ local function checkFieldThen(name, src, word, start, offset, parent, oop, resul } end -local function checkFieldOfRefs(refs, ast, word, start, offset, parent, oop, results, locals, isGlobal) +local function checkFieldOfRefs(refs, state, word, startPos, position, parent, oop, results, locals, isGlobal) local fields = {} local funcs = {} local count = 0 @@ -524,7 +508,7 @@ local function checkFieldOfRefs(refs, ast, word, start, offset, parent, oop, res if not name then goto CONTINUE end - if isSameSource(ast, src, start) then + if isSameSource(state, src, startPos) then goto CONTINUE end if isGlobal and locals and locals[name] then @@ -573,29 +557,29 @@ local function checkFieldOfRefs(refs, ast, word, start, offset, parent, oop, res end for name, src in util.sortPairs(fields) do if src then - checkFieldThen(name, src, word, start, offset, parent, oop, results) + checkFieldThen(name, src, word, startPos, position, parent, oop, results) end end end -local function checkGlobal(ast, word, start, offset, parent, oop, results) - local locals = guide.getVisibleLocals(ast.ast, offset) +local function checkGlobal(state, word, startPos, position, parent, oop, results) + local locals = guide.getVisibleLocals(state.ast, position) local globals = vm.getGlobalSets '*' - checkFieldOfRefs(globals, ast, word, start, offset, parent, oop, results, locals, 'global') + checkFieldOfRefs(globals, state, word, startPos, position, parent, oop, results, locals, 'global') end -local function checkField(ast, word, start, offset, parent, oop, results) +local function checkField(state, word, start, position, parent, oop, results) if parent.tag == '_ENV' or parent.special == '_G' then local globals = vm.getGlobalSets '*' - checkFieldOfRefs(globals, ast, word, start, offset, parent, oop, results) + checkFieldOfRefs(globals, state, word, start, position, parent, oop, results) else local refs = vm.getRefs(parent, '*') - checkFieldOfRefs(refs, ast, word, start, offset, parent, oop, results) + checkFieldOfRefs(refs, state, word, start, position, parent, oop, results) end end -local function checkTableField(ast, word, start, results) - local source = guide.eachSourceContain(ast.ast, start, function (source) +local function checkTableField(state, word, start, results) + local source = guide.eachSourceContain(state.ast, start, function (source) if source.start == start and source.parent and source.parent.type == 'table' then @@ -606,7 +590,7 @@ local function checkTableField(ast, word, start, results) return end local used = {} - guide.eachSourceType(ast.ast, 'tablefield', function (src) + guide.eachSourceType(state.ast, 'tablefield', function (src) if not src.field then return end @@ -623,7 +607,7 @@ local function checkTableField(ast, word, start, results) end) end -local function checkCommon(myUri, word, text, offset, results) +local function checkCommon(myUri, word, text, position, results) local showWord = config.get 'Lua.completion.showWord' if showWord == 'Disable' then return @@ -691,11 +675,14 @@ local function checkCommon(myUri, word, text, offset, results) end end end - for str, pos in text:gmatch '([%a_][%w_]+)()' do + local state = files.getState(myUri) + for str, offset in text:gmatch '([%a_][%w_]+)()' do if #results >= 100 then break end - if #str >= 3 and not used[str] and pos - 1 ~= offset then + if #str >= 3 + and not used[str] + and guide.offsetToPosition(state, offset - 1) ~= position then used[str] = true if matchKey(word, str) then results[#results+1] = { @@ -710,26 +697,26 @@ local function checkCommon(myUri, word, text, offset, results) end end -local function isInString(ast, offset) - return guide.eachSourceContain(ast.ast, offset, function (source) +local function isInString(state, position) + return guide.eachSourceContain(state.ast, position, function (source) if source.type == 'string' then return true end end) end -local function checkKeyWord(ast, text, start, offset, word, hasSpace, afterLocal, results) +local function checkKeyWord(state, text, start, position, word, hasSpace, afterLocal, results) local snipType = config.get 'Lua.completion.keywordSnippet' - local symbol = lookBackward.findSymbol(text, start - 1) + local symbol = lookBackward.findSymbol(text, guide.positionToOffset(state, start)) local isExp = symbol == '(' or symbol == ',' or symbol == '=' local info = { hasSpace = hasSpace, isExp = isExp, text = text, start = start, - uri = guide.getUri(ast.ast), - offset = offset, - ast = ast, + uri = guide.getUri(state.ast), + position = position, + state = state, } for _, data in ipairs(keyWordMap) do local key = data[1] @@ -789,9 +776,9 @@ local function checkKeyWord(ast, text, start, offset, word, hasSpace, afterLocal end end -local function checkProvideLocal(ast, word, start, results) +local function checkProvideLocal(state, word, start, results) local block - guide.eachSourceContain(ast.ast, start, function (source) + guide.eachSourceContain(state.ast, start, function (source) if source.type == 'function' or source.type == 'main' then block = source @@ -825,8 +812,8 @@ local function checkProvideLocal(ast, word, start, results) end) end -local function checkFunctionArgByDocParam(ast, word, start, results) - local func = guide.eachSourceContain(ast.ast, start, function (source) +local function checkFunctionArgByDocParam(state, word, startPos, results) + local func = guide.eachSourceContain(state.ast, startPos, function (source) if source.type == 'function' then return source end @@ -846,7 +833,7 @@ local function checkFunctionArgByDocParam(ast, word, start, results) end local firstArg = func.args and func.args[1] if not firstArg - or firstArg.start <= start and firstArg.finish >= start then + or firstArg.start <= startPos and firstArg.finish >= startPos then local firstParam = params[1] if firstParam and matchKey(word, firstParam.param[1]) then local label = {} @@ -870,16 +857,17 @@ local function checkFunctionArgByDocParam(ast, word, start, results) end end -local function isAfterLocal(text, start) - local pos = lookBackward.skipSpace(text, start-1) - local word = lookBackward.findWord(text, pos) +local function isAfterLocal(state, text, startPos) + local offset = guide.positionToOffset(state, startPos) + local pos = lookBackward.skipSpace(text, offset) + local word = lookBackward.findWord(text, pos) return word == 'local' end -local function checkUri(ast, text, offset, results) +local function checkUri(state, text, position, results) local collect = {} - local myUri = guide.getUri(ast.ast) - guide.eachSourceContain(ast.ast, offset, function (source) + local myUri = guide.getUri(state.ast) + guide.eachSourceContain(state.ast, position, function (source) if source.type ~= 'string' then return end @@ -910,7 +898,7 @@ local function checkUri(ast, text, offset, results) collect[info.expect] = { textEdit = { start = source.start + #source[2], - finish = offset, + finish = position, newText = info.expect, } } @@ -996,23 +984,27 @@ local function checkUri(ast, text, offset, results) end end -local function checkLenPlusOne(ast, text, offset, results) - guide.eachSourceContain(ast.ast, offset, function (source) +local function checkLenPlusOne(state, text, position, results) + guide.eachSourceContain(state.ast, position, function (source) if source.type == 'getindex' or source.type == 'setindex' then - local _, pos = text:find('%s*%[%s*%#', source.node.finish) - if not pos then + local finish = guide.positionToOffset(state, source.node.finish) + local _, offset = text:find('%s*%[%s*%#', finish) + if not offset then return end - local nodeText = text:sub(source.node.start, source.node.finish) - local writingText = trim(text:sub(pos + 1, offset - 1)) or '' + local start = guide.positionToOffset(state, source.node.start) + 1 + local nodeText = text:sub(start, finish) + local writingText = trim(text:sub(offset + 1, guide.positionToOffset(state, position))) or '' if not matchKey(writingText, nodeText) then return end + local offsetPos = guide.offsetToPosition(state, offset) - 1 if source.parent == guide.getParentBlock(source) then + local sourceFinish = guide.positionToOffset(state, source.finish) -- state - local label = text:match('%#[ \t]*', pos) .. nodeText .. '+1' - local eq = text:find('^%s*%]?%s*%=', source.finish) + local label = text:match('%#[ \t]*', offset) .. nodeText .. '+1' + local eq = text:find('^%s*%]?%s*%=', sourceFinish) local newText = label .. ']' if not eq then newText = newText .. ' = ' @@ -1022,20 +1014,20 @@ local function checkLenPlusOne(ast, text, offset, results) match = nodeText, kind = define.CompletionItemKind.Snippet, textEdit = { - start = pos, + start = offsetPos, finish = source.finish, newText = newText, }, } else -- exp - local label = text:match('%#[ \t]*', pos) .. nodeText + local label = text:match('%#[ \t]*', offset) .. nodeText local newText = label .. ']' results[#results+1] = { label = label, kind = define.CompletionItemKind.Snippet, textEdit = { - start = pos, + start = offsetPos, finish = source.finish, newText = newText, }, @@ -1049,7 +1041,7 @@ local function tryLabelInString(label, source) if not source or source.type ~= 'string' then return label end - local str = parser:grammar(label, 'String') + local str = parser.grammar(label, 'String') if not str then return label end @@ -1084,7 +1076,7 @@ local function mergeEnums(a, b, source) end end -local function checkTypingEnum(ast, text, offset, defs, str, results) +local function checkTypingEnum(state, text, position, defs, str, results) local enums = {} for _, def in ipairs(defs) do if def.type == 'doc.type.enum' @@ -1106,21 +1098,21 @@ local function checkTypingEnum(ast, text, offset, defs, str, results) end end -local function checkEqualEnumLeft(ast, text, offset, source, results) +local function checkEqualEnumLeft(state, text, position, source, results) if not source then return end - local str = guide.eachSourceContain(ast.ast, offset, function (src) + local str = guide.eachSourceContain(state.ast, position, function (src) if src.type == 'string' then return src end end) local defs = vm.getDefs(source) - checkTypingEnum(ast, text, offset, defs, str, results) + checkTypingEnum(state, text, position, defs, str, results) end -local function checkEqualEnum(ast, text, offset, results) - local start = lookBackward.findTargetSymbol(text, offset, '=') +local function checkEqualEnum(state, text, position, results) + local start = lookBackward.findTargetSymbol(text, guide.positionToOffset(state, position), '=') if not start then return end @@ -1131,7 +1123,7 @@ local function checkEqualEnum(ast, text, offset, results) eqOrNeq = true end start = lookBackward.skipSpace(text, start - 1) - local source = findNearestSource(ast, start) + local source = findNearestSource(state, guide.offsetToPosition(state, start)) if not source then return end @@ -1141,11 +1133,11 @@ local function checkEqualEnum(ast, text, offset, results) if source.type == 'call' and not eqOrNeq then return end - checkEqualEnumLeft(ast, text, offset, source, results) + checkEqualEnumLeft(state, text, position, source, results) end -local function checkEqualEnumInString(ast, text, offset, results) - local source = findNearestSource(ast, offset) +local function checkEqualEnumInString(state, text, position, results) + local source = findNearestSource(state, position) local parent = source.parent if parent.type == 'binary' then if source ~= parent[2] then @@ -1157,118 +1149,123 @@ local function checkEqualEnumInString(ast, text, offset, results) if parent.op.type ~= '==' and parent.op.type ~= '~=' then return end - checkEqualEnumLeft(ast, text, offset, parent[1], results) + checkEqualEnumLeft(state, text, position, parent[1], results) end if parent.type == 'local' then - checkEqualEnumLeft(ast, text, offset, parent, results) + checkEqualEnumLeft(state, text, position, parent, results) end if parent.type == 'setlocal' or parent.type == 'setglobal' or parent.type == 'setfield' or parent.type == 'setindex' then - checkEqualEnumLeft(ast, text, offset, parent.node, results) + checkEqualEnumLeft(state, text, position, parent.node, results) end end -local function isFuncArg(ast, offset) - return guide.eachSourceContain(ast.ast, offset, function (source) +local function isFuncArg(state, position) + return guide.eachSourceContain(state.ast, position, function (source) if source.type == 'funcargs' then return true end end) end -local function trySpecial(ast, text, offset, results) - if isInString(ast, offset) then - checkUri(ast, text, offset, results) - checkEqualEnumInString(ast, text, offset, results) +local function trySpecial(state, text, position, results) + if isInString(state, position) then + checkUri(state, text, position, results) + checkEqualEnumInString(state, text, position, results) return end -- x[#x+1] - checkLenPlusOne(ast, text, offset, results) + checkLenPlusOne(state, text, position, results) -- type(o) == - checkEqualEnum(ast, text, offset, results) + checkEqualEnum(state, text, position, results) end -local function tryIndex(ast, text, offset, results) - local parent, oop = findParentInStringIndex(ast, text, offset) +local function tryIndex(state, text, position, results) + local parent, oop = findParentInStringIndex(state, text, position) if not parent then return end local word = parent.next.index[1] - checkField(ast, word, offset, offset, parent, oop, results) + checkField(state, word, position, position, parent, oop, results) end -local function tryWord(ast, text, offset, triggerCharacter, results) +local function tryWord(state, text, position, triggerCharacter, results) + local offset = guide.positionToOffset(state, position) local finish = lookBackward.skipSpace(text, offset) local word, start = lookBackward.findWord(text, offset) + local startPos if not word then if triggerCharacter == nil then word = '' - start = offset + 1 + startPos = position + 1 else return nil end + else + startPos = guide.offsetToPosition(state, start - 1) end local hasSpace = triggerCharacter ~= nil and finish ~= offset - if isInString(ast, offset) then + if isInString(state, position) then if not hasSpace then if #results == 0 then - checkCommon(ast.uri, word, text, offset, results) + checkCommon(state.uri, word, text, position, results) end end else - local parent, oop = findParent(ast, text, start - 1) + local parent, oop = findParent(state, text, startPos) if parent then if not hasSpace then - checkField(ast, word, start, offset, parent, oop, results) + checkField(state, word, startPos, position, parent, oop, results) end - elseif isFuncArg(ast, offset) then - checkProvideLocal(ast, word, start, results) - checkFunctionArgByDocParam(ast, word, start, results) + elseif isFuncArg(state, position) then + checkProvideLocal(state, word, startPos, results) + checkFunctionArgByDocParam(state, word, startPos, results) else - local afterLocal = isAfterLocal(text, start) - local stop = checkKeyWord(ast, text, start, offset, word, hasSpace, afterLocal, results) + local afterLocal = isAfterLocal(state, text, startPos) + local stop = checkKeyWord(state, text, startPos, position, word, hasSpace, afterLocal, results) if stop then return end if not hasSpace then if afterLocal then - checkProvideLocal(ast, word, start, results) + checkProvideLocal(state, word, startPos, results) else - checkLocal(ast, word, start, results) - checkTableField(ast, word, start, results) - local env = guide.getENV(ast.ast, start) - checkGlobal(ast, word, start, offset, env, false, results) - checkModule(ast, word, start, results) + checkLocal(state, word, startPos, results) + checkTableField(state, word, startPos, results) + local env = guide.getENV(state.ast, startPos) + checkGlobal(state, word, startPos, position, env, false, results) + checkModule(state, word, startPos, results) end end end if not hasSpace then - checkCommon(ast.uri, word, text, offset, results) + checkCommon(state.uri, word, text, position, results) end end end -local function trySymbol(ast, text, offset, results) - local symbol, start = lookBackward.findSymbol(text, offset) +local function trySymbol(state, text, position, results) + local symbol, start = lookBackward.findSymbol(text, guide.positionToOffset(state, position)) if not symbol then return nil end - if isInString(ast, offset) then + if isInString(state, position) then return nil end + local startPos = guide.offsetToPosition(state, start) if symbol == '.' or symbol == ':' then - local parent, oop = findParent(ast, text, start) + local parent, oop = findParent(state, text, startPos) if parent then tracy.ZoneBeginN 'completion.trySymbol' - checkField(ast, '', start, offset, parent, oop, results) + checkField(state, '', startPos, position, parent, oop, results) tracy.ZoneEnd() end end if symbol == '(' then - checkFunctionArgByDocParam(ast, '', start, results) + checkFunctionArgByDocParam(state, '', startPos, results) end end @@ -1354,9 +1351,9 @@ local function getCallEnumsAndFuncs(source, index, oop) end end -local function findCall(ast, text, offset) +local function findCall(state, text, position) local call - guide.eachSourceContain(ast.ast, offset, function (src) + guide.eachSourceContain(state.ast, position, function (src) if src.type == 'call' then if not call or call.start < src.start then call = src @@ -1366,13 +1363,13 @@ local function findCall(ast, text, offset) return call end -local function getCallArgInfo(call, text, offset) +local function getCallArgInfo(call, text, position) if not call.args then return 1, nil, nil end local oop = call.node.type == 'getmethod' for index, arg in ipairs(call.args) do - if arg.start <= offset and arg.finish >= offset then + if arg.start <= position and arg.finish >= position then return index, arg, oop end end @@ -1391,7 +1388,7 @@ local function getFuncParamByCallIndex(func, index) return func.args[index] end -local function checkTableLiteralField(ast, text, offset, tbl, fields, results) +local function checkTableLiteralField(state, text, position, tbl, fields, results) local mark = {} for _, field in ipairs(tbl) do if field.type == 'tablefield' @@ -1407,9 +1404,9 @@ local function checkTableLiteralField(ast, text, offset, tbl, fields, results) return guide.getKeyName(a) < guide.getKeyName(b) end) -- {$} - local left = lookBackward.findWord(text, offset) + local left = lookBackward.findWord(text, guide.positionToOffset(state, position)) if not left then - local pos = lookBackward.findAnyPos(text, offset) + local pos = lookBackward.findAnyOffset(text, guide.positionToOffset(state, position)) local char = text:sub(pos, pos) if char == '{' or char == ',' or char == ';' then left = '' @@ -1435,8 +1432,8 @@ local function checkTableLiteralField(ast, text, offset, tbl, fields, results) end end -local function checkTableLiteralFieldByCall(ast, text, offset, call, defs, index, results) - local source = findNearestTableField(ast, offset) +local function checkTableLiteralFieldByCall(state, text, position, call, defs, index, results) + local source = findNearestTableField(state, position) if not source then return end @@ -1469,16 +1466,16 @@ local function checkTableLiteralFieldByCall(ast, text, offset, call, defs, index end ::CONTINUE:: end - checkTableLiteralField(ast, text, offset, tbl, fields, results) + checkTableLiteralField(state, text, position, tbl, fields, results) end -local function tryCallArg(ast, text, offset, results) - local call = findCall(ast, text, offset) +local function tryCallArg(state, text, position, results) + local call = findCall(state, text, position) if not call then return end local myResults = {} - local argIndex, arg, oop = getCallArgInfo(call, text, offset) + local argIndex, arg, oop = getCallArgInfo(call, text, position) if arg and arg.type == 'function' then return end @@ -1493,12 +1490,11 @@ local function tryCallArg(ast, text, offset, results) for _, enum in ipairs(myResults) do results[#results+1] = enum end - checkTableLiteralFieldByCall(ast, text, offset, call, defs, argIndex, results) + checkTableLiteralFieldByCall(state, text, position, call, defs, argIndex, results) end -local function tryTable(ast, text, offset, results) - offset = lookBackward.skipSpace(text, offset) - local source = findNearestTableField(ast, offset) +local function tryTable(state, text, position, results) + local source = findNearestTableField(state, position) if not source then return end @@ -1521,21 +1517,21 @@ local function tryTable(ast, text, offset, results) fields[#fields+1] = field end end - checkTableLiteralField(ast, text, offset, tbl, fields, results) + checkTableLiteralField(state, text, position, tbl, fields, results) end -local function getComment(ast, offset) - for _, comm in ipairs(ast.comms) do - if offset >= comm.start - 2 and offset <= comm.finish then +local function getComment(state, position) + for _, comm in ipairs(state.comms) do + if position > comm.start and position <= comm.finish then return comm end end return nil end -local function getLuaDoc(ast, offset) - for _, doc in ipairs(ast.ast.docs) do - if offset >= doc.start and offset <= doc.range then +local function getLuaDoc(state, position) + for _, doc in ipairs(state.ast.docs) do + if position >= doc.start and position <= doc.range then return doc end end @@ -1568,28 +1564,28 @@ local function tryLuaDocCate(word, results) end end -local function getLuaDocByContain(ast, offset) +local function getLuaDocByContain(state, position) local result local range = math.huge - guide.eachSourceContain(ast.ast.docs, offset, function (src) + guide.eachSourceContain(state.ast.docs, position, function (src) if not src.start then return end - if range >= offset - src.start - and offset <= src.finish then - range = offset - src.start + if range >= position - src.start + and position <= src.finish then + range = position - src.start result = src end end) return result end -local function getLuaDocByErr(ast, text, start, offset) +local function getLuaDocByErr(state, text, start, position) local targetError - for _, err in ipairs(ast.errs) do - if err.finish <= offset + for _, err in ipairs(state.errs) do + if err.finish <= position and err.start >= start then - if not text:sub(err.finish + 1, offset):find '%S' then + if not text:sub(err.finish + 1, position):find '%S' then targetError = err break end @@ -1599,8 +1595,8 @@ local function getLuaDocByErr(ast, text, start, offset) return nil end local targetDoc - for i = #ast.ast.docs, 1, -1 do - local doc = ast.ast.docs[i] + for i = #state.ast.docs, 1, -1 do + local doc = state.ast.docs[i] if doc.finish <= targetError.start then targetDoc = doc break @@ -1609,7 +1605,7 @@ local function getLuaDocByErr(ast, text, start, offset) return targetError, targetDoc end -local function tryLuaDocBySource(ast, offset, source, results) +local function tryLuaDocBySource(state, position, source, results) if source.type == 'doc.extends.name' then if source.parent.type == 'doc.class' then for _, doc in ipairs(vm.getDocDefines '*') do @@ -1621,7 +1617,7 @@ local function tryLuaDocBySource(ast, offset, source, results) kind = define.CompletionItemKind.Class, textEdit = doc[1]:find '[^%w_]' and { start = source.start, - finish = offset, + finish = position, newText = doc[1], }, } @@ -1639,7 +1635,7 @@ local function tryLuaDocBySource(ast, offset, source, results) kind = define.CompletionItemKind.Class, textEdit = doc[1]:find '[^%w_]' and { start = source.start, - finish = offset, + finish = position, newText = doc[1], }, } @@ -1648,8 +1644,8 @@ local function tryLuaDocBySource(ast, offset, source, results) return true elseif source.type == 'doc.param.name' then local funcs = {} - guide.eachSourceBetween(ast.ast, offset, math.huge, function (src) - if src.type == 'function' and src.start > offset then + guide.eachSourceBetween(state.ast, position, math.huge, function (src) + if src.type == 'function' and src.start > position then funcs[#funcs+1] = src end end) @@ -1702,7 +1698,7 @@ local function tryLuaDocBySource(ast, offset, source, results) return false end -local function tryLuaDocByErr(ast, offset, err, docState, results) +local function tryLuaDocByErr(state, position, err, docState, results) if err.type == 'LUADOC_MISS_CLASS_EXTENDS_NAME' then for _, doc in ipairs(vm.getDocDefines '*') do if doc.type == 'doc.class.name' @@ -1724,8 +1720,8 @@ local function tryLuaDocByErr(ast, offset, err, docState, results) end elseif err.type == 'LUADOC_MISS_PARAM_NAME' then local funcs = {} - guide.eachSourceBetween(ast.ast, offset, math.huge, function (src) - if src.type == 'function' and src.start > offset then + guide.eachSourceBetween(state.ast, position, math.huge, function (src) + if src.type == 'function' and src.start > position then funcs[#funcs+1] = src end end) @@ -1851,49 +1847,49 @@ local function tryLuaDocOfFunction(doc, results) } end -local function tryLuaDoc(ast, text, offset, results) - local doc = getLuaDoc(ast, offset) +local function tryLuaDoc(state, text, position, results) + local doc = getLuaDoc(state, position) if not doc then return end if doc.type == 'doc.comment' then - local line = text:sub(doc.start, doc.range) + local line = doc.originalComment.text -- 尝试 ---$ if line == '-' then tryLuaDocOfFunction(doc, results) return end -- 尝试 ---@$ - local cate = line:match('^-%s*@(%a*)$') + local cate = line:match('^-+%s*@(%a*)$') if cate then tryLuaDocCate(cate, results) return end end -- 根据输入中的source来补全 - local source = getLuaDocByContain(ast, offset) + local source = getLuaDocByContain(state, position) if source then - local suc = tryLuaDocBySource(ast, offset, source, results) + local suc = tryLuaDocBySource(state, position, source, results) if suc then return end end -- 根据附近的错误消息来补全 - local err, expectDoc = getLuaDocByErr(ast, text, doc.start, offset) + local err, expectDoc = getLuaDocByErr(state, text, doc.start, position) if err then - tryLuaDocByErr(ast, offset, err, expectDoc, results) + tryLuaDocByErr(state, position, err, expectDoc, results) return end end -local function tryComment(ast, text, offset, results) +local function tryComment(state, text, position, results) if #results > 0 then return end - local word = lookBackward.findWord(text, offset) - local doc = getLuaDoc(ast, offset) + local word = lookBackward.findWord(text, guide.positionToOffset(state, position)) + local doc = getLuaDoc(state, position) if not word then - local comment = getComment(ast, offset) + local comment = getComment(state, position) if comment.type == 'comment.short' or comment.type == 'comment.cshort' then if comment.text == '' then @@ -1912,23 +1908,24 @@ local function tryComment(ast, text, offset, results) if doc and doc.type ~= 'doc.comment' then return end - checkCommon(ast.uri, word, text, offset, results) + checkCommon(state.uri, word, text, position, results) end -local function makeCache(uri, offset, results) +local function makeCache(uri, position, results) local cache = workspace.getCache 'completion' if not uri then cache.results = nil return end local text = files.getText(uri) - local word = lookBackward.findWord(text, offset) + local state = files.getState(uri) + local word = lookBackward.findWord(text, guide.positionToOffset(state, position)) if not word or #word < 2 then cache.results = nil return end cache.results = results - cache.offset = offset + cache.position= position cache.word = word:lower() cache.length = #word end @@ -1950,13 +1947,14 @@ local function isValidCache(word, result) return false end -local function getCache(uri, offset) +local function getCache(uri, position) local cache = workspace.getCache 'completion' if not cache.results then return nil end local text = files.getText(uri) - local word = lookBackward.findWord(text, offset) + local state = files.getState(uri) + local word = lookBackward.findWord(text, guide.positionToOffset(state, position)) if not word then return nil end @@ -1980,7 +1978,7 @@ local function getCache(uri, offset) end if results.enableCommon then - checkCommon(uri, word, text, offset, results) + checkCommon(uri, word, text, position, results) end return cache.results @@ -1991,36 +1989,36 @@ local function clearCache() cache.results = nil end -local function completion(uri, offset, triggerCharacter) +local function completion(uri, position, triggerCharacter) tracy.ZoneBeginN 'completion cache' - local results = getCache(uri, offset) + local results = getCache(uri, position) tracy.ZoneEnd() if results then return results end tracy.ZoneBeginN 'completion #1' - local ast = files.getState(uri) + local state = files.getState(uri) local text = files.getText(uri) results = {} clearStack() tracy.ZoneEnd() tracy.ZoneBeginN 'completion #2' - if ast then - if getComment(ast, offset) then - tryLuaDoc(ast, text, offset, results) - tryComment(ast, text, offset, results) + if state then + if getComment(state, position) then + tryLuaDoc(state, text, position, results) + tryComment(state, text, position, results) else - trySpecial(ast, text, offset, results) - tryCallArg(ast, text, offset, results) - tryTable(ast, text, offset, results) - tryWord(ast, text, offset, triggerCharacter, results) - tryIndex(ast, text, offset, results) - trySymbol(ast, text, offset, results) + trySpecial(state, text, position, results) + tryCallArg(state, text, position, results) + tryTable(state, text, position, results) + tryWord(state, text, position, triggerCharacter, results) + tryIndex(state, text, position, results) + trySymbol(state, text, position, results) end else - local word = lookBackward.findWord(text, offset) + local word = lookBackward.findWord(text, guide.positionToOffset(state, position)) if word then - checkCommon(nil, word, text, offset, results) + checkCommon(nil, word, text, position, results) end end tracy.ZoneEnd() @@ -2031,7 +2029,7 @@ local function completion(uri, offset, triggerCharacter) end tracy.ZoneBeginN 'completion #3' - makeCache(uri, offset, results) + makeCache(uri, position, results) tracy.ZoneEnd() return results end diff --git a/script/core/diagnostics/count-down-loop.lua b/script/core/diagnostics/count-down-loop.lua index 1a7dcf7d..49c48880 100644 --- a/script/core/diagnostics/count-down-loop.lua +++ b/script/core/diagnostics/count-down-loop.lua @@ -10,30 +10,39 @@ return function (uri, callback) end guide.eachSourceType(state.ast, 'loop', function (source) - if not source.loc or not source.loc.value then - return - end local maxNumer = source.max and tonumber(source.max[1]) if maxNumer ~= 1 then return end - local minNumber = source.loc and source.loc.value and tonumber(source.loc.value[1]) + local minNumber = source.init and tonumber(source.init[1]) if minNumber and minNumber <= 1 then return end if not source.step then callback { - start = source.loc.value.start, + start = source.init.start, finish = source.max.finish, - message = lang.script('DIAG_COUNT_DOWN_LOOP', ('%s, %s'):format(text:sub(source.loc.value.start, source.max.finish), '-1')) + message = lang.script('DIAG_COUNT_DOWN_LOOP' + , ('%s, %s'):format(text:sub( + guide.positionToOffset(state, source.init.start), + guide.positionToOffset(state, source.max.finish) + ) + , '-1') + ) } else local stepNumber = tonumber(source.step[1]) if stepNumber and stepNumber > 0 then callback { - start = source.loc.value.start, + start = source.init.start, finish = source.step.finish, - message = lang.script('DIAG_COUNT_DOWN_LOOP', ('%s, -%s'):format(text:sub(source.loc.value.start, source.max.finish), source.step[1])) + message = lang.script('DIAG_COUNT_DOWN_LOOP' + , ('%s, -%s'):format(text:sub( + guide.positionToOffset(state, source.init.start), + guide.positionToOffset(state, source.max.finish) + ) + , source.step[1]) + ) } end end diff --git a/script/core/diagnostics/different-requires.lua b/script/core/diagnostics/different-requires.lua index 909342f4..fd7415b6 100644 --- a/script/core/diagnostics/different-requires.lua +++ b/script/core/diagnostics/different-requires.lua @@ -12,7 +12,7 @@ return function (uri, callback) end local cache = vm.getCache 'different-requires' guide.eachSpecialOf(state.ast, 'require', function (source) - local call = source.next + local call = source.parent if not call or call.type ~= 'call' then return end diff --git a/script/core/diagnostics/init.lua b/script/core/diagnostics/init.lua index 09688f6e..63a1bcf0 100644 --- a/script/core/diagnostics/init.lua +++ b/script/core/diagnostics/init.lua @@ -44,7 +44,7 @@ local function check(uri, name, results) if vm.isDiagDisabledAt(uri, result.start, name) then return end - if result.start == 0 then + if result.start < 0 then return end if mark[result.start] then diff --git a/script/core/diagnostics/newfield-call.lua b/script/core/diagnostics/newfield-call.lua index fe86ad66..669ed2bb 100644 --- a/script/core/diagnostics/newfield-call.lua +++ b/script/core/diagnostics/newfield-call.lua @@ -8,7 +8,6 @@ return function (uri, callback) return end - local lines = files.getLines(uri) local text = files.getText(uri) guide.eachSourceType(ast.ast, 'table', function (source) @@ -27,8 +26,8 @@ return function (uri, callback) local func = call.node local args = call.args if args then - local funcLine = guide.positionOf(lines, func.finish) - local argsLine = guide.positionOf(lines, args.start) + local funcLine = guide.rowColOf(func.finish) + local argsLine = guide.rowColOf(args.start) if argsLine > funcLine then callback { start = call.start, diff --git a/script/core/diagnostics/newline-call.lua b/script/core/diagnostics/newline-call.lua index 71dc33e2..dbb8c690 100644 --- a/script/core/diagnostics/newline-call.lua +++ b/script/core/diagnostics/newline-call.lua @@ -3,14 +3,13 @@ local guide = require 'parser.guide' local lang = require 'language' return function (uri, callback) - local ast = files.getState(uri) - local lines = files.getLines(uri) + local state = files.getState(uri) local text = files.getText(uri) - if not ast or not lines then + if not state then return end - guide.eachSourceType(ast.ast, 'call', function (source) + guide.eachSourceType(state.ast, 'call', function (source) local node = source.node local args = source.args if not args then @@ -21,13 +20,15 @@ return function (uri, callback) if not source.next then return end - if text:sub(args.start, args.start) ~= '(' - or text:sub(args.finish, args.finish) ~= ')' then + local startOffset = guide.positionToOffset(state, args.start) + 1 + local finishOffset = guide.positionToOffset(state, args.finish) + if text:sub(startOffset, startOffset) ~= '(' + or text:sub(finishOffset, finishOffset) ~= ')' then return end - local nodeRow = guide.positionOf(lines, node.finish) - local argRow = guide.positionOf(lines, args.start) + local nodeRow = guide.rowColOf(node.finish) + local argRow = guide.rowColOf(args.start) if nodeRow == argRow then return end diff --git a/script/core/diagnostics/redundant-value.lua b/script/core/diagnostics/redundant-value.lua index d6cd97a7..4c913330 100644 --- a/script/core/diagnostics/redundant-value.lua +++ b/script/core/diagnostics/redundant-value.lua @@ -1,24 +1,24 @@ local files = require 'files' local define = require 'proto.define' local lang = require 'language' +local guide = require 'parser.guide' +local await = require 'await' -return function (uri, callback, code) - local ast = files.getState(uri) - if not ast then +return function (uri, callback) + local state = files.getState(uri) + if not state then return end - local diags = ast.diags[code] - if not diags then - return - end - - for _, info in ipairs(diags) do - callback { - start = info.start, - finish = info.finish, - tags = { define.DiagnosticTag.Unnecessary }, - message = lang.script('DIAG_OVER_MAX_VALUES', info.max, info.passed) - } - end + guide.eachSource(state.ast, function (src) + await.delay() + if src.redundant then + callback { + start = src.start, + finish = src.finish, + tags = { define.DiagnosticTag.Unnecessary }, + message = lang.script('DIAG_OVER_MAX_VALUES', src.redundant.max, src.redundant.passed) + } + end + end) end diff --git a/script/core/diagnostics/trailing-space.lua b/script/core/diagnostics/trailing-space.lua index 824eb83f..cc51cf77 100644 --- a/script/core/diagnostics/trailing-space.lua +++ b/script/core/diagnostics/trailing-space.lua @@ -13,40 +13,43 @@ local function isInString(ast, offset) end return function (uri, callback) - local ast = files.getState(uri) - if not ast then + local state = files.getState(uri) + if not state then return end local text = files.getText(uri) - local lines = files.getLines(uri) - for i = 1, #lines do - local start = lines[i].start - local range = lines[i].range - local lastChar = text:sub(range, range) + local lines = state.lines + for i = 0, #lines do + local startOffset = lines[i] + local finishOffset = text:find('[\r\n]', startOffset) or (#text + 1) + local lastOffset = finishOffset - 1 + local lastChar = text:sub(lastOffset, lastOffset) if lastChar ~= ' ' and lastChar ~= '\t' then goto NEXT_LINE end - if isInString(ast.ast, range) then + local lastPos = guide.offsetToPosition(state, lastOffset) + if isInString(state.ast, lastPos) then goto NEXT_LINE end - local first = start - for n = range - 1, start, -1 do + local firstOffset = startOffset + for n = lastOffset - 1, startOffset, -1 do local char = text:sub(n, n) if char ~= ' ' and char ~= '\t' then - first = n + 1 + firstOffset = n + 1 break end end - if first == start then + local firstPos = guide.offsetToPosition(state, firstOffset) - 1 + if firstOffset == startOffset then callback { - start = first, - finish = range, + start = firstPos, + finish = lastPos, message = lang.script.DIAG_LINE_ONLY_SPACE, } else callback { - start = first, - finish = range, + start = firstPos, + finish = lastPos, message = lang.script.DIAG_LINE_POST_SPACE, } end diff --git a/script/core/diagnostics/undefined-global.lua b/script/core/diagnostics/undefined-global.lua index c7ddeac2..14754c16 100644 --- a/script/core/diagnostics/undefined-global.lua +++ b/script/core/diagnostics/undefined-global.lua @@ -5,6 +5,7 @@ local config = require 'config' local guide = require 'parser.guide' local noder = require 'core.noder' local collector = require 'core.collector' +local await = require 'await' local requireLike = { ['include'] = true, @@ -35,6 +36,7 @@ return function (uri, callback) if node.tag ~= '_ENV' then return end + await.delay() local id = 'def:' .. noder.getID(src) if not collector.has(id) then local message = lang.script('DIAG_UNDEF_GLOBAL', key) diff --git a/script/core/document-symbol.lua b/script/core/document-symbol.lua index 9f950d25..cfabedab 100644 --- a/script/core/document-symbol.lua +++ b/script/core/document-symbol.lua @@ -5,20 +5,26 @@ local define = require 'proto.define' local util = require 'utility' local function buildName(source, text) + local uri = guide.getUri(source) + local state = files.getState(uri) + local startOffset = guide.positionToOffset(state, source.start) if source.type == 'setmethod' or source.type == 'getmethod' then if source.method then - return text:sub(source.start, source.method.finish) + local finishOffset = guide.positionToOffset(state, source.method.finish) + return text:sub(startOffset + 1, finishOffset) end end if source.type == 'setfield' or source.type == 'tablefield' or source.type == 'getfield' then if source.field then - return text:sub(source.start, source.field.finish) + local finishOffset = guide.positionToOffset(state, source.field.finish) + return text:sub(startOffset + 1, finishOffset) end end - return text:sub(source.start, source.finish) + local finishOffset = guide.positionToOffset(state, source.finish) + return text:sub(startOffset + 1, finishOffset) end local function buildFunctionParams(func) @@ -208,7 +214,7 @@ local function buildAnonymousFunction(source, text, used, symbols) detail = ('%sfunction (%s)'):format(head, buildFunctionParams(source)), kind = define.SymbolKind.Function, range = { source.start, source.finish }, - selectionRange = { source.start, source.start }, + selectionRange = { source.keyword[1], source.keyword[2] }, valueRange = { source.start, source.finish }, } end diff --git a/script/core/find-source.lua b/script/core/find-source.lua index edbb1e2c..26a411e5 100644 --- a/script/core/find-source.lua +++ b/script/core/find-source.lua @@ -11,12 +11,12 @@ local function isValidFunctionPos(source, offset) return false end -return function (ast, offset, accept) +return function (ast, position, accept) local len = math.huge local result - guide.eachSourceContain(ast.ast, offset, function (source) + guide.eachSourceContain(ast.ast, position, function (source) if source.type == 'function' then - if not isValidFunctionPos(source, offset) then + if not isValidFunctionPos(source, position) then return end end diff --git a/script/core/folding.lua b/script/core/folding.lua index 51d1be6b..bd3ba5f3 100644 --- a/script/core/folding.lua +++ b/script/core/folding.lua @@ -146,22 +146,22 @@ local Care = { } return function (uri) - local ast = files.getState(uri) - local text = files.getText(uri) - if not ast or not text then + local state = files.getState(uri) + local text = files.getText(uri) + if not state or not text then return nil end local regions = {} local status = {} - guide.eachSource(ast.ast, function (source) + guide.eachSource(state.ast, function (source) local tp = source.type if Care[tp] then await.delay() Care[tp](source, text, regions) end end) - for _, source in ipairs(ast.comms) do + for _, source in ipairs(state.comms) do local tp = source.type if Care[tp] then await.delay() diff --git a/script/core/highlight.lua b/script/core/highlight.lua index 47b482d5..02f3c07f 100644 --- a/script/core/highlight.lua +++ b/script/core/highlight.lua @@ -54,12 +54,12 @@ local function find(source, uri, callback) end end -local function checkInIf(source, text, offset) +local function checkInIf(state, source, text, position) -- 检查 end - local endA = source.finish - #'end' + 1 - local endB = source.finish - if offset >= endA - and offset <= endB + local endB = guide.positionToOffset(state, source.finish) + local endA = endB - #'end' + 1 + if position >= source.finish - #'end' + and position <= source.finish and text:sub(endA, endB) == 'end' then return true end @@ -68,7 +68,7 @@ local function checkInIf(source, text, offset) for i = 1, #block.keyword, 2 do local start = block.keyword[i] local finish = block.keyword[i+1] - if offset >= start and offset <= finish then + if position >= start and position <= finish then return true end end @@ -76,12 +76,12 @@ local function checkInIf(source, text, offset) return false end -local function makeIf(source, text, callback) +local function makeIf(state, source, text, callback) -- end - local endA = source.finish - #'end' + 1 - local endB = source.finish + local endB = guide.positionToOffset(state, source.finish) + local endA = endB - #'end' + 1 if text:sub(endA, endB) == 'end' then - callback(endA, endB) + callback(source.finish - #'end', source.finish) end -- 每个子模块 for _, block in ipairs(source) do @@ -94,8 +94,8 @@ local function makeIf(source, text, callback) return false end -local function findKeyWord(ast, text, offset, callback) - guide.eachSourceContain(ast.ast, offset, function (source) +local function findKeyWord(state, text, position, callback) + guide.eachSourceContain(state.ast, position, function (source) if source.type == 'do' or source.type == 'function' or source.type == 'loop' @@ -106,7 +106,7 @@ local function findKeyWord(ast, text, offset, callback) for i = 1, #source.keyword, 2 do local start = source.keyword[i] local finish = source.keyword[i+1] - if offset >= start and offset <= finish then + if position >= start and position <= finish then ok = true break end @@ -119,9 +119,9 @@ local function findKeyWord(ast, text, offset, callback) end end elseif source.type == 'if' then - local ok = checkInIf(source, text, offset) + local ok = checkInIf(state, source, text, position) if ok then - makeIf(source, text, callback) + makeIf(state, source, text, callback) end end end) @@ -238,15 +238,15 @@ local function isLiteralValue(source) end return function (uri, offset) - local ast = files.getState(uri) - if not ast then + local state = files.getState(uri) + if not state then return nil end local text = files.getText(uri) local results = {} local mark = {} - local source = findSource(ast, offset, accept) + local source = findSource(state, offset, accept) if source then local isGlobal = guide.isGlobal(source) local isLiteral = isLiteralValue(source) @@ -344,7 +344,7 @@ return function (uri, offset) end) end - findKeyWord(ast, text, offset, function (start, finish) + findKeyWord(state, text, offset, function (start, finish) results[#results+1] = { start = start, finish = finish, @@ -352,7 +352,7 @@ return function (uri, offset) } end) - checkRegion(ast, text, offset, function (start, finish) + checkRegion(state, text, offset, function (start, finish) results[#results+1] = { start = start, finish = finish, diff --git a/script/core/hover/label.lua b/script/core/hover/label.lua index a29cf672..3322e0d3 100644 --- a/script/core/hover/label.lua +++ b/script/core/hover/label.lua @@ -50,8 +50,7 @@ local function asValue(source, title) local literal = infer.searchAndViewLiterals(source) local cont if not infer.hasType(source, 'string') - and not type:find('%[%]$') - and not type:find('%w%<') then + and not type:find('%[%]$') then if #vm.getRefs(source, '*') > 0 or infer.hasType(source, 'table') then cont = buildTable(source) diff --git a/script/core/infer.lua b/script/core/infer.lua index d6784c67..2915f7f5 100644 --- a/script/core/infer.lua +++ b/script/core/infer.lua @@ -371,9 +371,10 @@ function m.getDocName(doc) return nodeName .. '[]' end if doc.type == 'doc.type.table' then - local key = m.viewDocName(doc.tkey) or '?' + local node = m.viewDocName(doc.node) or '?' + local key = m.viewDocName(doc.tkey) or '?' local value = m.viewDocName(doc.tvalue) or '?' - return ('table<%s, %s>'):format(key, value) + return ('%s<%s, %s>'):format(node, key, value) end if doc.type == 'doc.type.function' then return m.viewDocFunction(doc) diff --git a/script/core/keyword.lua b/script/core/keyword.lua index b8e37605..295026d7 100644 --- a/script/core/keyword.lua +++ b/script/core/keyword.lua @@ -24,14 +24,12 @@ end", end return true end, function (info) - return guide.eachSourceContain(info.ast.ast, info.start, function (source) + return guide.eachSourceContain(info.state.ast, info.start, function (source) if source.type == 'while' or source.type == 'in' or source.type == 'loop' then - for i = 1, #source.keyword do - if info.start == source.keyword[i] then - return true - end + if source.finish - info.start <= 2 then + return true end end end) @@ -40,8 +38,9 @@ end", {'break'}, {'else'}, {'elseif', function (info, results) - if info.text:find('^%s*then', info.offset + 1) - or info.text:find('^%s*do', info.offset + 1) then + local offset = guide.positionToOffset(info.state, info.position) + if info.text:find('^%s*then', offset + 1) + or info.text:find('^%s*do', offset + 1) then return false end if info.hasSpace then @@ -155,8 +154,9 @@ end" end}, {'goto'}, {'if', function (info, results) - if info.text:find('^%s*then', info.offset + 1) - or info.text:find('^%s*do', info.offset + 1) then + local offset = guide.positionToOffset(info.state, info.position) + if info.text:find('^%s*then', offset + 1) + or info.text:find('^%s*do', offset + 1) then return false end if info.hasSpace then @@ -183,8 +183,9 @@ end" return true end}, {'in', function (info, results) - if info.text:find('^%s*then', info.offset + 1) - or info.text:find('^%s*do', info.offset + 1) then + local offset = guide.positionToOffset(info.state, info.position) + if info.text:find('^%s*then', offset + 1) + or info.text:find('^%s*do', offset + 1) then return false end if info.hasSpace then @@ -270,15 +271,16 @@ until $1" return false end}, {'then', function (info, results) - local lines = files.getLines(info.uri) - local pos, first = info.text:match('%S+%s+()(%S+)', info.start) + local startOffset = guide.positionToOffset(info.state, info.start) + local pos, first = info.text:match('%S+%s+()(%S+)', startOffset + 1) if first == 'end' or first == 'else' or first == 'elseif' then - local startRow = guide.positionOf(lines, info.start) - local finishRow = guide.positionOf(lines, pos) - local startSp = info.text:match('^%s*', lines[startRow].start + 1) - local finishSp = info.text:match('^%s*', lines[finishRow].start + 1) + local startRow = guide.rowColOf(info.start) + local finishPosition = guide.offsetToPosition(info.state, pos) + local finishRow = guide.rowColOf(finishPosition) + local startSp = info.text:match('^%s*', info.state.lines[startRow]) + local finishSp = info.text:match('^%s*', info.state.lines[finishRow]) if startSp == finishSp then return false end diff --git a/script/core/look-backward.lua b/script/core/look-backward.lua index ee89078f..2f90b768 100644 --- a/script/core/look-backward.lua +++ b/script/core/look-backward.lua @@ -77,7 +77,7 @@ function m.findTargetSymbol(text, offset, symbol) return nil end -function m.findAnyPos(text, offset) +function m.findAnyOffset(text, offset) for i = offset, 1, -1 do if not m.isSpace(text:sub(i, i)) then return i diff --git a/script/core/noder.lua b/script/core/noder.lua index 3fd316eb..d1388cb3 100644 --- a/script/core/noder.lua +++ b/script/core/noder.lua @@ -883,8 +883,8 @@ compileNodeMap = util.switch() : call(function (noders, id, source) if source.bindSources then for _, src in ipairs(source.bindSources) do - pushForward(noders, getID(src), id, INFO_REJECT_SET) - pushForward(noders, id, getID(src)) + pushForward(noders, getID(src), id) + pushBackward(noders, id, getID(src)) end end for _, enumUnit in ipairs(source.enums) do @@ -898,13 +898,16 @@ compileNodeMap = util.switch() pushForward(noders, id, unitID) if source.bindSources then for _, src in ipairs(source.bindSources) do - pushBackward(noders, unitID, getID(src), INFO_REJECT_SET) + pushBackward(noders, unitID, getID(src)) end end end end) : case 'doc.type.table' : call(function (noders, id, source) + if source.node then + pushForward(noders, id, getID(source.node), INFO_CLASS_TO_EXNTENDS) + end if source.tkey then local keyID = id .. TABLE_KEY pushForward(noders, keyID, getID(source.tkey)) @@ -969,7 +972,7 @@ compileNodeMap = util.switch() if source.bindSources then for _, src in ipairs(source.bindSources) do pushForward(noders, getID(src), id) - pushForward(noders, id, getID(src), INFO_REJECT_SET) + pushForward(noders, id, getID(src)) end end for _, field in ipairs(source.fields) do @@ -1231,6 +1234,55 @@ compileNodeMap = util.switch() end end end) + : case 'in' + : call(function (noders, id, source) + local keys = source.keys + local exps = source.exps + if not keys or not exps then + return + end + local node = exps[1] + local param1 = exps[2] + local param2 = exps[3] + if node.type == 'call' then + if not param1 then + param1 = { + type = 'select', + dummy = true, + sindex = 2, + start = node.start, + finish = node.finish, + vararg = node, + parent = source, + } + compileCallReturn(noders, node, getID(param1), 2) + if not param2 then + param2 = { + type = 'select', + dummy = true, + sindex = 3, + start = node.start, + finish = node.finish, + vararg = node, + parent = source, + } + compileCallReturn(noders, node, getID(param2), 3) + end + end + end + local call = { + type = 'call', + dummy = true, + start = source.keyword[3], + finish = exps[#exps].finish, + node = node, + args = { param1, param2 }, + parent = source, + } + for i = 1, #keys do + compileCallReturn(noders, call, getID(keys[i]), i) + end + end) : case 'main' : call(function (noders, id, source) if source.returns then @@ -1288,7 +1340,7 @@ function m.compileNode(noders, source) local id = getID(source) bindValue(noders, source, id) - if specialMap[source.special] then + if id and specialMap[source.special] then noders.skip[id] = true end @@ -1534,11 +1586,6 @@ local partNodersMap = util.switch() m.compilePartNodes(noders, ref) end end - - local nxt = source.next - if nxt then - m.compilePartNodes(noders, nxt) - end end) : case 'setlocal' : case 'getlocal' @@ -1554,6 +1601,14 @@ local partNodersMap = util.switch() if parent.value == source then m.compilePartNodes(noders, parent) end + + if parent.type == 'call' then + local node = parent.node + if node.special == 'rawset' + or node.special == 'rawget' then + m.compilePartNodes(noders, parent) + end + end end) : case 'setfield' : case 'getfield' @@ -1585,6 +1640,14 @@ local partNodersMap = util.switch() if parent.value == source then m.compilePartNodes(noders, parent) end + + if parent.type == 'call' then + local node = parent.node + if node.special == 'rawset' + or node.special == 'rawget' then + m.compilePartNodes(noders, parent) + end + end end) : case 'label' : call(function (noders, source) diff --git a/script/core/reference.lua b/script/core/reference.lua index 5f5831c6..067d2e23 100644 --- a/script/core/reference.lua +++ b/script/core/reference.lua @@ -52,13 +52,13 @@ local accept = { ['doc.alias.name'] = true, } -return function (uri, offset) +return function (uri, position) local ast = files.getState(uri) if not ast then return nil end - local source = findSource(ast, offset, accept) + local source = findSource(ast, position, accept) if not source then return nil end diff --git a/script/core/rename.lua b/script/core/rename.lua index 0ab7a055..0c48dbc4 100644 --- a/script/core/rename.lua +++ b/script/core/rename.lua @@ -36,12 +36,12 @@ local function isValidFunctionName(str) if isValidGlobal(str) then return true end - local pos = str:find(':', 1, true) - if not pos then + local offset = str:find(':', 1, true) + if not offset then return false end - return isValidGlobal(trim(str:sub(1, pos-1))) - and isValidName(trim(str:sub(pos+1))) + return isValidGlobal(trim(str:sub(1, offset-1))) + and isValidName(trim(str:sub(offset+1))) end local function isFunctionGlobalName(source) @@ -81,21 +81,26 @@ local function renameField(source, newname, callback) elseif parent.type == 'getmethod' then callback(source, source.start, source.finish, newname) elseif parent.type == 'setmethod' then - local uri = guide.getUri(source) - local text = files.getText(uri) + local uri = guide.getUri(source) + local text = files.getText(uri) + local state = files.getState(uri) local func = parent.value -- function mt:name () end --> mt['newname'] = function (self) end + local startOffset = guide.positionToOffset(state, parent.start) + 1 + local finishOffset = guide.positionToOffset(state, parent.node.finish) local newstr = string.format('%s[%s] = function ' - , text:sub(parent.start, parent.node.finish) + , text:sub(startOffset, finishOffset) , util.viewString(newname) ) callback(source, func.start, parent.finish, newstr) - local pl = text:find('(', parent.finish, true) + local finishOffset = guide.positionToOffset(state, parent.finish) + local pl = text:find('(', finishOffset, true) if pl then + local insertPos = guide.offsetToPosition(state, pl) if text:find('^%s*%)', pl + 1) then - callback(source, pl + 1, pl, 'self') + callback(source, insertPos, insertPos, 'self') else - callback(source, pl + 1, pl, 'self, ') + callback(source, insertPos, insertPos, 'self, ') end end end diff --git a/script/core/semantic-tokens.lua b/script/core/semantic-tokens.lua index 96cbc5e1..38b75227 100644 --- a/script/core/semantic-tokens.lua +++ b/script/core/semantic-tokens.lua @@ -5,6 +5,7 @@ local define = require 'proto.define' local vm = require 'vm' local util = require 'utility' local guide = require 'parser.guide' +local converter = require 'proto.converter' local Care = {} Care['setglobal'] = function (source, results) @@ -188,8 +189,8 @@ local function buildTokens(uri, results) local lastLine = 0 local lastStartChar = 0 for i, source in ipairs(results) do - local startPos = files.position(uri, source.start, 'left') - local finishPos = files.position(uri, source.finish, 'right') + local startPos = converter.packPosition(uri, source.start) + local finishPos = converter.packPosition(uri, source.finish) local line = startPos.line local startChar = startPos.character local deltaLine = line - lastLine @@ -213,15 +214,14 @@ local function buildTokens(uri, results) end return function (uri, start, finish) - local ast = files.getState(uri) - local lines = files.getLines(uri) + local state = files.getState(uri) local text = files.getText(uri) - if not ast then + if not state then return nil end local results = {} - guide.eachSourceBetween(ast.ast, start, finish, function (source) + guide.eachSourceBetween(state.ast, start, finish, function (source) local method = Care[source.type] if not method then return @@ -230,7 +230,7 @@ return function (uri, start, finish) await.delay() end) - for _, comm in ipairs(ast.comms) do + for _, comm in ipairs(state.comms) do if comm.type == 'comment.cshort' then results[#results+1] = { start = comm.start, diff --git a/script/core/signature.lua b/script/core/signature.lua index 26d9867c..007a3787 100644 --- a/script/core/signature.lua +++ b/script/core/signature.lua @@ -7,20 +7,22 @@ local guide = require 'parser.guide' local lookback = require 'core.look-backward' local function findNearCall(uri, ast, pos) - local text = files.getText(uri) + local text = files.getText(uri) + local state = files.getState(uri) local nearCall guide.eachSourceContain(ast.ast, pos, function (src) if src.type == 'call' or src.type == 'table' or src.type == 'function' then + local finishOffset = guide.positionToOffset(state, src.finish) -- call(),$ if src.finish <= pos - and text:sub(src.finish, src.finish) == ')' then + and text:sub(finishOffset, finishOffset) == ')' then return end -- {},$ if src.finish <= pos - and text:sub(src.finish, src.finish) == '}' then + and text:sub(finishOffset, finishOffset) == '}' then return end if not nearCall or nearCall.start <= src.start then @@ -56,13 +58,13 @@ local function makeOneSignature(source, oop, index) for start, finish in converted:gmatch '%s*()[^,]+()' do i = i + 1 params[i] = { - label = {start + argStart, finish - 1 + argStart}, + label = {start + argStart - 1, finish - 1 + argStart}, } end -- 不定参数 if index > i and i > 0 then local lastLabel = params[i].label - local text = label:sub(lastLabel[1], lastLabel[2]) + local text = label:sub(lastLabel[1] + 1, lastLabel[2]) if text == '...' then index = i end @@ -88,11 +90,15 @@ local function makeSignatures(text, call, pos) args[#args+1] = arg end end + local uri = guide.getUri(call) + local state = files.getState(uri) for i, arg in ipairs(args) do - local start = lookback.findTargetSymbol(text, arg.start - 1, '(') - or lookback.findTargetSymbol(text, arg.start - 1, ',') - or arg.start - if start > pos then + local startOffset = guide.positionToOffset(state, arg.start) + startOffset = lookback.findTargetSymbol(text, startOffset, '(') + or lookback.findTargetSymbol(text, startOffset, ',') + or startOffset + local startPos = guide.offsetToPosition(state, startOffset) + if startPos > pos then index = i - 1 break end @@ -102,7 +108,8 @@ local function makeSignatures(text, call, pos) end end if not index then - local backSymbol = lookback.findSymbol(text, pos) + local offset = guide.positionToOffset(state, pos) + local backSymbol = lookback.findSymbol(text, offset) if backSymbol == ',' or backSymbol == '(' then index = #args + 1 @@ -130,13 +137,14 @@ local function makeSignatures(text, call, pos) end return function (uri, pos) - local ast = files.getState(uri) - if not ast then + local state = files.getState(uri) + if not state then return nil end local text = files.getText(uri) - pos = lookback.skipSpace(text, pos) - local call = findNearCall(uri, ast, pos) + local offset = guide.positionToOffset(state, pos) + pos = guide.offsetToPosition(state, lookback.skipSpace(text, offset)) + local call = findNearCall(uri, state, pos) if not call then return nil end diff --git a/script/core/type-formatting.lua b/script/core/type-formatting.lua index a225d9d7..b946184b 100644 --- a/script/core/type-formatting.lua +++ b/script/core/type-formatting.lua @@ -2,85 +2,89 @@ local files = require 'files' local lookBackward = require 'core.look-backward' local guide = require "parser.guide" -local function insertIndentation(uri, offset, edits) - local lines = files.getLines(uri) - local text = files.getOriginText(uri) - local row = guide.positionOf(lines, offset) - local line = lines[row] - local indent = text:sub(line.start, line.finish):match '^%s*' +local function insertIndentation(uri, position, edits) + local text = files.getText(uri) + local state = files.getState(uri) + local row = guide.rowColOf(position) + local offset = state.lines[row] + local indent = text:match('^%s*', offset) for _, edit in ipairs(edits) do edit.text = edit.text:gsub('\n', '\n' .. indent) end end -local function findForward(text, offset, ...) - local pos = text:match('^[ \t]*()', offset) - if not pos then +local function findForward(uri, position, ...) + local text = files.getText(uri) + local state = files.getState(uri) + local offset = guide.positionToOffset(state, position) + local firstOffset = text:match('^[ \t]*()', offset + 1) + if not firstOffset then return nil end for _, symbol in ipairs { ... } do - if text:sub(pos, pos + #symbol - 1) == symbol then - return pos, symbol + if text:sub(firstOffset, firstOffset + #symbol - 1) == symbol then + return guide.offsetToPosition(state, firstOffset - 1), symbol end end return nil end -local function findBackward(text, offset, ...) - local pos = lookBackward.findAnyPos(text, offset) +local function findBackward(uri, position, ...) + local text = files.getText(uri) + local state = files.getState(uri) + local offset = guide.positionToOffset(state, position) + local lastOffset = lookBackward.findAnyOffset(text, offset) for _, symbol in ipairs { ... } do - if text:sub(pos - #symbol + 1, pos) == symbol then - return pos - #symbol + 1, symbol + if text:sub(lastOffset - #symbol + 1, lastOffset) == symbol then + return guide.offsetToPosition(state, lastOffset) end end return nil end -local function checkSplitOneLine(results, uri, offset, ch) +local function checkSplitOneLine(results, uri, position, ch) if ch ~= '\n' then return end - local text = files.getOriginText(uri) - local fOffset, fSymbol = findForward(text, offset + 1, 'end', '}') - if not fOffset then + local fPosition, fSymbol = findForward(uri, position, 'end', '}') + if not fPosition then return end - local bOffset, bSymbol = findBackward(text, offset, 'then', 'do', ')', '{') - if not bOffset then + local bPosition = findBackward(uri, position, 'then', 'do', ')', '{') + if not bPosition then return end local edits = {} edits[#edits+1] = { - start = bOffset + #bSymbol, - finish = offset, + start = bPosition, + finish = position, text = '\n\t', } edits[#edits+1] = { - start = offset + 1, - finish = fOffset + #fSymbol - 1, - text = '' + start = position, + finish = fPosition + 1, + text = '', } edits[#edits+1] = { - start = fOffset + #fSymbol, - finish = fOffset + #fSymbol - 1, - text = '\n' .. fSymbol + start = fPosition + 1, + finish = fPosition + 1, + text = '\n' .. fSymbol:sub(1, 1) } - insertIndentation(uri, bOffset, edits) + insertIndentation(uri, bPosition, edits) for _, edit in ipairs(edits) do results[#results+1] = edit end end -return function (uri, offset, ch) +return function (uri, position, ch) local ast = files.getState(uri) - local text = files.getOriginText(uri) - if not ast or not text then + if not ast then return nil end local results = {} -- split `function () $ end` - checkSplitOneLine(results, uri, offset, ch) + checkSplitOneLine(results, uri, position, ch) return results end diff --git a/script/files.lua b/script/files.lua index fa5474ef..659a1aa0 100644 --- a/script/files.lua +++ b/script/files.lua @@ -7,12 +7,10 @@ local proto = require 'proto' local lang = require 'language' local await = require 'await' local timer = require 'timer' -local plugin = require 'plugin' local util = require 'utility' local guide = require 'parser.guide' local smerger = require 'string-merger' local progress = require "progress" -local client = require 'client' local unicode @@ -35,8 +33,6 @@ m.assocMatcher = nil m.globalVersion = 0 m.fileCount = 0 m.astCount = 0 -m.linesMap = setmetatable({}, { __mode = 'v' }) -m.originLinesMap = setmetatable({}, { __mode = 'v' }) m.astMap = {} -- setmetatable({}, { __mode = 'v' }) --- 打开文件 @@ -100,6 +96,7 @@ function m.exists(uri) end local function pluginOnSetText(file, text) + local plugin = require 'plugin' file._diffInfo = nil local suc, result = plugin.dispatch('OnSetText', file.uri, text) if not suc then @@ -115,6 +112,7 @@ local function pluginOnSetText(file, text) suc, result, diffs = xpcall(smerger.mergeDiff, log.error, text, result) if suc then file._diffInfo = diffs + file.originLines = parser.lines(text) return result else if DEVELOP and result then @@ -160,8 +158,6 @@ function m.setText(uri, text, isTrust, instance) file.trusted = isTrust file.originText = text file.words = nil - m.linesMap[uri] = nil - m.originLinesMap[uri] = nil m.astMap[uri] = nil file.cache = {} file.cacheActiveTime = math.huge @@ -206,8 +202,6 @@ function m.setRawText(uri, text) local file = m.fileMap[uri] file.text = text file.originText = text - m.linesMap[uri] = nil - m.originLinesMap[uri] = nil m.astMap[uri] = nil end @@ -298,6 +292,17 @@ function m.getOriginText(uri) return file.originText end +--- 获取文件原始行表 +---@param uri uri +---@return integer[] +function m.getOriginLines(uri) + local file = m.fileMap[uri] + if not file then + return nil + end + return file.originLines +end + function m.getChildFiles(uri) local results = {} local uris = m.getAllUris() @@ -321,8 +326,6 @@ function m.remove(uri) end m.fileMap[uri] = nil m.astMap[uri] = nil - m.linesMap[uri] = nil - m.originLinesMap[uri] = nil m._pairsCache = nil m.flushFileCache(uri) @@ -346,7 +349,6 @@ function m.removeAll() m.fileCount = m.fileCount - 1 m.fileMap[uri] = nil m.astMap[uri] = nil - m.linesMap[uri] = nil m.onWatch('remove', uri) end end @@ -366,7 +368,6 @@ function m.removeAllClosed() m.fileCount = m.fileCount - 1 m.fileMap[uri] = nil m.astMap[uri] = nil - m.linesMap[uri] = nil m.onWatch('remove', uri) end end @@ -419,7 +420,8 @@ function m.eachDll() end function m.compileState(uri, text) - local ws = require 'workspace' + local ws = require 'workspace' + local client = require 'client' if not m.isOpen(uri) and not m.isLibrary(uri) and #text >= config.get 'Lua.workspace.preloadFileSize' * 1000 then @@ -446,14 +448,13 @@ function m.compileState(uri, text) local prog <close> = progress.create(lang.script.WINDOW_COMPILING, 0.5) prog:setMessage(ws.getRelativePath(uri)) local clock = os.clock() - local state, err = parser:compile(text - , 'lua' + local state, err = parser.compile(text + , 'Lua' , config.get 'Lua.runtime.version' , { special = config.get 'Lua.runtime.special', unicodeName = config.get 'Lua.runtime.unicodeName', nonstandardSymbol = config.get 'Lua.runtime.nonstandardSymbol', - delay = await.delay, } ) local passed = os.clock() - clock @@ -465,7 +466,7 @@ function m.compileState(uri, text) state.uri = uri state.ast.uri = uri local clock = os.clock() - parser:luadoc(state) + parser.luadoc(state) local passed = os.clock() - clock if passed > 0.1 then log.warn(('Parse LuaDoc of [%s] takes [%.3f] sec, size [%.3f] kb.'):format(uri, passed, #text / 1000)) @@ -529,44 +530,16 @@ function m.getVisibles(uri) end local visibles = {} for i, range in ipairs(ranges) do - local start, finish = m.unrange(uri, range) + local startRow = range.start.line + local finishRow = range['end'].line visibles[i] = { - start = start, - finish = finish, + start = guide.positionOf(startRow, 0), + finish = guide.positionOf(finishRow, 0), } end return visibles end ---- 获取文件行信息 ----@param uri uri ----@return table lines -function m.getLines(uri) - local file = m.fileMap[uri] - if not file then - return nil - end - local lines = m.linesMap[uri] - if not lines then - lines = parser:lines(file.text) - m.linesMap[uri] = lines - end - return lines -end - -function m.getOriginLines(uri) - local file = m.fileMap[uri] - if not file then - return nil - end - local lines = m.originLinesMap[uri] - if not lines then - lines = parser:lines(file.originText) - m.originLinesMap[uri] = lines - end - return lines -end - function m.getFile(uri) return m.fileMap[uri] or m.dllMap[uri] @@ -583,111 +556,6 @@ local function isNameChar(text) return false end ----@alias position table - ---- 获取 position 对应的光标位置 ----@param uri uri ----@param position position ----@param isFinish? boolean ----@return integer -function m.offset(uri, position, isFinish) - local file = m.getFile(uri) - local lines = m.getLines(uri) - local text = m.getText(uri) - if not file then - return 0 - end - if file._diffInfo then - lines = m.getOriginLines(uri) - text = m.getOriginText(uri) - end - local row = position.line + 1 - local start, finish, char - if row > #lines then - start, finish = guide.lineRange(lines, #lines) - start = start + 1 - char = util.utf8Len(text, start, finish) - else - start, finish = guide.lineRange(lines, row) - start = start + 1 - char = position.character - end - local utf8Len = util.utf8Len(text, start, finish) - local offset - if char <= 0 then - offset = start - else - if char >= utf8Len then - char = utf8Len - end - local left = utf8.offset(text, char, start) - local right = utf8.offset(text, char + 1, start) - if isFinish then - offset = left - else - offset = right - end - end - if file._diffInfo then - local start, finish = smerger.getOffset(file._diffInfo, offset) - if isFinish then - offset = finish - else - offset = start - end - end - return offset -end - ---- 获取 position 对应的光标位置(根据附近的单词) ----@param uri uri ----@param position position ----@return integer -function m.offsetOfWord(uri, position) - local file = m.getFile(uri) - local lines = m.getLines(uri) - local text = m.getText(uri) - if not file then - return 0 - end - if file._diffInfo then - lines = m.getOriginLines(uri) - text = m.getOriginText(uri) - end - local row = position.line + 1 - local start, finish, char - if row > #lines then - start, finish = guide.lineRange(lines, #lines) - start = start + 1 - char = util.utf8Len(text, start, finish) - else - start, finish = guide.lineRange(lines, row) - start = start + 1 - char = position.character - end - local utf8Len = util.utf8Len(text, start, finish) - local offset - if char <= 0 then - offset = start - else - if char >= utf8Len then - char = utf8Len - end - local left = utf8.offset(text, char, start) - local right = utf8.offset(text, char + 1, start) - if isNameChar(text:sub(left, right - 1)) - and not isNameChar(text:sub(right, right)) then - offset = left - else - offset = right - end - end - if file._diffInfo then - offset = smerger.getOffset(file._diffInfo, offset) - end - return offset -end - --- 将应用差异前的offset转换为应用差异后的offset ---@param uri uri ---@param offset integer @@ -720,76 +588,12 @@ function m.diffedOffsetBack(uri, offset) return smerger.getOffsetBack(file._diffInfo, offset) end ---- 将光标位置转化为 position ----@param uri uri ----@param offset integer ----@param leftOrRight? '"left"'|'"right"' ----@return position -function m.position(uri, offset, leftOrRight) - local file = m.getFile(uri) - local lines = m.getLines(uri) - local text = m.getText(uri) +function m.hasDiffed(uri) + local file = m.getFile(uri) if not file then - return { - line = 0, - character = 0, - } - end - if file._diffInfo then - local start, finish = smerger.getOffsetBack(file._diffInfo, offset) - if leftOrRight == 'right' then - offset = finish - else - offset = start - end - lines = m.getOriginLines(uri) - text = m.getOriginText(uri) - end - local row, col = guide.positionOf(lines, offset) - local start, finish = guide.lineRange(lines, row, true) - start = start + 1 - if col <= finish - start + 1 then - local ucol = util.utf8Len(text, start, start + col - 1) - if row < 1 then - row = 1 - end - if leftOrRight == 'left' and ucol > 0 then - ucol = ucol - 1 - end - return { - line = row - 1, - character = ucol, - } - else - return { - line = row - 1, - character = util.utf8Len(text, start, finish), - } + return false end -end - ---- 将起点与终点位置转化为 range ----@alias range table ----@param uri uri ----@param offset1 integer ----@param offset2 integer -function m.range(uri, offset1, offset2) - local range = { - start = m.position(uri, offset1, 'left'), - ['end'] = m.position(uri, offset2, 'right'), - } - return range -end - ---- convert `range` to `offsetStart` and `offsetFinish` ----@param uri table ----@param range table ----@return integer start ----@return integer finish -function m.unrange(uri, range) - local start = m.offset(uri, range.start, true) - local finish = m.offset(uri, range['end'], false) - return start, finish + return file._diffInfo ~= nil end --- 获取文件的自定义缓存信息(在文件内容更新后自动失效) @@ -922,8 +726,6 @@ end function m.flushCache() for uri, file in pairs(m.fileMap) do file.cacheActiveTime = math.huge - m.linesMap[uri] = nil - m.originLinesMap[uri] = nil m.astMap[uri] = nil file.cache = {} end @@ -935,8 +737,6 @@ function m.flushFileCache(uri) return end file.cacheActiveTime = math.huge - m.linesMap[uri] = nil - m.originLinesMap[uri] = nil m.astMap[uri] = nil file.cache = {} end diff --git a/script/parser/compile.lua b/script/parser/compile.lua index 207664cc..d6c6a526 100644 --- a/script/parser/compile.lua +++ b/script/parser/compile.lua @@ -1,4 +1,6 @@ local guide = require 'parser.guide' +local parse = require 'parser.parse' +local newparser = require 'parser.newparser' local type = type local tableInsert = table.insert local pairs = pairs @@ -566,8 +568,12 @@ local function PostCompile() end end -return function (self, lua, mode, version, options) - local state, err = self:parse(lua, mode, version, options) +return function (lua, mode, version, options) + do + local state, err = newparser(lua, mode, version, options) + return state, err + end + local state, err = parse(lua, mode, version, options) if not state then return nil, err end @@ -576,11 +582,7 @@ return function (self, lua, mode, version, options) --end local clock = os.clock() pushError = state.pushError - if version == 'Lua 5.1' or version == 'LuaJIT' then - ENVMode = '@fenv' - else - ENVMode = '_ENV' - end + ENVMode = state.ENVMode Compiled = {} GoToTag = {} LocalCount = 0 @@ -590,7 +592,6 @@ return function (self, lua, mode, version, options) Root.state = state end Options = options - state.ENVMode = ENVMode if type(state.ast) == 'table' then Compile(state.ast) end diff --git a/script/parser/grammar.lua b/script/parser/grammar.lua index 1a3913e0..154bd7ba 100644 --- a/script/parser/grammar.lua +++ b/script/parser/grammar.lua @@ -557,7 +557,7 @@ Lua <- Head? Head <- '#' (!%nl .)* ]] -return function (self, lua, mode) +return function (lua, mode) local gram = compiled[mode] or compiled['Lua'] local r, _, pos = gram:match(lua) if not r then diff --git a/script/parser/guide.lua b/script/parser/guide.lua index 6dcfbf95..b65d9680 100644 --- a/script/parser/guide.lua +++ b/script/parser/guide.lua @@ -54,6 +54,7 @@ local type = type ---@field ref parser.guide.object[] ---@field returnIndex integer ---@field docs parser.guide.object[] +---@field state table ---@field _root parser.guide.object ---@field _noders noders ---@field _mnode parser.guide.object @@ -85,13 +86,14 @@ local breakBlockTypes = { ['in'] = true, ['loop'] = true, ['repeat'] = true, + ['for'] = true, } local childMap = { ['main'] = {'#', 'docs'}, ['repeat'] = {'#', 'filter'}, ['while'] = {'filter', '#'}, - ['in'] = {'keys', '#'}, + ['in'] = {'keys', 'exps', '#'}, ['loop'] = {'loc', 'max', 'step', '#'}, ['if'] = {'#'}, ['ifblock'] = {'filter', '#'}, @@ -547,24 +549,24 @@ function m.getRange(source) return start, finish end ---- 判断source是否包含offset -function m.isContain(source, offset) +--- 判断source是否包含position +function m.isContain(source, position) local start, finish = m.getStartFinish(source) if not start then return false end - return start <= offset and finish >= offset + return start <= position and finish >= position end ---- 判断offset在source的影响范围内 +--- 判断position在source的影响范围内 --- --- 主要针对赋值等语句时,key包含value -function m.isInRange(source, offset) +function m.isInRange(source, position) local start, finish = m.getRange(source) if not start then return false end - return start <= offset and finish >= offset + return start <= position and finish >= position end function m.isBetween(source, tStart, tFinish) @@ -596,8 +598,8 @@ local function addChilds(list, obj) f(obj, list) end ---- 遍历所有包含offset的source -function m.eachSourceContain(ast, offset, callback) +--- 遍历所有包含position的source +function m.eachSourceContain(ast, position, callback) local list = { ast } local mark = {} while true do @@ -609,8 +611,8 @@ function m.eachSourceContain(ast, offset, callback) list[len] = nil if not mark[obj] then mark[obj] = true - if m.isInRange(obj, offset) then - if m.isContain(obj, offset) then + if m.isInRange(obj, position) then + if m.isContain(obj, position) then local res = callback(obj) if res ~= nil then return res @@ -722,15 +724,13 @@ function m.eachSource(ast, callback) end --- 获取指定的 special ----@param ast parser.guide.object ----@param name string ----@param callback fun(source: parser.guide.object) function m.eachSpecialOf(ast, name, callback) local root = m.getRoot(ast) - if not root.specials then + local state = root.state + if not state.specials then return end - local specials = root.specials[name] + local specials = state.specials[name] if not specials then return end @@ -739,92 +739,65 @@ function m.eachSpecialOf(ast, name, callback) end end ---- 获取光标偏移对应的坐标。 ---- 如果在换行符的右侧,则认为在新的一行。 ---- 第一行的行号是1不是0。 ----@param lines table ----@return integer {name = 'row'} ----@return integer {name = 'col'} -function m.positionOf(lines, offset) - if offset <= 0 then - return 1, 0 - end - local lastLine = lines[#lines] - if offset >= lastLine.finish then - return #lines, lastLine.finish - lastLine.start - end - local min = 1 - local max = #lines - for _ = 1, 100 do - if max <= min then - local line = lines[min] - return min, offset - line.start - end - local row = (max - min) // 2 + min - local line = lines[row] - if offset < line.start then - max = row - 1 - elseif offset >= line.finish then - min = row + 1 - else - return row, offset - line.start - end - end - error('Stack overflow!') +--- 将 position 拆分成行号与列号 +--- +--- 第一行是0 +---@param position integer +---@return integer row +---@return integer col +function m.rowColOf(position) + return position // 10000, position % 10000 end ---- 获取坐标对应的光标偏移。 ---- 一定会落在当前行的换行符左侧。 ---- 第一行的行号是1不是0。 ----@param lines table +--- 将行列合并为 position +--- +--- 第一行是0 ---@param row integer ---@param col integer ----@return integer {name = 'offset'} -function m.offsetOf(lines, row, col) - if row < 1 then - return 0 - end - if row > #lines then - local lastLine = lines[#lines] - return lastLine.finish - end - local line = lines[row] - local len = line.range - line.start - if col < 0 then - return line.start - elseif col > len then - return line.range - else - return line.start + col - end +---@return integer +function m.positionOf(row, col) + return row * 10000 + col end -function m.lineContent(lines, text, row, ignoreNL) - local line = lines[row] - if not line then - return '' - end - if ignoreNL then - return text:sub(line.start, line.range) - else - return text:sub(line.start, line.finish) - end +function m.positionToOffsetByLines(lines, position) + local row, col = m.rowColOf(position) + return lines[row] + col - 1 end -function m.lineRange(lines, row, ignoreNL) - local line = lines[row] - if not line then - return 0, 0 - end - if ignoreNL then - return line.start, line.range - else - return line.start, line.finish +--- 返回全文光标位置 +---@param state any +---@param position integer +function m.positionToOffset(state, position) + return m.positionToOffsetByLines(state.lines, position) +end + +function m.offsetToPositionByLines(lines, offset) + local left = 0 + local right = #lines + local row = 0 + while true do + row = (left + right) // 2 + if row == left then + if right ~= left then + if lines[right] <= offset then + row = right + end + end + break + end + local start = lines[row] - 1 + if start > offset then + right = row + else + left = row + end end + local col = offset - lines[row] + 1 + return m.positionOf(row, col) end -function m.lineData(lines, row) - return lines[row] +function m.offsetToPosition(state, offset) + return m.offsetToPositionByLines(state.lines, offset) end local isSetMap = { diff --git a/script/parser/init.lua b/script/parser/init.lua index ba40d145..219f8900 100644 --- a/script/parser/init.lua +++ b/script/parser/init.lua @@ -7,6 +7,7 @@ local api = { lines = require 'parser.lines', guide = require 'parser.guide', luadoc = require 'parser.luadoc', + tokens = require 'parser.tokens', } return api diff --git a/script/parser/lines.lua b/script/parser/lines.lua index 1aba0ae5..964aabf4 100644 --- a/script/parser/lines.lua +++ b/script/parser/lines.lua @@ -2,33 +2,23 @@ local sfind = string.find local ssub = string.sub ---@param text string -return function (self, text) +return function (text) local current = 1 local lines = {} - local i = 1 + lines[0] = 1 + local i = 0 while true do local pos = sfind(text,'[\r\n]', current) if not pos then break end - local line = { - start = current - 1, - range = pos - 1, - } - lines[i] = line i = i + 1 if ssub(text, pos, pos + 1) == '\r\n' then - current = pos + 2 - line.finish = pos + 1 + current = pos + 2 else - current = pos + 1 - line.finish = pos + current = pos + 1 end + lines[i] = current end - lines[i] = { - start = current - 1, - finish = #text, - range = #text, - } return lines end diff --git a/script/parser/luadoc.lua b/script/parser/luadoc.lua index da612e60..1645487d 100644 --- a/script/parser/luadoc.lua +++ b/script/parser/luadoc.lua @@ -1,11 +1,10 @@ local m = require 'lpeglabel' local re = require 'parser.relabel' -local lines = require 'parser.lines' local guide = require 'parser.guide' -local grammar = require 'parser.grammar' +local parser = require 'parser.newparser' local TokenTypes, TokenStarts, TokenFinishs, TokenContents -local Ci, Offset, pushError, Ct, NextComment, Lines +local Ci, Offset, pushError, NextComment, Lines local parseType local Parser = re.compile([[ Main <- (Token / Sp)* @@ -121,7 +120,6 @@ local function trim(str) end local function parseTokens(text, offset) - Ct = offset Ci = 0 Offset = offset TokenTypes = {} @@ -162,7 +160,7 @@ local function getFinish() if Ci == 0 then return Offset end - return TokenFinishs[Ci] + Offset + return TokenFinishs[Ci] + Offset + 1 end local function try(callback) @@ -257,7 +255,7 @@ local function parseClass(parent) pushError { type = 'LUADOC_MISS_EXTENDS_SYMBOL', start = result.finish + 1, - finish = getStart() - 1, + finish = getStart(), } return result end @@ -326,7 +324,7 @@ local function parseTypeUnitTable(parent, node) end nextSymbolOrError('>') - node.parent = result; + node.parent = result result.finish = getFinish() result.tkey = key result.tvalue = value @@ -554,7 +552,6 @@ function parseType(parent) enums = {}, resumes = {}, } - result.start = getStart() while true do local tp, content = peekToken() if not tp then @@ -632,10 +629,13 @@ function parseType(parent) end nextToken() end + if not result.start then + result.start = getFinish() + end result.finish = getFinish() result.firstFinish = result.finish - local row = guide.positionOf(Lines, result.finish) + local row = guide.rowColOf(result.finish) local function pushResume() local comments @@ -644,8 +644,9 @@ function parseType(parent) if not nextComm then return false end - local line = Lines[row + i + 1] - if not line or line.finish < nextComm.start then + local nextCommRow = guide.rowColOf(nextComm.start) + local currentRow = row + i + 1 + if currentRow < nextCommRow then return false end if nextComm.text:sub(1, 2) == '-@' then @@ -655,7 +656,7 @@ function parseType(parent) NextComment(i) row = row + i + 1 local finishPos = nextComm.text:find('#', 3) or #nextComm.text - parseTokens(nextComm.text:sub(3, finishPos), nextComm.start + 1) + parseTokens(nextComm.text:sub(3, finishPos), nextComm.start + 3) local resume = parseResume(result) if resume then if comments then @@ -950,7 +951,7 @@ local function parseVersion() if not tp then pushError { type = 'LUADOC_MISS_VERSION', - start = getStart(), + start = getFinish(), finish = getFinish(), } break @@ -1120,9 +1121,9 @@ local function trimTailComment(text) comment = text:sub(3) end if comment:find '^%s*[\'"[]' then - local result = grammar(nil, comment:gsub('^%s+', ''), 'string') - if result and result[1] then - comment = result[1][1] + local state = parser(comment:gsub('^%s+', ''), 'String') + if state and state.ast then + comment = state.ast[1] end end return comment @@ -1143,11 +1144,11 @@ local function buildLuaDoc(comment) local doc = text:sub(startPos + 1) - parseTokens(doc, comment.start + startPos - 1) + parseTokens(doc, comment.start + startPos + 1) local result = convertTokens() if result then result.range = comment.finish - local cstart = text:find('%S', (result.firstFinish or result.finish) - comment.start + 2) + local cstart = text:find('%S', (result.firstFinish or result.finish) - comment.start) if cstart and cstart < comment.finish then result.comment = { type = 'doc.tailcomment', @@ -1171,28 +1172,22 @@ local function buildLuaDoc(comment) } end ----当前行在注释doc前是否有代码 -local function haveCodeBeforeDocInCurLine(text, lineData, docStart) - return text:sub(lineData.start + 1, docStart - 1):find '[%w_]' -end - -local function isTailComment(lns, text, binded, doc) - local lastDoc = binded[#binded] - local lastDocStartRow = guide.positionOf(lns, lastDoc.originalComment.start) - local lastDocStartLineData = guide.lineData(lns, lastDocStartRow) - if haveCodeBeforeDocInCurLine(text, lastDocStartLineData, lastDoc.originalComment.start) then - return true - end - return false +local function isTailComment(text, binded) + local lastDoc = binded[#binded] + local left = lastDoc.originalComment.start + local row, col = guide.rowColOf(left) + local lineStart = Lines[row] or 0 + local hasCodeBefore = text:sub(lineStart, lineStart + col):find '[%w_]' + return hasCodeBefore end -local function isNextLine(lns, text, binded, doc) +local function isNextLine(binded, doc) if not binded then return false end local lastDoc = binded[#binded] - local lastRow = guide.positionOf(lns, lastDoc.finish) - local newRow = guide.positionOf(lns, doc.start) + local lastRow = guide.rowColOf(lastDoc.finish) + local newRow = guide.rowColOf(doc.start) return newRow - lastRow == 1 end @@ -1245,7 +1240,7 @@ local function bindDocsBetween(sources, binded, bindSources, start, finish) for i = index, max do local src = sources[i] if src and src.start >= start then - if src.start > finish then + if src.start >= finish then break end -- 遇到table后中断,处理以下情况: @@ -1255,8 +1250,16 @@ local function bindDocsBetween(sources, binded, bindSources, start, finish) break end if src.start >= start then - src.bindDocs = binded - bindSources[#bindSources+1] = src + if src.type == 'local' + or src.type == 'setglobal' + or src.type == 'tablefield' + or src.type == 'tableindex' + or src.type == 'setfield' + or src.type == 'setindex' + or src.type == 'function' then + src.bindDocs = binded + bindSources[#bindSources+1] = src + end end end end @@ -1318,7 +1321,7 @@ local function bindClassAndFields(binded) end end -local function bindDoc(sources, lns, binded) +local function bindDoc(sources, binded) if not binded then return end @@ -1332,12 +1335,10 @@ local function bindDoc(sources, lns, binded) doc.bindSources = bindSources end bindGeneric(binded) - local row = guide.positionOf(lns, lastDoc.finish) - local cstart, cfinish = guide.lineRange(lns, row) - local nstart, nfinish = guide.lineRange(lns, row + 1) - bindDocsBetween(sources, binded, bindSources, cstart, cfinish) + local row = guide.rowColOf(lastDoc.finish) + bindDocsBetween(sources, binded, bindSources, guide.positionOf(row, 0), lastDoc.start) if #bindSources == 0 then - bindDocsBetween(sources, binded, bindSources, nstart, nfinish) + bindDocsBetween(sources, binded, bindSources, guide.positionOf(row + 1, 0), guide.positionOf(row + 2, 0)) end bindParamAndReturnIndex(binded) bindClassAndFields(binded) @@ -1361,21 +1362,21 @@ local function bindDocs(state) end) local binded for _, doc in ipairs(state.ast.docs) do - if not isNextLine(Lines, text, binded, doc) then - bindDoc(sources, Lines, binded) + if not isNextLine(binded, doc) then + bindDoc(sources, binded) binded = {} state.ast.docs.groups[#state.ast.docs.groups+1] = binded end binded[#binded+1] = doc - if isTailComment(Lines, text, binded, doc) then - bindDoc(sources, Lines, binded) + if isTailComment(text, binded) then + bindDoc(sources, binded) binded = nil end end - bindDoc(sources, Lines, binded) + bindDoc(sources, binded) end -return function (_, state) +return function (state) local ast = state.ast local comments = state.comms table.sort(comments, function (a, b) @@ -1388,8 +1389,7 @@ return function (_, state) } pushError = state.pushError - - Lines = lines(nil, state.lua) + Lines = state.lines local ci = 1 NextComment = function (offset, peek) diff --git a/script/parser/newparser.lua b/script/parser/newparser.lua new file mode 100644 index 00000000..1b02a5d9 --- /dev/null +++ b/script/parser/newparser.lua @@ -0,0 +1,3623 @@ +local tokens = require 'parser.tokens' +local guide = require 'parser.guide' + +local sbyte = string.byte +local sfind = string.find +local smatch = string.match +local sgsub = string.gsub +local ssub = string.sub +local schar = string.char +local supper = string.upper +local uchar = utf8.char +local tconcat = table.concat +local tinsert = table.insert +local tointeger = math.tointeger +local mtype = math.type +local tonumber = tonumber +local maxinteger = math.maxinteger +local assert = assert +local next = next + +_ENV = nil + +---@alias parser.position integer + +---@param str string +---@return table<integer, boolean> +local function stringToCharMap(str) + local map = {} + local pos = 1 + while pos <= #str do + local byte = sbyte(str, pos, pos) + map[schar(byte)] = true + pos = pos + 1 + if ssub(str, pos, pos) == '-' + and pos < #str then + pos = pos + 1 + local byte2 = sbyte(str, pos, pos) + assert(byte < byte2) + for b = byte + 1, byte2 do + map[schar(b)] = true + end + pos = pos + 1 + end + end + return map +end + +local CharMapNumber = stringToCharMap '0-9' +local CharMapN16 = stringToCharMap 'xX' +local CharMapN2 = stringToCharMap 'bB' +local CharMapE10 = stringToCharMap 'eE' +local CharMapE16 = stringToCharMap 'pP' +local CharMapSign = stringToCharMap '+-' +local CharMapSB = stringToCharMap 'ao|~&=<>.*/%^+-' +local CharMapSU = stringToCharMap 'n#~!-' +local CharMapSimple = stringToCharMap '.:([\'"{' +local CharMapStrSH = stringToCharMap '\'"' +local CharMapStrLH = stringToCharMap '[' +local CharMapTSep = stringToCharMap ',;' +local CharMapWord = stringToCharMap '_a-zA-Z\x80-\xff' + +local EscMap = { + ['a'] = '\a', + ['b'] = '\b', + ['f'] = '\f', + ['n'] = '\n', + ['r'] = '\r', + ['t'] = '\t', + ['v'] = '\v', + ['\\'] = '\\', + ['\''] = '\'', + ['\"'] = '\"', +} + +local NLMap = { + ['\n'] = true, + ['\r'] = true, + ['\r\n'] = true, +} + +local LineMulti = 10000 + +-- goto 单独处理 +local KeyWord = { + ['and'] = true, + ['break'] = true, + ['do'] = true, + ['else'] = true, + ['elseif'] = true, + ['end'] = true, + ['false'] = true, + ['for'] = true, + ['function'] = true, + ['if'] = true, + ['in'] = true, + ['local'] = true, + ['nil'] = true, + ['not'] = true, + ['or'] = true, + ['repeat'] = true, + ['return'] = true, + ['then'] = true, + ['true'] = true, + ['until'] = true, + ['while'] = true, +} + +local Specials = { + ['_G'] = true, + ['rawset'] = true, + ['rawget'] = true, + ['setmetatable'] = true, + ['require'] = true, + ['dofile'] = true, + ['loadfile'] = true, + ['pcall'] = true, + ['xpcall'] = true, + ['pairs'] = true, + ['ipairs'] = true, +} + +local UnarySymbol = { + ['not'] = 11, + ['#'] = 11, + ['~'] = 11, + ['-'] = 11, +} + +local BinarySymbol = { + ['or'] = 1, + ['and'] = 2, + ['<='] = 3, + ['>='] = 3, + ['<'] = 3, + ['>'] = 3, + ['~='] = 3, + ['=='] = 3, + ['|'] = 4, + ['~'] = 5, + ['&'] = 6, + ['<<'] = 7, + ['>>'] = 7, + ['..'] = 8, + ['+'] = 9, + ['-'] = 9, + ['*'] = 10, + ['//'] = 10, + ['/'] = 10, + ['%'] = 10, + ['^'] = 12, +} + +local BinaryAlias = { + ['&&'] = 'and', + ['||'] = 'or', + ['!='] = '~=', +} + +local BinaryActionAlias = { + ['='] = '==', +} + +local UnaryAlias = { + ['!'] = 'not', +} + +local SymbolForward = { + [01] = true, + [02] = true, + [03] = true, + [04] = true, + [05] = true, + [06] = true, + [07] = true, + [08] = false, + [09] = true, + [10] = true, + [11] = true, + [12] = false, +} + +local GetToSetMap = { + ['getglobal'] = 'setglobal', + ['getlocal'] = 'setlocal', + ['getfield'] = 'setfield', + ['getindex'] = 'setindex', + ['getmethod'] = 'setmethod', +} + +local ChunkFinishMap = { + ['end'] = true, + ['else'] = true, + ['elseif'] = true, + ['in'] = true, + ['then'] = true, + ['until'] = true, + [';'] = true, + [']'] = true, + [')'] = true, + ['}'] = true, +} + +local ListFinishMap = { + ['end'] = true, + ['else'] = true, + ['elseif'] = true, + ['in'] = true, + ['then'] = true, + ['do'] = true, + ['until'] = true, + ['for'] = true, + ['if'] = true, + ['local'] = true, + ['repeat'] = true, + ['return'] = true, + ['while'] = true, +} + +local State, Lua, Line, LineOffset, Chunk, Tokens, Index, LastTokenFinish, Mode, LocalCount + +local LocalLimit = 200 + +local parseExp, parseAction + +local pushError + +local function addSpecial(name, obj) + if not State.specials then + State.specials = {} + end + if not State.specials[name] then + State.specials[name] = {} + end + State.specials[name][#State.specials[name]+1] = obj + obj.special = name +end + +---@param offset integer +---@param leftOrRight '"left"'|'"right"' +local function getPosition(offset, leftOrRight) + if not offset or offset > #Lua then + return LineMulti * Line + #Lua - LineOffset + 1 + end + if leftOrRight == 'left' then + return LineMulti * Line + offset - LineOffset + else + return LineMulti * Line + offset - LineOffset + 1 + end +end + +---@return string word +---@return parser.position startPosition +---@return parser.position finishPosition +---@return integer newOffset +local function peekWord() + local word = Tokens[Index + 1] + if not word then + return nil + end + if not CharMapWord[ssub(word, 1, 1)] then + return nil + end + local startPos = getPosition(Tokens[Index] , 'left') + local finishPos = getPosition(Tokens[Index] + #word - 1, 'right') + return word, startPos, finishPos +end + +local function lastRightPosition() + if Index < 2 then + return 0 + end + local token = Tokens[Index - 1] + if NLMap[token] then + return LastTokenFinish + elseif token then + return getPosition(Tokens[Index - 2] + #token - 1, 'right') + else + return getPosition(#Lua, 'right') + end +end + +local function missSymbol(symbol, start, finish) + pushError { + type = 'MISS_SYMBOL', + start = start or lastRightPosition(), + finish = finish or start or lastRightPosition(), + info = { + symbol = symbol, + } + } +end + +local function missExp() + pushError { + type = 'MISS_EXP', + start = lastRightPosition(), + finish = lastRightPosition(), + } +end + +local function missName(pos) + pushError { + type = 'MISS_NAME', + start = pos or lastRightPosition(), + finish = pos or lastRightPosition(), + } +end + +local function missEnd(relatedStart, relatedFinish) + pushError { + type = 'MISS_SYMBOL', + start = lastRightPosition(), + finish = lastRightPosition(), + info = { + symbol = 'end', + related = { + { + start = relatedStart, + finish = relatedFinish, + } + } + } + } + pushError { + type = 'MISS_END', + start = relatedStart, + finish = relatedFinish, + } +end + +local function unknownSymbol(start, finish, word) + local token = word or Tokens[Index + 1] + if not token then + return false + end + pushError { + type = 'UNKNOWN_SYMBOL', + start = start or getPosition(Tokens[Index], 'left'), + finish = finish or getPosition(Tokens[Index] + #token - 1, 'right'), + info = { + symbol = token, + } + } + return true +end + +local function skipUnknownSymbol(stopSymbol) + if unknownSymbol() then + Index = Index + 2 + return true + end + return false +end + +local function skipNL() + local token = Tokens[Index + 1] + if NLMap[token] then + if Index >= 2 and not NLMap[Tokens[Index - 1]] then + LastTokenFinish = getPosition(Tokens[Index - 2] + #Tokens[Index - 1] - 1, 'right') + end + Line = Line + 1 + LineOffset = Tokens[Index] + #token + Index = Index + 2 + State.lines[Line] = LineOffset + return true + end + return false +end + +local function fastForwardToken(offset) + while true do + local myOffset = Tokens[Index] + if not myOffset + or myOffset >= offset then + break + end + local token = Tokens[Index + 1] + if NLMap[token] then + Line = Line + 1 + LineOffset = Tokens[Index] + #token + State.lines[Line] = LineOffset + end + Index = Index + 2 + end +end + +local function resolveLongString(finishMark) + skipNL() + local miss + local start = Tokens[Index] + local finishOffset = sfind(Lua, finishMark, start, true) + if not finishOffset then + finishOffset = #Lua + 1 + miss = true + end + local stringResult = ssub(Lua, start, finishOffset - 1) + local lastLN = stringResult:find '[\r\n][^\r\n]*$' + if lastLN then + local result = stringResult + : gsub('\r\n?', '\n') + stringResult = result + end + fastForwardToken(finishOffset + #finishMark) + if miss then + local pos = getPosition(finishOffset - 1, 'right') + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = finishMark, + }, + fix = { + title = 'ADD_LSTRING_END', + { + start = pos, + finish = pos, + text = finishMark, + } + }, + } + end + return stringResult, getPosition(finishOffset + #finishMark - 1, 'right') +end + +local function parseLongString() + local start, finish, mark = sfind(Lua, '^(%[%=*%[)', Tokens[Index]) + if not mark then + return nil + end + fastForwardToken(finish + 1) + local startPos = getPosition(start, 'left') + local finishMark = sgsub(mark, '%[', ']') + local stringResult, finishPos = resolveLongString(finishMark) + return { + type = 'string', + start = startPos, + finish = finishPos, + [1] = stringResult, + [2] = mark, + } +end + +local function pushCommentHeadError(left) + if State.options.nonstandardSymbol and State.options.nonstandardSymbol['//'] then + return + end + pushError { + type = 'ERR_COMMENT_PREFIX', + start = left, + finish = left + 2, + fix = { + title = 'FIX_COMMENT_PREFIX', + { + start = left, + finish = left + 2, + text = '--', + }, + } + } +end + +local function pushLongCommentError(left, right) + if State.options.nonstandardSymbol and State.options.nonstandardSymbol['/**/'] then + return + end + pushError { + type = 'ERR_C_LONG_COMMENT', + start = left, + finish = right, + fix = { + title = 'FIX_C_LONG_COMMENT', + { + start = left, + finish = left + 2, + text = '--[[', + }, + { + start = right - 2, + finish = right, + text = '--]]' + }, + } + } +end + +local function skipComment(isAction) + local token = Tokens[Index + 1] + if token == '--' + or (token == '//' and isAction) then + local start = Tokens[Index] + local left = getPosition(start, 'left') + local chead = false + if token == '//' then + chead = true + pushCommentHeadError(left) + end + Index = Index + 2 + local longComment = parseLongString() + if longComment then + longComment.type = 'comment.long' + longComment.text = longComment[1] + longComment[1] = nil + longComment[2] = nil + State.comms[#State.comms+1] = longComment + return true + end + while true do + local nl = Tokens[Index + 1] + if not nl or NLMap[nl] then + break + end + Index = Index + 2 + end + State.comms[#State.comms+1] = { + type = chead and 'comment.cshort' or 'comment.short', + start = left, + finish = getPosition(Tokens[Index], 'right'), + text = ssub(Lua, start + 2, Tokens[Index] and (Tokens[Index] - 1) or #Lua), + } + return true + end + if token == '/*' then + local start = Tokens[Index] + local left = getPosition(start, 'left') + Index = Index + 2 + local result, right = resolveLongString '*/' + pushLongCommentError(left, right) + State.comms[#State.comms+1] = { + type = 'comment.long', + start = left, + finish = right, + text = result, + } + return true + end + return false +end + +local function skipSpace(isAction) + repeat until not skipNL() + and not skipComment(isAction) +end + +local function expectAssign() + local token = Tokens[Index + 1] + if token == '=' then + Index = Index + 2 + return true + end + if token == '==' then + local left = getPosition(Tokens[Index], 'left') + local right = getPosition(Tokens[Index] + #token - 1, 'right') + pushError { + type = 'ERR_ASSIGN_AS_EQ', + start = left, + finish = right, + fix = { + title = 'FIX_ASSIGN_AS_EQ', + { + start = left, + finish = right, + text = '=', + } + } + } + Index = Index + 2 + return true + end + return false +end + +local function parseLocalAttrs() + local attrs + while true do + skipSpace() + local token = Tokens[Index + 1] + if token ~= '<' then + break + end + if not attrs then + attrs = {} + end + local attr = { + type = 'localattr', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index], 'right'), + } + attrs[#attrs+1] = attr + Index = Index + 2 + skipSpace() + local word, wstart, wfinish = peekWord() + if word then + attr[1] = word + attr.finish = wfinish + Index = Index + 2 + else + missName() + end + attr.finish = lastRightPosition() + skipSpace() + if Tokens[Index + 1] == '>' then + attr.finish = getPosition(Tokens[Index], 'right') + Index = Index + 2 + elseif Tokens[Index + 1] == '>=' then + attr.finish = getPosition(Tokens[Index], 'right') + pushError { + type = 'MISS_SPACE_BETWEEN', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 1, 'right'), + } + Index = Index + 2 + else + missSymbol '>' + end + if State.version ~= 'Lua 5.4' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = attr.start, + finish = attr.finish, + version = 'Lua 5.4', + info = { + version = State.version + } + } + end + end + return attrs +end + +local function createLocal(obj, attrs) + if not obj then + return nil + end + obj.type = 'local' + obj.effect = obj.finish + + if attrs then + obj.attrs = attrs + for i = 1, #attrs do + local attr = attrs[i] + attr.parent = obj + end + end + + local chunk = Chunk[#Chunk] + if chunk then + local locals = chunk.locals + if not locals then + locals = {} + chunk.locals = locals + end + locals[#locals+1] = obj + LocalCount = LocalCount + 1 + if LocalCount > LocalLimit then + pushError { + type = 'LOCAL_LIMIT', + start = obj.start, + finish = obj.finish, + } + end + end + return obj +end + +local function pushChunk(chunk) + Chunk[#Chunk+1] = chunk +end + +local function resolveLable(label, obj) + if not label.ref then + label.ref = {} + end + label.ref[#label.ref+1] = obj + obj.node = label + + -- 如果有局部变量在 goto 与 label 之间声明, + -- 并在 label 之后使用,则算作语法错误 + + -- 如果 label 在 goto 之前声明,那么不会有中间声明的局部变量 + if obj.start > label.start then + return + end + + local block = guide.getBlock(obj) + local locals = block and block.locals + if not locals then + return + end + + for i = 1, #locals do + local loc = locals[i] + -- 检查局部变量声明位置为 goto 与 label 之间 + if loc.start < obj.start or loc.finish > label.finish then + goto CONTINUE + end + -- 检查局部变量的使用位置在 label 之后 + local refs = loc.ref + if not refs then + goto CONTINUE + end + for j = 1, #refs do + local ref = refs[j] + if ref.finish > label.finish then + pushError { + type = 'JUMP_LOCAL_SCOPE', + start = obj.start, + finish = obj.finish, + info = { + loc = loc[1], + }, + relative = { + { + start = label.start, + finish = label.finish, + }, + { + start = loc.start, + finish = loc.finish, + } + }, + } + return + end + end + ::CONTINUE:: + end +end + +local function resolveGoTo(gotos) + for i = 1, #gotos do + local action = gotos[i] + local label = guide.getLabel(action, action[1]) + if label then + resolveLable(label, action) + else + pushError { + type = 'NO_VISIBLE_LABEL', + start = action.start, + finish = action.finish, + info = { + label = action[1], + } + } + end + end +end + +local function popChunk() + local chunk = Chunk[#Chunk] + if chunk.gotos then + resolveGoTo(chunk.gotos) + chunk.gotos = nil + end + local lastAction = chunk[#chunk] + if lastAction then + chunk.finish = lastAction.finish + end + Chunk[#Chunk] = nil +end + +local function parseNil() + if Tokens[Index + 1] ~= 'nil' then + return nil + end + local offset = Tokens[Index] + Index = Index + 2 + return { + type = 'nil', + start = getPosition(offset, 'left'), + finish = getPosition(offset + 2, 'right'), + } +end + +local function parseBoolean() + local word = Tokens[Index+1] + if word ~= 'true' + and word ~= 'false' then + return nil + end + local start = getPosition(Tokens[Index], 'left') + local finish = getPosition(Tokens[Index] + #word - 1, 'right') + Index = Index + 2 + return { + type = 'boolean', + start = start, + finish = finish, + [1] = word == 'true' and true or false, + } +end + +local function parseStringUnicode() + local offset = Tokens[Index] + 1 + if ssub(Lua, offset, offset) ~= '{' then + local pos = getPosition(offset, 'left') + missSymbol('{', pos) + return nil, offset + end + local leftPos = getPosition(offset, 'left') + local x16 = smatch(Lua, '^%w*', offset + 1) + local rightPos = getPosition(offset + #x16, 'right') + offset = offset + #x16 + 1 + if ssub(Lua, offset, offset) == '}' then + offset = offset + 1 + rightPos = rightPos + 1 + else + missSymbol('}', rightPos) + end + offset = offset + 1 + if #x16 == 0 then + pushError { + type = 'UTF8_SMALL', + start = leftPos, + finish = rightPos, + } + return '', offset + end + if State.version ~= 'Lua 5.3' + and State.version ~= 'Lua 5.4' + and State.version ~= 'LuaJIT' + then + pushError { + type = 'ERR_ESC', + start = leftPos - 2, + finish = rightPos, + version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + info = { + version = State.version, + } + } + return nil, offset + end + local byte = tonumber(x16, 16) + if not byte then + for i = 1, #x16 do + if not tonumber(ssub(x16, i, i), 16) then + pushError { + type = 'MUST_X16', + start = leftPos + i, + finish = leftPos + i + 1, + } + end + end + return nil, offset + end + if State.version == 'Lua 5.4' then + if byte < 0 or byte > 0x7FFFFFFF then + pushError { + type = 'UTF8_MAX', + start = leftPos, + finish = rightPos, + info = { + min = '00000000', + max = '7FFFFFFF', + } + } + return nil, offset + end + else + if byte < 0 or byte > 0x10FFFF then + pushError { + type = 'UTF8_MAX', + start = leftPos, + finish = rightPos, + version = byte <= 0x7FFFFFFF and 'Lua 5.4' or nil, + info = { + min = '000000', + max = '10FFFF', + } + } + end + end + if byte >= 0 and byte <= 0x10FFFF then + return uchar(byte), offset + end + return '', offset +end + +local stringPool = {} +local function parseShortString() + local mark = Tokens[Index+1] + local startOffset = Tokens[Index] + local startPos = getPosition(startOffset, 'left') + Index = Index + 2 + -- empty string + if Tokens[Index+1] == mark then + local finishPos = getPosition(Tokens[Index], 'right') + Index = Index + 2 + return { + type = 'string', + start = startPos, + finish = finishPos, + [1] = '', + [2] = mark, + } + end + local stringIndex = 0 + local currentOffset = startOffset + 1 + while true do + local token = Tokens[Index + 1] + if token == mark then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1) + Index = Index + 2 + break + end + if NLMap[token] then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1) + missSymbol(mark) + break + end + if not token then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = ssub(Lua, currentOffset) + missSymbol(mark) + break + end + if token == '\\' then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1) + currentOffset = Tokens[Index] + Index = Index + 2 + -- has space? + if Tokens[Index] - currentOffset > 1 then + pushError { + type = 'ERR_ESC', + start = getPosition(currentOffset, 'left'), + finish = getPosition(currentOffset + 1, 'right'), + } + goto CONTINUE + end + local nextToken = ssub(Tokens[Index + 1], 1, 1) + if EscMap[nextToken] then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = EscMap[nextToken] + currentOffset = Tokens[Index] + #nextToken + Index = Index + 2 + goto CONTINUE + end + if nextToken == mark then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = mark + currentOffset = Tokens[Index] + #nextToken + Index = Index + 2 + goto CONTINUE + end + if nextToken == 'z' then + Index = Index + 2 + repeat until not skipNL() + currentOffset = Tokens[Index] + goto CONTINUE + end + if CharMapNumber[nextToken] then + local numbers = smatch(Tokens[Index + 1], '^%d+') + if #numbers > 3 then + numbers = ssub(numbers, 1, 3) + end + currentOffset = Tokens[Index] + #numbers + fastForwardToken(currentOffset) + local byte = tointeger(numbers) + if byte <= 255 then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = schar(byte) + else + -- TODO pushError + end + goto CONTINUE + end + if nextToken == 'x' then + local left = getPosition(Tokens[Index] - 1, 'left') + local x16 = ssub(Tokens[Index + 1], 2, 3) + local byte = tonumber(x16, 16) + if byte then + currentOffset = Tokens[Index] + 3 + stringIndex = stringIndex + 1 + stringPool[stringIndex] = schar(byte) + else + currentOffset = Tokens[Index] + 1 + pushError { + type = 'MISS_ESC_X', + start = getPosition(currentOffset, 'left'), + finish = getPosition(currentOffset + 1, 'right'), + } + end + if State.version == 'Lua 5.1' then + pushError { + type = 'ERR_ESC', + start = left, + finish = left + 4, + version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + info = { + version = State.version, + } + } + end + Index = Index + 2 + goto CONTINUE + end + if nextToken == 'u' then + local str, newOffset = parseStringUnicode() + if str then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = str + end + currentOffset = newOffset + fastForwardToken(currentOffset - 1) + goto CONTINUE + end + if NLMap[nextToken] then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = '\n' + currentOffset = Tokens[Index] + #nextToken + skipNL() + goto CONTINUE + end + pushError { + type = 'ERR_ESC', + start = getPosition(currentOffset, 'left'), + finish = getPosition(currentOffset + 1, 'right'), + } + end + Index = Index + 2 + ::CONTINUE:: + end + local stringResult = tconcat(stringPool, '', 1, stringIndex) + return { + type = 'string', + start = startPos, + finish = lastRightPosition(), + [1] = stringResult, + [2] = mark, + } +end + +local function parseString() + local c = Tokens[Index + 1] + if CharMapStrSH[c] then + return parseShortString() + end + if CharMapStrLH[c] then + return parseLongString() + end + return nil +end + +local function parseNumber10(start) + local integer = true + local integerPart = smatch(Lua, '^%d*', start) + local offset = start + #integerPart + -- float part + if ssub(Lua, offset, offset) == '.' then + local floatPart = smatch(Lua, '^%d*', offset + 1) + integer = false + offset = offset + #floatPart + 1 + end + -- exp part + local echar = ssub(Lua, offset, offset) + if CharMapE10[echar] then + integer = false + offset = offset + 1 + local nextChar = ssub(Lua, offset, offset) + if CharMapSign[nextChar] then + offset = offset + 1 + end + local exp = smatch(Lua, '^%d*', offset) + offset = offset + #exp + if #exp == 0 then + pushError { + type = 'MISS_EXPONENT', + start = getPosition(offset - 1, 'right'), + finish = getPosition(offset - 1, 'right'), + } + end + end + return tonumber(ssub(Lua, start, offset - 1)), offset, integer +end + +local function parseNumber16(start) + local integerPart = smatch(Lua, '^[%da-fA-F]*', start) + local offset = start + #integerPart + local integer = true + -- float part + if ssub(Lua, offset, offset) == '.' then + local floatPart = smatch(Lua, '^[%da-fA-F]*', offset + 1) + integer = false + offset = offset + #floatPart + 1 + if #integerPart == 0 and #floatPart == 0 then + pushError { + type = 'MUST_X16', + start = getPosition(offset - 1, 'right'), + finish = getPosition(offset - 1, 'right'), + } + end + else + if #integerPart == 0 then + pushError { + type = 'MUST_X16', + start = getPosition(offset - 1, 'right'), + finish = getPosition(offset - 1, 'right'), + } + return 0, offset + end + end + -- exp part + local echar = ssub(Lua, offset, offset) + if CharMapE16[echar] then + integer = false + offset = offset + 1 + local nextChar = ssub(Lua, offset, offset) + if CharMapSign[nextChar] then + offset = offset + 1 + end + local exp = smatch(Lua, '^%d*', offset) + offset = offset + #exp + end + local n = tonumber(ssub(Lua, start - 2, offset - 1)) + return n, offset, integer +end + +local function parseNumber2(start) + local bins = smatch(Lua, '^[01]*', start) + local offset = start + #bins + if State.version ~= 'LuaJIT' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = getPosition(start - 2, 'left'), + finish = getPosition(offset - 1, 'right'), + version = 'LuaJIT', + info = { + version = 'Lua 5.4', + } + } + end + return tonumber(bins, 2), offset +end + +local function dropNumberTail(offset, integer) + local _, finish, word = sfind(Lua, '^([%.%w_\x80-\xff]+)', offset) + if not finish then + return offset + end + if integer then + if supper(ssub(word, 1, 2)) == 'LL' then + if State.version ~= 'LuaJIT' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = getPosition(offset, 'left'), + finish = getPosition(offset + 1, 'right'), + version = 'LuaJIT', + info = { + version = State.version, + } + } + end + offset = offset + 2 + word = ssub(word, offset) + elseif supper(ssub(word, 1, 3)) == 'ULL' then + if State.version ~= 'LuaJIT' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = getPosition(offset, 'left'), + finish = getPosition(offset + 2, 'right'), + version = 'LuaJIT', + info = { + version = State.version, + } + } + end + offset = offset + 3 + word = ssub(word, offset) + end + end + if supper(ssub(word, 1, 1)) == 'I' then + if State.version ~= 'LuaJIT' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = getPosition(offset, 'left'), + finish = getPosition(offset, 'right'), + version = 'LuaJIT', + info = { + version = State.version, + } + } + end + offset = offset + 1 + word = ssub(word, offset) + end + if #word > 0 then + pushError { + type = 'MALFORMED_NUMBER', + start = getPosition(offset, 'left'), + finish = getPosition(finish, 'right'), + } + end + return finish + 1 +end + +local function parseNumber() + local offset = Tokens[Index] + if not offset then + return nil + end + local startPos = getPosition(offset, 'left') + local neg + if ssub(Lua, offset, offset) == '-' then + neg = true + offset = offset + 1 + end + local number, integer + local firstChar = ssub(Lua, offset, offset) + if firstChar == '.' then + number, offset = parseNumber10(offset) + integer = false + elseif firstChar == '0' then + local nextChar = ssub(Lua, offset + 1, offset + 1) + if CharMapN16[nextChar] then + number, offset, integer = parseNumber16(offset + 2) + elseif CharMapN2[nextChar] then + number, offset = parseNumber2(offset + 2) + integer = true + else + number, offset, integer = parseNumber10(offset) + end + elseif CharMapNumber[firstChar] then + number, offset, integer = parseNumber10(offset) + else + return nil + end + if not number then + number = 0 + end + if neg then + number = - number + end + local result = { + type = integer and 'integer' or 'number', + start = startPos, + finish = getPosition(offset - 1, 'right'), + [1] = number, + } + offset = dropNumberTail(offset, integer) + fastForwardToken(offset) + return result +end + +local function isKeyWord(word) + if KeyWord[word] then + return true + end + if word == 'goto' then + return State.version ~= 'Lua 5.1' + end + return false +end + +local function parseName() + local word = peekWord() + if not word then + return nil + end + if ChunkFinishMap[word] then + return nil + end + local startPos = getPosition(Tokens[Index], 'left') + local finishPos = getPosition(Tokens[Index] + #word - 1, 'right') + Index = Index + 2 + if not State.options.unicodeName and word:find '[\x80-\xff]' then + pushError { + type = 'UNICODE_NAME', + start = startPos, + finish = finishPos, + } + end + if isKeyWord(word) then + pushError { + type = 'KEYWORD', + start = startPos, + finish = finishPos, + } + end + return { + type = 'name', + start = startPos, + finish = finishPos, + [1] = word, + } +end + +local function parseNameOrList() + local first = parseName() + if not first then + return nil + end + skipSpace() + local list + while true do + if Tokens[Index + 1] ~= ',' then + break + end + Index = Index + 2 + skipSpace() + local name = parseName() + if not name then + missName() + break + end + if not list then + list = { + type = 'list', + start = first.start, + finish = first.finish, + [1] = first + } + end + list[#list+1] = name + list.finish = name.finish + end + return list or first +end + +local function dropTail() + local token = Tokens[Index + 1] + if token ~= '?' + and token ~= ':' then + return + end + local pl, pt, pp = 0, 0, 0 + while true do + local token = Tokens[Index + 1] + if not token then + break + end + if NLMap[token] then + break + end + if token == ',' then + if pl > 0 + or pt > 0 + or pp > 0 then + goto CONTINUE + else + break + end + end + if token == '<' then + pl = pl + 1 + goto CONTINUE + end + if token == '{' then + pt = pt + 1 + goto CONTINUE + end + if token == '(' then + pp = pp + 1 + goto CONTINUE + end + if token == '>' then + if pl <= 0 then + break + end + pl = pl - 1 + goto CONTINUE + end + if token == '}' then + if pt <= 0 then + break + end + pt = pt - 1 + goto CONTINUE + end + if token == ')' then + if pp <= 0 then + break + end + pp = pp - 1 + goto CONTINUE + end + ::CONTINUE:: + Index = Index + 2 + end +end + +local function parseExpList(mini) + local list + local wantSep = false + while true do + skipSpace() + local token = Tokens[Index + 1] + if not token then + break + end + if ListFinishMap[token] then + break + end + if token == ',' then + local sepPos = getPosition(Tokens[Index], 'right') + if not wantSep then + pushError { + type = 'UNEXPECT_SYMBOL', + start = getPosition(Tokens[Index], 'left'), + finish = sepPos, + info = { + symbol = ',', + } + } + end + wantSep = false + Index = Index + 2 + goto CONTINUE + else + if mini then + if wantSep then + break + end + local nextToken = peekWord() + if isKeyWord(nextToken) + and nextToken ~= 'function' + and nextToken ~= 'true' + and nextToken ~= 'false' + and nextToken ~= 'nil' + and nextToken ~= 'not' then + break + end + end + local exp = parseExp() + if not exp then + break + end + dropTail() + if wantSep then + missSymbol(',', list[#list].finish, exp.start) + end + wantSep = true + if not list then + list = { + type = 'list', + start = exp.start, + } + end + list[#list+1] = exp + list.finish = exp.finish + exp.parent = list + end + ::CONTINUE:: + end + if not list then + return nil + end + if not wantSep then + missExp() + end + return list +end + +local function parseIndex() + local start = getPosition(Tokens[Index], 'left') + Index = Index + 2 + skipSpace() + local exp = parseExp() + local index = { + type = 'index', + start = start, + finish = exp and exp.finish or (start + 1), + index = exp + } + if exp then + exp.parent = index + else + missExp() + end + skipSpace() + if Tokens[Index + 1] == ']' then + index.finish = getPosition(Tokens[Index], 'right') + Index = Index + 2 + else + missSymbol ']' + end + return index +end + +local function parseTable() + local tbl = { + type = 'table', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index], 'right'), + } + Index = Index + 2 + local index = 0 + local wantSep = false + while true do + skipSpace() + local token = Tokens[Index + 1] + if token == '}' then + Index = Index + 2 + break + end + if CharMapTSep[token] then + if not wantSep then + missExp() + end + wantSep = false + Index = Index + 2 + goto CONTINUE + end + local lastRight = lastRightPosition() + + local exp = parseExp(true) + if exp then + if wantSep then + pushError { + type = 'MISS_SEP_IN_TABLE', + start = lastRight, + finish = exp.start, + } + end + wantSep = true + index = index + 1 + if exp.type == 'varargs' then + tbl[index] = exp + exp.parent = tbl + goto CONTINUE + end + if exp.type == 'getlocal' + or exp.type == 'getglobal' then + skipSpace() + if expectAssign() then + local eqRight = lastRightPosition() + skipSpace() + local fvalue = parseExp() + local tfield = { + type = 'tablefield', + start = exp.start, + finish = fvalue and fvalue.finish or eqRight, + parent = tbl, + field = exp, + value = fvalue, + } + exp.type = 'field' + exp.parent = tfield + if fvalue then + fvalue.parent = tfield + else + missExp() + end + tbl[index] = tfield + goto CONTINUE + end + end + local texp = { + type = 'tableexp', + start = exp.start, + finish = exp.finish, + tindex = index, + parent = tbl, + value = exp, + } + exp.parent = texp + tbl[index] = texp + goto CONTINUE + end + + if token == '[' then + if wantSep then + pushError { + type = 'MISS_SEP_IN_TABLE', + start = lastRight, + finish = getPosition(Tokens[Index], 'left'), + } + end + wantSep = true + index = index + 1 + local tindex = parseIndex() + skipSpace() + if expectAssign() then + skipSpace() + local ivalue = parseExp() + tindex.type = 'tableindex' + tindex.parent = tbl + if ivalue then + ivalue.parent = tindex + tindex.finish = ivalue.finish + tindex.value = ivalue + else + missExp() + end + tbl[index] = tindex + else + missSymbol '=' + end + goto CONTINUE + end + + missSymbol '}' + break + ::CONTINUE:: + end + tbl.finish = getPosition(Tokens[Index - 2], 'right') + return tbl +end + +local function parseSimple(node, funcName) + local lastMethod + while true do + if lastMethod and node.node == lastMethod then + if node.type ~= 'call' then + missSymbol('(', node.node.finish, node.node.finish) + end + lastMethod = nil + end + skipSpace() + local token = Tokens[Index + 1] + if token == '.' then + local dot = { + type = token, + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index], 'right'), + } + Index = Index + 2 + skipSpace() + local field = parseName() + local getfield = { + type = 'getfield', + start = node.start, + finish = lastRightPosition(), + node = node, + dot = dot, + field = field + } + if field then + field.parent = getfield + field.type = 'field' + else + pushError { + type = 'MISS_FIELD', + start = lastRightPosition(), + finish = lastRightPosition(), + } + end + node.parent = getfield + node.next = getfield + node = getfield + elseif token == ':' then + local colon = { + type = token, + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index], 'right'), + } + Index = Index + 2 + skipSpace() + local method = parseName() + local getmethod = { + type = 'getmethod', + start = node.start, + finish = lastRightPosition(), + node = node, + colon = colon, + method = method + } + if method then + method.parent = getmethod + method.type = 'method' + else + pushError { + type = 'MISS_METHOD', + start = lastRightPosition(), + finish = lastRightPosition(), + } + end + node.parent = getmethod + node.next = getmethod + node = getmethod + if lastMethod then + missSymbol('(', node.node.finish, node.node.finish) + end + lastMethod = getmethod + elseif token == '(' then + if funcName then + break + end + local startPos = getPosition(Tokens[Index], 'left') + local call = { + type = 'call', + start = node.start, + node = node, + } + Index = Index + 2 + local args = parseExpList() + if Tokens[Index + 1] == ')' then + call.finish = getPosition(Tokens[Index], 'right') + Index = Index + 2 + else + call.finish = lastRightPosition() + missSymbol ')' + end + if args then + args.type = 'callargs' + args.start = startPos + args.finish = call.finish + args.parent = call + call.args = args + end + if node.type == 'getmethod' then + -- dummy param `self` + if not call.args then + call.args = { + type = 'callargs', + start = call.start, + finish = call.finish, + parent = call, + } + end + local newNode = {} + for k, v in next, call.node.node do + newNode[k] = v + end + newNode.mirror = call.node.node + newNode.dummy = true + newNode.parent = call.args + call.node.node.mirror = newNode + tinsert(call.args, 1, newNode) + end + node.parent = call + node = call + elseif token == '{' then + if funcName then + break + end + local tbl = parseTable() + local call = { + type = 'call', + start = node.start, + finish = tbl.finish, + node = node, + } + local args = { + type = 'callargs', + start = tbl.start, + finish = tbl.finish, + parent = call, + [1] = tbl, + } + call.args = args + tbl.parent = args + node.parent = call + node = call + elseif CharMapStrSH[token] then + if funcName then + break + end + local str = parseShortString() + local call = { + type = 'call', + start = node.start, + finish = str.finish, + node = node, + } + local args = { + type = 'callargs', + start = str.start, + finish = str.finish, + parent = call, + [1] = str, + } + call.args = args + str.parent = args + node.parent = call + node = call + elseif CharMapStrLH[token] then + local str = parseLongString() + if str then + if funcName then + break + end + local call = { + type = 'call', + start = node.start, + finish = str.finish, + node = node, + } + local args = { + type = 'callargs', + start = str.start, + finish = str.finish, + parent = call, + [1] = str, + } + call.args = args + str.parent = args + node.parent = call + node = call + else + local index = parseIndex() + local bstart = index.start + index.type = 'getindex' + index.start = node.start + index.node = node + node.next = index + node.parent = index + node = index + if funcName then + pushError { + type = 'INDEX_IN_FUNC_NAME', + start = bstart, + finish = index.finish, + } + end + end + else + break + end + end + if node.type == 'call' + and node.node == lastMethod then + lastMethod = nil + end + if node == lastMethod then + if funcName then + lastMethod = nil + end + end + if lastMethod then + missSymbol('(', lastMethod.finish) + end + return node +end + +local function parseVarargs() + local varargs = { + type = 'varargs', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 2, 'right'), + } + Index = Index + 2 + for i = #Chunk, 1, -1 do + local chunk = Chunk[i] + if chunk.vararg then + if not chunk.vararg.ref then + chunk.vararg.ref = {} + end + chunk.vararg.ref[#chunk.vararg.ref+1] = varargs + varargs.node = chunk.vararg + break + end + if chunk.type == 'function' then + break + end + end + if not varargs.node + and Mode == 'Lua' + and Chunk[#Chunk].type ~= 'main' then + pushError { + type = 'UNEXPECT_DOTS', + start = varargs.start, + finish = varargs.finish, + } + end + return varargs +end + +local function parseParen() + local pl = Tokens[Index] + local paren = { + type = 'paren', + start = getPosition(pl, 'left'), + finish = getPosition(pl, 'right') + } + Index = Index + 2 + skipSpace() + local exp = parseExp() + if exp then + paren.exp = exp + paren.finish = exp.finish + exp.parent = paren + else + missExp() + end + skipSpace() + if Tokens[Index + 1] == ')' then + paren.finish = getPosition(Tokens[Index], 'right') + Index = Index + 2 + else + missSymbol ')' + end + return paren +end + +local function getLocal(name, pos) + for i = #Chunk, 1, -1 do + local chunk = Chunk[i] + local locals = chunk.locals + if locals then + local res + for n = 1, #locals do + local loc = locals[n] + if loc.effect > pos then + break + end + if loc[1] == name then + if not res or res.effect < loc.effect then + res = loc + end + end + end + if res then + return res + end + end + end +end + +local function resolveName(node) + if not node then + return nil + end + local loc = getLocal(node[1], node.start) + if loc then + node.type = 'getlocal' + node.node = loc + if not loc.ref then + loc.ref = {} + end + loc.ref[#loc.ref+1] = node + if loc.special then + addSpecial(loc.special, node) + end + else + node.type = 'getglobal' + local env = getLocal(State.ENVMode, node.start) + if env then + node.node = env + if not env.ref then + env.ref = {} + end + env.ref[#env.ref+1] = node + end + end + local name = node[1] + if Specials[name] then + addSpecial(name, node) + else + local ospeicals = State.options.special + if ospeicals and ospeicals[name] then + addSpecial(name, node) + end + end + return node +end + +local function isChunkFinishToken(token) + local currentChunk = Chunk[#Chunk] + if not currentChunk then + return false + end + local tp = currentChunk.type + if tp == 'main' then + return false + end + if tp == 'for' + or tp == 'in' + or tp == 'loop' then + return token == 'end' + end + if tp == 'if' + or tp == 'ifblock' + or tp == 'elseifblock' + or tp == 'elseblock' then + return token == 'then' + or token == 'end' + or token == 'else' + or token == 'elseif' + end + return true +end + +local function parseActions() + local rtn, last + while true do + skipSpace(true) + local token = Tokens[Index + 1] + if token == ';' then + Index = Index + 2 + goto CONTINUE + end + if ChunkFinishMap[token] + and isChunkFinishToken(token) then + break + end + local action, failed = parseAction() + if failed then + break + end + if action then + if action.type == 'return' then + rtn = action + end + last = action + end + ::CONTINUE:: + end + if rtn and rtn ~= last then + pushError { + type = 'ACTION_AFTER_RETURN', + start = rtn.start, + finish = rtn.finish, + } + end +end + +local function parseParams(params) + local lastSep + local hasDots + while true do + skipSpace() + local token = Tokens[Index + 1] + if not token or token == ')' then + if lastSep then + missName() + end + break + end + if token == ',' then + if lastSep or lastSep == nil then + missName() + else + lastSep = true + end + Index = Index + 2 + goto CONTINUE + end + if token == '...' then + if lastSep == false then + missSymbol ',' + end + lastSep = false + if not params then + params = {} + end + local vararg = { + type = '...', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 2, 'right'), + parent = params, + } + local chunk = Chunk[#Chunk] + chunk.vararg = vararg + params[#params+1] = vararg + if hasDots then + pushError { + type = 'ARGS_AFTER_DOTS', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 2, 'right'), + } + end + hasDots = true + Index = Index + 2 + goto CONTINUE + end + if CharMapWord[ssub(token, 1, 1)] then + if lastSep == false then + missSymbol ',' + end + lastSep = false + if not params then + params = {} + end + params[#params+1] = createLocal { + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + #token - 1, 'right'), + parent = params, + [1] = token, + } + if hasDots then + pushError { + type = 'ARGS_AFTER_DOTS', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + #token - 1, 'right'), + } + end + Index = Index + 2 + goto CONTINUE + end + skipUnknownSymbol '%,%)%.' + ::CONTINUE:: + end + return params +end + +local function parseFunction(isLocal, isAction) + local funcLeft = getPosition(Tokens[Index], 'left') + local funcRight = getPosition(Tokens[Index] + 7, 'right') + local func = { + type = 'function', + start = funcLeft, + finish = funcRight, + keyword = { + [1] = funcLeft, + [2] = funcRight, + }, + } + Index = Index + 2 + local LastLocalCount = LocalCount + LocalCount = 0 + skipSpace() + local hasLeftParen = Tokens[Index + 1] == '(' + if not hasLeftParen then + local name = parseName() + if name then + local simple = parseSimple(name, true) + if isLocal then + if simple == name then + createLocal(name) + else + resolveName(name) + pushError { + type = 'UNEXPECT_LFUNC_NAME', + start = simple.start, + finish = simple.finish, + } + end + else + resolveName(name) + end + func.name = simple + func.finish = simple.finish + if not isAction then + pushError { + type = 'UNEXPECT_EFUNC_NAME', + start = simple.start, + finish = simple.finish, + } + end + skipSpace() + hasLeftParen = Tokens[Index + 1] == '(' + end + end + pushChunk(func) + local params + if func.name and func.name.type == 'getmethod' then + if func.name.type == 'getmethod' then + params = {} + params[1] = createLocal { + start = funcRight, + finish = funcRight, + method = func.name, + parent = params, + tag = 'self', + dummy = true, + [1] = 'self', + } + end + end + if hasLeftParen then + local parenLeft = getPosition(Tokens[Index], 'left') + Index = Index + 2 + params = parseParams(params) + if params then + params.type = 'funcargs' + params.start = parenLeft + params.finish = lastRightPosition() + params.parent = func + func.args = params + end + skipSpace() + if Tokens[Index + 1] == ')' then + local parenRight = getPosition(Tokens[Index], 'right') + func.finish = parenRight + if params then + params.finish = parenRight + end + Index = Index + 2 + skipSpace() + else + func.finish = lastRightPosition() + if params then + params.finish = func.finish + end + missSymbol ')' + end + else + missSymbol '(' + end + parseActions() + popChunk() + if Tokens[Index + 1] == 'end' then + local endLeft = getPosition(Tokens[Index], 'left') + local endRight = getPosition(Tokens[Index] + 2, 'right') + func.keyword[3] = endLeft + func.keyword[4] = endRight + func.finish = endRight + Index = Index + 2 + else + missEnd(funcLeft, funcRight) + end + LocalCount = LastLocalCount + return func +end + +local function parseExpUnit() + local token = Tokens[Index + 1] + if token == '(' then + local paren = parseParen() + return parseSimple(paren, false) + end + + if token == '...' then + local varargs = parseVarargs() + return varargs + end + + if token == '{' then + local table = parseTable() + return table + end + + if CharMapStrSH[token] then + local string = parseShortString() + return string + end + + if CharMapStrLH[token] then + local string = parseLongString() + return string + end + + local number = parseNumber() + if number then + return number + end + + if ChunkFinishMap[token] then + return nil + end + + if token == 'nil' then + return parseNil() + end + + if token == 'true' + or token == 'false' then + return parseBoolean() + end + + if token == 'function' then + return parseFunction() + end + + local node = parseName() + if node then + return parseSimple(resolveName(node), false) + end + + return nil +end + +local function parseUnaryOP() + local token = Tokens[Index + 1] + local symbol = UnarySymbol[token] and token or UnaryAlias[token] + if not symbol then + return nil + end + local myLevel = UnarySymbol[symbol] + local op = { + type = symbol, + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + #symbol - 1, 'right'), + } + Index = Index + 2 + return op, myLevel +end + +---@param level integer # op level must greater than this level +local function parseBinaryOP(asAction, level) + local token = Tokens[Index + 1] + local symbol = (BinarySymbol[token] and token) + or BinaryAlias[token] + or (not asAction and BinaryActionAlias[token]) + if not symbol then + return nil + end + local myLevel = BinarySymbol[symbol] + if level and myLevel < level then + return nil + end + local op = { + type = symbol, + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + #token - 1, 'right'), + } + if not asAction then + if token == '=' then + pushError { + type = 'ERR_EQ_AS_ASSIGN', + start = op.start, + finish = op.finish, + fix = { + title = 'FIX_EQ_AS_ASSIGN', + { + start = op.start, + finish = op.finish, + text = '==', + } + } + } + end + end + if BinaryAlias[token] then + if State.options.nonstandardSymbol and State.options.nonstandardSymbol[token] then + else + pushError { + type = 'ERR_NONSTANDARD_SYMBOL', + start = op.start, + finish = op.finish, + info = { + symbol = symbol, + }, + fix = { + title = 'FIX_NONSTANDARD_SYMBOL', + symbol = symbol, + { + start = op.start, + finish = op.finish, + text = symbol, + }, + } + } + end + end + if token == '//' + or token == '<<' + or token == '>>' then + if State.version ~= 'Lua 5.3' + and State.version ~= 'Lua 5.4' then + pushError { + type = 'UNSUPPORT_SYMBOL', + version = {'Lua 5.3', 'Lua 5.4'}, + start = op.start, + finish = op.finish, + info = { + version = State.version, + } + } + end + end + Index = Index + 2 + return op, myLevel +end + +function parseExp(asAction, level) + local exp + local uop, uopLevel = parseUnaryOP() + if uop then + skipSpace() + local child = parseExp(asAction, uopLevel) + -- 预计算负数 + if uop.type == '-' + and child + and (child.type == 'number' or child.type == 'integer') then + child.start = uop.start + child[1] = - child[1] + exp = child + else + exp = { + type = 'unary', + op = uop, + start = uop.start, + finish = child and child.finish or uop.finish, + [1] = child, + } + if child then + child.parent = exp + else + missExp() + end + end + else + exp = parseExpUnit() + if not exp then + return nil + end + end + + while true do + skipSpace() + local bop, bopLevel = parseBinaryOP(asAction, level) + if not bop then + break + end + + ::AGAIN:: + skipSpace() + local isForward = SymbolForward[bopLevel] + local child = parseExp(asAction, isForward and (bopLevel + 0.5) or bopLevel) + if not child then + if skipUnknownSymbol() then + goto AGAIN + else + missExp() + end + end + local bin = { + type = 'binary', + start = exp.start, + finish = child and child.finish or bop.finish, + op = bop, + [1] = exp, + [2] = child + } + exp.parent = bin + if child then + child.parent = bin + end + exp = bin + end + + return exp +end + +local function skipSeps() + while true do + skipSpace() + if Tokens[Index + 1] == ',' then + missExp() + Index = Index + 2 + else + break + end + end +end + +---@return parser.guide.object first +---@return parser.guide.object second +---@return parser.guide.object[] rest +local function parseSetValues() + skipSpace() + local first = parseExp() + if not first then + return nil + end + skipSpace() + if Tokens[Index + 1] ~= ',' then + return first + end + Index = Index + 2 + skipSeps() + local second = parseExp() + if not second then + missExp() + return first + end + skipSpace() + if Tokens[Index + 1] ~= ',' then + return first, second + end + Index = Index + 2 + skipSeps() + local third = parseExp() + if not third then + missExp() + return first, second + end + + local rest = { third } + while true do + skipSpace() + if Tokens[Index + 1] ~= ',' then + return first, second, rest + end + Index = Index + 2 + skipSeps() + local exp = parseExp() + if not exp then + missExp() + return first, second, rest + end + rest[#rest+1] = exp + end +end + +local function pushActionIntoCurrentChunk(action) + local chunk = Chunk[#Chunk] + if chunk then + chunk[#chunk+1] = action + action.parent = chunk + end +end + +---@return parser.guide.object second +---@return parser.guide.object[] rest +local function parseVarTails(parser, isLocal) + if Tokens[Index + 1] ~= ',' then + return + end + Index = Index + 2 + skipSpace() + local second = parser(true) + if not second then + missName() + return + end + if isLocal then + createLocal(second, parseLocalAttrs()) + second.effect = maxinteger + end + skipSpace() + if Tokens[Index + 1] ~= ',' then + return second + end + Index = Index + 2 + skipSeps() + local third = parser(true) + if not third then + missName() + return second + end + if isLocal then + createLocal(third, parseLocalAttrs()) + third.effect = maxinteger + end + local rest = { third } + while true do + skipSpace() + if Tokens[Index + 1] ~= ',' then + return second, rest + end + Index = Index + 2 + skipSeps() + local name = parser(true) + if not name then + missName() + return second, rest + end + if isLocal then + createLocal(name, parseLocalAttrs()) + name.effect = maxinteger + end + rest[#rest+1] = name + end +end + +local function bindValue(n, v, index, lastValue, isLocal, isSet) + if isLocal then + n.effect = lastRightPosition() + if v and v.special then + addSpecial(v.special, n) + end + elseif isSet then + n.type = GetToSetMap[n.type] or n.type + if n.type == 'setlocal' then + local loc = n.node + if loc.attrs then + pushError { + type = 'SET_CONST', + start = n.start, + finish = n.finish, + } + end + end + end + if not v and lastValue then + if lastValue.type == 'call' + or lastValue.type == 'varargs' then + v = lastValue + if not v.extParent then + v.extParent = {} + end + end + end + if v then + if v.type == 'call' + or v.type == 'varargs' then + local select = { + type = 'select', + sindex = index, + start = v.start, + finish = v.finish, + vararg = v + } + if v.parent then + v.extParent[#v.extParent+1] = select + else + v.parent = select + end + v = select + end + n.value = v + n.range = v.finish + v.parent = n + if isLocal then + n.effect = lastRightPosition() + end + end +end + +local function parseMultiVars(n1, parser, isLocal) + local n2, nrest = parseVarTails(parser, isLocal) + skipSpace() + local v1, v2, vrest + local isSet + local max = 1 + if expectAssign() then + v1, v2, vrest = parseSetValues() + isSet = true + if not v1 then + missExp() + end + end + bindValue(n1, v1, 1, nil, isLocal, isSet) + local lastValue = v1 + if n2 then + max = 2 + bindValue(n2, v2, 2, lastValue, isLocal, isSet) + lastValue = v2 or lastValue + pushActionIntoCurrentChunk(n2) + end + if nrest then + for i = 1, #nrest do + local n = nrest[i] + local v = vrest and vrest[i] + max = i + 2 + bindValue(n, v, max, lastValue, isLocal, isSet) + lastValue = v or lastValue + pushActionIntoCurrentChunk(n) + end + end + + if v2 and not n2 then + v2.redundant = { + max = max, + passed = 2, + } + pushActionIntoCurrentChunk(v2) + end + if vrest then + for i = 1, #vrest do + local v = vrest[i] + if not nrest or not nrest[i] then + v.redundant = { + max = max, + passed = i + 2, + } + pushActionIntoCurrentChunk(v) + end + end + end + + return n1, isSet +end + +local function compileExpAsAction(exp) + pushActionIntoCurrentChunk(exp) + if GetToSetMap[exp.type] then + skipSpace() + local action, isSet = parseMultiVars(exp, parseExp) + if isSet + or action.type == 'getmethod' then + return action + end + end + + if exp.type == 'call' then + return exp + end + + if exp.type == 'binary' then + if GetToSetMap[exp[1].type] then + local op = exp.op + if op.type == '==' then + pushError { + type = 'ERR_ASSIGN_AS_EQ', + start = op.start, + finish = op.finish, + fix = { + title = 'FIX_ASSIGN_AS_EQ', + { + start = op.start, + finish = op.finish, + text = '=', + } + } + } + return + end + end + end + + pushError { + type = 'EXP_IN_ACTION', + start = exp.start, + finish = exp.finish, + } +end + +local function parseLocal() + Index = Index + 2 + skipSpace() + local word = peekWord() + if not word then + missName() + return nil + end + + if word == 'function' then + local func = parseFunction(true, true) + local name = func.name + if name then + func.name = nil + name.value = func + name.vstart = func.start + name.range = func.finish + func.parent = name + pushActionIntoCurrentChunk(name) + return name + else + missName(func.keyword[2]) + pushActionIntoCurrentChunk(func) + return func + end + end + + local name = parseName() + if not name then + missName() + return nil + end + local loc = createLocal(name, parseLocalAttrs()) + loc.effect = maxinteger + pushActionIntoCurrentChunk(loc) + skipSpace() + parseMultiVars(loc, parseName, true) + loc.effect = lastRightPosition() + + return loc +end + +local function parseDo() + local doLeft = getPosition(Tokens[Index], 'left') + local doRight = getPosition(Tokens[Index] + 1, 'right') + local obj = { + type = 'do', + start = doLeft, + finish = doRight, + keyword = { + [1] = doLeft, + [2] = doRight, + }, + } + Index = Index + 2 + pushActionIntoCurrentChunk(obj) + pushChunk(obj) + parseActions() + popChunk() + if Tokens[Index + 1] == 'end' then + obj.finish = getPosition(Tokens[Index] + 2, 'right') + obj.keyword[3] = getPosition(Tokens[Index], 'left') + obj.keyword[4] = getPosition(Tokens[Index] + 2, 'right') + Index = Index + 2 + else + missEnd(doLeft, doRight) + end + if obj.locals then + LocalCount = LocalCount - #obj.locals + end + + return obj +end + +local function parseReturn() + local returnLeft = getPosition(Tokens[Index], 'left') + local returnRight = getPosition(Tokens[Index] + 5, 'right') + Index = Index + 2 + skipSpace() + local rtn = parseExpList(true) + if rtn then + rtn.type = 'return' + rtn.start = returnLeft + else + rtn = { + type = 'return', + start = returnLeft, + finish = returnRight, + } + end + pushActionIntoCurrentChunk(rtn) + for i = #Chunk, 1, -1 do + local func = Chunk[i] + if func.type == 'function' + or func.type == 'main' then + if not func.returns then + func.returns = {} + end + func.returns[#func.returns+1] = rtn + break + end + end + + return rtn +end + +local function parseLabel() + local left = getPosition(Tokens[Index], 'left') + Index = Index + 2 + skipSpace() + local label = parseName() + skipSpace() + + if not label then + missName() + end + + if Tokens[Index + 1] == '::' then + Index = Index + 2 + else + if label then + missSymbol '::' + end + end + + if not label then + return nil + end + + label.type = 'label' + pushActionIntoCurrentChunk(label) + + local block = guide.getBlock(label) + if block then + if not block.labels then + block.labels = {} + end + local name = label[1] + local olabel = guide.getLabel(block, name) + if olabel then + if State.version == 'Lua 5.4' + or block == guide.getBlock(olabel) then + pushError { + type = 'REDEFINED_LABEL', + start = label.start, + finish = label.finish, + relative = { + { + olabel.start, + olabel.finish, + } + } + } + end + end + block.labels[name] = label + end + + if State.version == 'Lua 5.1' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = left, + finish = lastRightPosition(), + version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + info = { + version = State.version, + } + } + return + end + return label +end + +local function parseGoTo() + Index = Index + 2 + skipSpace() + + local action = parseName() + if not action then + missName() + return nil + end + + action.type = 'goto' + + for i = #Chunk, 1, -1 do + local chunk = Chunk[i] + if chunk.type == 'function' + or chunk.type == 'main' then + if not chunk.gotos then + chunk.gotos = {} + end + chunk.gotos[#chunk.gotos+1] = action + break + end + end + + pushActionIntoCurrentChunk(action) + return action +end + +local function parseIfBlock(parent) + local ifLeft = getPosition(Tokens[Index], 'left') + local ifRight = getPosition(Tokens[Index] + 1, 'right') + Index = Index + 2 + local ifblock = { + type = 'ifblock', + parent = parent, + start = ifLeft, + finish = ifRight, + keyword = { + [1] = ifLeft, + [2] = ifRight, + } + } + skipSpace() + local filter = parseExp() + if filter then + ifblock.filter = filter + ifblock.finish = filter.finish + filter.parent = ifblock + else + missExp() + end + skipSpace() + local thenToken = Tokens[Index + 1] + if thenToken == 'then' + or thenToken == 'do' then + ifblock.finish = getPosition(Tokens[Index] + #thenToken - 1, 'right') + ifblock.keyword[3] = getPosition(Tokens[Index], 'left') + ifblock.keyword[4] = ifblock.finish + if thenToken == 'do' then + pushError { + type = 'ERR_THEN_AS_DO', + start = ifblock.keyword[3], + finish = ifblock.keyword[4], + fix = { + title = 'FIX_THEN_AS_DO', + { + start = ifblock.keyword[3], + finish = ifblock.keyword[4], + text = 'then', + } + } + } + end + Index = Index + 2 + else + missSymbol 'then' + end + pushChunk(ifblock) + parseActions() + popChunk() + if ifblock.locals then + LocalCount = LocalCount - #ifblock.locals + end + return ifblock +end + +local function parseElseIfBlock(parent) + local ifLeft = getPosition(Tokens[Index], 'left') + local ifRight = getPosition(Tokens[Index] + 5, 'right') + local elseifblock = { + type = 'elseifblock', + parent = parent, + start = ifLeft, + finish = ifRight, + keyword = { + [1] = ifLeft, + [2] = ifRight, + } + } + Index = Index + 2 + skipSpace() + local filter = parseExp() + if filter then + elseifblock.filter = filter + elseifblock.finish = filter.finish + filter.parent = elseifblock + else + missExp() + end + skipSpace() + local thenToken = Tokens[Index + 1] + if thenToken == 'then' + or thenToken == 'do' then + elseifblock.finish = getPosition(Tokens[Index] + #thenToken - 1, 'right') + elseifblock.keyword[3] = getPosition(Tokens[Index], 'left') + elseifblock.keyword[4] = elseifblock.finish + if thenToken == 'do' then + pushError { + type = 'ERR_THEN_AS_DO', + start = elseifblock.keyword[3], + finish = elseifblock.keyword[4], + fix = { + title = 'FIX_THEN_AS_DO', + { + start = elseifblock.keyword[3], + finish = elseifblock.keyword[4], + text = 'then', + } + } + } + end + Index = Index + 2 + else + missSymbol 'then' + end + pushChunk(elseifblock) + parseActions() + popChunk() + if elseifblock.locals then + LocalCount = LocalCount - #elseifblock.locals + end + return elseifblock +end + +local function parseElseBlock(parent) + local ifLeft = getPosition(Tokens[Index], 'left') + local ifRight = getPosition(Tokens[Index] + 3, 'right') + local elseblock = { + type = 'elseblock', + parent = parent, + start = ifLeft, + finish = ifRight, + keyword = { + [1] = ifLeft, + [2] = ifRight, + } + } + Index = Index + 2 + skipSpace() + pushChunk(elseblock) + parseActions() + popChunk() + if elseblock.locals then + LocalCount = LocalCount - #elseblock.locals + end + return elseblock +end + +local function parseIf() + local token = Tokens[Index + 1] + local left = getPosition(Tokens[Index], 'left') + local action = { + type = 'if', + start = left, + finish = getPosition(Tokens[Index] + #token - 1, 'right'), + } + pushActionIntoCurrentChunk(action) + if token ~= 'if' then + missSymbol('if', left, left) + end + local hasElse + while true do + local word = Tokens[Index + 1] + local child + if word == 'if' then + child = parseIfBlock(action) + elseif word == 'elseif' then + child = parseElseIfBlock(action) + elseif word == 'else' then + child = parseElseBlock(action) + end + if not child then + break + end + if hasElse then + pushError { + type = 'BLOCK_AFTER_ELSE', + start = child.start, + finish = child.finish, + } + end + if word == 'else' then + hasElse = true + end + action[#action+1] = child + action.finish = child.finish + skipSpace() + end + + if Tokens[Index + 1] == 'end' then + action.finish = getPosition(Tokens[Index] + 2, 'right') + Index = Index + 2 + else + missEnd(action[1].keyword[1], action[1].keyword[2]) + end + + return action +end + +local function parseFor() + local action = { + type = 'for', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 2, 'right'), + keyword = {}, + } + action.keyword[1] = action.start + action.keyword[2] = action.finish + Index = Index + 2 + pushActionIntoCurrentChunk(action) + pushChunk(action) + skipSpace() + local nameOrList = parseNameOrList() + if not nameOrList then + missName() + end + skipSpace() + -- for i = + if expectAssign() then + action.type = 'loop' + + skipSpace() + local expList = parseExpList() + local name + if nameOrList then + if nameOrList.type == 'name' then + name = nameOrList + else + name = nameOrList[1] + end + end + if name then + local loc = createLocal(name) + loc.parent = action + action.finish = name.finish + action.loc = loc + end + if expList then + local value = expList[1] + if value then + value.parent = action + action.init = value + action.finish = expList[#expList].finish + end + local max = expList[2] + if max then + max.parent = action + action.max = max + action.finish = max.finish + else + pushError { + type = 'MISS_LOOP_MAX', + start = lastRightPosition(), + finish = lastRightPosition(), + } + end + local step = expList[3] + if step then + step.parent = action + action.step = step + action.finish = step.finish + end + else + pushError { + type = 'MISS_LOOP_MIN', + start = lastRightPosition(), + finish = lastRightPosition(), + } + end + + if action.loc then + action.loc.effect = action.finish + end + elseif Tokens[Index + 1] == 'in' then + action.type = 'in' + local inLeft = getPosition(Tokens[Index], 'left') + local inRight = getPosition(Tokens[Index] + 1, 'right') + Index = Index + 2 + skipSpace() + + local exps = parseExpList() + + action.finish = inRight + action.keyword[3] = inLeft + action.keyword[4] = inRight + + local list + if nameOrList and nameOrList.type == 'name' then + list = { + type = 'list', + start = nameOrList.start, + finish = nameOrList.finish, + [1] = nameOrList, + } + else + list = nameOrList + end + + if exps then + local lastExp = exps[#exps] + if lastExp then + action.finish = lastExp.finish + end + + action.exps = exps + for i = 1, #exps do + local exp = exps[i] + exp.parent = action + end + else + missExp() + end + + if list then + local lastName = list[#list] + list.range = lastName and lastName.range or inRight + action.keys = list + for i = 1, #list do + local loc = createLocal(list[i]) + loc.parent = action + loc.effect = action.finish + end + end + else + missSymbol 'in' + end + + skipSpace() + local doToken = Tokens[Index + 1] + if doToken == 'do' + or doToken == 'then' then + local left = getPosition(Tokens[Index], 'left') + local right = getPosition(Tokens[Index] + #doToken - 1, 'right') + action.finish = left + action.keyword[#action.keyword+1] = left + action.keyword[#action.keyword+1] = right + if doToken == 'then' then + pushError { + type = 'ERR_DO_AS_THEN', + start = left, + finish = right, + fix = { + title = 'FIX_DO_AS_THEN', + { + start = left, + finish = right, + text = 'do', + } + } + } + end + Index = Index + 2 + else + missSymbol 'do' + end + + skipSpace() + parseActions() + popChunk() + + skipSpace() + if Tokens[Index + 1] == 'end' then + action.finish = getPosition(Tokens[Index] + 2, 'right') + action.keyword[#action.keyword+1] = getPosition(Tokens[Index], 'left') + action.keyword[#action.keyword+1] = action.finish + Index = Index + 2 + else + missEnd(action.keyword[1], action.keyword[2]) + end + + if action.locals then + LocalCount = LocalCount - #action.locals + end + + return action +end + +local function parseWhile() + local action = { + type = 'while', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 4, 'right'), + keyword = {}, + } + action.keyword[1] = action.start + action.keyword[2] = action.finish + Index = Index + 2 + + skipSpace() + local nextToken = Tokens[Index + 1] + local filter = nextToken ~= 'do' + and nextToken ~= 'then' + and parseExp() + if filter then + action.filter = filter + action.finish = filter.finish + filter.parent = action + else + missExp() + end + + skipSpace() + local doToken = Tokens[Index + 1] + if doToken == 'do' + or doToken == 'then' then + local left = getPosition(Tokens[Index], 'left') + local right = getPosition(Tokens[Index] + #doToken - 1, 'right') + action.finish = left + action.keyword[#action.keyword+1] = left + action.keyword[#action.keyword+1] = right + if doToken == 'then' then + pushError { + type = 'ERR_DO_AS_THEN', + start = left, + finish = right, + fix = { + title = 'FIX_DO_AS_THEN', + { + start = left, + finish = right, + text = 'do', + } + } + } + end + Index = Index + 2 + else + missSymbol 'do' + end + + pushActionIntoCurrentChunk(action) + pushChunk(action) + skipSpace() + parseActions() + popChunk() + + skipSpace() + if Tokens[Index + 1] == 'end' then + action.finish = getPosition(Tokens[Index] + 2, 'right') + action.keyword[#action.keyword+1] = getPosition(Tokens[Index], 'left') + action.keyword[#action.keyword+1] = action.finish + Index = Index + 2 + else + missEnd(action.keyword[1], action.keyword[2]) + end + + if action.locals then + LocalCount = LocalCount - #action.locals + end + + return action +end + +local function parseRepeat() + local action = { + type = 'repeat', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 5, 'right'), + keyword = {}, + } + action.keyword[1] = action.start + action.keyword[2] = action.finish + Index = Index + 2 + + pushActionIntoCurrentChunk(action) + pushChunk(action) + skipSpace() + parseActions() + + skipSpace() + if Tokens[Index + 1] == 'until' then + action.finish = getPosition(Tokens[Index] + 4, 'right') + action.keyword[#action.keyword+1] = getPosition(Tokens[Index], 'left') + action.keyword[#action.keyword+1] = action.finish + Index = Index + 2 + + skipSpace() + local filter = parseExp() + if filter then + action.filter = filter + filter.parent = action + else + missExp() + end + + else + missSymbol 'until' + end + + popChunk() + if action.filter then + action.finish = action.filter.finish + end + + if action.locals then + LocalCount = LocalCount - #action.locals + end + + return action +end + +local function parseBreak() + local returnLeft = getPosition(Tokens[Index], 'left') + local returnRight = getPosition(Tokens[Index] + 4, 'right') + Index = Index + 2 + skipSpace() + local action = { + type = 'break', + start = returnLeft, + finish = returnRight, + } + + local ok + for i = #Chunk, 1, -1 do + local chunk = Chunk[i] + if chunk.type == 'function' then + break + end + if chunk.type == 'while' + or chunk.type == 'in' + or chunk.type == 'loop' + or chunk.type == 'repeat' + or chunk.type == 'for' then + if not chunk.breaks then + chunk.breaks = {} + end + chunk.breaks[#chunk.breaks+1] = action + ok = true + break + end + end + if not ok and Mode == 'Lua' then + pushError { + type = 'BREAK_OUTSIDE', + start = action.start, + finish = action.finish, + } + end + + pushActionIntoCurrentChunk(action) + return action +end + +function parseAction() + local token = Tokens[Index + 1] + + if token == '::' then + return parseLabel() + end + + if token == 'local' then + return parseLocal() + end + + if token == 'if' + or token == 'elseif' + or token == 'else' then + return parseIf() + end + + if token == 'for' then + return parseFor() + end + + if token == 'do' then + return parseDo() + end + + if token == 'return' then + return parseReturn() + end + + if token == 'break' then + return parseBreak() + end + + if token == 'while' then + return parseWhile() + end + + if token == 'repeat' then + return parseRepeat() + end + + if token == 'goto' then + return parseGoTo() + end + + if token == 'function' then + local exp = parseFunction(false, true) + local name = exp.name + if name then + exp.name = nil + name.type = GetToSetMap[name.type] + name.value = exp + name.vstart = exp.start + name.range = exp.finish + exp.parent = name + pushActionIntoCurrentChunk(name) + return name + else + pushActionIntoCurrentChunk(exp) + missName(exp.keyword[2]) + return exp + end + end + + local exp = parseExp(true) + if exp then + local action = compileExpAsAction(exp) + if action then + return action + end + end + return nil, true +end + +local function skipFirstComment() + if Tokens[Index + 1] ~= '#' then + return + end + while true do + Index = Index + 2 + local token = Tokens[Index + 1] + if not token then + break + end + if NLMap[token] then + skipNL() + break + end + end +end + +local function parseLua() + local main = { + type = 'main', + start = 0, + finish = 0, + } + pushChunk(main) + createLocal{ + type = 'local', + start = -1, + finish = -1, + effect = -1, + parent = main, + tag = '_ENV', + special= '_G', + [1] = State.ENVMode, + } + LocalCount = 0 + skipFirstComment() + while true do + parseActions() + if Index <= #Tokens then + unknownSymbol() + Index = Index + 2 + else + break + end + end + popChunk() + main.finish = getPosition(#Lua, 'right') + + return main +end + +local function initState(lua, version, options) + Lua = lua + Line = 0 + LineOffset = 1 + LastTokenFinish = 0 + LocalCount = 0 + Chunk = {} + Tokens = tokens(lua) + Index = 1 + local state = { + version = version, + lua = lua, + ast = {}, + errs = {}, + comms = {}, + lines = { + [0] = 1, + }, + options = options or {}, + } + State = state + if version == 'Lua 5.1' or version == 'LuaJIT' then + state.ENVMode = '@fenv' + else + state.ENVMode = '_ENV' + end + + pushError = function (err) + local errs = state.errs + if err.finish < err.start then + err.finish = err.start + end + local last = errs[#errs] + if last then + if last.start <= err.start and last.finish >= err.finish then + return + end + end + err.level = err.level or 'error' + errs[#errs+1] = err + return err + end + + state.pushError = pushError +end + +return function (lua, mode, version, options) + Mode = mode + initState(lua, version, options) + skipSpace() + if mode == 'Lua' then + State.ast = parseLua() + elseif mode == 'Nil' then + State.ast = parseNil() + elseif mode == 'Boolean' then + State.ast = parseBoolean() + elseif mode == 'String' then + State.ast = parseString() + elseif mode == 'Number' then + State.ast = parseNumber() + elseif mode == 'Exp' then + State.ast = parseExp() + elseif mode == 'Action' then + State.ast = parseAction() + end + State.ast.state = State + + return State +end diff --git a/script/parser/parse.lua b/script/parser/parse.lua index 9b8d5496..e7c7d177 100644 --- a/script/parser/parse.lua +++ b/script/parser/parse.lua @@ -1,6 +1,7 @@ -local ast = require 'parser.ast' +local ast = require 'parser.ast' +local grammar = require 'parser.grammar' -return function (self, lua, mode, version, options) +local function buildState(lua, version, options) local errs = {} local diags = {} local comms = {} @@ -36,9 +37,19 @@ return function (self, lua, mode, version, options) comms[#comms+1] = comment end } + if version == 'Lua 5.1' or version == 'LuaJIT' then + state.ENVMode = '@fenv' + else + state.ENVMode = '_ENV' + end + return state +end + +return function (lua, mode, version, options) + local state = buildState(lua, version, options) local clock = os.clock() ast.init(state) - local suc, res, err = xpcall(self.grammar, debug.traceback, self, lua, mode) + local suc, res, err = xpcall(grammar, debug.traceback, lua, mode) ast.close() if not suc then return nil, res diff --git a/script/parser/tokens.lua b/script/parser/tokens.lua new file mode 100644 index 00000000..958f292e --- /dev/null +++ b/script/parser/tokens.lua @@ -0,0 +1,38 @@ +local m = require 'lpeglabel' + +local Sp = m.S' \t' +local Nl = m.P'\r\n' + m.S'\r\n' +local Number = m.R'09'^1 +local Word = m.R('AZ', 'az', '__', '\x80\xff') * m.R('AZ', 'az', '09', '__', '\x80\xff')^0 +local Symbol = m.P'==' + + m.P'~=' + + m.P'--' + + m.P'<<' + + m.P'>>' + + m.P'<=' + + m.P'>=' + + m.P'//' + + m.P'...' + + m.P'..' + + m.P'::' + -- incorrect + + m.P'!=' + + m.P'&&' + + m.P'||' + + m.P'/*' + + m.P'*/' + + m.P'+=' + + m.P'-=' + + m.P'*=' + + m.P'/=' + -- singles + + m.S'+-*/!#%^&()={}[]|\\\'":;<>,.?~`' +local Unknown = (1 - Number - Word - Symbol - Sp - Nl)^1 +local Token = m.Cp() * m.C(Nl + Number + Word + Symbol + Unknown) + +local Parser = m.Ct((Sp^1 + Token)^0) + +return function (lua) + local results = Parser:match(lua) + return results +end diff --git a/script/proto/converter.lua b/script/proto/converter.lua new file mode 100644 index 00000000..19d592fe --- /dev/null +++ b/script/proto/converter.lua @@ -0,0 +1,168 @@ +local guide = require 'parser.guide' +local files = require 'files' + +local m = {} + +---@alias position {line: integer, character: integer} + +local function rawPackPosition(uri, pos) + local row, col = guide.rowColOf(pos) + if col > 0 then + local state = files.getState(uri) + local text = files.getText(uri) + if text then + local lineOffset = state.lines[row] + col = utf8.len(text, lineOffset, lineOffset + col - 1, true) + end + end + return { + line = row, + character = col, + } +end + +local function diffedPackPosition(uri, pos) + local state = files.getState(uri) + local offset = guide.positionToOffset(state, pos) + local originOffset = files.diffedOffsetBack(uri, offset) + local originLines = files.getOriginLines(uri) + local originPos = guide.offsetToPositionByLines(originLines, originOffset) + local row, col = guide.rowColOf(originPos) + if col > 0 then + local text = files.getOriginText(uri) + if text then + local lineOffset = originLines[row] + col = utf8.len(text, lineOffset, lineOffset + col - 1, true) + end + end + return { + line = row, + character = col, + } +end + +---@param uri uri +---@param pos integer +---@return position +function m.packPosition(uri, pos) + if files.hasDiffed(uri) then + return diffedPackPosition(uri, pos) + else + return rawPackPosition(uri, pos) + end +end + +local function rawUnpackPosition(uri, position) + local row, col = position.line, position.character + if col > 0 then + local state = files.getState(uri) + local text = files.getText(uri) + if text then + local lineOffset = state.lines[row] + local textOffset = utf8.offset(text, col + 1, lineOffset) + if textOffset then + col = textOffset - lineOffset + end + end + end + local pos = guide.positionOf(row, col) + return pos +end + +local function diffedUnpackPosition(uri, position) + local row, col = position.line, position.character + local originLines = files.getOriginLines(uri) + if col > 0 then + local text = files.getOriginText(uri) + if text then + local lineOffset = originLines[row] + local textOffset = utf8.offset(text, col + 1, lineOffset) + if textOffset then + col = textOffset - lineOffset + end + end + end + local state = files.getState(uri) + local originPos = guide.positionOf(row, col) + local originOffset = guide.positionToOffsetByLines(originLines, originPos) + local offset = files.diffedOffset(uri, originOffset) + local pos = guide.offsetToPosition(state, offset) + return pos +end + +---@param uri uri +---@param position position +---@return integer +function m.unpackPosition(uri, position) + if files.hasDiffed(uri) then + return diffedUnpackPosition(uri, position) + else + return rawUnpackPosition(uri, position) + end +end + +---@alias range {start: position, end: position} + +---@param uri uri +---@param start integer +---@param finish integer +---@return range +function m.packRange(uri, start, finish) + local range = { + start = m.packPosition(uri, start), + ['end'] = m.packPosition(uri, finish), + } + return range +end + +---@param uri uri +---@param range range +---@return integer start +---@return integer finish +function m.unpackRange(uri, range) + local start = m.unpackPosition(uri, range.start) + local finish = m.unpackPosition(uri, range['end']) + return start, finish +end + +---@alias location {uri: uri, range: range} + +---@param uri string +---@param range range +---@return location +function m.location(uri, range) + return { + uri = uri, + range = range, + } +end + +---@alias locationLink {targetUri:uri, targetRange: range, targetSelectionRange: range, originSelectionRange: range} + +---@param uri string +---@param range range +---@param selection range +---@param origin range +---@return locationLink +function m.locationLink(uri, range, selection, origin) + return { + targetUri = uri, + targetRange = range, + targetSelectionRange = selection, + originSelectionRange = origin, + } +end + +---@alias textEdit {range: range, newText: string} + +---@param range range +---@param newtext string +---@return textEdit +function m.textEdit(range, newtext) + return { + range = range, + newText = newtext, + } +end + +return m diff --git a/script/proto/define.lua b/script/proto/define.lua index 2e4116fd..fe7d171b 100644 --- a/script/proto/define.lua +++ b/script/proto/define.lua @@ -1,37 +1,5 @@ local m = {} ----@alias location table ----@param uri string ----@param range range ----@return location -function m.location(uri, range) - return { - uri = uri, - range = range, - } -end - ----@alias locationLink table ----@param uri string ----@param range range ----@param selection range ----@param origin range -function m.locationLink(uri, range, selection, origin) - return { - targetUri = uri, - targetRange = range, - targetSelectionRange = selection, - originSelectionRange = origin, - } -end - -function m.textEdit(range, newtext) - return { - range = range, - newText = newtext, - } -end - --- 诊断等级 m.DiagnosticSeverity = { Error = 1, @@ -61,14 +29,14 @@ m.DiagnosticDefaultSeverity = { ['redefined-local'] = 'Hint', ['newline-call'] = 'Information', ['newfield-call'] = 'Warning', - ['redundant-parameter'] = 'Hint', + ['redundant-parameter'] = 'Warning', ['ambiguity-1'] = 'Warning', ['lowercase-global'] = 'Information', ['undefined-env-child'] = 'Information', ['duplicate-index'] = 'Warning', ['duplicate-set-field'] = 'Warning', ['empty-block'] = 'Hint', - ['redundant-value'] = 'Hint', + ['redundant-value'] = 'Warning', ['code-after-break'] = 'Hint', ['unbalanced-assignments'] = 'Warning', ['close-non-object'] = 'Warning', diff --git a/script/proto/proto.lua b/script/proto/proto.lua index a0880011..61306b9f 100644 --- a/script/proto/proto.lua +++ b/script/proto/proto.lua @@ -4,7 +4,6 @@ local await = require 'await' local pub = require 'pub' local jsonrpc = require 'jsonrpc' local define = require 'proto.define' -local timer = require 'timer' local json = require 'json' local reqCounter = util.counter() @@ -121,6 +120,9 @@ function m.doMethod(proto) end await.call(function () --log.debug('Start method:', method) + if proto.id then + await.setID('proto:' .. proto.id) + end local clock = os.clock() local ok = true local res @@ -134,6 +136,7 @@ function m.doMethod(proto) if not proto.id then return end + await.close('proto:' .. proto.id) if ok then m.response(proto.id, res) else diff --git a/script/provider/diagnostic.lua b/script/provider/diagnostic.lua index db377ea0..382b73d9 100644 --- a/script/provider/diagnostic.lua +++ b/script/provider/diagnostic.lua @@ -1,14 +1,15 @@ -local await = require 'await' -local proto = require 'proto.proto' -local define = require 'proto.define' -local lang = require 'language' -local files = require 'files' -local config = require 'config' -local core = require 'core.diagnostics' -local util = require 'utility' -local ws = require 'workspace' -local progress = require "progress" -local client = require 'client' +local await = require 'await' +local proto = require 'proto.proto' +local define = require 'proto.define' +local lang = require 'language' +local files = require 'files' +local config = require 'config' +local core = require 'core.diagnostics' +local util = require 'utility' +local ws = require 'workspace' +local progress = require "progress" +local client = require 'client' +local converter = require 'proto.converter' local m = {} m._start = false @@ -45,17 +46,17 @@ local function buildSyntaxError(uri, err) else rmessage = text:sub(rel.start, rel.finish) end - local relUri = rel.uri + local relUri = rel.uri or uri relatedInformation[#relatedInformation+1] = { message = rmessage, - location = define.location(relUri, files.range(relUri, rel.start, rel.finish)), + location = converter.location(relUri, converter.packRange(relUri, rel.start, rel.finish)), } end end return { code = err.type:lower():gsub('_', '-'), - range = files.range(uri, err.start, err.finish), + range = converter.packRange(uri, err.start, err.finish), severity = define.DiagnosticSeverity.Error, source = lang.script.DIAG_SYNTAX_CHECK, message = message, @@ -75,13 +76,13 @@ local function buildDiagnostic(uri, diag) local rtext = files.getText(rel.uri) relatedInformation[#relatedInformation+1] = { message = rel.message or rtext:sub(rel.start, rel.finish), - location = define.location(rel.uri, files.range(rel.uri, rel.start, rel.finish)) + location = converter.location(rel.uri, converter.packRange(rel.uri, rel.start, rel.finish)) } end end return { - range = files.range(uri, diag.start, diag.finish), + range = converter.packRange(uri, diag.start, diag.finish), source = lang.script.DIAG_DIAGNOSTICS, severity = diag.level, message = diag.message, diff --git a/script/provider/provider.lua b/script/provider/provider.lua index e1bd0f4f..070652ef 100644 --- a/script/provider/provider.lua +++ b/script/provider/provider.lua @@ -13,6 +13,7 @@ local lang = require 'language' local progress = require 'progress' local tm = require 'text-merger' local cfgLoader = require 'config.loader' +local converter = require 'proto.converter' local function updateConfig() local new @@ -165,8 +166,11 @@ proto.on('textDocument/didChange', function (params) end) proto.on('textDocument/hover', function (params) + local doc = params.textDocument + local uri = doc.uri await.close 'hover' await.setID 'hover' + await.setID('update:' .. uri) if not workspace.isReady() then local count, max = workspace.getLoadProcess() return { @@ -178,13 +182,11 @@ proto.on('textDocument/hover', function (params) end local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_HOVER, 0.5) local core = require 'core.hover' - local doc = params.textDocument - local uri = doc.uri if not files.exists(uri) then return nil end - local offset = files.offsetOfWord(uri, params.position) - local hover, source = core.byUri(uri, offset) + local pos = converter.unpackPosition(uri, params.position) + local hover, source = core.byUri(uri, pos) if not hover then return nil end @@ -193,7 +195,7 @@ proto.on('textDocument/hover', function (params) value = tostring(hover), kind = 'markdown', }, - range = files.range(uri, source.start, source.finish), + range = converter.packRange(uri, source.start, source.finish), } end) @@ -205,8 +207,8 @@ proto.on('textDocument/definition', function (params) if not files.exists(uri) then return nil end - local offset = files.offsetOfWord(uri, params.position) - local result = core(uri, offset) + local pos = converter.unpackPosition(uri, params.position) + local result = core(uri, pos) if not result then return nil end @@ -216,14 +218,14 @@ proto.on('textDocument/definition', function (params) if targetUri then if files.exists(targetUri) then if client.getAbility 'textDocument.definition.linkSupport' then - response[i] = define.locationLink(targetUri - , files.range(targetUri, info.target.start, info.target.finish) - , files.range(targetUri, info.target.start, info.target.finish) - , files.range(uri, info.source.start, info.source.finish) + response[i] = converter.locationLink(targetUri + , converter.packRange(targetUri, info.target.start, info.target.finish) + , converter.packRange(targetUri, info.target.start, info.target.finish) + , converter.packRange(uri, info.source.start, info.source.finish) ) else - response[i] = define.location(targetUri - , files.range(targetUri, info.target.start, info.target.finish) + response[i] = converter.location(targetUri + , converter.packRange(targetUri, info.target.start, info.target.finish) ) end end @@ -240,8 +242,8 @@ proto.on('textDocument/typeDefinition', function (params) if not files.exists(uri) then return nil end - local offset = files.offsetOfWord(uri, params.position) - local result = core(uri, offset) + local pos = converter.unpackPosition(uri, params.position) + local result = core(uri, pos) if not result then return nil end @@ -251,14 +253,14 @@ proto.on('textDocument/typeDefinition', function (params) if targetUri then if files.exists(targetUri) then if client.getAbility 'textDocument.typeDefinition.linkSupport' then - response[i] = define.locationLink(targetUri - , files.range(targetUri, info.target.start, info.target.finish) - , files.range(targetUri, info.target.start, info.target.finish) - , files.range(uri, info.source.start, info.source.finish) + response[i] = converter.locationLink(targetUri + , converter.packRange(targetUri, info.target.start, info.target.finish) + , converter.packRange(targetUri, info.target.start, info.target.finish) + , converter.packRange(uri, info.source.start, info.source.finish) ) else - response[i] = define.location(targetUri - , files.range(targetUri, info.target.start, info.target.finish) + response[i] = converter.location(targetUri + , converter.packRange(targetUri, info.target.start, info.target.finish) ) end end @@ -275,16 +277,16 @@ proto.on('textDocument/references', function (params) if not files.exists(uri) then return nil end - local offset = files.offsetOfWord(uri, params.position) - local result = core(uri, offset) + local pos = converter.unpackPosition(uri, params.position) + local result = core(uri, pos) if not result then return nil end local response = {} for i, info in ipairs(result) do local targetUri = info.uri - response[i] = define.location(targetUri - , files.range(targetUri, info.target.start, info.target.finish) + response[i] = converter.location(targetUri + , converter.packRange(targetUri, info.target.start, info.target.finish) ) end return response @@ -296,15 +298,15 @@ proto.on('textDocument/documentHighlight', function (params) if not files.exists(uri) then return nil end - local offset = files.offsetOfWord(uri, params.position) - local result = core(uri, offset) + local pos = converter.unpackPosition(uri, params.position) + local result = core(uri, pos) if not result then return nil end local response = {} for _, info in ipairs(result) do response[#response+1] = { - range = files.range(uri, info.start, info.finish), + range = converter.packRange(uri, info.start, info.finish), kind = info.kind, } end @@ -319,8 +321,8 @@ proto.on('textDocument/rename', function (params) if not files.exists(uri) then return nil end - local offset = files.offsetOfWord(uri, params.position) - local result = core.rename(uri, offset, params.newName) + local pos = converter.unpackPosition(uri, params.position) + local result = core.rename(uri, pos, params.newName) if not result then return nil end @@ -332,7 +334,7 @@ proto.on('textDocument/rename', function (params) if not workspaceEdit.changes[ruri] then workspaceEdit.changes[ruri] = {} end - local textEdit = define.textEdit(files.range(ruri, info.start, info.finish), info.text) + local textEdit = converter.textEdit(converter.packRange(ruri, info.start, info.finish), info.text) workspaceEdit.changes[ruri][#workspaceEdit.changes[ruri]+1] = textEdit end return workspaceEdit @@ -344,20 +346,22 @@ proto.on('textDocument/prepareRename', function (params) if not files.exists(uri) then return nil end - local offset = files.offsetOfWord(uri, params.position) - local result = core.prepareRename(uri, offset) + local pos = converter.unpackPosition(uri, params.position) + local result = core.prepareRename(uri, pos) if not result then return nil end return { - range = files.range(uri, result.start, result.finish), + range = converter.packRange(uri, result.start, result.finish), placeholder = result.text, } end) proto.on('textDocument/completion', function (params) + local uri = params.textDocument.uri await.close 'completion' await.setID 'completion' + await.setID('update:' .. uri) if not workspace.isReady() then local count, max = workspace.getLoadProcess() return { @@ -377,7 +381,6 @@ proto.on('textDocument/completion', function (params) local core = require 'core.completion' --log.debug('textDocument/completion') --log.debug('completion:', params.context and params.context.triggerKind, params.context and params.context.triggerCharacter) - local uri = params.textDocument.uri if not files.exists(uri) then return nil end @@ -391,8 +394,8 @@ proto.on('textDocument/completion', function (params) end await.setPriority(1000) local clock = os.clock() - local offset = files.offset(uri, params.position) - local result = core.completion(uri, offset - 1, triggerCharacter) + local pos = converter.unpackPosition(uri, params.position) + local result = core.completion(uri, pos, triggerCharacter) local passed = os.clock() - clock if passed > 0.1 then log.warn(('Completion takes %.3f sec.'):format(passed)) @@ -417,7 +420,7 @@ proto.on('textDocument/completion', function (params) commitCharacters = res.commitCharacters, command = res.command, textEdit = res.textEdit and { - range = files.range( + range = converter.packRange( uri, res.textEdit.start, res.textEdit.finish @@ -428,7 +431,7 @@ proto.on('textDocument/completion', function (params) local t = {} for j, edit in ipairs(res.additionalTextEdits) do t[j] = { - range = files.range( + range = converter.packRange( uri, edit.start, edit.finish @@ -474,10 +477,11 @@ proto.on('completionItem/resolve', function (item) if not item.data then return item end - await.close 'completion.resolve' - await.setID 'completion.resolve' local id = item.data.id local uri = item.data.uri + await.close 'completion.resolve' + await.setID 'completion.resolve' + await.setID('update:' .. uri) --await.setPriority(1000) local resolved = core.resolve(id) if not resolved then @@ -492,7 +496,7 @@ proto.on('completionItem/resolve', function (item) local t = {} for j, edit in ipairs(resolved.additionalTextEdits) do t[j] = { - range = files.range( + range = converter.packRange( uri, edit.start, edit.finish @@ -517,9 +521,10 @@ proto.on('textDocument/signatureHelp', function (params) end await.close('signatureHelp') await.setID('signatureHelp') - local offset = files.offset(uri, params.position) + await.setID('update:' .. uri) + local pos = converter.unpackPosition(uri, params.position) local core = require 'core.signature' - local results = core(uri, offset - 1) + local results = core(uri, pos) if not results then return nil end @@ -529,7 +534,7 @@ proto.on('textDocument/signatureHelp', function (params) for j, param in ipairs(result.params) do parameters[j] = { label = { - param.label[1] - 1, + param.label[1], param.label[2], } } @@ -562,12 +567,12 @@ proto.on('textDocument/documentSymbol', function (params) local function convert(symbol) await.delay() - symbol.range = files.range( + symbol.range = converter.packRange( uri, symbol.range[1], symbol.range[2] ) - symbol.selectionRange = files.range( + symbol.selectionRange = converter.packRange( uri, symbol.selectionRange[1], symbol.selectionRange[2] @@ -599,7 +604,7 @@ proto.on('textDocument/codeAction', function (params) return nil end - local start, finish = files.unrange(uri, range) + local start, finish = converter.unpackRange(uri, range) local results = core(uri, start, finish, diagnostics) if not results or #results == 0 then @@ -610,7 +615,7 @@ proto.on('textDocument/codeAction', function (params) if res.edit then for turi, changes in pairs(res.edit.changes) do for _, change in ipairs(changes) do - change.range = files.range(turi, change.start, change.finish) + change.range = converter.packRange(turi, change.start, change.finish) change.start = nil change.finish = nil end @@ -648,6 +653,7 @@ proto.on('workspace/symbol', function (params) await.close('workspace/symbol') await.setID('workspace/symbol') + await.setID('files.version') local symbols = core(params.query) if not symbols or #symbols == 0 then @@ -655,9 +661,9 @@ proto.on('workspace/symbol', function (params) end local function convert(symbol) - symbol.location = define.location( + symbol.location = converter.location( symbol.uri, - files.range( + converter.packRange( symbol.uri, symbol.range[1], symbol.range[2] @@ -675,12 +681,13 @@ end) proto.on('textDocument/semanticTokens/full', function (params) - workspace.awaitReady() + local uri = params.textDocument.uri await.close('textDocument/semanticTokens/full') await.setID('textDocument/semanticTokens/full') + await.setID('update:' .. uri) + workspace.awaitReady() local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SEMANTIC_FULL, 0.5) local core = require 'core.semantic-tokens' - local uri = params.textDocument.uri local text = files.getText(uri) if not text then return nil @@ -692,12 +699,13 @@ proto.on('textDocument/semanticTokens/full', function (params) end) proto.on('textDocument/semanticTokens/range', function (params) + local uri = params.textDocument.uri + await.close('textDocument/semanticTokens/range') + await.setID('textDocument/semanticTokens/range') + await.setID('update:' .. uri) workspace.awaitReady() - --await.close('textDocument/semanticTokens/range') - --await.setID('textDocument/semanticTokens/range') local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SEMANTIC_RANGE, 0.5) local core = require 'core.semantic-tokens' - local uri = params.textDocument.uri local cache = files.getOpenedCache(uri) local start, finish if cache and not cache['firstSemantic'] then @@ -705,8 +713,7 @@ proto.on('textDocument/semanticTokens/range', function (params) start = 0 finish = #files.getText(uri) else - start = files.offsetOfWord(uri, params.range.start) - finish = files.offsetOfWord(uri, params.range['end']) + start, finish = converter.unpackRange(uri, params.range) end local results = core(uri, start, finish) return { @@ -722,6 +729,7 @@ proto.on('textDocument/foldingRange', function (params) end await.close('textDocument/foldingRange') await.setID('textDocument/foldingRange') + await.setID('update:' .. uri) local regions = core(uri) if not regions then return nil @@ -729,8 +737,8 @@ proto.on('textDocument/foldingRange', function (params) local results = {} for _, region in ipairs(regions) do - local startLine = files.position(uri, region.start, 'left').line - local endLine = files.position(uri, region.finish, 'right').line + local startLine = converter.packPosition(uri, region.start).line + local endLine = converter.packPosition(uri, region.finish).line if not region.hideLastLine then endLine = endLine - 1 end @@ -778,8 +786,8 @@ proto.on('textDocument/onTypeFormatting', function (params) return nil end local core = require 'core.type-formatting' - local offset = files.offset(uri, params.position) - local edits = core(uri, offset - 1, ch) + local pos = converter.unpackPosition(uri, params.position) + local edits = core(uri, pos, ch) if not edits or #edits == 0 then return nil end @@ -790,13 +798,17 @@ proto.on('textDocument/onTypeFormatting', function (params) local results = {} for i, edit in ipairs(edits) do results[i] = { - range = files.range(uri, edit.start, edit.finish), + range = converter.packRange(uri, edit.start, edit.finish), newText = edit.text:gsub('\t', tab), } end return results end) +proto.on('$/cancelRequest', function (params) + await.close('proto:' .. params.id) +end) + proto.on('$/requestHint', function (params) local core = require 'core.hint' if not config.get 'Lua.hint.enable' then @@ -804,13 +816,13 @@ proto.on('$/requestHint', function (params) end workspace.awaitReady() local uri = params.textDocument.uri - local start, finish = files.unrange(uri, params.range) + local start, finish = converter.unpackRange(uri, params.range) local results = core(uri, start, finish) local hintResults = {} for i, res in ipairs(results) do hintResults[i] = { text = res.text, - pos = files.position(uri, res.offset, res.where), + pos = converter.packPosition(uri, res.offset), kind = res.kind, } end @@ -826,6 +838,7 @@ do return end await.setID(awaitID) + await.setID('update:' .. uri) workspace.awaitReady() local visibles = files.getVisibles(uri) if not visibles then @@ -840,7 +853,7 @@ do for _, edit in ipairs(piece) do edits[#edits+1] = { text = edit.text, - pos = files.position(uri, edit.offset, edit.where), + pos = converter.packPosition(uri, edit.offset), } end end diff --git a/script/vm/getDocs.lua b/script/vm/getDocs.lua index c0205654..0c6b1695 100644 --- a/script/vm/getDocs.lua +++ b/script/vm/getDocs.lua @@ -177,7 +177,6 @@ function vm.isDeprecated(value, deep) end local function makeDiagRange(uri, doc, results) - local lines = files.getLines(uri) local names if doc.names then names = {} @@ -186,57 +185,51 @@ local function makeDiagRange(uri, doc, results) names[name] = true end end - local row = guide.positionOf(lines, doc.start) + local row = guide.rowColOf(doc.start) if doc.mode == 'disable-next-line' then - if lines[row+1] then - results[#results+1] = { - mode = 'disable', - names = names, - offset = lines[row+1].start, - source = doc, - } - results[#results+1] = { - mode = 'enable', - names = names, - offset = lines[row+1].finish, - source = doc, - } - end + results[#results+1] = { + mode = 'disable', + names = names, + row = row + 1, + source = doc, + } + results[#results+1] = { + mode = 'enable', + names = names, + row = row + 2, + source = doc, + } elseif doc.mode == 'disable-line' then results[#results+1] = { mode = 'disable', names = names, - offset = lines[row].start, + row = row, source = doc, } results[#results+1] = { mode = 'enable', names = names, - offset = lines[row].finish, + row = row + 1, source = doc, } elseif doc.mode == 'disable' then - if lines[row+1] then - results[#results+1] = { - mode = 'disable', - names = names, - offset = lines[row+1].start, - source = doc, - } - end + results[#results+1] = { + mode = 'disable', + names = names, + row = row + 1, + source = doc, + } elseif doc.mode == 'enable' then - if lines[row+1] then - results[#results+1] = { - mode = 'enable', - names = names, - offset = lines[row+1].start, - source = doc, - } - end + results[#results+1] = { + mode = 'enable', + names = names, + row = row + 1, + source = doc, + } end end -function vm.isDiagDisabledAt(uri, offset, name) +function vm.isDiagDisabledAt(uri, position, name) local status = files.getState(uri) if not status then return false @@ -253,29 +246,26 @@ function vm.isDiagDisabledAt(uri, offset, name) end end table.sort(cache.diagnosticRanges, function (a, b) - return a.offset < b.offset + return a.row < b.row end) end if #cache.diagnosticRanges == 0 then return false end - local stack = {} + local myRow = guide.rowColOf(position) + local count = 0 for _, range in ipairs(cache.diagnosticRanges) do - if range.offset <= offset then + if range.row <= myRow then if not range.names or range.names[name] then if range.mode == 'disable' then - stack[#stack+1] = range + count = count + 1 elseif range.mode == 'enable' then - stack[#stack] = nil + count = count - 1 end end else break end end - local current = stack[#stack] - if not current then - return false - end - return true + return count > 0 end diff --git a/script/vm/getLinks.lua b/script/vm/getLinks.lua index 161396f6..d2332504 100644 --- a/script/vm/getLinks.lua +++ b/script/vm/getLinks.lua @@ -6,12 +6,12 @@ local files = require 'files' local function getFileLinks(uri) local ws = require 'workspace' local links = {} - local ast = files.getState(uri) - if not ast then + local state = files.getState(uri) + if not state then return links end tracy.ZoneBeginN('getFileLinks') - guide.eachSpecialOf(ast.ast, 'require', function (source) + guide.eachSpecialOf(state.ast, 'require', function (source) local call = source.parent if not call or call.type ~= 'call' then return diff --git a/test/basic/noder.lua b/test/basic/noder.lua index bc572a4c..87b34e12 100644 --- a/test/basic/noder.lua +++ b/test/basic/noder.lua @@ -2,6 +2,7 @@ local noder = require 'core.noder' local files = require 'files' local util = require 'utility' local guide = require 'parser.guide' +local catch = require 'catch' local function getSource(pos) local ast = files.getState('') @@ -30,12 +31,9 @@ local CARE = {} local function TEST(script) return function (expect) files.removeAll() - local start = script:find('<?', 1, true) - local finish = script:find('?>', 1, true) - local pos = (start + finish) // 2 + 1 - local newScript = script:gsub('<[!?]', ' '):gsub('[!?]>', ' ') + local newScript, catched = catch(script, '?') files.setText('', newScript) - local source = getSource(pos) + local source = getSource(catched['?'][1][1]) assert(source) local result = { id = noder.getID(source), @@ -53,21 +51,21 @@ CARE['id'] = true TEST [[ local <?x?> ]] { - id = 'l:9', + id = 'l:6', } TEST [[ local x print(<?x?>) ]] { - id = 'l:7', + id = 'l:6', } TEST [[ local x <?x?> = 1 ]] { - id = 'l:7', + id = 'l:6', } TEST [[ @@ -86,14 +84,14 @@ TEST [[ local x print(x.y.<?z?>) ]] { - id = 'l:7|.y|.z', + id = 'l:6|.y|.z', } TEST [[ local x function x:<?f?>() end ]] { - id = 'l:7|.f', + id = 'l:6|.f', } TEST [[ @@ -113,7 +111,7 @@ TEST [[ <?x?> = 1, } ]] { - id = 't:1|.x', + id = 't:0|.x', } TEST [[ @@ -134,12 +132,12 @@ TEST [[ ::<?label?>:: goto label ]] { - id = 'l:5', + id = 'l:2', } TEST [[ ::label:: goto <?label?> ]] { - id = 'l:3', + id = 'l:2', } diff --git a/test/catch.lua b/test/catch.lua new file mode 100644 index 00000000..849be09d --- /dev/null +++ b/test/catch.lua @@ -0,0 +1,84 @@ +local m = require 'lpeglabel' + +local mt = {} + +local function catchedTable() + return setmetatable({}, mt) +end + +function mt.__add(a, b) + if not a or not b then + return a or b + end + local t = catchedTable() + for _, v in ipairs(a) do + t[#t+1] = v + end + for _, v in ipairs(b) do + t[#t+1] = v + end + return t +end + +local function parseTokens(script, seps) + local parser = m.P { + m.Ct(m.V 'Token'^0), + Token = m.Cp() * (m.V 'Mark' + m.V 'Nl' + m.V 'Text'), + Mark = m.Cc 'ML' * m.P '<' * m.C(m.S(seps)) + + m.Cc 'MR' * m.C(m.S(seps)) * m.P '>', + Nl = m.Cc 'NL' * m.C(m.P '\r\n' + m.S '\r\n'), + Text = m.Cc 'TX' * m.C((1 - m.V 'Nl' - m.V 'Mark')^1), + } + local results = parser:match(script) + return results +end + +---@param script string +---@param seps string +return function (script, seps) + local tokens = parseTokens(script, seps) + local newBuf = {} + local result = {} + local marks = {} + + local lineOffset = 1 + local line = 0 + local skipOffset = 0 + for i = 1, #tokens, 3 do + local offset = tokens[i + 0] + local mode = tokens[i + 1] + local text = tokens[i + 2] + if mode == 'TX' then + newBuf[#newBuf+1] = text + end + if mode == 'NL' then + newBuf[#newBuf+1] = text + line = line + 1 + lineOffset = offset + #text - skipOffset + end + if mode == 'ML' then + marks[#marks+1] = { + char = text, + position = line * 10000 + offset - skipOffset - lineOffset, + } + skipOffset = skipOffset + 1 + #text + end + if mode == 'MR' then + for j = #marks, 1, -1 do + local mark = marks[j] + if mark.char == text then + local position = line * 10000 + offset - skipOffset - lineOffset + if not result[text] then + result[text] = catchedTable() + end + result[text][#result[text]+1] = { mark.position, position } + table.remove(marks, j) + break + end + end + skipOffset = skipOffset + 1 + #text + end + end + + return table.concat(newBuf), result +end diff --git a/test/code_action/init.lua b/test/code_action/init.lua index 11ce6ec7..70d8c9ec 100644 --- a/test/code_action/init.lua +++ b/test/code_action/init.lua @@ -1,6 +1,7 @@ local core = require 'core.code-action' local files = require 'files' local lang = require 'language' +local catch = require 'catch' rawset(_G, 'TEST', true) @@ -38,11 +39,10 @@ end function TEST(script) return function (expect) files.removeAll() - local start = script:find('<?', 1, true) - local finish = script:find('?>', 1, true) - local new_script = script:gsub('<[!?]', ' '):gsub('[!?]>', ' ') - files.setText('', new_script) - local results = core('', start, finish) + + local newScript, catched = catch(script, '?') + files.setText('', newScript) + local results = core('', catched['?'][1][1], catched['?'][1][2]) assert(results) assert(eq(expect, results)) end diff --git a/test/command/auto-require.lua b/test/command/auto-require.lua index 94bb5069..a52662fb 100644 --- a/test/command/auto-require.lua +++ b/test/command/auto-require.lua @@ -3,7 +3,7 @@ local files = require 'files' local autoRequire = require 'core.command.autoRequire' local client = require 'client' -local findInsertOffset = util.getUpvalue(autoRequire, 'findInsertOffset') +local findInsertRow = util.getUpvalue(autoRequire, 'findInsertRow') local applyAutoRequire = util.getUpvalue(autoRequire, 'applyAutoRequire') local originEditText = client.editText @@ -19,18 +19,25 @@ function TEST(text) files.removeAll() files.setText('', text) EditResult = nil - local offset, fmt = findInsertOffset('') - applyAutoRequire('', offset, name, name, fmt) + local row, fmt = findInsertRow('') + applyAutoRequire('', row, name, name, fmt) assert(util.equal(EditResult, expect)) end end end --- TODO change to position TEST '' 'test' { start = 0, - finish = -1, - text = '\nlocal test = require "test"\n' + finish = 0, + text = 'local test = require "test"\n' +} + +TEST [[ +local aaaaaa = require 'aaa' +]] 'test' { + start = 10000, + finish = 10000, + text = 'local test = require \'test\'\n' } client.editText = originEditText diff --git a/test/completion/common.lua b/test/completion/common.lua index 2efa63ad..33cbdf4d 100644 --- a/test/completion/common.lua +++ b/test/completion/common.lua @@ -8,7 +8,7 @@ config.set('Lua.completion.showWord', 'Enable') TEST [[ local zabcde -za$ +za<??> ]] { { @@ -20,7 +20,7 @@ za$ TEST [[ local zabcdefg local zabcde -zabcde$ +zabcde<??> ]] { { @@ -35,7 +35,7 @@ zabcde$ TEST [[ local zabcdefg -za$ +za<??> local zabcde ]] { @@ -51,7 +51,7 @@ local zabcde TEST [[ local zabcde -zace$ +zace<??> ]] { { @@ -63,7 +63,7 @@ zace$ TEST [[ ZABC = x local zabc -zac$ +zac<??> ]] { { @@ -77,7 +77,7 @@ zac$ } TEST [[ -ass$ +ass<??> ]] { { @@ -92,7 +92,7 @@ ass$ TEST [[ local assert = 1 -ass$ +ass<??> ]] { { @@ -103,7 +103,7 @@ ass$ TEST [[ local assert = 1 -_G.ass$ +_G.ass<??> ]] { { @@ -119,7 +119,7 @@ _G.ass$ TEST [[ local function ffff(a, b) end -ff$ +ff<??> ]] { { @@ -134,7 +134,7 @@ ff$ TEST [[ local zabc = 1 -z$ +z<??> ]] { { @@ -145,7 +145,7 @@ z$ TEST [[ local zabc = 1.0 -z$ +z<??> ]] { { @@ -158,7 +158,7 @@ TEST [[ local t = { abc = 1, } -t.ab$ +t.ab<??> ]] { { @@ -172,7 +172,7 @@ local t = { abc = 1, } local n = t.abc -t.ab$ +t.ab<??> ]] { { @@ -187,7 +187,7 @@ mt.ggg = 1 function mt:get(a, b) return 1 end -mt:g$ +mt:g<??> ]] { { @@ -205,7 +205,7 @@ mt:g$ } TEST [[ -loc$ +loc<??> ]] { { @@ -220,7 +220,7 @@ loc$ IgnoreFunction = true TEST [[ -do$ +do<??> ]] { { @@ -234,7 +234,7 @@ do$ } TEST [[ -while true d$ +while true d<??> ]] { { @@ -248,18 +248,18 @@ while true d$ } TEST [[ -results$ +results<??> ]] (nil) TEST [[ -result$ +result<??> local results ]] (EXISTS) TEST [[ -local a$ +local a<??> local function f(fff) fff = ast @@ -280,7 +280,7 @@ end TEST [[ t.a = {} t.b = {} -t.$ +t.<??> ]] { { @@ -296,7 +296,7 @@ t.$ TEST [[ t.a = {} t.b = {} -t. $ +t. <??> ]] { { @@ -314,7 +314,7 @@ TEST [[ t.a = {} function t:b() end -t:$ +t:<??> ]] { { @@ -331,7 +331,7 @@ TEST [[ local t = { a = {}, } -t.$ +t.<??> xxx() ]] { @@ -342,14 +342,14 @@ xxx() } TEST [[ -(''):$ +(''):<??> ]] (EXISTS) TEST [[ local zzz -return 'aa' .. zz$ +return 'aa' .. zz<??> ]] { { @@ -358,9 +358,9 @@ return 'aa' .. zz$ }, } -TEST 'local s = "a:$"' (nil) +TEST 'local s = "a:<??>"' (nil) -TEST 'debug.$' +TEST 'debug.<??>' (EXISTS) IgnoreFunction = true @@ -371,7 +371,7 @@ local xxxx = { } local t = { - x$ + x<??> } ]] { @@ -392,7 +392,7 @@ local t = { TEST [[ print(ff2) local faa -local f$ +local f<??> print(fff) ]] { @@ -419,7 +419,7 @@ print(fff) } TEST [[ -local function f(ff$) +local function f(ff<??>) print(fff) end ]] @@ -431,19 +431,19 @@ end } TEST [[ -collectgarbage($) +collectgarbage(<??>) ]] (EXISTS) TEST [[ -collectgarbage('$') +collectgarbage('<??>') ]] { { label = "'collect'", kind = define.CompletionItemKind.EnumMember, textEdit = { - start = 16, + start = 15, finish = 17, newText = "'collect'", }, @@ -452,7 +452,7 @@ collectgarbage('$') label = "'stop'", kind = define.CompletionItemKind.EnumMember, textEdit = { - start = 16, + start = 15, finish = 17, newText = "'stop'", }, @@ -461,7 +461,7 @@ collectgarbage('$') label = "'restart'", kind = define.CompletionItemKind.EnumMember, textEdit = { - start = 16, + start = 15, finish = 17, newText = "'restart'", }, @@ -470,7 +470,7 @@ collectgarbage('$') label = "'count'", kind = define.CompletionItemKind.EnumMember, textEdit = { - start = 16, + start = 15, finish = 17, newText = "'count'", }, @@ -479,7 +479,7 @@ collectgarbage('$') label = "'step'", kind = define.CompletionItemKind.EnumMember, textEdit = { - start = 16, + start = 15, finish = 17, newText = "'step'", }, @@ -488,7 +488,7 @@ collectgarbage('$') label = "'isrunning'", kind = define.CompletionItemKind.EnumMember, textEdit = { - start = 16, + start = 15, finish = 17, newText = "'isrunning'", }, @@ -497,7 +497,7 @@ collectgarbage('$') label = "'incremental'", kind = define.CompletionItemKind.EnumMember, textEdit = { - start = 16, + start = 15, finish = 17, newText = "'incremental'", }, @@ -506,7 +506,7 @@ collectgarbage('$') label = "'generational'", kind = define.CompletionItemKind.EnumMember, textEdit = { - start = 16, + start = 15, finish = 17, newText = "'generational'", }, @@ -514,7 +514,7 @@ collectgarbage('$') } TEST [[ -io.read($) +io.read(<??>) ]] { { @@ -536,25 +536,25 @@ io.read($) } TEST [[ -io.open('', $) +io.open('', <??>) ]] (EXISTS) TEST [[ -local function f(a, $) +local function f(a, <??>) end ]] (nil) TEST [[ -self.results.list[#$] +self.results.list[#<??>] ]] { { label = '#self.results.list+1', kind = define.CompletionItemKind.Snippet, textEdit = { - start = 19, + start = 18, finish = 20, newText = '#self.results.list+1] = ', }, @@ -562,7 +562,7 @@ self.results.list[#$] } TEST [[ -self.results.list[#$] +self.results.list[#<??>] local n = 1 ]] { @@ -570,7 +570,7 @@ local n = 1 label = '#self.results.list+1', kind = define.CompletionItemKind.Snippet, textEdit = { - start = 19, + start = 18, finish = 20, newText = '#self.results.list+1] = ', }, @@ -578,14 +578,14 @@ local n = 1 } TEST [[ -self.results.list[#$] = 1 +self.results.list[#<??>] = 1 ]] { { label = '#self.results.list+1', kind = define.CompletionItemKind.Snippet, textEdit = { - start = 19, + start = 18, finish = 20, newText = '#self.results.list+1]', }, @@ -593,14 +593,14 @@ self.results.list[#$] = 1 } TEST [[ -self.results.list[#self.re$] +self.results.list[#self.re<??>] ]] { { label = '#self.results.list+1', kind = define.CompletionItemKind.Snippet, textEdit = { - start = 19, + start = 18, finish = 27, newText = '#self.results.list+1] = ', }, @@ -612,14 +612,14 @@ self.results.list[#self.re$] } TEST [[ -fff[#ff$] +fff[#ff<??>] ]] { { label = '#fff+1', kind = define.CompletionItemKind.Snippet, textEdit = { - start = 5, + start = 4, finish = 8, newText = '#fff+1] = ', }, @@ -631,14 +631,14 @@ fff[#ff$] } TEST [[ -local _ = fff.kkk[#$] +local _ = fff.kkk[#<??>] ]] { { label = '#fff.kkk', kind = define.CompletionItemKind.Snippet, textEdit = { - start = 19, + start = 18, finish = 20, newText = '#fff.kkk]', }, @@ -646,14 +646,14 @@ local _ = fff.kkk[#$] } TEST [[ -fff.kkk[#$].yy +fff.kkk[#<??>].yy ]] { { label = '#fff.kkk', kind = define.CompletionItemKind.Snippet, textEdit = { - start = 9, + start = 8, finish = 10, newText = '#fff.kkk]', }, @@ -665,7 +665,7 @@ local t = { a = 1, } -t . $ +t . <??> ]] (EXISTS) @@ -674,7 +674,7 @@ local t = { a = 1, } -t . $ b +t . <??> b ]] (EXISTS) @@ -683,7 +683,7 @@ local t = { a = 1, } -t $ +t <??> ]] (nil) @@ -692,13 +692,13 @@ local t = { a = 1, } -t $. +t <??>. ]] (nil) TEST [[ local xxxx -xxxx$ +xxxx<??> ]] { { @@ -710,7 +710,7 @@ xxxx$ TEST [[ local xxxx local XXXX -xxxx$ +xxxx<??> ]] { { @@ -727,7 +727,7 @@ TEST [[ local t = { xxxxx = 1, } -xx$ +xx<??> ]] { { @@ -738,7 +738,7 @@ xx$ TEST [[ local index -tbl[inde$] +tbl[inde<??>] ]] { { @@ -753,7 +753,7 @@ return function () a = {}, b = {}, } - t.$ + t.<??> end ]] { @@ -769,7 +769,7 @@ end TEST [[ local ast = 1 -local t = 'as$' +local t = 'as<??>' local ask = 1 ]] (EXISTS) @@ -777,7 +777,7 @@ local ask = 1 TEST [[ local add -function f(ad$) +function f(ad<??>) local _ = add end ]] @@ -789,25 +789,25 @@ end } TEST [[ -function table.i$ +function table.i<??> ]] (EXISTS) TEST [[ do - xx.$ + xx.<??> end ]] (nil) TEST [[ -print(io.$) +print(io.<??>) ]] (EXISTS) require 'config'.set('Lua.runtime.version', 'Lua 5.4') --TEST [[ ---local $ +--local <??> --]] --{ -- { @@ -821,7 +821,7 @@ require 'config'.set('Lua.runtime.version', 'Lua 5.4') --} -- --TEST [[ ---local <toc$ +--local <toc<??> --]] --{ -- { @@ -835,7 +835,7 @@ local mt = {} mt.__index = mt local t = setmetatable({}, mt) -t.$ +t.<??> ]] { { @@ -848,7 +848,7 @@ TEST [[ local elseaaa ELSE = 1 if a then -else$ +else<??> ]] { { @@ -877,7 +877,7 @@ Cared['insertText'] = true IgnoreFunction = false TEST [[ local xpcal -xpcal$ +xpcal<??> ]] { { @@ -900,7 +900,7 @@ TEST [[ function mt:f(a, b, c) end -mt:f$ +mt:f<??> ]] { { @@ -916,7 +916,7 @@ mt:f$ } TEST [[ -function$ +function<??> ]] { { @@ -934,7 +934,7 @@ end", } TEST [[ -local t = function$ +local t = function<??> ]] { { @@ -956,7 +956,7 @@ IgnoreFunction = true TEST [[ local function f() if a then - else$ + else<??> end ]] { @@ -979,21 +979,21 @@ local t = { ['a.b.c'] = {} } -t.$ +t.<??> ]] { { label = 'a.b.c', kind = define.CompletionItemKind.Field, textEdit = { - start = 37, - finish = 36, + start = 40002, + finish = 40002, newText = '["a.b.c"]', }, additionalTextEdits = { { - start = 36, - finish = 36, + start = 40001, + finish = 40002, newText = '', }, }, @@ -1005,21 +1005,21 @@ local t = { ['a.b.c'] = {} } -t. $ +t. <??> ]] { { label = 'a.b.c', kind = define.CompletionItemKind.Field, textEdit = { - start = 40, - finish = 39, + start = 40005, + finish = 40005, newText = '["a.b.c"]', }, additionalTextEdits = { { - start = 36, - finish = 36, + start = 40001, + finish = 40002, newText = '', }, }, @@ -1031,15 +1031,15 @@ local t = { ['a.b.c'] = {} } -t['$'] +t['<??>'] ]] { { label = 'a.b.c', kind = define.CompletionItemKind.Field, textEdit = { - start = 38, - finish = 37, + start = 40003, + finish = 40003, newText = 'a.b.c', } } @@ -1048,33 +1048,33 @@ t['$'] TEST [[ _ENV['z.b.c'] = {} -z$ +z<??> ]] { { label = 'z.b.c', kind = define.CompletionItemKind.Field, textEdit = { - start = 21, - finish = 21, + start = 20000, + finish = 20001, newText = '_ENV["z.b.c"]', }, }, } TEST [[ -io.close(1, $) +io.close(1, <??>) ]] (nil) TEST [[ -io$ +io<??> ]] (EXISTS) IgnoreFunction = false TEST [[ -loadstring$ +loadstring<??> ]] { { @@ -1090,7 +1090,7 @@ loadstring$ } --TEST [[ ---bit32$ +--bit32<??> --]] --{ -- { @@ -1103,7 +1103,7 @@ loadstring$ TEST [[ function loadstring() end -loadstring$ +loadstring<??> ]] { { @@ -1127,7 +1127,7 @@ loadstring$ } TEST [[ -debug.setcsta$ +debug.setcsta<??> ]] { { @@ -1143,12 +1143,12 @@ debug.setcsta$ } TEST [[ ----@$ +---@<??> ]] (EXISTS) TEST [[ ----@cl$ +---@cl<??> ]] { { @@ -1159,7 +1159,7 @@ TEST [[ TEST [[ ---@class ZABC ----@class ZBBC : Z$ +---@class ZBBC : Z<??> ]] { { @@ -1170,14 +1170,14 @@ TEST [[ TEST [[ ---@class ZABC ----@class ZBBC : $ +---@class ZBBC : <??> ]] (EXISTS) TEST [[ ---@class zabc local abcd ----@type za$ +---@type za<??> ]] { { @@ -1189,14 +1189,14 @@ local abcd TEST [[ ---@class abc local abcd ----@type $ +---@type <??> ]] (EXISTS) TEST [[ ---@class zabc local abcd ----@type zxxx|z$ +---@type zxxx|z<??> ]] { { @@ -1207,7 +1207,7 @@ local abcd TEST [[ ---@alias zabc zabb ----@type za$ +---@type za<??> ]] { { @@ -1218,7 +1218,7 @@ TEST [[ TEST [[ ---@class ZClass ----@param x ZC$ +---@param x ZC<??> ]] { { @@ -1229,7 +1229,7 @@ TEST [[ Cared['insertText'] = true TEST [[ ----@param $ +---@param <??> function f(a, b, c) end ]] @@ -1257,7 +1257,7 @@ a ${1:any} } TEST [[ ----@param $ +---@param <??> function f(a, b, c) end function f2(a) end @@ -1286,7 +1286,7 @@ a ${1:any} } TEST [[ ----@param aa$ +---@param aa<??> function f(aaa, bbb, ccc) end ]] @@ -1299,7 +1299,7 @@ end TEST [[ local function f() - ---@param $ + ---@param <??> function f(a, b, c) end end @@ -1328,7 +1328,7 @@ a ${1:any} } TEST [[ ----@param $ +---@param <??> function mt:f(a, b, c, ...) end ]] @@ -1360,7 +1360,7 @@ a ${1:any} } TEST [[ ----@param aaa $ +---@param aaa <??> function f(aaa, bbb, ccc) end ]] @@ -1369,7 +1369,7 @@ end TEST [[ ---@param xyz Class ---@param xxx Class -function f(x$) +function f(x<??>) ]] { { @@ -1389,7 +1389,7 @@ function f(x$) TEST [[ ---@param xyz Class ---@param xxx Class -function f($ +function f(<??> ]] { { @@ -1409,7 +1409,7 @@ function f($ TEST [[ ---@param xyz Class ---@param xxx Class -function f($) +function f(<??>) ]] { { @@ -1428,7 +1428,7 @@ function f($) TEST [[ local function f() - ---@t$ + ---@t<??> end ]] { @@ -1443,7 +1443,7 @@ TEST [[ ---@field name string ---@field id integer local mt = {} -mt.$ +mt.<??> ]] { { @@ -1461,7 +1461,7 @@ TEST [[ function f(y, x) end -f(1, $) +f(1, <??>) ]] { { @@ -1483,7 +1483,7 @@ TEST [[ function f(y, x) end -f(1,$) +f(1,<??>) ]] { { @@ -1505,7 +1505,7 @@ TEST [[ function f(x) end -f($) +f(<??>) ]] { { @@ -1528,7 +1528,7 @@ TEST [[ function f(x) end -f($) +f(<??>) ]] { { @@ -1550,7 +1550,7 @@ TEST [[ function f(x) end -f('$') +f('<??>') ]] { { @@ -1586,7 +1586,7 @@ TEST [[ local function f(x) end -f($) +f(<??>) ]] { { @@ -1619,7 +1619,7 @@ end ---comment 3 ---| '3' -f($) +f(<??>) ]] { { @@ -1638,7 +1638,7 @@ function f(x) end f(function () - $ + <??> end) ]] (nil) @@ -1652,7 +1652,7 @@ TEST [[ ---@return string local function zzzzz(list, sep, i, j) end -zzz$ +zzz<??> ]] { { @@ -1672,7 +1672,7 @@ Cared['description'] = true TEST [[ --- abc zzz = 1 -zz$ +zz<??> ]] { { @@ -1695,7 +1695,7 @@ TEST [[ ---| "'选项2'" # 注释2 function f(x) end -f($) +f(<??>) ]] { { @@ -1711,7 +1711,7 @@ f($) } TEST [[ -utf8.charpatter$ +utf8.charpatter<??> ]] { { @@ -1726,7 +1726,7 @@ TEST [[ ---@type "'a'"|"'b'"|"'c'" local x -print(x == $) +print(x == <??>) ]] { { @@ -1747,7 +1747,7 @@ TEST [[ ---@type "'a'"|"'b'"|"'c'" local x -x = $ +x = <??> ]] { { @@ -1768,7 +1768,7 @@ TEST [[ ---@type "'a'"|"'b'"|"'c'" local x -print(x == '$') +print(x == '<??>') ]] { { @@ -1792,7 +1792,7 @@ TEST [[ ---@type "'a'"|"'b'"|"'c'" local x -x = '$' +x = '<??>' ]] { { @@ -1815,24 +1815,24 @@ x = '$' TEST [[ local t = type() -print(t == $) +print(t == <??>) ]] (EXISTS) TEST [[ -if type(arg) == '$' +if type(arg) == '<??>' ]] (EXISTS) TEST [[ -if type(arg) == $ +if type(arg) == <??> ]] (EXISTS) TEST [[ ---@type string local s -s.$ +s.<??> ]] (EXISTS) @@ -1841,7 +1841,7 @@ TEST [[ local t local vvv = assert(t) -vvv$ +vvv<??> ]] { { @@ -1857,7 +1857,7 @@ TEST [[ ---@param callback fun(x: number, y: number):string local function f(callback) end -f($) +f(<??>) ]] { { @@ -1871,7 +1871,7 @@ end", } TEST [[ ----$ +---<??> local function f(a, b, c) return a + 1, b .. '', c[1] end @@ -1894,7 +1894,7 @@ ${1:comment}\ Cared['insertText'] = nil TEST [[ ---$ +--<??> ]] { { @@ -1918,7 +1918,7 @@ TEST [[ local function f(x) end f({ - $ + <??> }) ]] { @@ -1942,7 +1942,7 @@ local function f(x) end f({ aaa = 1, - $ + <??> }) ]] { @@ -1960,7 +1960,7 @@ TEST [[ ---@param x cc local function f(x) end -f({aaa = 1,$}) +f({aaa = 1,<??>}) ]] { { @@ -1977,7 +1977,7 @@ TEST [[ ---@param x cc local function f(x) end -f({aaa $}) +f({aaa <??>}) ]] (nil) @@ -1988,7 +1988,7 @@ TEST [[ ---@param x cc local function f(x) end -f({if$}) +f({if<??>}) ]] { include = true, @@ -2008,7 +2008,7 @@ local function f(x) end f({ { - $ + <??> } }) ]] @@ -2020,7 +2020,7 @@ local function f() end local s = f() -s.$ +s.<??> ]] (EXISTS) @@ -2032,7 +2032,7 @@ TEST [[ ---@type cc local t -print(t.aa$) +print(t.aa<??>) ]] { { @@ -2050,7 +2050,7 @@ TEST [[ ---@type table<string, "'a'"|"'b'"|"'c'"> local x -x.a = $ +x.a = <??> ]] { { @@ -2071,7 +2071,7 @@ TEST [[ ---@type table<string, "'a'"|"'b'"|"'c'"> local x -x['a'] = $ +x['a'] = <??> ]] { { @@ -2091,7 +2091,7 @@ x['a'] = $ TEST [[ ---@type table<string, "'a'"|"'b'"|"'c'"> local x = { - a = $ + a = <??> } ]] { @@ -2112,7 +2112,7 @@ local x = { TEST [[ ---@type table<string, "'a'"|"'b'"|"'c'"> local x = { - ['a'] = $ + ['a'] = <??> } ]] { @@ -2138,7 +2138,7 @@ local m function m.f() end -m.f$ +m.f<??> ]]{ { label = "f()", @@ -2154,7 +2154,7 @@ m.f$ Cared['insertText'] = nil TEST [[ -if true then$ +if true then<??> ]] { { @@ -2168,7 +2168,7 @@ if true then$ } TEST [[ -if true then$ +if true then<??> end ]] { @@ -2179,7 +2179,7 @@ end } TEST [[ -if true then$ +if true then<??> else ]] { @@ -2190,7 +2190,7 @@ else } TEST [[ -if true then$ +if true then<??> elseif ]] { @@ -2202,7 +2202,7 @@ elseif TEST [[ do - if true then$ + if true then<??> end ]] { @@ -2226,7 +2226,7 @@ local function f(x, ...) end f(1, { - $ + <??> }) ]] { @@ -2250,7 +2250,7 @@ local function f(x, ...) end f(1, {}, {}, { - $ + <??> }) ]] { @@ -2271,7 +2271,7 @@ TEST [[ ---@type C local t = { - $ + <??> } ]] @@ -2293,7 +2293,7 @@ TEST [[ ---@type C local t = { - x$ + x<??> } ]] @@ -2306,19 +2306,19 @@ local t = { } TEST [[ -if $ then +if <??> then ]] (nil) TEST [[ -elseif $ then +elseif <??> then ]] (nil) TEST [[ ---@type iolib local t = { - $ + <??> ]] (EXISTS) @@ -2329,7 +2329,7 @@ TEST [[ ---@param t A function api(t) end -api({$}) +api({<??>}) ]] (EXISTS) @@ -2340,22 +2340,22 @@ TEST [[ ---@param t A function m:api(t) end -m:api({$}) +m:api({<??>}) ]] (EXISTS) TEST [[ ---@class AAA.BBB ----@type AAA.$ +---@type AAA.<??> ]] { { label = 'AAA.BBB', kind = define.CompletionItemKind.Class, textEdit = { - start = 29, - finish = 32, + start = 20009, + finish = 20013, newText = 'AAA.BBB', }, } @@ -2365,7 +2365,7 @@ Cared['insertText'] = true TEST [[ ---@overload fun(a: any, b: any) local function zzzz(a) end -zzzz$ +zzzz<??> ]] { { @@ -2406,13 +2406,13 @@ local tarray local b = tdirect -- type . here, shows "world" -- Inferred by index -local c = tarray[1].$ -- type . here, no auto completion +local c = tarray[1].<??> -- type . here, no auto completion ]] (EXISTS) TEST [[ local function f() - if type() == '$' then + if type() == '<??>' then end end ]] @@ -2425,7 +2425,7 @@ GGG = 1 GGG = function () end -GGG$ +GGG<??> ]] { { @@ -2446,7 +2446,7 @@ local t = {} t.GGG = function () end -t.GGG$ +t.GGG<??> ]] { { @@ -2463,7 +2463,7 @@ TEST [[ ---@param f fun(a: any, b: any):boolean local function f(f) end -f(fun$) +f(fun<??>) ]] { { @@ -2471,8 +2471,8 @@ f(fun$) kind = define.CompletionItemKind.Function, textEdit = { newText = 'function (${1:a}, ${2:b})\n\t$0\nend', - start = 68, - finish = 70, + start = 30002, + finish = 30005, } }, { @@ -2489,7 +2489,7 @@ TEST [[ ---@type {[1]: number} local t -t.$ +t.<??> ]] { { @@ -2497,13 +2497,13 @@ t.$ kind = define.CompletionItemKind.Field, textEdit = { newText = '[1]', - start = 35, - finish = 34, + start = 30002, + finish = 30002, }, additionalTextEdits = { { - start = 34, - finish = 34, + start = 30001, + finish = 30002, newText = '', }, }, @@ -2514,7 +2514,7 @@ TEST [[ ---@type {[1]: number} local t -t.$ +t.<??> ]] { { @@ -2522,13 +2522,13 @@ t.$ kind = define.CompletionItemKind.Field, textEdit = { newText = '[1]', - start = 35, - finish = 34, + start = 30002, + finish = 30002, }, additionalTextEdits = { { - start = 34, - finish = 34, + start = 30001, + finish = 30002, newText = '', }, }, @@ -2543,7 +2543,7 @@ TEST [[ local function f(x) end -local r = f('$') +local r = f('<??>') ]] { { @@ -2551,8 +2551,8 @@ local r = f('$') kind = define.CompletionItemKind.EnumMember, textEdit = { newText = "'aaa'", - start = 103, - finish = 104, + start = 70012, + finish = 70014, }, }, { @@ -2560,8 +2560,8 @@ local r = f('$') kind = define.CompletionItemKind.EnumMember, textEdit = { newText = "'bbb'", - start = 103, - finish = 104, + start = 70012, + finish = 70014, }, }, } @@ -2570,7 +2570,7 @@ TEST [[ ---@type fun(x: "'aaa'"|"'bbb'") local f -f('$') +f('<??>') ]] { { @@ -2578,8 +2578,8 @@ f('$') kind = define.CompletionItemKind.EnumMember, textEdit = { newText = "'aaa'", - start = 45, - finish = 46, + start = 30002, + finish = 30004, }, }, { @@ -2587,8 +2587,8 @@ f('$') kind = define.CompletionItemKind.EnumMember, textEdit = { newText = "'bbb'", - start = 45, - finish = 46, + start = 30002, + finish = 30004, }, }, } @@ -2598,7 +2598,7 @@ TEST [[ ---@field on fun() local c -c:$ +c:<??> ]] { { @@ -2612,7 +2612,7 @@ TEST [[ ---@field on fun(x: "'aaa'"|"'bbb'") local c -c:on($) +c:on(<??>) ]] (EXISTS) @@ -2621,7 +2621,7 @@ TEST [[ ---@field on fun(x: "'aaa'"|"'bbb'") local c -c:on('$') +c:on('<??>') ]] (EXISTS) @@ -2632,7 +2632,7 @@ function m.f() end m.f() -m.$ +m.<??> ]] { [1] = EXISTS, @@ -2647,7 +2647,7 @@ function class1:method1() end ---@class class2 : class1 class2 = {} -class2:$ +class2:<??> ]] { diff --git a/test/completion/continue.lua b/test/completion/continue.lua index 4159ea7a..63f970e9 100644 --- a/test/completion/continue.lua +++ b/test/completion/continue.lua @@ -9,7 +9,7 @@ ContinueTyping = true TEST [[ local zabcde -za$ +za<??> ]] { { @@ -20,7 +20,7 @@ za$ TEST [[ -- zabcde -io.z$ +io.z<??> ]] { { @@ -32,7 +32,7 @@ io.z$ TEST [[ -- provider -pro$ +pro<??> ]] { { @@ -45,7 +45,7 @@ TEST [[ ---@param n '"abcdefg"' local function f(n) end -f 'abc$' +f 'abc<??>' ]] { { @@ -59,7 +59,7 @@ TEST [[ ---@type '"abcdefg"' local t -if t == 'abc$' +if t == 'abc<??>' ]] { { diff --git a/test/completion/init.lua b/test/completion/init.lua index 24c8932f..051c288c 100644 --- a/test/completion/init.lua +++ b/test/completion/init.lua @@ -1,5 +1,6 @@ local core = require 'core.completion' local files = require 'files' +local catch = require 'catch' EXISTS = {'EXISTS'} @@ -64,17 +65,17 @@ ContinueTyping = false function TEST(script) return function (expect) files.removeAll() - local pos = script:find('$', 1, true) - 1 - local new_script = script:gsub('%$', '') + local newScript, catched = catch(script, '?') - files.setText('', new_script) + files.setText('', newScript) core.clearCache() + local inputPos = catched['?'][1][1] if ContinueTyping then - local triggerCharacter = script:sub(pos - 1, pos - 1) - core.completion('', pos - 1, triggerCharacter) + local triggerCharacter = script:sub(inputPos - 1, inputPos - 1) + core.completion('', inputPos, triggerCharacter) end - local triggerCharacter = script:sub(pos, pos) - local result = core.completion('', pos, triggerCharacter) + local triggerCharacter = script:sub(inputPos, inputPos) + local result = core.completion('', inputPos, triggerCharacter) if not expect then assert(result == nil) return diff --git a/test/crossfile/allreferences.lua b/test/crossfile/allreferences.lua index 056fa416..bddc159d 100644 --- a/test/crossfile/allreferences.lua +++ b/test/crossfile/allreferences.lua @@ -9,10 +9,9 @@ TEST { { path = 'lib.lua', content = [[ - return <~function~> () - end + return <!<?function?> () + end!> ]], - target = {22, 50}, }, } @@ -21,7 +20,7 @@ TEST { path = 'a.lua', content = [[ local m = {} - function m.<?func?>() + function m.<~func~>() end return m ]], @@ -39,7 +38,7 @@ TEST { { path = 'a.lua', content = [[ - return <?function () end?> + return <~function () end~> ]], }, { @@ -72,7 +71,7 @@ TEST { { path = 'a.lua', content = [[ - local function <?f?>() + local function <~f~>() end return { @@ -126,7 +125,7 @@ TEST { local t = require 'a' local <!f!> = t.<!f!> - <?f?>() + <~f~>() return { <!f!> = <!f!>, @@ -139,7 +138,7 @@ TEST { { path = 'a.lua', content = [[ - local function <?f?>() + local function <~f~>() end return { @@ -202,7 +201,7 @@ TEST { { path = 'a.lua', content = [[ - local <?t?> = require 'b' + local <~t~> = require 'b' return <!t!> ]] }, diff --git a/test/crossfile/completion.lua b/test/crossfile/completion.lua index d26398ca..9c5ed96e 100644 --- a/test/crossfile/completion.lua +++ b/test/crossfile/completion.lua @@ -4,6 +4,7 @@ local furi = require 'file-uri' local platform = require 'bee.platform' local util = require 'utility' local config = require 'config' +local catch = require 'catch' rawset(_G, 'TEST', true) @@ -88,8 +89,9 @@ function TEST(data) local uri = furi.encode(info.path) local script = info.content if info.main then - pos = script:find('$', 1, true) - 1 - script = script:gsub('%$', '') + local newScript, catched = catch(script, '?') + pos = catched['?'][1][1] + script = newScript mainUri = uri end files.setText(uri, script) @@ -139,7 +141,7 @@ TEST { }, { path = 'test.lua', - content = 'require "a$"', + content = 'require "a<??>"', main = true, }, completion = { @@ -178,7 +180,7 @@ TEST { }, { path = 'test.lua', - content = 'require "A$"', + content = 'require "A<??>"', main = true, }, completion = { @@ -201,7 +203,7 @@ TEST { }, { path = 'test.lua', - content = 'require "a$"', + content = 'require "a<??>"', main = true, }, completion = { @@ -229,7 +231,7 @@ TEST { }, { path = 'test.lua', - content = 'require "abc$"', + content = 'require "abc<??>"', main = true, }, completion = { @@ -257,7 +259,7 @@ TEST { }, { path = 'test.lua', - content = 'require "abc$"', + content = 'require "abc<??>"', main = true, }, completion = { @@ -290,7 +292,7 @@ TEST { }, { path = 'test.lua', - content = 'require "abc.i$"', + content = 'require "abc.i<??>"', main = true, }, completion = { @@ -315,7 +317,7 @@ TEST { }, { path = 'test.lua', - content = 'require "abc/i$"', + content = 'require "abc/i<??>"', main = true, }, completion = { @@ -339,7 +341,7 @@ TEST { }, { path = 'test.lua', - content = 'require "core.co$"', + content = 'require "core.co<??>"', main = true, }, completion = { @@ -362,7 +364,7 @@ TEST { }, { path = 'abc/test.lua', - content = 'require "x$"', + content = 'require "x<??>"', main = true, }, completion = { @@ -397,7 +399,7 @@ TEST { }, { path = 'main.lua', - content = 'require "x$"', + content = 'require "x<??>"', main = true, }, completion = { @@ -428,7 +430,7 @@ TEST { }, { path = 'main.lua', - content = 'require "x$"', + content = 'require "x<??>"', main = true, }, completion = { @@ -457,7 +459,7 @@ TEST { path = 'b.lua', content = [[ local t = require 'a' - t.$ + t.<??> ]], main = true, }, @@ -499,7 +501,7 @@ TEST { { path = 'b.lua', content = [[ - zab$ + zab<??> ]], main = true, }, @@ -533,7 +535,7 @@ TEST { { path = 'b.lua', content = [[ - zab$ + zab<??> ]], main = true, }, @@ -557,7 +559,7 @@ TEST { path = 'a.lua', content = [[ local japi = require 'jass.japi' - japi.xxxaaaax$ + japi.xxxaaaax<??> ]], main = true, }, @@ -571,7 +573,7 @@ TEST { { path = 'xxxx.lua', content = [[ - require 'xx$' + require 'xx<??>' ]], main = true, }, @@ -592,7 +594,7 @@ TEST { { path = 'main.lua', content = [[ - require 'xx$' + require 'xx<??>' ]], main = true, }, @@ -613,7 +615,7 @@ TEST { { path = 'main.lua', content = [[ - require [=[xx$]=]' + require [=[xx<??>]=]' ]], main = true, }, @@ -635,7 +637,7 @@ TEST { { path = 'main.lua', content = [[ - dofile 'ab$' + dofile 'ab<??>' ]], main = true, }, @@ -656,7 +658,7 @@ TEST { { path = 'main.lua', content = [[ - dofile 'ab$' + dofile 'ab<??>' ]], main = true, }, @@ -686,7 +688,7 @@ TEST { content = [[ local t = require 'a' local v = setmetatable({}, {__index = t}) - v.$ + v.<??> ]] }, completion = { @@ -712,7 +714,7 @@ TEST { content = [[ local z = require 'a' - z$ + z<??> ]], main = true, }, @@ -743,7 +745,7 @@ TEST { path = 'main.lua', main = true, content = [[ - myfun$ + myfun<??> ]], }, completion = { @@ -774,7 +776,7 @@ TEST { path = 'main.lua', main = true, content = [[ - myfun$ + myfun<??> ]], }, completion = { @@ -807,7 +809,7 @@ TEST { path = 'main.lua', main = true, content = [[ - A.$ + A.<??> ]], }, completion = EXISTS, @@ -819,7 +821,7 @@ TEST { path = 'main.lua', main = true, content = [[ - require'$ + require'<??> ]] }, completion = EXISTS diff --git a/test/crossfile/definition.lua b/test/crossfile/definition.lua index 66e9b269..01bf0202 100644 --- a/test/crossfile/definition.lua +++ b/test/crossfile/definition.lua @@ -3,26 +3,10 @@ local furi = require 'file-uri' local core = require 'core.definition' local config = require 'config' local platform = require 'bee.platform' +local catch = require 'catch' rawset(_G, 'TEST', true) -local function catch_target(script, sep) - local list = {} - local cur = 1 - local cut = 0 - while true do - local start, finish = script:find(('<%%%s.-%%%s>'):format(sep, sep), cur) - if not start then - break - end - list[#list+1] = { start - cut, finish - 4 - cut } - cur = finish + 1 - cut = cut + 4 - end - local new_script = script:gsub(('<%%%s(.-)%%%s>'):format(sep, sep), '%1') - return new_script, list -end - local function founded(targets, results) if #targets ~= #results then return false @@ -50,32 +34,26 @@ function TEST(datas) local sourceUri for i, data in ipairs(datas) do local uri = furi.encode(data.path) - local new, list = catch_target(data.content, '!') - if new ~= data.content or data.target then - if data.target then - targetList[#targetList+1] = { - data.target[1], - data.target[2], - uri, - } - else - for _, position in ipairs(list) do - targetList[#targetList+1] = { - position[1], - position[2], - uri, - } - end - end - data.content = new + local newScript, catched = catch(data.content, '!?~') + for _, position in ipairs(catched['!'] or {}) do + targetList[#targetList+1] = { + position[1], + position[2], + uri, + } + end + for _, position in ipairs(catched['~'] or {}) do + targetList[#targetList+1] = { + position[1], + position[2], + uri, + } end - new, list = catch_target(data.content, '?') - if new ~= data.content then - sourceList = list + if catched['?'] or catched['~'] then + sourceList = catched['?'] or catched['~'] sourceUri = uri - data.content = new end - files.setText(uri, data.content) + files.setText(uri, newScript) end local sourcePos = (sourceList[1][1] + sourceList[1][2]) // 2 @@ -98,8 +76,7 @@ end TEST { { path = 'a.lua', - content = '', - target = {0, 0}, + content = '<!!>', }, { path = 'b.lua', @@ -110,8 +87,7 @@ TEST { TEST { { path = 'aaa/bbb.lua', - content = '', - target = {0, 0}, + content = '<!!>', }, { path = 'b.lua', @@ -122,8 +98,7 @@ TEST { TEST { { path = '@bbb.lua', - content = '', - target = {0, 0}, + content = '<!!>', }, { path = 'b.lua', @@ -134,8 +109,7 @@ TEST { TEST { { path = 'aaa/bbb.lua', - content = '', - target = {0, 0}, + content = '<!!>', }, { path = 'b.lua', @@ -150,8 +124,7 @@ TEST { }, { path = 'b.lua', - content = 'local <?t?> = require "a"', - target = {7, 7}, + content = 'local <~t~> = require "a"', }, } @@ -176,8 +149,7 @@ TEST { }, { path = 'b.lua', - content = 'local <?t?> = require "a"', - target = {7, 7}, + content = 'local <~t~> = require "a"', }, } @@ -245,9 +217,8 @@ TEST { { path = 'b.lua', content = [[ - local <?t?> = require 'a' + local <~t~> = require 'a' ]], - target = {19, 19}, }, } diff --git a/test/crossfile/diagnostic.lua b/test/crossfile/diagnostic.lua index a430cb4e..adc2961f 100644 --- a/test/crossfile/diagnostic.lua +++ b/test/crossfile/diagnostic.lua @@ -3,28 +3,12 @@ local furi = require 'file-uri' local core = require 'core.diagnostics' local config = require 'config' local platform = require 'bee.platform' +local catch = require 'catch' config.get 'Lua.diagnostics.neededFileStatus'['deprecated'] = 'Any' rawset(_G, 'TEST', true) -local function catch_target(script, sep) - local list = {} - local cur = 1 - local cut = 0 - while true do - local start, finish = script:find(('<%%%s.-%%%s>'):format(sep, sep), cur) - if not start then - break - end - list[#list+1] = { start - cut, finish - 4 - cut } - cur = finish + 1 - cut = cut + 4 - end - local new_script = script:gsub(('<%%%s(.-)%%%s>'):format(sep, sep), '%1') - return new_script, list -end - local function founded(targets, results) if #targets ~= #results then return false @@ -48,19 +32,18 @@ function TEST(datas) files.removeAll() local targetList = {} - local sourceUri for _, data in ipairs(datas) do local uri = furi.encode(data.path) - local new, list = catch_target(data.content, '!') - for _, position in ipairs(list) do + local newScript, catched = catch(data.content, '!') + for _, position in ipairs(catched['!'] or {}) do targetList[#targetList+1] = { position[1], position[2], uri, } end - data.content = new - files.setText(uri, new) + data.content = newScript + files.setText(uri, newScript) end local result = {} diff --git a/test/crossfile/hover.lua b/test/crossfile/hover.lua index a877d226..1c46214c 100644 --- a/test/crossfile/hover.lua +++ b/test/crossfile/hover.lua @@ -2,6 +2,7 @@ local files = require 'files' local furi = require 'file-uri' local core = require 'core.hover' local config = require 'config' +local catch = require 'catch' rawset(_G, 'TEST', true) @@ -36,36 +37,19 @@ local function eq(a, b) return a == b end -local function catch_target(script, sep) - local list = {} - local cur = 1 - local cut = 0 - while true do - local start, finish = script:find(('<%%%s.-%%%s>'):format(sep, sep), cur) - if not start then - break - end - list[#list+1] = { start - cut, finish - 4 - cut } - cur = finish + 1 - cut = cut + 4 - end - local new_script = script:gsub(('<%%%s(.-)%%%s>'):format(sep, sep), '%1') - return new_script, list -end - function TEST(expect) files.removeAll() local targetScript = expect[1].content local targetUri = furi.encode(expect[1].path) - local sourceScript, sourceList = catch_target(expect[2].content, '?') + local sourceScript, sourceList = catch(expect[2].content, '?') local sourceUri = furi.encode(expect[2].path) files.setText(targetUri, targetScript) files.setText(sourceUri, sourceScript) - local sourcePos = (sourceList[1][1] + sourceList[1][2]) // 2 + local sourcePos = (sourceList['?'][1][1] + sourceList['?'][1][2]) // 2 local hover = core.byUri(sourceUri, sourcePos) assert(hover) hover = tostring(hover):gsub('\r\n', '\n') diff --git a/test/crossfile/references.lua b/test/crossfile/references.lua index 6c28b34b..145792b5 100644 --- a/test/crossfile/references.lua +++ b/test/crossfile/references.lua @@ -1,6 +1,7 @@ local files = require 'files' local furi = require 'file-uri' local core = require 'core.reference' +local catch = require 'catch' rawset(_G, 'TEST', true) @@ -32,21 +33,6 @@ local function eq(a, b) return a == b end -local function catch_target(script, sep) - local list = {} - local cur = 1 - while true do - local start, finish = script:find(('<%%%s.-%%%s>'):format(sep, sep), cur) - if not start then - break - end - list[#list+1] = { start + 2, finish - 2 } - cur = finish + 1 - end - local new_script = script:gsub(('<%%%s(.-)%%%s>'):format(sep, sep), ' %1 ') - return new_script, list -end - local function founded(targets, results) if #targets ~= #results then return false @@ -74,37 +60,9 @@ function TEST(datas) local sourceUri for i, data in ipairs(datas) do local uri = furi.encode(data.path) - local new, list = catch_target(data.content, '!') - if new ~= data.content or data.target then - if data.target then - targetList[#targetList+1] = { - data.target[1], - data.target[2], - uri, - } - else - for _, position in ipairs(list) do - targetList[#targetList+1] = { - position[1], - position[2], - uri, - } - end - end - data.content = new - end - new, list = catch_target(data.content, '~') - if new ~= data.content then - sourceList = list - sourceUri = uri - data.content = new - end - new, list = catch_target(data.content, '?') - if new ~= data.content then - sourceList = list - sourceUri = uri - data.content = new - for _, position in ipairs(list) do + local newScript, catched = catch(data.content, '!?~') + if catched['!'] or catched['~'] then + for _, position in ipairs(catched['!'] + catched['~']) do targetList[#targetList+1] = { position[1], position[2], @@ -112,7 +70,11 @@ function TEST(datas) } end end - files.setText(uri, data.content) + if catched['?'] or catched['~'] then + sourceList = catched['?'] + catched['~'] + sourceUri = uri + end + files.setText(uri, newScript) end local sourcePos = (sourceList[1][1] + sourceList[1][2]) // 2 @@ -143,7 +105,7 @@ TEST { { path = 'a.lua', content = [[ - local <?f?> = require 'lib' + local <~f~> = require 'lib' ]], }, } @@ -158,7 +120,7 @@ TEST { { path = 'b.lua', content = [[ - print(<?ROOT?>) + print(<~ROOT~>) ]], }, } @@ -167,7 +129,7 @@ TEST { { path = 'a.lua', content = [[ - <?ROOT?> = 1 + <~ROOT~> = 1 ]], }, { @@ -183,7 +145,7 @@ TEST { path = 'a.lua', content = [[ local f = require 'lib' - local <?o?> = f() + local <~o~> = f() ]], }, { @@ -212,7 +174,7 @@ TEST { ---@class A local mt - function mt.<?f?>() + function mt.<~f~>() end ]] } diff --git a/test/definition/init.lua b/test/definition/init.lua index 85bcd5d5..2e87b0c2 100644 --- a/test/definition/init.lua +++ b/test/definition/init.lua @@ -1,23 +1,10 @@ local core = require 'core.definition' local files = require 'files' local vm = require 'vm' +local catch = require 'catch' rawset(_G, 'TEST', true) -local function catch_target(script) - local list = {} - local cur = 1 - while true do - local start, finish = script:find('<!.-!>', cur) - if not start then - break - end - list[#list+1] = { start + 2, finish - 2 } - cur = finish + 1 - end - return list -end - local function founded(targets, results) if #targets ~= #results then return false @@ -36,15 +23,11 @@ end function TEST(script) files.removeAll() - script = script:gsub('\n', '\r\n') - local target = catch_target(script) - local start = script:find('<?', 1, true) - local finish = script:find('?>', 1, true) - local pos = (start + finish) // 2 + 1 - local new_script = script:gsub('<[!?]', ' '):gsub('[!?]>', ' ') - files.setText('', new_script) + local newScript, catched = catch(script, '!?') - local results = core('', pos) + files.setText('', newScript) + + local results = core('', catched['?'][1][1]) if results then local positions = {} for i, result in ipairs(results) do @@ -52,15 +35,9 @@ function TEST(script) positions[i] = { result.target.start, result.target.finish } end end - if not founded(target, positions) then - core('', pos) - end - assert(founded(target, positions)) + assert(founded(catched['!'] or {}, positions)) else - if #target ~= 0 then - core('', pos) - end - assert(#target == 0) + assert(catched['!'] == nil) end end diff --git a/test/definition/table.lua b/test/definition/table.lua index d63cc655..61e8746d 100644 --- a/test/definition/table.lua +++ b/test/definition/table.lua @@ -54,7 +54,7 @@ t[<?"method"?>]() TEST [[ local t t[<!"longString"!>] = 1 -t[<?[==[longString]==]?>]() +t[ <?[==[longString]==]?> ]() ]] TEST [[ diff --git a/test/diagnostics/init.lua b/test/diagnostics/init.lua index 79b267c3..e3bf9516 100644 --- a/test/diagnostics/init.lua +++ b/test/diagnostics/init.lua @@ -2,36 +2,12 @@ local core = require 'core.diagnostics' local files = require 'files' local config = require 'config' local util = require 'utility' +local catch = require 'catch' config.get 'Lua.diagnostics.neededFileStatus'['deprecated'] = 'Any' rawset(_G, 'TEST', true) -local function catch_target(script, ...) - local list = {} - local function catch(buf) - local cur = 1 - local cut = 0 - while true do - local start, finish = buf:find('<!.-!>', cur) - if not start then - break - end - list[#list+1] = { start - cut, finish - 4 - cut } - cur = finish + 1 - cut = cut + 4 - end - end - catch(script) - if ... then - for _, buf in ipairs {...} do - catch(buf) - end - end - local new_script = script:gsub('<!(.-)!>', '%1') - return new_script, list -end - local function founded(targets, results) if #targets ~= #results then return false @@ -50,8 +26,8 @@ end function TEST(script, ...) files.removeAll() - local new_script, target = catch_target(script, ...) - files.setText('', new_script) + local newScript, catched = catch(script, '!') + files.setText('', newScript) files.open('') local datas = {} core('', function (results) @@ -65,11 +41,11 @@ function TEST(script, ...) end if results[1] then - if not founded(target, results) then - error(('%s\n%s'):format(util.dump(target), util.dump(results))) + if not founded(catched['!'] or {}, results) then + error(('%s\n%s'):format(util.dump(catched['!']), util.dump(results))) end else - assert(#target == 0) + assert(catched['!'] == nil) end end @@ -99,15 +75,11 @@ local <!t!> = {} <!t!>.a = 1 ]] -TEST([[ -local <!function x() +TEST [[ +local <!function <!x!>() end!> -]], -[[ -local function <!x!>() -end ]] -) + TEST [[ local <!x!> = <!function () end!> @@ -118,21 +90,13 @@ local <!x!> <!x!> = <!function () end!> ]] -TEST([[ +TEST [[ local <!function x() end!> -local <!function y() +local <!function <!y!>() x() end!> -]], -[[ -local function x() -end -local function <!y!>() - x() -end ]] -) TEST [[ local print, _G @@ -155,6 +119,11 @@ TEST [[ ]] TEST [[ + +<! !> +]] + +TEST [[ X = 1<! !> ]] diff --git a/test/document_symbol/init.lua b/test/document_symbol/init.lua index d745d53f..d3168197 100644 --- a/test/document_symbol/init.lua +++ b/test/document_symbol/init.lua @@ -63,8 +63,8 @@ A = 1 name = 'A', detail = 'global number = 1', kind = define.SymbolKind.Class, - range = {1, 5}, - selectionRange = {1, 1}, + range = {0, 5}, + selectionRange = {0, 1}, } } @@ -77,9 +77,9 @@ end name = 'f', detail = 'function ()', kind = define.SymbolKind.Function, - range = {7, 22}, - selectionRange = {16, 16}, - valueRange = {7, 22}, + range = {6, 10003}, + selectionRange = {15, 16}, + valueRange = {6, 10003}, } } @@ -92,9 +92,9 @@ end name = 'f', detail = 'function ()', kind = define.SymbolKind.Function, - range = {1, 16}, - selectionRange = {10, 10}, - valueRange = {1, 16}, + range = {0, 10003}, + selectionRange = {9, 10}, + valueRange = {0, 10003}, } } @@ -107,9 +107,9 @@ end name = '', detail = 'return function ()', kind = define.SymbolKind.Function, - range = {8, 22}, - selectionRange = {8, 8}, - valueRange = {8, 22}, + range = {7, 10003}, + selectionRange = {7, 15}, + valueRange = {7, 10003}, } } @@ -122,9 +122,9 @@ end name = 'f', detail = 'function ()', kind = define.SymbolKind.Function, - range = {1, 19}, - selectionRange = {1, 1}, - valueRange = {5, 19}, + range = {0, 10003}, + selectionRange = {0, 1}, + valueRange = {4, 10003}, } } @@ -137,9 +137,9 @@ end name = 'f', detail = 'function ()', kind = define.SymbolKind.Function, - range = {7, 25}, - selectionRange = {7, 7}, - valueRange = {11, 25}, + range = {6, 10003}, + selectionRange = {6, 7}, + valueRange = {10, 10003}, } } @@ -152,9 +152,9 @@ end name = 'mt:add', detail = 'function ()', kind = define.SymbolKind.Method, - range = {1, 21}, - selectionRange = {10, 15}, - valueRange = {1, 21}, + range = {0, 10003}, + selectionRange = {9, 15}, + valueRange = {0, 10003}, } } @@ -173,25 +173,25 @@ end name = 'A', detail = 'function ()', kind = define.SymbolKind.Function, - range = {1, 68}, - selectionRange = {10, 10}, - valueRange = {1, 68}, + range = {0, 50003}, + selectionRange = {9, 10}, + valueRange = {0, 50003}, children = { [1] = { name = 'A1', detail = 'function ()', kind = define.SymbolKind.Function, - range = {18, 38}, - selectionRange = {27, 28}, - valueRange = {18, 38}, + range = {10004, 20007}, + selectionRange = {10013, 10015}, + valueRange = {10004, 20007}, }, [2] = { name = 'A2', detail = 'function ()', kind = define.SymbolKind.Function, - range = {44, 64}, - selectionRange = {53, 54}, - valueRange = {44, 64}, + range = {30004, 40007}, + selectionRange = {30013, 30015}, + valueRange = {30004, 40007}, }, }, }, @@ -199,9 +199,9 @@ end name = 'B', detail = 'function ()', kind = define.SymbolKind.Function, - range = {70, 85}, - selectionRange = {79, 79}, - valueRange = {70, 85}, + range = {60000, 70003}, + selectionRange = {60009, 60010}, + valueRange = {60000, 70003}, }, } @@ -220,31 +220,31 @@ local z name = 'x', detail = 'local number = 1', kind = define.SymbolKind.Variable, - range = {7, 11}, - selectionRange = {7, 7}, + range = {6, 11}, + selectionRange = {6, 7}, }, [2] = { name = 'f', detail = 'function ()', kind = define.SymbolKind.Function, - range = {19, 79}, - selectionRange = {28, 28}, - valueRange = {19, 79}, + range = {10006, 50003}, + selectionRange = {10015, 10016}, + valueRange = {10006, 50003}, children = { [1] = { name = 'x', detail = 'local string = "x"', kind = define.SymbolKind.Variable, - range = {42, 48}, - selectionRange = {42, 42}, + range = {20010, 20017}, + selectionRange = {20010, 20011}, }, [2] = { name = 'y', detail = 'local {}', kind = define.SymbolKind.Variable, - range = {60, 65}, - selectionRange = {60, 60}, - valueRange = {64, 65}, + range = {30010, 30016}, + selectionRange = {30010, 30011}, + valueRange = {30014, 30016}, }, --[3] = { -- name = 'z', @@ -260,15 +260,15 @@ local z name = 'y', detail = 'local boolean = true', kind = define.SymbolKind.Variable, - range = {87, 94}, - selectionRange = {87, 87}, + range = {60006, 60014}, + selectionRange = {60006, 60007}, }, [4] = { name = 'z', detail = 'local', kind = define.SymbolKind.Variable, - range = {102, 102}, - selectionRange = {102, 102}, + range = {70006, 70007}, + selectionRange = {70006, 70007}, }, } @@ -284,30 +284,30 @@ local t = { name = 't', detail = 'local {a, b, c}', kind = define.SymbolKind.Variable, - range = {7, 46}, - selectionRange = {7, 7}, - valueRange = {11, 46}, + range = {6, 40001}, + selectionRange = {6, 7}, + valueRange = {10, 40001}, children = { [1] = { name = 'a', detail = 'field number = 1', kind = define.SymbolKind.Property, - range = {17, 21}, - selectionRange = {17, 17}, + range = {10004, 10009}, + selectionRange = {10004, 10005}, }, [2] = { name = 'b', detail = 'field number = 2', kind = define.SymbolKind.Property, - range = {28, 32}, - selectionRange = {28, 28}, + range = {20004, 20009}, + selectionRange = {20004, 20005}, }, [3] = { name = 'c', detail = 'field number = 3', kind = define.SymbolKind.Property, - range = {39, 43}, - selectionRange = {39, 39}, + range = {30004, 30009}, + selectionRange = {30004, 30005}, }, } } @@ -325,24 +325,24 @@ local t = { name = 't', detail = 'local {a}', kind = define.SymbolKind.Variable, - range = {7, 44}, - selectionRange = {7, 7}, - valueRange = {11, 44}, + range = {6, 40001}, + selectionRange = {6, 7}, + valueRange = {10, 40001}, children = { [1] = { name = 'a', detail = 'field {b}', kind = define.SymbolKind.Property, - range = {17, 42}, - selectionRange = {17, 17}, - valueRange = {21, 42}, + range = {10004, 30005}, + selectionRange = {10004, 10005}, + valueRange = {10008, 30005}, children = { [1] = { name = 'b', detail = EXISTS, kind = define.SymbolKind.Property, - range = {31, 35}, - selectionRange = {31, 31}, + range = {20008, 20013}, + selectionRange = {20008, 20009}, } } }, @@ -360,16 +360,16 @@ g = 1 name = 'g', detail = 'function ()', kind = define.SymbolKind.Function, - range = {7, 22}, - selectionRange = {16, 16}, - valueRange = {7, 22}, + range = {6, 10003}, + selectionRange = {15, 16}, + valueRange = {6, 10003}, }, [2] = { name = 'g', detail = 'setlocal number = 1', kind = define.SymbolKind.Variable, - range = {25, 29}, - selectionRange = {25, 25}, + range = {30000, 30005}, + selectionRange = {30000, 30001}, } } @@ -383,30 +383,30 @@ end name = 'f', detail = 'function (a, b, ...)', kind = define.SymbolKind.Function, - range = {1, 58}, - selectionRange = {10, 10}, - valueRange = {1, 58}, + range = {0, 30003}, + selectionRange = {9, 10}, + valueRange = {0, 30003}, children = { [1] = { name = 'a', detail = 'param', kind = define.SymbolKind.Constant, - range = {12, 12}, - selectionRange = {12, 12}, + range = {11, 12}, + selectionRange = {11, 12}, }, [2] = { name = 'b', detail = 'param', kind = define.SymbolKind.Constant, - range = {15, 15}, - selectionRange = {15, 15}, + range = {14, 15}, + selectionRange = {14, 15}, }, [3] = { name = 'x', detail = 'local', kind = define.SymbolKind.Variable, - range = {33, 39}, - selectionRange = {33, 33}, + range = {10010, 10017}, + selectionRange = {10010, 10011}, } } }, @@ -424,17 +424,17 @@ local v = t name = 't', detail = 'local {a, b}', kind = define.SymbolKind.Variable, - range = {7, 35}, - selectionRange = {7, 7}, - valueRange = {11, 35}, + range = {6, 30001}, + selectionRange = {6, 7}, + valueRange = {10, 30001}, children = EXISTS, }, [2] = { name = 'v', detail = 'local', kind = define.SymbolKind.Variable, - range = {44, 48}, - selectionRange = {44, 44}, + range = {50006, 50011}, + selectionRange = {50006, 50007}, }, } @@ -446,47 +446,47 @@ local function name = 'x', detail = 'local', kind = define.SymbolKind.Variable, - range = {7, 7}, - selectionRange = {7, 7}, + range = {6, 7}, + selectionRange = {6, 7}, }, [2] = { name = "", detail = "function ()", kind = 12, - range = {15, 22}, - selectionRange = {15, 15}, - valueRange = {15, 22}, + range = {10006, 10014}, + selectionRange = {10006, 10014}, + valueRange = {10006, 10014}, }, } TEST [[ local a, b = { - x = 1, - y = 1, - z = 1, + x1 = 1, + y1 = 1, + z1 = 1, }, { - x = 1, - y = 1, - z = 1, + x2 = 1, + y2= 1, + z2 = 1, } ]]{ [1] = { name = 'a', - detail = 'local {x, y, z}', + detail = 'local {x1, y1, z1}', kind = define.SymbolKind.Variable, - range = {7, 49}, - selectionRange = {7, 7}, - valueRange = {14, 49}, + range = {6, 40001}, + selectionRange = {6, 7}, + valueRange = {13, 40001}, children = EXISTS, }, [2] = { name = 'b', - detail = 'local {x, y, z}', + detail = 'local {x2, y2, z2}', kind = define.SymbolKind.Variable, - range = {10, 87}, - selectionRange = {10, 10}, - valueRange = {52, 87}, + range = {9, 80001}, + selectionRange = {9, 10}, + valueRange = {40003, 80001}, children = EXISTS, } } @@ -504,24 +504,24 @@ end name = 'x', detail = 'function ()', kind = define.SymbolKind.Function, - range = {7, 22}, - selectionRange = {16, 16}, - valueRange = {7, 22}, + range = {6, 10003}, + selectionRange = {15, 16}, + valueRange = {6, 10003}, }, [2] = { name = 'f', detail = 'function ()', kind = define.SymbolKind.Function, - range = {31, 58}, - selectionRange = {40, 40}, - valueRange = {31, 58}, + range = {30006, 50003}, + selectionRange = {30015, 30016}, + valueRange = {30006, 50003}, children = { [1] = { name = 'c', detail = 'local', kind = define.SymbolKind.Variable, - range = {54, 54}, - selectionRange = {54, 54}, + range = {40010, 40011}, + selectionRange = {40010, 40011}, }, }, } @@ -537,16 +537,16 @@ local t = f({ name = 't', detail = 'local', kind = define.SymbolKind.Variable, - range = {7, 26}, - selectionRange = {7, 7}, - valueRange = {11, 26}, + range = {6, 20002}, + selectionRange = {6, 7}, + valueRange = {10, 20002}, children = { [1] = { name = 'k', detail = 'field number = 1', kind = define.SymbolKind.Property, - range = {19, 23}, - selectionRange = {19, 19}, + range = {10004, 10009}, + selectionRange = {10004, 10005}, } } } @@ -563,31 +563,31 @@ end name = 't', detail = 'local {}', kind = define.SymbolKind.Variable, - range = {7, 12}, - selectionRange = {7, 7}, - valueRange = {11, 12}, + range = {6, 12}, + selectionRange = {6, 7}, + valueRange = {10, 12}, }, [2] = { name = 'f', detail = 'function (a, b)', kind = define.SymbolKind.Function, - range = {21, 40}, - selectionRange = {30, 30}, - valueRange = {21, 40}, + range = {20006, 30003}, + selectionRange = {20015, 20016}, + valueRange = {20006, 30003}, children = { [1] = { name = 'a', detail = 'param', kind = define.SymbolKind.Constant, - range = {32, 32}, - selectionRange = {32, 32}, + range = {20017, 20018}, + selectionRange = {20017, 20018}, }, [2] = { name = 'b', detail = 'param', kind = define.SymbolKind.Constant, - range = {35, 35}, - selectionRange = {35, 35}, + range = {20020, 20021}, + selectionRange = {20020, 20021}, } } } @@ -604,17 +604,17 @@ local a = f { name = 'a', detail = 'local', kind = define.SymbolKind.Variable, - range = {7, 43}, - selectionRange = {7, 7}, - valueRange = {11, 43}, + range = {6, 30001}, + selectionRange = {6, 7}, + valueRange = {10, 30001}, children = { [1] = { name = 'x', detail = 'function ()', kind = define.SymbolKind.Function, - range = {19, 41}, - selectionRange = {19, 19}, - valueRange = {23, 41}, + range = {10004, 20007}, + selectionRange = {10004, 10005}, + valueRange = {10008, 20007}, } } } @@ -630,9 +630,9 @@ end) name = '', detail = 'table.sort -> function (a, b)', kind = define.SymbolKind.Function, - range = {15, 50}, - selectionRange = {15, 15}, - valueRange = {15, 50}, + range = {14, 20003}, + selectionRange = {14, 22}, + valueRange = {14, 20003}, children = EXISTS, } } @@ -643,32 +643,32 @@ local root = { local function function_inside_function() end end - } +} ]] { [1] = { name = 'root', detail = 'local {inner_function}', kind = define.SymbolKind.Variable, - range = {7, 123}, - selectionRange = {7, 10}, - valueRange = {14, 123}, + range = {6, 50001}, + selectionRange = {6, 10}, + valueRange = {13, 50001}, children = { [1] = { name = 'inner_function', detail = 'function ()', kind = define.SymbolKind.Function, - range = {20, 117}, - selectionRange = {20, 33}, - valueRange = {37, 117}, + range = {10004, 40007}, + selectionRange = {10004, 10018}, + valueRange = {10021, 40007}, children = { [1] = { name = 'function_inside_function', detail = 'function ()', kind = define.SymbolKind.Function, - range = {63, 109}, - selectionRange = {72, 95}, - valueRange = {63, 109}, + range = {20014, 30011}, + selectionRange = {20023, 20047}, + valueRange = {20014, 30011}, }, }, }, diff --git a/test/example/guide.txt b/test/example/guide.txt index da8d5c32..cff05faf 100644 --- a/test/example/guide.txt +++ b/test/example/guide.txt @@ -586,7 +586,7 @@ end ---@param lines table ---@return integer {name = 'row'} ---@return integer {name = 'col'} -function m.positionOf(lines, offset) +function m.rowColOf(lines, offset) if offset < 1 then return 0, 0 end @@ -619,7 +619,7 @@ end ---@param row integer ---@param col integer ---@return integer {name = 'offset'} -function m.offsetOf(lines, row, col) +function m.positionOf(lines, row, col) if row < 1 then return 0 end diff --git a/test/full/example.lua b/test/full/example.lua index 5596233e..20de5528 100644 --- a/test/full/example.lua +++ b/test/full/example.lua @@ -12,26 +12,24 @@ local function testIfExit(path) config.set('Lua.workspace.preloadFileSize', 1000000000) local buf = util.loadFile(path:string()) if buf then - local vm + local state local clock = os.clock() local max = 1 local need - local parseClock = 0 local compileClock = 0 local luadocClock = 0 local noderClock = 0 local total for i = 1, max do - vm = TEST(buf) + state = TEST(buf) local luadocStart = os.clock() - luadoc(nil, vm) + luadoc(state) local luadocPassed = os.clock() - luadocStart local passed = os.clock() - clock local noderStart = os.clock() local noderPassed = os.clock() - noderStart - parseClock = parseClock + vm.parseClock - compileClock = compileClock + vm.compileClock + compileClock = compileClock + state.compileClock luadocClock = luadocClock + luadocPassed noderClock = noderClock + noderPassed if passed >= 1.0 or i == max then @@ -40,10 +38,9 @@ local function testIfExit(path) break end end - print(('基准编译测试[%s]单次耗时:%.10f(解析:%.10f, 编译:%.10f, LuaDoc: %.10f, Noder: %.10f)'):format( + print(('基准编译测试[%s]单次耗时:%.10f(解析:%.10f, LuaDoc: %.10f, Noder: %.10f)'):format( path:filename():string(), need, - parseClock / total, compileClock / total, luadocClock / total, noderClock / total @@ -52,7 +49,6 @@ local function testIfExit(path) local clock = os.clock() local max = 100 local need - local lines = parser:lines(buf) for i = 1, max do files.removeAll() files.open('') diff --git a/test/full/init.lua b/test/full/init.lua index 89d9b8b3..9584db6f 100644 --- a/test/full/init.lua +++ b/test/full/init.lua @@ -5,9 +5,10 @@ local util = require 'utility' rawset(_G, 'TEST', true) function TEST(script) - local ast = parser:compile(script, 'lua', 'Lua 5.3') - assert(ast) - return ast + local clock = os.clock() + local state = parser.compile(script, 'Lua', 'Lua 5.3') + state.compileClock = os.clock() - clock + return state end local function startCollectDiagTimes() diff --git a/test/highlight/init.lua b/test/highlight/init.lua index 2bf639fa..7faa9e08 100644 --- a/test/highlight/init.lua +++ b/test/highlight/init.lua @@ -1,22 +1,6 @@ local core = require 'core.highlight' local files = require 'files' - -local function catch_target(script) - local list = {} - local cur = 1 - while true do - local start, finish = script:find('<[!?].-[!?]>', cur) - if not start then - break - end - list[#list+1] = { - start = start + 2, - finish = finish - 2, - } - cur = finish + 1 - end - return list -end +local catch = require 'catch' local function founded(targets, results) if #targets ~= #results then @@ -35,20 +19,18 @@ local function founded(targets, results) end function TEST(script) - local target = catch_target(script) - for _, enter in ipairs(target) do - local start, finish = enter.start, enter.finish - files.removeAll() + files.removeAll() + local newScript, catched = catch(script, '!') + files.setText('', newScript) + for _, enter in ipairs(catched['!']) do + local start, finish = enter[1], enter[2] local pos = (start + finish) // 2 - local new_script = script:gsub('<[!?~]', ' '):gsub('[!?~]>', ' ') - files.setText('', new_script) - local positions = core('', pos) - if positions then - assert(founded(target, positions)) - else - assert(#target == 0) + local results = {} + for _, position in ipairs(positions) do + results[#results+1] = { position.start, position.finish } end + assert(founded(catched['!'], results)) end end diff --git a/test/hover/init.lua b/test/hover/init.lua index 3f07ea30..8058317d 100644 --- a/test/hover/init.lua +++ b/test/hover/init.lua @@ -1,7 +1,6 @@ local core = require 'core.hover' -local findSource = require 'core.find-source' -local getLabel = require 'core.hover.label' local files = require 'files' +local catch = require 'catch' rawset(_G, 'TEST', true) @@ -23,12 +22,9 @@ local accept = { function TEST(script) return function (expect) files.removeAll() - local start = script:find('<?', 1, true) - local finish = script:find('?>', 1, true) - local pos = (start + finish) // 2 + 1 - local new_script = script:gsub('<[!?]', ' '):gsub('[!?]>', ' ') - files.setText('', new_script) - local hover = core.byUri('', pos) + local newScript, catched = catch(script, '?') + files.setText('', newScript) + local hover = core.byUri('', catched['?'][1][1]) assert(hover) expect = expect:gsub('^[\r\n]*(.-)[\r\n]*$', '%1'):gsub('\r\n', '\n') local label = tostring(hover):match('```lua[\r\n]*(.-)[\r\n]*```'):gsub('\r\n', '\n') @@ -1703,3 +1699,18 @@ print(b.<?x?>) [[ field A.x: any ]] + +TEST [[ +---@class A +---@field x number +---@field y number + +---@type A<string, number> +local <?t?> +]] +[[ +local t: A<string, number> { + x: number, + y: number, +} +]] diff --git a/test/references/common.lua b/test/references/common.lua index 3669486b..4245a79d 100644 --- a/test/references/common.lua +++ b/test/references/common.lua @@ -221,30 +221,12 @@ TEST [[ ---@class A local a -a.<!xxx!> = 1 - ---@type A local b -b.xxx = 1 -- Dont search this - ----@class C: A -local c -print(c.<?xxx?>) -]] - -TEST [[ ----@class A -local <?a?> - ----@class B -local b - -b.xx = 1 -- Dont search this - ----@type B +---@type A local c ----@type A -c.<!xx!> = 1 +b.<?x?> = 1 +c.<!x!> = 1 ]] diff --git a/test/references/init.lua b/test/references/init.lua index 2fba92e5..351c80b6 100644 --- a/test/references/init.lua +++ b/test/references/init.lua @@ -1,19 +1,6 @@ local core = require 'core.reference' local files = require 'files' - -local function catch_target(script) - local list = {} - local cur = 1 - while true do - local start, finish = script:find('<[!?].-[!?]>', cur) - if not start then - break - end - list[#list+1] = { start + 2, finish - 2 } - cur = finish + 1 - end - return list -end +local catch = require 'catch' local function founded(targets, results) if #targets ~= #results then @@ -33,14 +20,12 @@ end function TEST(script) files.removeAll() - local expect = catch_target(script) - local start = script:find('<[?~]') - local finish = script:find('[?~]>') - local pos = (start + finish) // 2 + 1 - local new_script = script:gsub('<[!?~]', ' '):gsub('[!?~]>', ' ') - files.setText('', new_script) + local newScript, catched = catch(script, '!?~') + files.setText('', newScript) - local results = core('', pos) + local input = catched['?'] + catched['~'] + local expect = catched['!'] + catched['?'] + local results = core('', input[1][1]) if results then local positions = {} for i, result in ipairs(results) do diff --git a/test/rename/init.lua b/test/rename/init.lua index 4b10756e..b20c0279 100644 --- a/test/rename/init.lua +++ b/test/rename/init.lua @@ -1,16 +1,21 @@ local core = require 'core.rename' local files = require 'files' +local catch = require 'catch' +local guide = require 'parser.guide' local function replace(text, positions) + local state = files.getState('') local buf = {} table.sort(positions, function (a, b) return a.start < b.start end) local lastPos = 1 for _, info in ipairs(positions) do - buf[#buf+1] = text:sub(lastPos, info.start - 1) + local start = guide.positionToOffset(state, info.start) + local finish = guide.positionToOffset(state, info.finish) + buf[#buf+1] = text:sub(lastPos, start) buf[#buf+1] = info.text - lastPos = info.finish + 1 + lastPos = finish + 1 end buf[#buf+1] = text:sub(lastPos) return table.concat(buf) @@ -21,10 +26,12 @@ function TEST(oldName, newName) return function (expectScript) files.removeAll() files.setText('', oldScript) - local pos = oldScript:find('[^%w_]'..oldName..'[^%w_]') - assert(pos) + local state = files.getState('') + local offset = oldScript:find('[^%w_]'..oldName..'[^%w_]') + assert(offset) + local position = guide.offsetToPosition(state, offset) - local positions = core.rename('', pos+1, newName) + local positions = core.rename('', position, newName) local script = oldScript if positions then script = replace(script, positions) diff --git a/test/signature/init.lua b/test/signature/init.lua index 27051310..43bce29e 100644 --- a/test/signature/init.lua +++ b/test/signature/init.lua @@ -1,26 +1,25 @@ -local core = require 'core.signature' +local core = require 'core.signature' local files = require 'files' +local catch = require 'catch' rawset(_G, 'TEST', true) function TEST(script) return function (expect) - local pos = script:find('$', 1, true) - 1 - local new_script = script:gsub('%$', '') + local newScript, catched1 = catch(script, '?') + local newExpect, catched2 = catch(expect or '', '!') files.removeAll() - files.setText('', new_script) - local hovers = core('', pos) + files.setText('', newScript) + local hovers = core('', catched1['?'][1][1]) if hovers then assert(expect) local hover = hovers[#hovers] - local label = hover.label:gsub('^[\r\n]*(.-)[\r\n]*$', '%1'):gsub('\r\n', '\n') - expect.label = expect.label:gsub('^[\r\n]*(.-)[\r\n]*$', '%1'):gsub('\r\n', '\n') local arg = hover.params[hover.index].label - assert(expect.label == label) - assert(expect.arg[1] == arg[1]) - assert(expect.arg[2] == arg[2]) + assert(newExpect == hover.label) + assert(catched2['!'][1][1] == arg[1]) + assert(catched2['!'][1][2] == arg[2]) else assert(expect == nil) end @@ -31,93 +30,67 @@ TEST [[ local function x(a, b) end -x($ +x(<??> ]] -{ - label = "function x(a: any, b: any)", - arg = {12, 17}, -} +'function x(<!a: any!>, b: any)' TEST [[ local function x(a, b) end -x($) +x(<??>) ]] -{ - label = "function x(a: any, b: any)", - arg = {12, 17}, -} +'function x(<!a: any!>, b: any)' TEST [[ local function x(a, b) end -x(xxx$) +x(xxx<??>) ]] -{ - label = "function x(a: any, b: any)", - arg = {12, 17}, -} +'function x(<!a: any!>, b: any)' TEST [[ local function x(a, b) end -x(xxx, $) +x(xxx, <??>) ]] -{ - label = "function x(a: any, b: any)", - arg = {20, 25}, -} +'function x(a: any, <!b: any!>)' TEST [[ function mt:f(a) end -mt:f($ +mt:f(<??> ]] -{ - label = 'function mt:f(a: any)', - arg = {15, 20}, -} +'function mt:f(<!a: any!>)' TEST [[ local function x(a, b) return 1 end -x($ +x(<??> ]] -{ - label = "function x(a: any, b: any)", - arg = {12, 17}, -} +'function x(<!a: any!>, b: any)' TEST [[ local function x(a, ...) return 1 end -x(1, 2, 3, $ +x(1, 2, 3, <??> ]] -{ - label = "function x(a: any, ...)", - arg = {20, 22}, -} +'function x(a: any, <!...!>)' TEST [[ -(''):sub($ +(''):sub(<??> ]] -{ - label = [[ -function string:sub(i: integer, j?: integer) -]], - arg = {21, 30}, -} +'function string:sub(<!i: integer!>, j?: integer)' TEST [[ -(''):sub(1)$ +(''):sub(1)<??> ]] (nil) @@ -125,39 +98,29 @@ TEST [[ local function f(a, b, c) end -f(1, 'string$') +f(1, 'string<??>') ]] -{ - label = [[ -function f(a: any, b: any, c: any) -]], - arg = {20, 25}, -} +'function f(a: any, <!b: any!>, c: any)' TEST [[ -pcall(function () $ end) +pcall(function () <??> end) ]] (nil) TEST [[ -table.unpack {$} +table.unpack {<??>} ]] (nil) TEST [[ ---@type fun(x: number, y: number):boolean local zzzz -zzzz($) +zzzz(<??>) ]] -{ - label = [[ -function zzzz(x: number, y: number) -]], - arg = {15, 23}, -} +'function zzzz(<!x: number!>, y: number)' TEST [[ -('abc'):format(f($)) +('abc'):format(f(<??>)) ]] (nil) @@ -166,14 +129,9 @@ function Foo(param01, param02) end -Foo($) +Foo(<??>) ]] -{ - label = [[ -function Foo(param01: any, param02: any) -]], - arg = {14, 25}, -} +'function Foo(<!param01: any!>, param02: any)' TEST [[ function f1(a, b) @@ -182,51 +140,31 @@ end function f2(c, d) end -f2(f1(),$) +f2(f1(),<??>) ]] -{ - label = [[ -function f2(c: any, d: any) -]], - arg = {21, 26}, -} +'function f2(c: any, <!d: any!>)' TEST [[ local function f(a, b, c) end -f({},$) +f({},<??>) ]] -{ - label = [[ -function f(a: any, b: any, c: any) -]], - arg = {20, 25}, -} +'function f(a: any, <!b: any!>, c: any)' TEST [[ -for _ in pairs($) do +for _ in pairs(<??>) do end ]] -{ - label = [[ -function pairs(t: <T>) -]], - arg = {16, 21}, -} +'function pairs(<!t: <T>!>)' TEST [[ function m:f() end -m.f($) +m.f(<??>) ]] -{ - label = [[ -function m.f(self: table) -]], - arg = {14, 24}, -} +'function m.f(<!self: table!>)' TEST [[ ---@alias nnn table<number, string> @@ -234,59 +172,41 @@ TEST [[ ---@param x nnn local function f(x, y, z) end -f($) +f(<??>) ]] -{ - label = [[ -function f(x: table<number, string>, y: any, z: any) -]], - arg = {12, 35}, -} - +'function f(<!x: table<number, string>!>, y: any, z: any)' TEST [[ local function x(a, b) end -x( aaaa $, 2) +x( aaaa <??>, 2) ]] -{ - label = "function x(a: any, b: any)", - arg = {12, 17}, -} +"function x(<!a: any!>, b: any)" TEST [[ local function x(a, b) end -x($ aaaa , 2) +x(<??> aaaa , 2) ]] -{ - label = "function x(a: any, b: any)", - arg = {12, 17}, -} +'function x(<!a: any!>, b: any)' TEST [[ local function x(a, b) end -x(aaaa ,$ 2) +x(aaaa ,<??> 2) ]] -{ - label = "function x(a: any, b: any)", - arg = {20, 25}, -} +'function x(a: any, <!b: any!>)' TEST [[ local function x(a, b) end -x(aaaa , 2 $) +x(aaaa , 2 <??>) ]] -{ - label = "function x(a: any, b: any)", - arg = {20, 25}, -} +'function x(a: any, <!b: any!>)' TEST [[ local fooC @@ -298,9 +218,6 @@ function fooC(callback, par) end fooC(function (x, s) -end,$) +end,<??>) ]] -{ - label = 'function fooC(callback: fun(x: number, s: string):nil, par: number)', - arg = {56, 66}, -} +'function fooC(callback: fun(x: number, s: string):nil, <!par: number!>)' diff --git a/test/type_formatting/init.lua b/test/type_formatting/init.lua index 9178ea4f..46b8223d 100644 --- a/test/type_formatting/init.lua +++ b/test/type_formatting/init.lua @@ -1,16 +1,16 @@ local core = require 'core.type-formatting' local files = require 'files' local util = require 'utility' +local catch = require 'catch' rawset(_G, 'TEST', true) function TEST(script) return function (expect) - local pos = script:find('$', 1, true) - 1 - local new_script = script:gsub('%$', '') files.removeAll() - files.setText('', new_script) - local edits = core('', pos, expect.ch) + local newScript, catched = catch(script, '?') + files.setText('', newScript) + local edits = core('', catched['?'][1][1], expect.ch) if edits then assert(expect.edits) assert(util.equal(edits, expect.edits)) @@ -21,80 +21,104 @@ function TEST(script) end TEST [[ -if true then $ end +if true then <??> end ]] { ch = '\n', edits = { { - start = 13, + start = 12, finish = 13, text = '\n\t', }, { - start = 14, - finish = 17, + start = 13, + finish = 15, text = '', }, { - start = 18, - finish = 17, - text = '\nend', + start = 15, + finish = 15, + text = '\ne', }, } } TEST [[ -if true then $end +if true then <??>end ]] { ch = '\n', edits = { { - start = 13, + start = 12, finish = 13, text = '\n\t', }, { - start = 14, - finish = 16, + start = 13, + finish = 14, text = '', }, { - start = 17, - finish = 16, - text = '\nend', + start = 14, + finish = 14, + text = '\ne', }, } } TEST [[ -if true then$end +if true then<??>end ]] { ch = '\n', edits = { { - start = 13, + start = 12, finish = 12, text = '\n\t', }, { - start = 13, - finish = 15, + start = 12, + finish = 13, text = '', }, { + start = 13, + finish = 13, + text = '\ne', + }, + } +} + +TEST [[ + if true then<??>end +]] +{ + ch = '\n', + edits = { + { start = 16, - finish = 15, - text = '\nend', + finish = 16, + text = '\n \t', + }, + { + start = 16, + finish = 17, + text = '', + }, + { + start = 17, + finish = 17, + text = '\n e', }, } } TEST [[ if true then - $ + <??> end ]] { diff --git a/test/type_inference/init.lua b/test/type_inference/init.lua index a20a96e9..23ebf54e 100644 --- a/test/type_inference/init.lua +++ b/test/type_inference/init.lua @@ -1,8 +1,8 @@ local files = require 'files' -local vm = require 'vm' local guide = require 'parser.guide' local infer = require 'core.infer' local config = require 'config' +local catch = require 'catch' rawset(_G, 'TEST', true) @@ -24,12 +24,9 @@ end function TEST(wanted) return function (script) files.removeAll() - local start = script:find('<?', 1, true) - local finish = script:find('?>', 1, true) - local pos = (start + finish) // 2 + 1 - local newScript = script:gsub('<[!?]', ' '):gsub('[!?]>', ' ') + local newScript, catched = catch(script, '?') files.setText('', newScript) - local source = getSource(pos) + local source = getSource(catched['?'][1][1]) assert(source) local result = infer.searchAndViewInfers(source) if wanted ~= result then @@ -356,6 +353,13 @@ TEST 'table<string, number>' [[ local <?x?> ]] +TEST 'A<string, number>' [[ +---@class A + +---@type A<string, number> +local <?x?> +]] + TEST 'table' [[ self.<?t?>[#self.t+1] = {} ]] |