diff --git a/.vscode/launch.json b/.vscode/launch.json index c51acbd..5a355cd 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,13 +4,24 @@ { "type": "extensionHost", "request": "launch", - "name": "LuaPandaExtension", + "name": "Launch Client", "runtimeExecutable": "${execPath}", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ "${workspaceFolder}/out/**/*.js" ] - + "args": ["--extensionDevelopmentPath=${workspaceRoot}"], + "outFiles": ["${workspaceRoot}/out/**/*.js", "${workspaceRoot}/out/common/*.js"] + }, + { + "type": "node", + "request": "attach", + "name": "Attach to Server", + "port": 6009, + "restart": true, + "outFiles": ["${workspaceRoot}/out/code/server/**/*.js"] + } + ], + "compounds": [ + { + "name": "Client + Server", + "configurations": ["Launch Client", "Attach to Server"] } ] } diff --git a/.vscodeignore b/.vscodeignore index 25137a5..b31e1ab 100755 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,5 +1,6 @@ .vscode/**/* .gitignore +.github .travis.yml appveyor.yml src/**/* @@ -8,3 +9,6 @@ out/tests/**/* Debugger/debugger_lib/*.h Debugger/debugger_lib/*.cpp Docs/**/* +luasocketBin/**/* +lua504Test/**/* +vsix/**/* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2572cb4..58a8830 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,62 @@ +## 3.3.0 + ++ 修复了 VSCode 1.82 下插件执行错误的问题 ++ lua 504 下 mac arm / win x64 已支持 chook,其他平台没有机器测试,所以未出 libpdebug 库。有需要大家可以自行打包 plibdebug 库,也可提 mr。 ++ 更新了版本间的 mr + + #108 调试启动比较晚时,已经创建的协程无法调试 / 调试堆栈碰到c函数被打断 + + #114 修复调试栈中有C函数时,监听的变量获取错误的bug + + #109 无法动态attach到debug + + #139 做了 5.4.3 下 luasocket sock:receive() 默认行为不一致导致的错误 + + #152 launch.json 启动参数 program 路径带有空格则启动失败 ++ 因依赖库版本太旧无法兼容新版本 VSCode,删除了导出符号用于代码提示功能 + + + +## 3.2.0 + ++ 代码提示支持大小写不敏感,无论输入大小写都能提示对应的符号。 ++ 支持多端调试(multi target), 在一个VSCode面板中可以启动多个不同port的调试端,连接多个lua进程。 ++ 支持反转client-server。目前 vscode 插件作为 server , lua 进程作为 client。支持通过设置反转,方便真机调试。 ++ 支持require 路径中含有 . , 目前只支持require("A/B"), 后续支持require("A.B")的形式,无需设置 ++ 在autoPath模式支持同名文件 ++ 重新测试和优化真机调试,修复真机调试socket连接可能存在的问题 + + + +## 3.1.0 + ++ 新增了导出 slua 符号用于代码提示。 + ++ 重构了定义跳转和代码提示框架,提升了分析效率。自测修复了 #49 大量lua工程下无法工作的问题。 ++ 修复了 \#47 table 成员展示问题。 ++ 修复了 #46 配置 program 后 , attach 启动拉起新进程的问题。增加了一个attach启动项。 ++ 实现了 #44 提出的 terminal 复用的建议。 + ++ 优化了升级提示,修复了可视化界面的一些展示错误 + + + +## 3.0.2 + ++ 新增了导出c++符号给 lua 做代码提示,支持slua-unreal + + + +## 3.0.1 + ++ 修复可视化配置的bug ++ 在可视化界面增加lua辅助工具开关 + + + +## 3.0.0 + ++ 调试器可视化配置 ++ 调试器lua文件自动提示升级 ++ 加入代码分析功能,主要提供:代码补全,代码片段,定义跳转,生成注释,符号列表功能 + + + ## 2.3.0 + 增加了自动路径识别功能 diff --git a/Debugger/DebugTools.lua b/Debugger/DebugTools.lua deleted file mode 100644 index bf3143f..0000000 --- a/Debugger/DebugTools.lua +++ /dev/null @@ -1,584 +0,0 @@ ---工具函数 -local DebugTools = {}; -local this = DebugTools; - -function this.getFileSource() - local info = debug.getinfo(1, "S") - for k,v in pairs(info) do - if k == "source" then - return v; - end - end -end - ---序列化并打印table -function this.printTable(t, name ,indent) - local str = (this.show(t, name, indent)); - print(str); -end - ---序列化并返回table -function this.serializeTable(t, name, indent) - local str = (this.show(t, name, indent)) - return str -end - ---[[ -Author: Julio Manuel Fernandez-Diaz -Date: January 12, 2007 -Modified slightly by RiciLake to avoid the unnecessary table traversal in tablecount() -Formats tables with cycles recursively to any depth. -The output is returned as a string. -References to other tables are shown as values. -Self references are indicated. -The string returned is "Lua code", which can be procesed -(in the case in which indent is composed by spaces or "--"). -Userdata and function keys and values are shown as strings, -which logically are exactly not equivalent to the original code. -This routine can serve for pretty formating tables with -proper indentations, apart from printing them: -print(table.show(t, "t")) -- a typical use -Heavily based on "Saving tables with cycles", PIL2, p. 113. -Arguments: -t is the table. -name is the name of the table (optional) -indent is a first indentation (optional). ---]] -function this.show(t, name, indent) - local cart -- a container - local autoref -- for self references - - local function isemptytable(t) return next(t) == nil end - - local function basicSerialize (o) - local so = tostring(o) - if type(o) == "function" then - local info = debug.getinfo(o, "S") - -- info.name is nil because o is not a calling level - if info.what == "C" then - return string.format("%q", so .. ", C function") - else - -- the information is defined through lines - return string.format("%q", so .. ", defined in (" .. - info.linedefined .. "-" .. info.lastlinedefined .. - ")" .. info.source) - end - elseif type(o) == "number" or type(o) == "boolean" then - return so - else - return string.format("%q", so) - end - end - - local function addtocart (value, name, indent, saved, field) - indent = indent or "" - saved = saved or {} - field = field or name - - cart = cart .. indent .. field - - if type(value) ~= "table" then - cart = cart .. " = " .. basicSerialize(value) .. ";\n" - else - if saved[value] then - cart = cart .. " = {}; -- " .. saved[value] - .. " (self reference)\n" - autoref = autoref .. name .. " = " .. saved[value] .. ";\n" - else - saved[value] = name - --if tablecount(value) == 0 then - if isemptytable(value) then - cart = cart .. " = {};\n" - else - cart = cart .. " = {\n" - for k, v in pairs(value) do - k = basicSerialize(k) - local fname = string.format("%s[%s]", name, k) - field = string.format("[%s]", k) - -- three spaces between levels - addtocart(v, fname, indent .. " ", saved, field) - end - cart = cart .. indent .. "};\n" - end - end - end - end - - name = name or "PRINT_Table" - if type(t) ~= "table" then - return name .. " = " .. basicSerialize(t) - end - cart, autoref = "", "" - addtocart(t, name, indent) - return cart .. autoref -end - ------------------------------------------------------------------------------ --- JSON4Lua: JSON encoding / decoding support for the Lua language. --- json Module. --- Author: Craig Mason-Jones --- Homepage: http://github.com/craigmj/json4lua/ --- Version: 1.0.0 --- This module is released under the MIT License (MIT). --- Please see LICENCE.txt for details. --- --- USAGE: --- This module exposes two functions: --- json.encode(o) --- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string. --- json.decode(json_string) --- Returns a Lua object populated with the data encoded in the JSON string json_string. --- --- REQUIREMENTS: --- compat-5.1 if using Lua 5.0 --- --- CHANGELOG --- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix). --- Fixed Lua 5.1 compatibility issues. --- Introduced json.null to have null values in associative arrays. --- json.encode() performance improvement (more than 50%) through table.concat rather than .. --- Introduced decode ability to ignore /**/ comments in the JSON string. --- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays. ------------------------------------------------------------------------------ - -function this.createJson() - ----------------------------------------------------------------------------- - -- Imports and dependencies - ----------------------------------------------------------------------------- - local math = require('math') - local string = require("string") - local table = require("table") - - ----------------------------------------------------------------------------- - -- Module declaration - ----------------------------------------------------------------------------- - local json = {} -- Public namespace - local json_private = {} -- Private namespace - - -- Public constants - json.EMPTY_ARRAY={} - json.EMPTY_OBJECT={} - - -- Public functions - - -- Private functions - local decode_scanArray - local decode_scanComment - local decode_scanConstant - local decode_scanNumber - local decode_scanObject - local decode_scanString - local decode_scanWhitespace - local encodeString - local isArray - local isEncodable - - ----------------------------------------------------------------------------- - -- PUBLIC FUNCTIONS - ----------------------------------------------------------------------------- - --- Encodes an arbitrary Lua object / variable. - -- @param v The Lua object / variable to be JSON encoded. - -- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode) - function json.encode (v) - -- Handle nil values - if v==nil then - return "null" - end - - local vtype = type(v) - - -- Handle strings - if vtype=='string' then - return '"' .. json_private.encodeString(v) .. '"' -- Need to handle encoding in string - end - - -- Handle booleans - if vtype=='number' or vtype=='boolean' then - return tostring(v) - end - - -- Handle tables - if vtype=='table' then - local rval = {} - -- Consider arrays separately - local bArray, maxCount = isArray(v) - if bArray then - for i = 1,maxCount do - table.insert(rval, json.encode(v[i])) - end - else -- An object, not an array - for i,j in pairs(v) do - if isEncodable(i) and isEncodable(j) then - table.insert(rval, '"' .. json_private.encodeString(i) .. '":' .. json.encode(j)) - end - end - end - if bArray then - return '[' .. table.concat(rval,',') ..']' - else - return '{' .. table.concat(rval,',') .. '}' - end - end - - -- Handle null values - if vtype=='function' and v==json.null then - return 'null' - end - - assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. tostring(v)) - end - - - --- Decodes a JSON string and returns the decoded value as a Lua data structure / value. - -- @param s The string to scan. - -- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1. - -- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil, - -- and the position of the first character after - -- the scanned JSON object. - function json.decode(s, startPos) - startPos = startPos and startPos or 1 - startPos = decode_scanWhitespace(s,startPos) - assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') - local curChar = string.sub(s,startPos,startPos) - -- Object - if curChar=='{' then - return decode_scanObject(s,startPos) - end - -- Array - if curChar=='[' then - return decode_scanArray(s,startPos) - end - -- Number - if string.find("+-0123456789.e", curChar, 1, true) then - return decode_scanNumber(s,startPos) - end - -- String - if curChar==[["]] or curChar==[[']] then - return decode_scanString(s,startPos) - end - if string.sub(s,startPos,startPos+1)=='/*' then - return json.decode(s, decode_scanComment(s,startPos)) - end - -- Otherwise, it must be a constant - return decode_scanConstant(s,startPos) - end - - --- The null function allows one to specify a null value in an associative array (which is otherwise - -- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null } - function json.null() - return json.null -- so json.null() will also return null ;-) - end - ----------------------------------------------------------------------------- - -- Internal, PRIVATE functions. - -- Following a Python-like convention, I have prefixed all these 'PRIVATE' - -- functions with an underscore. - ----------------------------------------------------------------------------- - - --- Scans an array from JSON into a Lua object - -- startPos begins at the start of the array. - -- Returns the array and the next starting position - -- @param s The string being scanned. - -- @param startPos The starting position for the scan. - -- @return table, int The scanned array as a table, and the position of the next character to scan. - function decode_scanArray(s,startPos) - local array = {} -- The return value - local stringLen = string.len(s) - assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) - startPos = startPos + 1 - -- Infinite loop for array elements - local index = 1 - repeat - startPos = decode_scanWhitespace(s,startPos) - assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') - local curChar = string.sub(s,startPos,startPos) - if (curChar==']') then - return array, startPos+1 - end - if (curChar==',') then - startPos = decode_scanWhitespace(s,startPos+1) - end - assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') - local object - object, startPos = json.decode(s,startPos) - array[index] = object - index = index + 1 - until false - end - - --- Scans a comment and discards the comment. - -- Returns the position of the next character following the comment. - -- @param string s The JSON string to scan. - -- @param int startPos The starting position of the comment - function decode_scanComment(s, startPos) - assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) - local endPos = string.find(s,'*/',startPos+2) - assert(endPos~=nil, "Unterminated comment in string at " .. startPos) - return endPos+2 - end - - --- Scans for given constants: true, false or null - -- Returns the appropriate Lua type, and the position of the next character to read. - -- @param s The string being scanned. - -- @param startPos The position in the string at which to start scanning. - -- @return object, int The object (true, false or nil) and the position at which the next character should be - -- scanned. - function decode_scanConstant(s, startPos) - local consts = { ["true"] = true, ["false"] = false, ["null"] = nil } - local constNames = {"true","false","null"} - - for i,k in pairs(constNames) do - if string.sub(s,startPos, startPos + string.len(k) -1 )==k then - return consts[k], startPos + string.len(k) - end - end - assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) - end - - --- Scans a number from the JSON encoded string. - -- (in fact, also is able to scan numeric +- eqns, which is not - -- in the JSON spec.) - -- Returns the number, and the position of the next character - -- after the number. - -- @param s The string being scanned. - -- @param startPos The position at which to start scanning. - -- @return number, int The extracted number and the position of the next character to scan. - function decode_scanNumber(s,startPos) - local endPos = startPos+1 - local stringLen = string.len(s) - local acceptableChars = "+-0123456789.e" - while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) - and endPos<=stringLen - ) do - endPos = endPos + 1 - end - -- local stringValue = 'return ' .. string.sub(s, startPos, endPos - 1) - -- local stringEval = loadstring(stringValue) - -- assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) - local numberValue = string.sub(s, startPos, endPos - 1) - return numberValue, endPos - end - - --- Scans a JSON object into a Lua object. - -- startPos begins at the start of the object. - -- Returns the object and the next starting position. - -- @param s The string being scanned. - -- @param startPos The starting position of the scan. - -- @return table, int The scanned object as a table and the position of the next character to scan. - function decode_scanObject(s,startPos) - local object = {} - local stringLen = string.len(s) - local key, value - assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s) - startPos = startPos + 1 - repeat - startPos = decode_scanWhitespace(s,startPos) - assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') - local curChar = string.sub(s,startPos,startPos) - if (curChar=='}') then - return object,startPos+1 - end - if (curChar==',') then - startPos = decode_scanWhitespace(s,startPos+1) - end - assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') - -- Scan the key - key, startPos = json.decode(s,startPos) - assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) - startPos = decode_scanWhitespace(s,startPos) - assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) - assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) - startPos = decode_scanWhitespace(s,startPos+1) - assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) - value, startPos = json.decode(s,startPos) - object[key]=value - until false -- infinite loop while key-value pairs are found - end - - -- START SoniEx2 - -- Initialize some things used by decode_scanString - -- You know, for efficiency - local escapeSequences = { - ["\\t"] = "\t", - ["\\f"] = "\f", - ["\\r"] = "\r", - ["\\n"] = "\n", - ["\\b"] = "\b" - } - setmetatable(escapeSequences, {__index = function(t,k) - -- skip "\" aka strip escape - return string.sub(k,2) - end}) - -- END SoniEx2 - - --- Scans a JSON string from the opening inverted comma or single quote to the - -- end of the string. - -- Returns the string extracted as a Lua string, - -- and the position of the next non-string character - -- (after the closing inverted comma or single quote). - -- @param s The string being scanned. - -- @param startPos The starting position of the scan. - -- @return string, int The extracted string as a Lua string, and the next character to parse. - function decode_scanString(s,startPos) - assert(startPos, 'decode_scanString(..) called without start position') - local startChar = string.sub(s,startPos,startPos) - -- START SoniEx2 - -- PS: I don't think single quotes are valid JSON - assert(startChar == [["]] or startChar == [[']],'decode_scanString called for a non-string') - --assert(startPos, "String decoding failed: missing closing " .. startChar .. " for string at position " .. oldStart) - local t = {} - local i,j = startPos,startPos - while string.find(s, startChar, j+1) ~= j+1 do - local oldj = j - i,j = string.find(s, "\\.", j+1) - local x,y = string.find(s, startChar, oldj+1) - if not i or x < i then - i,j = x,y-1 - end - table.insert(t, string.sub(s, oldj+1, i-1)) - if string.sub(s, i, j) == "\\u" then - local a = string.sub(s,j+1,j+4) - j = j + 4 - local n = tonumber(a, 16) - assert(n, "String decoding failed: bad Unicode escape " .. a .. " at position " .. i .. " : " .. j) - -- math.floor(x/2^y) == lazy right shift - -- a % 2^b == bitwise_and(a, (2^b)-1) - -- 64 = 2^6 - -- 4096 = 2^12 (or 2^6 * 2^6) - local x - if n < 0x80 then - x = string.char(n % 0x80) - elseif n < 0x800 then - -- [110x xxxx] [10xx xxxx] - x = string.char(0xC0 + (math.floor(n/64) % 0x20), 0x80 + (n % 0x40)) - else - -- [1110 xxxx] [10xx xxxx] [10xx xxxx] - x = string.char(0xE0 + (math.floor(n/4096) % 0x10), 0x80 + (math.floor(n/64) % 0x40), 0x80 + (n % 0x40)) - end - table.insert(t, x) - else - table.insert(t, escapeSequences[string.sub(s, i, j)]) - end - end - table.insert(t,string.sub(j, j+1)) - assert(string.find(s, startChar, j+1), "String decoding failed: missing closing " .. startChar .. " at position " .. j .. "(for string at position " .. startPos .. ")") - return table.concat(t,""), j+2 - -- END SoniEx2 - end - - --- Scans a JSON string skipping all whitespace from the current start position. - -- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached. - -- @param s The string being scanned - -- @param startPos The starting position where we should begin removing whitespace. - -- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string - -- was reached. - function decode_scanWhitespace(s,startPos) - local whitespace=" \n\r\t" - local stringLen = string.len(s) - while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do - startPos = startPos + 1 - end - return startPos - end - - --- Encodes a string to be JSON-compatible. - -- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-) - -- @param s The string to return as a JSON encoded (i.e. backquoted string) - -- @return The string appropriately escaped. - - local escapeList = { - ['"'] = '\\"', - ['\\'] = '\\\\', - ['/'] = '\\/', - ['\b'] = '\\b', - ['\f'] = '\\f', - ['\n'] = '\\n', - ['\r'] = '\\r', - ['\t'] = '\\t' - } - - function json_private.encodeString(s) - local s = tostring(s) - return s:gsub(".", function(c) return escapeList[c] end) -- SoniEx2: 5.0 compat - end - - -- Determines whether the given Lua type is an array or a table / dictionary. - -- We consider any table an array if it has indexes 1..n for its n items, and no - -- other data in the table. - -- I think this method is currently a little 'flaky', but can't think of a good way around it yet... - -- @param t The table to evaluate as an array - -- @return boolean, number True if the table can be represented as an array, false otherwise. If true, - -- the second returned value is the maximum - -- number of indexed elements in the array. - function isArray(t) - -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable - -- (with the possible exception of 'n') - if (t == json.EMPTY_ARRAY) then return true, 0 end - if (t == json.EMPTY_OBJECT) then return false end - - local maxIndex = 0 - for k,v in pairs(t) do - if (type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair - if (not isEncodable(v)) then return false end -- All array elements must be encodable - maxIndex = math.max(maxIndex,k) - else - if (k=='n') then - if v ~= (t.n or #t) then return false end -- False if n does not hold the number of elements - else -- Else of (k=='n') - if isEncodable(v) then return false end - end -- End of (k~='n') - end -- End of k,v not an indexed pair - end -- End of loop across all pairs - return true, maxIndex - end - - --- Determines whether the given Lua object / table / variable can be JSON encoded. The only - -- types that are JSON encodable are: string, boolean, number, nil, table and json.null. - -- In this implementation, all other types are ignored. - -- @param o The object to examine. - -- @return boolean True if the object should be JSON encoded, false if it should be ignored. - function isEncodable(o) - local t = type(o) - return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or - (t=='function' and o==json.null) - end - return json -end - --- Sourced from http://lua-users.org/wiki/BaseSixtyFour - --- Lua 5.1+ base64 v3.0 (c) 2009 by Alex Kloss --- licensed under the terms of the LGPL2 - --- character table string -local base64CharTable='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' - --- encoding -function this.base64encode(data) - return ((data:gsub('.', function(x) - local r,b='',x:byte() - for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end - return r; - end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) - if (#x < 6) then return '' end - local c=0 - for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end - return base64CharTable:sub(c+1,c+1) - end)..({ '', '==', '=' })[#data%3+1]) -end - --- decoding -function this.base64decode(data) - data = string.gsub(data, '[^'..base64CharTable..'=]', '') - return (data:gsub('.', function(x) - if (x == '=') then return '' end - local r,f='',(base64CharTable:find(x)-1) - for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end - return r; - end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) - if (#x ~= 8) then return '' end - local c=0 - for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end - return string.char(c) - end)) -end -return this; diff --git a/Debugger/LuaPanda.lua b/Debugger/LuaPanda.lua index 8161ce6..059126a 100644 --- a/Debugger/LuaPanda.lua +++ b/Debugger/LuaPanda.lua @@ -1,63 +1,69 @@ ---[[ -Tencent is pleased to support the open source community by making LuaPanda available. -Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. -Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at -https://opensource.org/licenses/BSD-3-Clause -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - -API: - LuaPanda.printToVSCode(logStr, printLevel, type) - 打印日志到VSCode Output下Debugger/log中 - @printLevel: debug(0)/info(1)/error(2) 这里的日志等级需高于launch.json中配置等级日志才能输出 (可选参数,默认0) - @type: 0:VSCode output console 1:VSCode tip (可选参数,默认0) - - LuaPanda.BP() - 强制打断点,可以在协程中使用。建议使用以下写法: - local ret = LuaPanda and LuaPanda.BP and LuaPanda.BP(); - 如果成功加入断点ret返回true,否则是nil - - LuaPanda.getInfo() - 返回获取调试器信息。包括版本号,是否使用lib库,系统是否支持loadstring(load方法)。返回值类型string, 推荐在调试控制台中使用。 - - LuaPanda.doctor() - 返回对当前环境的诊断信息,提示可能存在的问题。返回值类型string, 推荐在调试控制台中使用。 - - LuaPanda.getCWD() - 用户可以调用或在调试控制台中输出这个函数,返回帮助设置CWD的路径。比如 - cwd: F:/1/2/3/4/5 - getinfo: @../../../../../unreal_10/slua-unreal_1018/Content//Lua/TestArray.lua - format: f:/unreal_10/slua-unreal_1018/Content/Lua/TestArray.lua - cwd是vscode传来的配置路径。getinfo是通过getinfo获取到的正在运行的文件路径。format是经过 cwd + getinfo 整合后的格式化路径。 - format是传给VSCode的最终路径。 - 如果format路径和文件真实路径不符,导致VSCode找不到文件,通过调整工程中launch.json的cwd,使format路径和真实路径一致。 - 返回值类型string, 推荐在调试控制台中使用。 - - LuaPanda.getBreaks() - 获取断点信息,返回值类型string, 推荐在调试控制台中使用。 - - LuaPanda.serializeTable(table) - 把table序列化为字符串,返回值类型是string。 -]] +-- Tencent is pleased to support the open source community by making LuaPanda available. +-- Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +-- Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +-- https://opensource.org/licenses/BSD-3-Clause +-- Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +-- API: +-- LuaPanda.printToVSCode(logStr, printLevel, type) +-- 打印日志到VSCode Output下 LuaPanda Debugger 中 +-- @printLevel: debug(0)/info(1)/error(2) 这里的日志等级需高于launch.json中配置等级日志才能输出 (可选参数,默认0) +-- @type(可选参数,默认0): 0:VSCode output console 1:VSCode tip 2:VSCode debug console + +-- LuaPanda.BP() +-- 强制打断点,可以在协程中使用。建议使用以下写法: +-- local ret = LuaPanda and LuaPanda.BP and LuaPanda.BP(); +-- 如果成功加入断点ret返回true,否则是nil + +-- LuaPanda.getInfo() +-- 返回获取调试器信息。包括版本号,是否使用lib库,系统是否支持loadstring(load方法)。返回值类型string, 推荐在调试控制台中使用。 + +-- LuaPanda.testBreakpoint() +-- 测试断点,用于分析路径错误导致断点无法停止的情况。测试方法是 +-- 1. launch.json 中开启 stopOnEntry, 或者在代码中加入LuaPanda.BP()。 +-- 2. 运行调试器和 lua 进程,当停止在 stopOnEntry 或者 LuaPanda.BP() 时在调试控制台输入 LuaPanda.testBreakpoint() +-- 3. 根据提示更新断点后再次输入 LuaPanda.testBreakpoint()。此时系统会输出一些提示,帮助用户分析断点可能无法停止的原因。 + +-- LuaPanda.doctor() +-- 返回对当前环境的诊断信息,提示可能存在的问题。返回值类型string, 推荐在调试控制台中使用。 + +-- LuaPanda.getBreaks() +-- 获取断点信息,推荐在调试控制台中使用。 + +-- LuaPanda.serializeTable(table) +-- 把table序列化为字符串,返回值类型是string。 + +-- LuaPanda.stopAttach() +-- 断开连接,停止attach,本次被调试程序运行过程无法再次进行attach连接。 + +-- 其他说明: +-- 关于真机调试,首次使用真机调试时要注意下方"用户设置项"中的配置 +-- 1. 确定 attach 开关打开: openAttachMode = true; 这样可以避免先启动手机app之后启动调试器无法连接。 +-- 2. 把连接时间放长: connectTimeoutSec 设置为 0.5 或者 1。首次尝试真机调试时这个值可以设置大一点,之后再根据自己的网络状况向下调整。 +-- 调试方法可以参考 github 文档 --用户设置项 local openAttachMode = true; --是否开启attach模式。attach模式开启后可以在任意时刻启动vscode连接调试。缺点是没有连接调试时也会略降低lua执行效率(会不断进行attach请求) local attachInterval = 1; --attach间隔时间(s) +local connectTimeoutSec = 0.005; --lua进程作为Client时, 连接超时时间, 单位s. 时间过长等待attach时会造成卡顿,时间过短可能无法连接。建议值0.005 - 0.05 +local listeningTimeoutSec = 0.5; -- lua进程作为Server时,连接超时时间, 单位s. 时间过长等待attach时会造成卡顿,时间过短可能无法连接。建议值0.1 - 1 +local userDotInRequire = true; --兼容require中使用 require(a.b) 和 require(a/b) 的形式引用文件夹中的文件,默认无需修改 +local traversalUserData = false; --如果可以的话(取决于userdata原表中的__pairs),展示userdata中的元素。 如果在调试器中展开userdata时有错误,请关闭此项. local customGetSocketInstance = nil; --支持用户实现一个自定义调用luasocket的函数,函数返回值必须是一个socket实例。例: function() return require("socket.core").tcp() end; local consoleLogLevel = 2; --打印在控制台(print)的日志等级 0 : all/ 1: info/ 2: error. -local connectTimeoutSec = 0.005; --等待连接超时时间, 单位s. 时间过长等待attach时会造成卡顿,时间过短可能无法连接。建议值0.005 - 0.05 --用户设置项END -local debuggerVer = "2.3.0"; --debugger版本号 +local debuggerVer = "3.3.1"; --debugger版本号 LuaPanda = {}; local this = LuaPanda; -local tools = require("DebugTools"); --引用的开源工具,包括json解析和table展开工具等 +local tools = {}; --引用的开源工具,包括json解析和table展开工具等 this.tools = tools; this.curStackId = 0; --json处理 -local json = tools.createJson() +local json; --hook状态列表 local hookState = { - DISCONNECT_HOOK = 0, --断开连接 + DISCONNECT_HOOK = 0, --断开连接 LITE_HOOK = 1, --全局无断点 MID_HOOK = 2, --全局有断点,本文件无断点 ALL_HOOK = 3, --本文件有断点 @@ -87,7 +93,7 @@ local breaks = {}; --保存断点的数组 this.breaks = breaks; --供hookLib调用 local recCallbackId = ""; --VSCode端传过来的配置,在VSCode端的launch配置,传过来并赋值 -local luaFileExtension = ""; --脚本后缀 +local luaFileExtension = ""; --vscode传过来的脚本后缀 local cwd = ""; --工作路径 local DebuggerFileName = ""; --Debugger文件名(原始,未经path处理), 函数中会自动获取 local DebuggerToolsName = ""; @@ -95,13 +101,16 @@ local lastRunFunction = {}; --上一个执行过的函数。在有些复杂 local currentCallStack = {}; --获取当前调用堆栈信息 local hitBP = false; --BP()中的强制断点命中标记 local TempFilePath_luaString = ""; --VSCode端配置的临时文件存放路径 -local connectHost; --记录连接端IP -local connectPort; --记录连接端口号 -local sock; --tcp socket +local recordHost; --记录连接端IP +local recordPort; --记录连接端口号 +local sock; --lua socket 文件描述符 +local server; --server 描述符 local OSType; --VSCode识别出的系统类型,也可以自行设置。Windows_NT | Linux | Darwin local clibPath; --chook库在VScode端的路径,也可自行设置。 local hookLib; --chook库的引用实例 local adapterVer; --VScode传来的adapter版本号 +local truncatedOPath; --VScode中用户设置的用于截断opath路径的标志,注意这里可以接受lua魔法字符 +local distinguishSameNameFile = false; --是否区分lua同名文件中的断点,在VScode launch.json 中 distinguishSameNameFile 控制 --标记位 local logLevel = 1; --日志等级all/info/error. 此设置对应的是VSCode端设置的日志等级. local variableRefIdx = 1; --变量索引 @@ -109,7 +118,7 @@ local variableRefTab = {}; --变量记录table local lastRunFilePath = ""; --最后执行的文件路径 local pathCaseSensitivity = true; --路径是否发大小写敏感,这个选项接收VScode设置,请勿在此处更改 local recvMsgQueue = {}; --接收的消息队列 -local coroutinePool = {}; --保存用户协程的队列 +local coroutinePool = setmetatable({}, {__mode = "v"}); --保存用户协程的队列 local winDiskSymbolUpper = false;--设置win下盘符的大小写。以此确保从VSCode中传入的断点路径,cwd和从lua虚拟机获得的文件路径盘符大小写一致 local isNeedB64EncodeStr = false;-- 记录是否使用base64编码字符串 local loadclibErrReason = 'launch.json文件的配置项useCHook被设置为false.'; @@ -120,6 +129,9 @@ local isAbsolutePath = false; local stopOnEntry; --用户在VSCode端设置的是否打开stopOnEntry local userSetUseClib; --用户在VSCode端设置的是否是用clib库 local autoPathMode = false; +local autoExt; --调试器启动时自动获取到的后缀, 用于检测lua虚拟机返回的路径是否带有文件后缀。他可以是空值或者".lua"等 +local luaProcessAsServer; +local testBreakpointFlag = false; -- 测试断点的标志位。结合 LuaPanda.testBreakpoint() 测试断点无法停止的原因 --Step控制标记位 local stepOverCounter = 0; --STEPOVER over计数器 local stepOutCounter = 0; --STEPOVER out计数器 @@ -127,12 +139,17 @@ local HOOK_LEVEL = 3; --调用栈偏移量,使用clib时为3,lua local isUseLoadstring = 0; local debugger_loadString; --临时变量 +local recordBreakPointPath; --记录最后一个[可能命中]的断点,用于getInfo以及doctor的断点测试 local coroutineCreate; --用来记录lua原始的coroutine.create函数 local stopConnectTime = 0; --用来临时记录stop断开连接的时间 local isInMainThread; local receiveMsgTimer = 0; +local isUserSetClibPath = false; --用户是否在本文件中自设了clib路径 +local hitBpTwiceCheck; -- 命中断点的Vscode校验结果,默认true (true是命中,false是未命中) local formatPathCache = {}; -- getinfo -> format -local isUserSetClibPath = false; --用户是否在本文件中自设了clib路径 +function this.formatPathCache() return formatPathCache; end +local fakeBreakPointCache = {}; --其中用 路径-{行号列表} 形式保存错误命中信息 +function this.fakeBreakPointCache() return fakeBreakPointCache; end --5.1/5.3兼容 if _VERSION == "Lua 5.1" then debugger_loadString = loadstring; @@ -156,13 +173,58 @@ local env = setmetatable({ }, { -- 流程 ----------------------------------------------------------------------------- +---this.bindServer 当lua进程作为Server时,server绑定函数 +--- server 在bind时创建, 连接成功后关闭listen , disconnect时置空。reconnect时会查询server,没有的话重新绑定,如果已存在直接accept +function this.bindServer(host, port) + server = sock + server:settimeout(listeningTimeoutSec); + assert(server:bind(host, port)); + server:setoption("reuseaddr", true); --防止已连接状态下新的连接进入,不再reuse + assert(server:listen(0)); +end + +-- 以lua作为服务端的形式启动调试器 +-- @host 绑定ip , 默认 0.0.0.0 +-- @port 绑定port, 默认 8818 +function this.startServer(host, port) + host = tostring(host or "0.0.0.0") ; + port = tonumber(port) or 8818; + luaProcessAsServer = true; + this.printToConsole("Debugger start as SERVER. bind host:" .. host .. " port:".. tostring(port), 1); + if sock ~= nil then + this.printToConsole("[Warning] 调试器已经启动,请不要再次调用start()" , 1); + return; + end + + --尝试初次连接 + this.changeRunState(runState.DISCONNECT); + if not this.reGetSock() then + this.printToConsole("[Error] LuaPanda debugger start success , but get Socket fail , please install luasocket!", 2); + return; + end + recordHost = host; + recordPort = port; + + this.bindServer(recordHost, recordPort); + local connectSuccess = server:accept(); + sock = connectSuccess; + + if connectSuccess then + this.printToConsole("First connect success!"); + this.connectSuccess(); + else + this.printToConsole("First connect failed!"); + this.changeHookState(hookState.DISCONNECT_HOOK); + end +end + -- 启动调试器 -- @host adapter端ip, 默认127.0.0.1 -- @port adapter端port ,默认8818 function this.start(host, port) host = tostring(host or "127.0.0.1") ; port = tonumber(port) or 8818; - this.printToConsole("Debugger start. connect host:" .. host .. " port:".. tostring(port), 1); + this.printToConsole("Debugger start as CLIENT. connect host:" .. host .. " port:".. tostring(port), 1); if sock ~= nil then this.printToConsole("[Warning] 调试器已经启动,请不要再次调用start()" , 1); return; @@ -174,20 +236,39 @@ function this.start(host, port) this.printToConsole("[Error] Start debugger but get Socket fail , please install luasocket!", 2); return; end - connectHost = host; - connectPort = port; - local sockSuccess = sock and sock:connect(connectHost, connectPort); - if sockSuccess ~= nil then - this.printToConsole("first connect success!"); + recordHost = host; + recordPort = port; + + sock:settimeout(connectTimeoutSec); + local connectSuccess = this.sockConnect(sock); + + if connectSuccess then + this.printToConsole("First connect success!"); this.connectSuccess(); else - this.printToConsole("first connect failed!"); + this.printToConsole("First connect failed!"); this.changeHookState(hookState.DISCONNECT_HOOK); end end +function this.sockConnect(sock) + if sock then + local connectSuccess, status = sock:connect(recordHost, recordPort); + if status == "connection refused" or (not connectSuccess and status == "already connected") then + this.reGetSock(); + end + + return connectSuccess + end + return nil; +end + -- 连接成功,开始初始化 function this.connectSuccess() + if server then + server:close(); -- 停止listen + end + this.changeRunState(runState.WAIT_CMD); this.printToConsole("connectSuccess", 1); --设置初始状态 @@ -198,8 +279,9 @@ function this.connectSuccess() local info = debug.getinfo(1, "S") for k,v in pairs(info) do if k == "source" then - DebuggerFileName = v; - this.printToVSCode("DebuggerFileName:" .. tostring(DebuggerFileName)); + DebuggerFileName = tostring(v); + -- 从代码中去后缀 + autoExt = DebuggerFileName:gsub('.*[Ll][Uu][Aa][Pp][Aa][Nn][Dd][Aa]', ''); if hookLib ~= nil then hookLib.sync_debugger_path(DebuggerFileName); @@ -223,20 +305,7 @@ function this.connectSuccess() this.changeHookState(hookState.ALL_HOOK); if hookLib == nil then --协程调试 - if coroutineCreate == nil and type(coroutine.create) == "function" then - this.printToConsole("change coroutine.create"); - coroutineCreate = coroutine.create; - coroutine.create = function(...) - local co = coroutineCreate(...) - table.insert(coroutinePool, co); - --运行状态下,创建协程即启动hook - this.changeCoroutineHookState(); - return co; - end - else - this.printToConsole("restart coroutine"); - this.changeCoroutineHookState(); - end + this.changeCoroutinesHookState(); end end @@ -248,6 +317,7 @@ function this.clearData() -- reset breaks breaks = {}; formatPathCache = {}; + fakeBreakPointCache = {}; this.breaks = breaks; if hookLib ~= nil then hookLib.sync_breakpoints(); --清空断点信息 @@ -255,6 +325,20 @@ function this.clearData() end end +-- 本次连接过程中停止attach ,以提高运行效率 +function this.stopAttach() + openAttachMode = false; + this.printToConsole("Debugger stopAttach", 1); + this.clearData() + this.changeHookState( hookState.DISCONNECT_HOOK ); + stopConnectTime = os.time(); + this.changeRunState(runState.DISCONNECT); + if sock ~= nil then + sock:close(); + if luaProcessAsServer and server then server = nil; end; + end +end + --断开连接 function this.disconnect() this.printToConsole("Debugger disconnect", 1); @@ -265,9 +349,11 @@ function this.disconnect() if sock ~= nil then sock:close(); + sock = nil; + server = nil; end - if connectPort == nil or connectHost == nil then + if recordHost == nil or recordPort == nil then --异常情况处理, 在调用LuaPanda.start()前首先调用了LuaPanda.disconnect() this.printToConsole("[Warning] User call LuaPanda.disconnect() before set debug ip & port, please call LuaPanda.start() first!", 2); return; @@ -276,6 +362,22 @@ function this.disconnect() this.reGetSock(); end +function this.replaceCoroutineFuncs() + if hookLib == nil then + if coroutineCreate == nil and type(coroutine.create) == "function" then + this.printToConsole("change coroutine.create"); + coroutineCreate = coroutine.create; + coroutine.create = function(...) + local co = coroutineCreate(...) + table.insert(coroutinePool, co); + --运行状态下,创建协程即启动hook + this.changeCoroutineHookState(co, currentHookState); + return co; + end + end + end +end + ----------------------------------------------------------------------------- -- 调试器通用方法 ----------------------------------------------------------------------------- @@ -284,9 +386,26 @@ function this.getBreaks() return breaks; end +---testBreakpoint 测试断点 +function this.testBreakpoint() + if recordBreakPointPath and recordBreakPointPath ~= "" then + -- testBreakpointFlag = false; + return this.breakpointTestInfo(); + else + local strTable = {}; + strTable[#strTable + 1] = "正在准备进行断点测试,请按照如下步骤操作\n" + strTable[#strTable + 1] = "1. 请[删除]当前项目中所有断点;\n" + strTable[#strTable + 1] = "2. 在当前停止行打一个断点;\n" + strTable[#strTable + 1] = "3. 再次运行 'LuaPanda.testBreakpoint()'" + testBreakpointFlag = true; + + return table.concat(strTable); + end +end + -- 返回路径相关信息 -- cwd:配置的工程路径 | info["source"]:通过 debug.getinfo 获得执行文件的路径 | format:格式化后的文件路径 -function this.getCWD() +function this.breakpointTestInfo() local ly = this.getSpecificFunctionStackLevel(lastRunFunction.func); if type(ly) ~= "number" then ly = 2; @@ -296,7 +415,54 @@ function this.getCWD() runSource = this.getPath(tostring(hookLib.get_last_source())); end local info = debug.getinfo(ly, "S"); - return "cwd: "..cwd .."\ngetinfo: ".. info["source"] .. "\nformat: " .. tostring(runSource) ; + local NormalizedPath = this.formatOpath(info["source"]); + NormalizedPath = this.truncatedPath(NormalizedPath, truncatedOPath); + + local strTable = {} + local FormatedPath = tostring(runSource); + strTable[#strTable + 1] = "\n- BreakPoint Test:" + strTable[#strTable + 1] = "\nUser set lua extension: ." .. tostring(luaFileExtension); + strTable[#strTable + 1] = "\nAuto get lua extension: " .. tostring(autoExt); + if truncatedOPath and truncatedOPath ~= '' then + strTable[#strTable + 1] = "\nUser set truncatedOPath: " .. truncatedOPath; + end + strTable[#strTable + 1] = "\nGetInfo: ".. info["source"]; + strTable[#strTable + 1] = "\nNormalized: " .. NormalizedPath; + strTable[#strTable + 1] = "\nFormated: " .. FormatedPath; + if recordBreakPointPath and recordBreakPointPath ~= "" then + strTable[#strTable + 1] = "\nBreakpoint: " .. recordBreakPointPath; + end + + if not autoPathMode then + if isAbsolutePath then + strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的是绝对路径,Formated使用GetInfo路径。" .. winDiskSymbolTip; + else + strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的路径(GetInfo)是相对路径,调试器运行依赖的绝对路径(Formated)是来源于cwd+GetInfo拼接。如Formated路径错误请尝试调整cwd或改变VSCode打开文件夹的位置。也可以在Formated对应的文件下打一个断点,调整直到Formated和Breaks Info中断点路径完全一致。" .. winDiskSymbolTip; + end + else + strTable[#strTable + 1] = "\n说明:自动路径(autoPathMode)模式已开启。"; + if recordBreakPointPath and recordBreakPointPath ~= "" then + if string.find(recordBreakPointPath , FormatedPath, (-1) * string.len(FormatedPath) , true) then + -- 短路径断点命中 + if distinguishSameNameFile == false then + strTable[#strTable + 1] = "本文件中断点可正常命中。" + strTable[#strTable + 1] = "同名文件中的断点识别(distinguishSameNameFile) 未开启,请确保 VSCode 断点不要存在于同名 lua 文件中。"; + else + strTable[#strTable + 1] = "同名文件中的断点识别(distinguishSameNameFile) 已开启。"; + if string.find(recordBreakPointPath, NormalizedPath, 1, true) then + strTable[#strTable + 1] = "本文件中断点可被正常命中" + else + strTable[#strTable + 1] = "断点可能无法被命中,因为 lua 虚拟机中获得的路径 Normalized 不是断点路径 Breakpoint 的子串。 如有需要,可以在 launch.json 中设置 truncatedOPath 来去除 Normalized 部分路径。" + end + end + else + strTable[#strTable + 1] = "断点未被命中,原因是 Formated 不是 Breakpoint 路径的子串,或者 Formated 和 Breakpoint 文件后缀不一致" + end + else + strTable[#strTable + 1] = "如果要进行断点测试,请使用 LuaPanda.testBreakpoint()。" + end + end + return table.concat(strTable) end --返回版本号等配置 @@ -307,7 +473,7 @@ function this.getBaseInfo() jitVer = "," .. tostring(jit.version); end - strTable[#strTable + 1] = "Lua Ver:" .. _VERSION .. jitVer .." | adapterVer:" .. tostring(adapterVer) .. " | Debugger Ver:" .. tostring(debuggerVer); + strTable[#strTable + 1] = "Lua Ver:" .. _VERSION .. jitVer .." | Adapter Ver:" .. tostring(adapterVer) .. " | Debugger Ver:" .. tostring(debuggerVer); local moreInfoStr = ""; if hookLib ~= nil then local clibVer, forluaVer = hookLib.sync_getLibVersion(); @@ -325,7 +491,9 @@ function this.getBaseInfo() strTable[#strTable + 1] = " | supportREPL:".. tostring(outputIsUseLoadstring); strTable[#strTable + 1] = " | useBase64EncodeString:".. tostring(isNeedB64EncodeStr); - strTable[#strTable + 1] = " | codeEnv:" .. tostring(OSType) .. '\n'; + strTable[#strTable + 1] = " | codeEnv:" .. tostring(OSType); + strTable[#strTable + 1] = " | distinguishSameNameFile:" .. tostring(distinguishSameNameFile) .. '\n'; + strTable[#strTable + 1] = moreInfoStr; if OSTypeErrTip ~= nil and OSTypeErrTip ~= '' then strTable[#strTable + 1] = '\n' ..OSTypeErrTip; @@ -363,11 +531,23 @@ function this.doctor() local lua_ver; if _VERSION == "Lua 5.1" then lua_ver = "501"; + elseif _VERSION == "Lua 5.4" then + lua_ver = "504"; else lua_ver = "503"; end local x86Path = clibPath .. platform .."/x86/".. lua_ver .. clibExt; local x64Path = clibPath .. platform .."/x86_64/".. lua_ver .. clibExt; + local armPath = clibPath .. platform .."/arm_64/".. lua_ver .. clibExt; + + if platform == "mac" then + -- mac下先检测arm库 + strTable[#strTable + 1] = "尝试引用arm库: ".. armPath; + if this.tryRequireClib("libpdebug", armPath) then + strTable[#strTable + 1] = "\n引用成功"; + return; + end + end strTable[#strTable + 1] = "尝试引用x64库: ".. x64Path; if this.tryRequireClib("libpdebug", x64Path) then @@ -410,7 +590,7 @@ function this.doctor() --和断点匹配了 fileMatch = true; -- retStr = retStr .. "\n请对比如下路径:\n"; - strTable[#strTable + 1] = this.getCWD(); + strTable[#strTable + 1] = this.breakpointTestInfo(); strTable[#strTable + 1] = "\nfilepath: " .. key; if isAbsolutePath then strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的是绝对路径,format使用getinfo路径。"; @@ -478,25 +658,15 @@ function this.getInfo() strTable[#strTable + 1] = "\n\n- Path Info: \n"; strTable[#strTable + 1] = "clibPath: " .. tostring(clibPath) .. '\n'; - strTable[#strTable + 1] = "debugger: " .. this.getPath(DebuggerFileName) .. '\n'; - strTable[#strTable + 1] = this.getCWD(); - - if not autoPathMode then - if isAbsolutePath then - strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的是绝对路径,format使用getinfo路径。" .. winDiskSymbolTip; - else - strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的路径(getinfo)是相对路径,调试器运行依赖的绝对路径(format)是来源于cwd+getinfo拼接。如format路径错误请尝试调整cwd或改变VSCode打开文件夹的位置。也可以在format对应的文件下打一个断点,调整直到format和Breaks Info中断点路径完全一致。" .. winDiskSymbolTip; - end - else - strTable[#strTable + 1] = "\n说明:已开启autoPathMode自动路径模式,调试器会根据getinfo获得的文件名自动查找文件位置,请确保VSCode打开的工程中不存在同名lua文件。"; - end + strTable[#strTable + 1] = "debugger: " .. DebuggerFileName .. " | " .. this.getPath(DebuggerFileName) .. '\n'; + strTable[#strTable + 1] = "cwd : " .. cwd .. '\n'; + strTable[#strTable + 1] = this.breakpointTestInfo(); if pathErrTip ~= nil and pathErrTip ~= '' then strTable[#strTable + 1] = '\n' .. pathErrTip; end - strTable[#strTable + 1] = "\n\n- Breaks Info: \n"; - strTable[#strTable + 1] = this.serializeTable(this.getBreaks(), "breaks"); + strTable[#strTable + 1] = "\n\n- Breaks Info: \nUse 'LuaPanda.getBreaks()' to watch."; return table.concat(strTable); end @@ -632,9 +802,11 @@ function this.printToVSCode(str, printLevel, type) local sendTab = {}; sendTab["callbackId"] = "0"; if type == 0 then - sendTab["cmd"] = "log"; - else + sendTab["cmd"] = "output"; + elseif type == 1 then sendTab["cmd"] = "tip"; + else -- type == 2 + sendTab["cmd"] = "debug_console"; end sendTab["info"] = {}; sendTab["info"]["logInfo"] = tostring(str); @@ -708,6 +880,46 @@ end function this.setCacheFormatPath(source, dest) formatPathCache[source] = dest; end + +-- 处理 opath(info.source) 的函数, 生成一个规范的路径函数(和VScode端checkRightPath逻辑完全一致) +function this.formatOpath(opath) + -- delete @ + if opath:sub(1,1) == '@' then + opath = opath:sub(2); + end + -- change ./ to / + if opath:sub(1,2) == './' then + opath = opath:sub(2); + end + + opath = this.genUnifiedPath(opath); + + -- lower + if pathCaseSensitivity == false then + opath = string.lower(opath); + end + --把filename去除后缀 + if autoExt == nil or autoExt == '' then + -- 在虚拟机返回路径没有后缀的情况下,用户必须自设后缀 + -- 确定filePath中最后一个.xxx 不等于用户配置的后缀, 则把所有的. 转为 / + if not opath:find(luaFileExtension , (-1) * luaFileExtension:len(), true) then + -- getinfo 路径没有后缀,把 . 全部替换成 / ,我们不允许用户在文件(或文件夹)名称中出现"." , 因为无法区分 + opath = string.gsub(opath, "%.", "/"); + else + -- 有后缀,那么把除后缀外的部分中的. 转为 / + opath = this.changePotToSep(opath, luaFileExtension); + end + else + -- 虚拟机路径有后缀 + opath = this.changePotToSep(opath, autoExt); + end + + -- 截取 路径+文件名 (不带后缀) + -- change pot to / + -- opath = string.gsub(opath, "%.", "/"); + return opath; +end + ----------------------------------------------------------------------------- -- 内存相关 ----------------------------------------------------------------------------- @@ -726,22 +938,23 @@ end ----------------------------------------------------------------------------- -- 刷新socket -- @return true/false 刷新成功/失败 -function this.reGetSock() +function this.reGetSock() + if server then return true end + if sock ~= nil then pcall(function() sock:close() end); end - --call ue4 luasocket + + --call slua-unreal luasocket sock = lua_extension and lua_extension.luasocket and lua_extension.luasocket().tcp(); if sock == nil then - --call u3d luasocket + --call normal luasocket if pcall(function() sock = require("socket.core").tcp(); end) then this.printToConsole("reGetSock success"); - sock:settimeout(connectTimeoutSec); else --call custom function to get socket if customGetSocketInstance and pcall( function() sock = customGetSocketInstance(); end ) then this.printToConsole("reGetSock custom success"); - sock:settimeout(connectTimeoutSec); else this.printToConsole("[Error] reGetSock fail", 2); return false; @@ -750,35 +963,51 @@ function this.reGetSock() else --set ue4 luasocket this.printToConsole("reGetSock ue4 success"); - sock:settimeout(connectTimeoutSec); end return true; end -- 定时(以函数return为时机) 进行attach连接 +-- 返回值 hook 可以继续往下走时返回1 ,无需继续时返回0 function this.reConnect() if currentHookState == hookState.DISCONNECT_HOOK then if os.time() - stopConnectTime < attachInterval then - this.printToConsole("Reconnect time less than 1s"); - this.printToConsole("os.time:".. os.time() .. " | stopConnectTime:" ..stopConnectTime); - return 1; + -- 未到重连时间 + -- this.printToConsole("Reconnect time less than 1s"); + -- this.printToConsole("os.time:".. os.time() .. " | stopConnectTime:" ..stopConnectTime); + return 0; end - + this.printToConsole("Reconnect !"); if sock == nil then this.reGetSock(); end - local sockSuccess, status = sock:connect(connectHost, connectPort); - if sockSuccess == 1 or status == "already connected" then + local connectSuccess; + if luaProcessAsServer == true and currentRunState == runState.DISCONNECT then + -- 在 Server 模式下,以及当前处于未连接状态下,才尝试accept新链接。如果不判断可能会出现多次连接,导致sock被覆盖 + if server == nil then + this.bindServer(recordHost, recordPort); + end + + sock = server:accept(); + connectSuccess = sock; + else + sock:settimeout(connectTimeoutSec); + connectSuccess = this.sockConnect(sock); + end + + if connectSuccess then this.printToConsole("reconnect success"); this.connectSuccess(); + return 1; else - this.printToConsole("reconnect failed . retCode:" .. tostring(sockSuccess) .. " status:" .. status); + this.printToConsole("reconnect failed" ); stopConnectTime = os.time(); + return 0; end - return 1; end - return 0; + -- 不必重连,正常继续运行 + return 1; end -- 向adapter发消息 @@ -824,7 +1053,22 @@ function this.dataProcess( dataStr ) end if dataTable.cmd == "continue" then - this.changeRunState(runState.RUN); + local info = dataTable.info; + if info.isFakeHit == "true" and info.fakeBKPath and info.fakeBKLine then + -- 设置校验结果标志位,以便hook流程知道结果 + hitBpTwiceCheck = false; + if hookLib ~= nil and hookLib.set_bp_twice_check_res then + -- 把结果同步给C + hookLib.set_bp_twice_check_res(0); + end + -- 把假断点的信息加入cache + if nil == fakeBreakPointCache[info.fakeBKPath] then + fakeBreakPointCache[info.fakeBKPath] = {}; + end + table.insert(fakeBreakPointCache[info.fakeBKPath] ,info.fakeBKLine); + else + this.changeRunState(runState.RUN); + end local msgTab = this.getMsgTable("continue", this.getCallbackId()); this.sendMsg(msgTab); @@ -848,14 +1092,42 @@ function this.dataProcess( dataStr ) elseif dataTable.cmd == "setBreakPoint" then this.printToVSCode("dataTable.cmd == setBreakPoint"); + -- 设置断点时,把 fakeBreakPointCache 清空。这是一个简单的做法,也可以清除具体的条目 + fakeBreakPointCache = {} local bkPath = dataTable.info.path; bkPath = this.genUnifiedPath(bkPath); + if testBreakpointFlag then + recordBreakPointPath = bkPath; + end if autoPathMode then -- 自动路径模式下,仅保留文件名 - bkPath = this.getFilenameFromPath(bkPath); + -- table[文件名.后缀] -- [fullpath] -- [line , type] + -- | - [fullpath] -- [line , type] + + local bkShortPath = this.getFilenameFromPath(bkPath); + if breaks[bkShortPath] == nil then + breaks[bkShortPath] = {}; + end + breaks[bkShortPath][bkPath] = dataTable.info.bks; + -- 当v为空时,从断点列表中去除文件 + for k, v in pairs(breaks[bkShortPath]) do + if next(v) == nil then + breaks[bkShortPath][k] = nil; + end + end + else + if breaks[bkPath] == nil then + breaks[bkPath] = {}; + end + -- 两级 bk path 是为了和自动路径模式结构保持一致 + breaks[bkPath][bkPath] = dataTable.info.bks; + -- 当v为空时,从断点列表中去除文件 + for k, v in pairs(breaks[bkPath]) do + if next(v) == nil then + breaks[bkPath][k] = nil; + end + end end - this.printToVSCode("setBreakPoint path:"..tostring(bkPath)); - breaks[bkPath] = dataTable.info.bks; -- 当v为空时,从断点列表中去除文件 for k, v in pairs(breaks) do @@ -920,7 +1192,7 @@ function this.dataProcess( dataStr ) elseif tonumber(newValue) and needFindVariable == true then newValue = tonumber(newValue); needFindVariable = false; end - -- 如果新值是基础类型,则不需边历 + -- 如果新值是基础类型,则不需遍历 if dataTable.info.stackId ~= nil and tonumber(dataTable.info.stackId) ~= nil and tonumber(dataTable.info.stackId) > 1 then this.curStackId = tonumber(dataTable.info.stackId); else @@ -1007,7 +1279,7 @@ function this.dataProcess( dataStr ) isNeedB64EncodeStr = false; end --path - luaFileExtension = dataTable.info.luaFileExtension + luaFileExtension = dataTable.info.luaFileExtension; local TempFilePath = dataTable.info.TempFilePath; if TempFilePath:sub(-1, -1) == [[\]] or TempFilePath:sub(-1, -1) == [[/]] then TempFilePath = TempFilePath:sub(1, -2); @@ -1025,10 +1297,18 @@ function this.dataProcess( dataStr ) if dataTable.info.pathCaseSensitivity == "true" then pathCaseSensitivity = true; + truncatedOPath = dataTable.info.truncatedOPath or ""; else pathCaseSensitivity = false; + truncatedOPath = string.lower(dataTable.info.truncatedOPath or ""); end - + + if dataTable.info.distinguishSameNameFile == "true" then + distinguishSameNameFile = true; + else + distinguishSameNameFile = false; + end + --OS type if nil == OSType then --用户未主动设置OSType, 接收VSCode传来的数据 @@ -1057,7 +1337,7 @@ function this.dataProcess( dataStr ) isUserSetClibPath = true; end - --查找c++的hook库是否存在 + --查找c++的hook库是否存在. 当lua5.4时默认不使用c库 if tostring(dataTable.info.useCHook) == "true" then userSetUseClib = true; --用户确定使用clib if isUserSetClibPath == true then --如果用户自设了clib路径 @@ -1077,17 +1357,25 @@ function this.dataProcess( dataStr ) local lua_ver; if _VERSION == "Lua 5.1" then lua_ver = "501"; + elseif _VERSION == "Lua 5.4" then + lua_ver = "504"; else lua_ver = "503"; end local x86Path = clibPath.. platform .."/x86/".. lua_ver .. clibExt; local x64Path = clibPath.. platform .."/x86_64/".. lua_ver .. clibExt; + local armPath = clibPath .. platform .."/arm_64/".. lua_ver .. clibExt; if luapanda_chook ~= nil then hookLib = luapanda_chook; else - if not(this.tryRequireClib("libpdebug", x64Path) or this.tryRequireClib("libpdebug", x86Path)) then + local requireCLibSuccess = false; + if platform == "mac" then + requireCLibSuccess = this.tryRequireClib("libpdebug", armPath) + end + + if not requireCLibSuccess and not(this.tryRequireClib("libpdebug", x64Path) or this.tryRequireClib("libpdebug", x86Path)) then this.printToVSCode("Require clib failed, use Lua to continue debug, use LuaPanda.doctor() for more information.", 1); end end @@ -1104,6 +1392,11 @@ function this.dataProcess( dataStr ) if hookLib ~= nil then isUseHookLib = 1; --同步数据给c hook + local luaVerTable = this.stringSplit(debuggerVer , '%.'); + local luaVerNum = luaVerTable[1] * 10000 + luaVerTable[2] * 100 + luaVerTable[3]; + if hookLib.sync_lua_debugger_ver then + hookLib.sync_lua_debugger_ver(luaVerNum); + end -- hookLib.sync_config(logLevel, pathCaseSensitivity and 1 or 0, autoPathMode and 1 or 0); hookLib.sync_config(logLevel, pathCaseSensitivity and 1 or 0); hookLib.sync_tempfile_path(TempFilePath_luaString); @@ -1154,7 +1447,9 @@ function this.dataProcess( dataStr ) --停止hook,已不在处理任何断点信息,也就不会产生日志等。发送消息后等待前端主动断开连接 local msgTab = this.getMsgTable("stopRun", this.getCallbackId()); this.sendMsg(msgTab); - this.disconnect(); + if not luaProcessAsServer then + this.disconnect(); + end elseif "LuaGarbageCollect" == dataTable.cmd then this.printToVSCode("collect garbage!"); collectgarbage("collect"); @@ -1271,7 +1566,7 @@ function this.receiveMessage( timeoutSec ) this.printToConsole("[debugger error]接收信息失败 | reason: socket == nil", 2); return; end - local response, err = sock:receive(); + local response, err = sock:receive("*l"); if response == nil then if err == "closed" then this.printToConsole("[debugger error]接收信息失败 | reason:"..err, 2); @@ -1351,44 +1646,76 @@ function this.getStackTable( level ) end local stackTab = {}; local userFuncSteakLevel = 0; --用户函数的steaklevel + local clevel = 0 repeat local info = debug.getinfo(functionLevel, "SlLnf") if info == nil then break; end - if info.source == "=[C]" then - break; - end - - local ss = {}; - ss.file = this.getPath(info); - ss.name = "文件名"; --这里要做截取 - ss.line = tostring(info.currentline); - --使用hookLib时,堆栈有偏移量,这里统一调用栈顶编号2 - local ssindex = functionLevel - 3; - if hookLib ~= nil then - ssindex = ssindex + 2; - end - ss.index = tostring(ssindex); - table.insert(stackTab,ss); - --把数据存入currentCallStack - local callStackInfo = {}; - callStackInfo.name = ss.file; - callStackInfo.line = ss.line; - callStackInfo.func = info.func; --保存的function - callStackInfo.realLy = functionLevel; --真实堆栈层functionLevel(仅debug时用) - table.insert(currentCallStack, callStackInfo); - - --level赋值 - if userFuncSteakLevel == 0 then - userFuncSteakLevel = functionLevel; + if info.source ~= "=[C]" then + local ss = {}; + ss.file = this.getPath(info); + local oPathFormated = this.formatOpath(info.source) ; --从lua虚拟机获得的原始路径, 它用于帮助定位VScode端原始lua文件的位置(存在重名文件的情况)。 + ss.oPath = this.truncatedPath(oPathFormated, truncatedOPath); + ss.name = "文件名"; --这里要做截取 + ss.line = tostring(info.currentline); + --使用hookLib时,堆栈有偏移量,这里统一调用栈顶编号2 + local ssindex = functionLevel - 3; + if hookLib ~= nil then + ssindex = ssindex + 2; + end + ss.index = tostring(ssindex); + table.insert(stackTab,ss); + --把数据存入currentCallStack + local callStackInfo = {}; + callStackInfo.name = ss.file; + callStackInfo.line = ss.line; + callStackInfo.func = info.func; --保存的function + callStackInfo.realLy = functionLevel; --真实堆栈层functionLevel(仅debug时用) + table.insert(currentCallStack, callStackInfo); + + --level赋值 + if userFuncSteakLevel == 0 then + userFuncSteakLevel = functionLevel; + end + else + local callStackInfo = {}; + callStackInfo.name = info.source; + callStackInfo.line = info.currentline; --C函数行号 + callStackInfo.func = info.func; --保存的function + callStackInfo.realLy = functionLevel; --真实堆栈层functionLevel(仅debug时用) + table.insert(currentCallStack, callStackInfo); + clevel = clevel + 1 end functionLevel = functionLevel + 1; until info == nil return stackTab, userFuncSteakLevel; end ---这个方法是根据工程中的cwd和luaFileExtension修改 +-- 把路径中去除后缀部分的.变为/, +-- @filePath 被替换的路径 +-- @ext 后缀(后缀前的 . 不会被替换) +function this.changePotToSep(filePath, ext) + local idx = filePath:find(ext, (-1) * ext:len() , true) + if idx then + local tmp = filePath:sub(1, idx - 1):gsub("%.", "/"); + filePath = tmp .. ext; + end + return filePath; +end + +--- this.truncatedPath 从 beTruncatedPath 字符串中去除 rep 匹配到的部分 +function this.truncatedPath(beTruncatedPath, rep) + if beTruncatedPath and beTruncatedPath ~= '' and rep and rep ~= "" then + local _, lastIdx = string.find(beTruncatedPath , rep); + if lastIdx then + beTruncatedPath = string.sub(beTruncatedPath, lastIdx + 1); + end + end + return beTruncatedPath; +end + +--这个方法是根据的cwd和luaFileExtension对getInfo获取到的路径进行标准化 -- @info getInfo获取的包含调用信息table function this.getPath( info ) local filePath = info; @@ -1401,9 +1728,37 @@ function this.getPath( info ) return cachePath; end - -- originalPath是getInfo的原始路径,后面用来填充缓存key + -- originalPath是getInfo的原始路径,后面用来填充路径缓存的key local originalPath = filePath; + --如果路径头部有@,去除 + if filePath:sub(1,1) == '@' then + filePath = filePath:sub(2); + end + + --如果路径头部有./,去除 + if filePath:sub(1,2) == './' then + filePath = filePath:sub(3); + end + -- getPath的参数路径可能来自于hook, 也可能是一个已标准的路径 + if userDotInRequire then + if autoExt == nil or autoExt == '' then + -- 在虚拟机返回路径没有后缀的情况下,用户必须自设后缀 + -- 确定filePath中最后一个.xxx 不等于用户配置的后缀, 则把所有的. 转为 / + if not filePath:find(luaFileExtension , (-1) * luaFileExtension:len(), true) then + -- getinfo 路径没有后缀,把 . 全部替换成 / ,我们不允许用户在文件(或文件夹)名称中出现"." , 因为无法区分 + filePath = string.gsub(filePath, "%.", "/"); + else + -- 有后缀,那么把除后缀外的部分中的. 转为 / + filePath = this.changePotToSep(filePath, luaFileExtension); + end + + else + -- 虚拟机路径有后缀 + filePath = this.changePotToSep(filePath, autoExt); + end + end + --后缀处理 if luaFileExtension ~= "" then --判断后缀中是否包含%1等魔法字符.用于从lua虚拟机获取到的路径含.的情况 @@ -1415,10 +1770,6 @@ function this.getPath( info ) end end - --如果路径头部有@,去除 - if filePath:sub(1,1) == '@' then - filePath = filePath:sub(2); - end if not autoPathMode then --绝对路径和相对路径的处理 | 若在Mac下以/开头,或者在Win下以*:开头,说明是绝对路径,不需要再拼。 @@ -1446,7 +1797,7 @@ function this.getPath( info ) return filePath; end ---从路径中获取文件名 +--从路径中获取[文件名.后缀] function this.getFilenameFromPath(path) if path == nil then return ''; @@ -1512,45 +1863,81 @@ function this.checkCurrentLayerisLua( checkLayer ) return nil; end +-- 在 fakeBreakPointCache 中查询此断点是否真实存在 +-- 因为同名文件的影响, 有些断点是命中错误的。经过VScode校验后,这些错误命中的断点信息被存在fakeBreakPointCache中 +function this.checkRealHitBreakpoint( oPath, line ) + -- 在假命中列表中搜索,如果本行有过假命中记录,返回 false + if oPath and fakeBreakPointCache[oPath] then + for _, value in ipairs(fakeBreakPointCache[oPath]) do + if tonumber(value) == tonumber(line) then + this.printToVSCode("cache hit bp in same name file. source:" .. tostring(oPath) .. " line:" .. tostring(line)); + return false; + end + end + end + return true; +end ------------------------断点处理------------------------- --- 参数info是当前堆栈信息 --- @info getInfo获取的当前调用信息 -function this.isHitBreakpoint( info ) - local curLine = tostring(info.currentline); - local breakpointPath = info.source; - local isPathHit = false; - +--- this.isHitBreakpoint 判断断点是否命中。这个方法在c mod以及lua中都有调用 +-- @param breakpointPath 文件名+后缀 +-- @param opath getinfo path +-- @param curLine 当前执行行号 +function this.isHitBreakpoint(breakpointPath, opath, curLine) if breaks[breakpointPath] then - isPathHit = true; - end - - if isPathHit then - for k,v in ipairs(breaks[breakpointPath]) do - if tostring(v["line"]) == tostring(curLine) then - -- type是TS中的枚举类型,其定义在BreakPoint.tx文件中 - --[[ - enum BreakpointType { - conditionBreakpoint = 0, - logPoint, - lineBreakpoint - } - ]] - - if v["type"] == "0" then - -- condition breakpoint - -- 注意此处不要使用尾调用,否则会影响调用栈,导致Lua5.3和Lua5.1中调用栈层级不同 - local conditionRet = this.IsMeetCondition(v["condition"]); - return conditionRet; - elseif v["type"] == "1" then - -- log point - this.printToVSCode("[log point output]: " .. v["logMessage"], 1); - else - -- line breakpoint - return true; + local oPathFormated; + for fullpath, fullpathNode in pairs(breaks[breakpointPath]) do + recordBreakPointPath = fullpath; --这里是为了兼容用户断点行号没有打对的情况 + local line_hit,cur_node = false,{}; + for _, node in ipairs(fullpathNode) do + if tonumber(node["line"]) == tonumber(curLine) then + line_hit = true; -- fullpath 文件中 有行号命中 + cur_node = node; + recordBreakPointPath = fullpath; --行号命中后,再设置一次,保证路径准确 + break; + end + end + + -- 在lua端不知道是否有同名文件,基本思路是先取文件名,用文件名和breakpointArray 进行匹配。 + -- 当文件名匹配上时,可能存在多个同名文件中存在断点的情况。这时候需要用 oPath 和 fullpath 进行对比,取出正确的。 + -- 当本地文件中有断点,lua做了初步命中后,可能存在 stack , 断点文件有同名的情况。这就需要vscode端也需要checkfullpath函数,使用opath进行文件校验。 + if line_hit then + if oPathFormated == nil then + -- 为了避免性能消耗,仅在行号命中时才处理 opath 到标准化路径 + oPathFormated = this.formatOpath(opath); + -- 截取 + oPathFormated = this.truncatedPath(oPathFormated, truncatedOPath); + end + + if (not distinguishSameNameFile) or (string.match(fullpath, oPathFormated ) and this.checkRealHitBreakpoint(opath, curLine)) then + -- type是TS中的枚举类型,其定义在BreakPoint.tx文件中 + -- enum BreakpointType { + -- conditionBreakpoint = 0, + -- logPoint, + -- lineBreakpoint + -- } + + -- 处理断点 + if cur_node["type"] == "0" then + -- condition breakpoint + -- 注意此处不要使用尾调用,否则会影响调用栈,导致Lua5.3和Lua5.1中调用栈层级不同 + local conditionRet = this.IsMeetCondition(cur_node["condition"]); + -- this.printToVSCode("Condition BK: condition:" .. cur_node["condition"] .. " conditionRet " .. tostring(conditionRet)); + return conditionRet; + elseif cur_node["type"] == "1" then + -- log point + this.printToVSCode("[LogPoint Output]: " .. cur_node["logMessage"], 2, 2); + return false; + else + -- line breakpoint + return true; + end end end end + else + testBreakpointFlag = false; --如果用户打开了测试断点的标志位而未主动关闭,会在lua继续运行时关闭。 + recordBreakPointPath = ''; --当切换文件时置空,避免提示给用户错误信息 end return false; end @@ -1563,7 +1950,12 @@ function this.IsMeetCondition(conditionExp) currentCallStack = {}; variableRefTab = {}; variableRefIdx = 1; - this.getStackTable(); + if hookLib then + this.getStackTable(4); + else + this.getStackTable(); + end + this.curStackId = 2; --在用户空间最上层执行 local conditionExpTable = {["varName"] = conditionExp} @@ -1655,12 +2047,14 @@ function this.checkfuncHasBreakpoint(sLine, eLine, fileName) return true; end - if #breaks[fileName] <= 0 then + if this.getTableMemberNum(breaks[fileName]) <= 0 then return false; else - for k,v in ipairs(breaks[fileName]) do - if tonumber(v.line) > sLine and tonumber(v.line) <= eLine then - return true; + for k,v in pairs(breaks[fileName]) do + for _, node in ipairs(v) do + if tonumber(node.line) > sLine and tonumber(node.line) <= eLine then + return true; + end end end end @@ -1671,7 +2065,7 @@ end -- @event 执行状态(call,return,line) -- @line 行号 function this.debug_hook(event, line) - if this.reConnect() == 1 then return; end + if this.reConnect() == 0 then return; end if logLevel == 0 then local logTable = {"-----enter debug_hook-----\n", "event:", event, " line:", tostring(line), " currentHookState:",currentHookState," currentRunState:", currentRunState}; @@ -1762,6 +2156,7 @@ function this.real_hook_process(info) --标准路径处理 if jumpFlag == false then + info.orininal_source = info.source; --使用 info.orininal_source 记录lua虚拟机传来的原始路径 info.source = this.getPath(info); end --本次执行的函数和上次执行的函数作对比,防止在一行停留两次 @@ -1792,22 +2187,40 @@ function this.real_hook_process(info) if tostring(event) == "line" and jumpFlag == false then if currentRunState == runState.RUN or currentRunState == runState.STEPOVER or currentRunState == runState.STEPIN or currentRunState == runState.STEPOUT then --断点判断 - isHit = this.isHitBreakpoint(info) or hitBP; + isHit = this.isHitBreakpoint(info.source, info.orininal_source, info.currentline) or hitBP; if isHit == true then - this.printToVSCode(" + HitBreakpoint true"); - hitBP = false; --hitBP是断点硬性命中标记 + this.printToVSCode("HitBreakpoint!"); + --备份信息 + local recordStepOverCounter = stepOverCounter; + local recordStepOutCounter = stepOutCounter; + local recordCurrentRunState = currentRunState; --计数器清0 stepOverCounter = 0; stepOutCounter = 0; this.changeRunState(runState.HIT_BREAKPOINT); - --发消息并等待 - this.SendMsgWithStack("stopOnBreakpoint"); + hitBpTwiceCheck = true; -- 命中标志默认设置为true, 如果校验通过,会保留这个标记,校验失败会修改 + if hitBP then + hitBP = false; --hitBP是断点硬性命中标记 + --发消息并等待 + this.SendMsgWithStack("stopOnCodeBreakpoint"); + else + --发消息并等待 + this.SendMsgWithStack("stopOnBreakpoint"); + --若二次校验未命中,恢复状态 + if hitBpTwiceCheck == false then + isHit = false; + -- 确认未命中,把状态恢复,继续运行 + this.changeRunState(recordCurrentRunState); + stepOverCounter = recordStepOverCounter; + stepOutCounter = recordStepOutCounter; + end + end end end end - if isHit == true then - return; + if isHit == true then + return; end if currentRunState == runState.STEPOVER then @@ -1929,7 +2342,7 @@ function this.changeHookState( s ) end --coroutine if hookLib == nil then - this.changeCoroutineHookState(); + this.changeCoroutinesHookState(); end end @@ -1962,26 +2375,33 @@ end -- 修改协程状态 -- @s hook标志位 -function this.changeCoroutineHookState(s) +function this.changeCoroutinesHookState(s) s = s or currentHookState; this.printToConsole("change [Coroutine] HookState: "..tostring(s)); for k ,co in pairs(coroutinePool) do if coroutine.status(co) == "dead" then - table.remove(coroutinePool, k) + coroutinePool[k] = nil else - if s == hookState.DISCONNECT_HOOK then - if openAttachMode == true then - debug.sethook(co, this.debug_hook, "r", 1000000); - else - debug.sethook(co, this.debug_hook, ""); - end - elseif s == hookState.LITE_HOOK then debug.sethook(co , this.debug_hook, "r"); - elseif s == hookState.MID_HOOK then debug.sethook(co , this.debug_hook, "rc"); - elseif s == hookState.ALL_HOOK then debug.sethook(co , this.debug_hook, "lrc"); - end + this.changeCoroutineHookState(co, s) end end end + +function this.changeCoroutineHookState(co, s) + if s == hookState.DISCONNECT_HOOK then + if openAttachMode == true then + debug.sethook(co, this.debug_hook, "r", 1000000); + else + debug.sethook(co, this.debug_hook, ""); + end + elseif s == hookState.LITE_HOOK then + debug.sethook(co , this.debug_hook, "r"); + elseif s == hookState.MID_HOOK then + debug.sethook(co , this.debug_hook, "rc"); + elseif s == hookState.ALL_HOOK then + debug.sethook(co , this.debug_hook, "lrc"); + end +end -------------------------变量处理相关----------------------------- --清空REPL的env环境 @@ -2042,7 +2462,7 @@ function this.createWatchedVariableInfo(variableName, variableIns) xpcall(function() var.value = tostring(variableIns) end , function() var.value = tostring(type(variableIns)) .. " [value can't trans to string]" end ); var.variablesReference = "0"; --这个地方必须用“0”, 以免variableRefTab[0]出错 - if var.type == "table" or var.type == "function" then + if var.type == "table" or var.type == "function" or var.type == "userdata" then var.variablesReference = variableRefIdx; variableRefTab[variableRefIdx] = variableIns; variableRefIdx = variableRefIdx + 1; @@ -2292,11 +2712,15 @@ function this.getVariableRef( refStr ) if tostring(type(variableRefTab[varRef])) == "table" then for n,v in pairs(variableRefTab[varRef]) do local var = {}; - var.name = tostring(n); + if type(n) == "string" then + var.name = '"' .. tostring(n) .. '"'; + else + var.name = tostring(n); + end var.type = tostring(type(v)); xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .. " [value can't trans to string]" end ); var.variablesReference = "0"; - if var.type == "table" or var.type == "function" then + if var.type == "table" or var.type == "function" or var.type == "userdata" then var.variablesReference = variableRefIdx; variableRefTab[variableRefIdx] = v; variableRefIdx = variableRefIdx + 1; @@ -2336,6 +2760,28 @@ function this.getVariableRef( refStr ) variableRefTab[variableRefIdx] = udMtTable; variableRefIdx = variableRefIdx + 1; table.insert(varTab, var); + + if traversalUserData and udMtTable.__pairs ~= nil and type(udMtTable.__pairs) == "function" then + for n,v in pairs(variableRefTab[varRef]) do + local var = {}; + var.name = tostring(n); + var.type = tostring(type(v)); + xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .. " [value can't trans to string]" end ); + var.variablesReference = "0"; + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = v; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(v); + var.value = memberNum .." Members ".. ( var.value or '' ); + end + elseif var.type == "string" then + var.value = '"' ..v.. '"'; + end + table.insert(varTab, var); + end + end end end return varTab; @@ -2352,7 +2798,7 @@ function this.getGlobalVariable( ... ) var.type = tostring(type(v)); xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .." [value can't trans to string]" end ); var.variablesReference = "0"; - if var.type == "table" or var.type == "function" then + if var.type == "table" or var.type == "function" or var.type == "userdata" then var.variablesReference = variableRefIdx; variableRefTab[variableRefIdx] = v; variableRefIdx = variableRefIdx + 1; @@ -2395,7 +2841,7 @@ function this.getUpValueVariable( checkFunc , isFormatVariable) if isGetValue == false then xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .. " [value can't trans to string]" end ); - if var.type == "table" or var.type == "function" then + if var.type == "table" or var.type == "function" or var.type == "userdata" then var.variablesReference = variableRefIdx; variableRefTab[variableRefIdx] = v; variableRefIdx = variableRefIdx + 1; @@ -2448,7 +2894,7 @@ function this.getVariable( checkLayer, isFormatVariable , offset) end --(*temporary)是系统变量,过滤掉。这里假设(*temporary)仅出现在最后 - if "(*temporary)" ~= tostring(n) then + if "(*temporary)" ~= tostring(n) and "(temporary)" ~= tostring(n) then local var = {}; var.name = n; var.type = tostring(type(v)); @@ -2457,7 +2903,7 @@ function this.getVariable( checkLayer, isFormatVariable , offset) if isGetValue == false then xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .. " [value can't trans to string]" end ); - if var.type == "table" or var.type == "function" then + if var.type == "table" or var.type == "function" or var.type == "userdata" then var.variablesReference = variableRefIdx; variableRefTab[variableRefIdx] = v; variableRefIdx = variableRefIdx + 1; @@ -2530,7 +2976,7 @@ function this.processExp(msgTable) var.type = tostring(type(retString)); xpcall(function() var.value = tostring(retString) end , function(e) var.value = tostring(type(retString)) .. " [value can't trans to string] ".. e; var.isSuccess = false; end); var.variablesReference = "0"; - if var.type == "table" or var.type == "function" then + if var.type == "table" or var.type == "function" or var.type == "userdata" then variableRefTab[variableRefIdx] = retString; var.variablesReference = variableRefIdx; variableRefIdx = variableRefIdx + 1; @@ -2575,7 +3021,7 @@ function this.processWatchedExp(msgTable) xpcall(function() var.value = tostring(retString) end , function() var.value = tostring(type(retString)) .. " [value can't trans to string]"; var.isSuccess = "false"; end ); var.variablesReference = "0"; - if var.type == "table" or var.type == "function" then + if var.type == "table" or var.type == "function" or var.type == "userdata" then variableRefTab[variableRefIdx] = retString; var.variablesReference = variableRefIdx; variableRefIdx = variableRefIdx + 1; @@ -2592,5 +3038,589 @@ function this.processWatchedExp(msgTable) return retTab; end + +function tools.getFileSource() + local info = debug.getinfo(1, "S") + for k,v in pairs(info) do + if k == "source" then + return v; + end + end +end + +--序列化并打印table +function tools.printTable(t, name ,indent) + local str = (tools.show(t, name, indent)); + print(str); +end + +--序列化并返回table +function tools.serializeTable(t, name, indent) + local str = (tools.show(t, name, indent)) + return str +end + +--[[ +Author: Julio Manuel Fernandez-Diaz +Date: January 12, 2007 +Modified slightly by RiciLake to avoid the unnecessary table traversal in tablecount() +Formats tables with cycles recursively to any depth. +The output is returned as a string. +References to other tables are shown as values. +Self references are indicated. +The string returned is "Lua code", which can be procesed +(in the case in which indent is composed by spaces or "--"). +Userdata and function keys and values are shown as strings, +which logically are exactly not equivalent to the original code. +This routine can serve for pretty formating tables with +proper indentations, apart from printing them: +print(table.show(t, "t")) -- a typical use +Heavily based on "Saving tables with cycles", PIL2, p. 113. +Arguments: +t is the table. +name is the name of the table (optional) +indent is a first indentation (optional). +--]] +function tools.show(t, name, indent) + local cart -- a container + local autoref -- for self references + + local function isemptytable(t) return next(t) == nil end + + local function basicSerialize (o) + local so = tostring(o) + if type(o) == "function" then + local info = debug.getinfo(o, "S") + -- info.name is nil because o is not a calling level + if info.what == "C" then + return string.format("%q", so .. ", C function") + else + -- the information is defined through lines + return string.format("%q", so .. ", defined in (" .. + info.linedefined .. "-" .. info.lastlinedefined .. + ")" .. info.source) + end + elseif type(o) == "number" or type(o) == "boolean" then + return so + else + return string.format("%q", so) + end + end + + local function addtocart (value, name, indent, saved, field) + indent = indent or "" + saved = saved or {} + field = field or name + + cart = cart .. indent .. field + + if type(value) ~= "table" then + cart = cart .. " = " .. basicSerialize(value) .. ";\n" + else + if saved[value] then + cart = cart .. " = {}; -- " .. saved[value] + .. " (self reference)\n" + autoref = autoref .. name .. " = " .. saved[value] .. ";\n" + else + saved[value] = name + --if tablecount(value) == 0 then + if isemptytable(value) then + cart = cart .. " = {};\n" + else + cart = cart .. " = {\n" + for k, v in pairs(value) do + k = basicSerialize(k) + local fname = string.format("%s[%s]", name, k) + field = string.format("[%s]", k) + -- three spaces between levels + addtocart(v, fname, indent .. " ", saved, field) + end + cart = cart .. indent .. "};\n" + end + end + end + end + + name = name or "PRINT_Table" + if type(t) ~= "table" then + return name .. " = " .. basicSerialize(t) + end + cart, autoref = "", "" + addtocart(t, name, indent) + return cart .. autoref +end + +----------------------------------------------------------------------------- +-- JSON4Lua: JSON encoding / decoding support for the Lua language. +-- json Module. +-- Author: Craig Mason-Jones +-- Homepage: http://github.com/craigmj/json4lua/ +-- Version: 1.0.0 +-- This module is released under the MIT License (MIT). +-- Please see LICENCE.txt for details. +-- +-- USAGE: +-- This module exposes two functions: +-- json.encode(o) +-- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string. +-- json.decode(json_string) +-- Returns a Lua object populated with the data encoded in the JSON string json_string. +-- +-- REQUIREMENTS: +-- compat-5.1 if using Lua 5.0 +-- +-- CHANGELOG +-- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix). +-- Fixed Lua 5.1 compatibility issues. +-- Introduced json.null to have null values in associative arrays. +-- json.encode() performance improvement (more than 50%) through table.concat rather than .. +-- Introduced decode ability to ignore /**/ comments in the JSON string. +-- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays. +----------------------------------------------------------------------------- + +function tools.createJson() + ----------------------------------------------------------------------------- + -- Imports and dependencies + ----------------------------------------------------------------------------- + local math = require('math') + local string = require("string") + local table = require("table") + + ----------------------------------------------------------------------------- + -- Module declaration + ----------------------------------------------------------------------------- + local json = {} -- Public namespace + local json_private = {} -- Private namespace + + -- Public constants + json.EMPTY_ARRAY={} + json.EMPTY_OBJECT={} + + -- Public functions + + -- Private functions + local decode_scanArray + local decode_scanComment + local decode_scanConstant + local decode_scanNumber + local decode_scanObject + local decode_scanString + local decode_scanWhitespace + local encodeString + local isArray + local isEncodable + + ----------------------------------------------------------------------------- + -- PUBLIC FUNCTIONS + ----------------------------------------------------------------------------- + --- Encodes an arbitrary Lua object / variable. + -- @param v The Lua object / variable to be JSON encoded. + -- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode) + function json.encode (v) + -- Handle nil values + if v==nil then + return "null" + end + + local vtype = type(v) + + -- Handle strings + if vtype=='string' then + return '"' .. json_private.encodeString(v) .. '"' -- Need to handle encoding in string + end + + -- Handle booleans + if vtype=='number' or vtype=='boolean' then + return tostring(v) + end + + -- Handle tables + if vtype=='table' then + local rval = {} + -- Consider arrays separately + local bArray, maxCount = isArray(v) + if bArray then + for i = 1,maxCount do + table.insert(rval, json.encode(v[i])) + end + else -- An object, not an array + for i,j in pairs(v) do + if isEncodable(i) and isEncodable(j) then + table.insert(rval, '"' .. json_private.encodeString(i) .. '":' .. json.encode(j)) + end + end + end + if bArray then + return '[' .. table.concat(rval,',') ..']' + else + return '{' .. table.concat(rval,',') .. '}' + end + end + + -- Handle null values + if vtype=='function' and v==json.null then + return 'null' + end + + assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. tostring(v)) + end + + + --- Decodes a JSON string and returns the decoded value as a Lua data structure / value. + -- @param s The string to scan. + -- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1. + -- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil, + -- and the position of the first character after + -- the scanned JSON object. + function json.decode(s, startPos) + startPos = startPos and startPos or 1 + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') + local curChar = string.sub(s,startPos,startPos) + -- Object + if curChar=='{' then + return decode_scanObject(s,startPos) + end + -- Array + if curChar=='[' then + return decode_scanArray(s,startPos) + end + -- Number + if string.find("+-0123456789.e", curChar, 1, true) then + return decode_scanNumber(s,startPos) + end + -- String + if curChar==[["]] or curChar==[[']] then + return decode_scanString(s,startPos) + end + if string.sub(s,startPos,startPos+1)=='/*' then + return json.decode(s, decode_scanComment(s,startPos)) + end + -- Otherwise, it must be a constant + return decode_scanConstant(s,startPos) + end + + --- The null function allows one to specify a null value in an associative array (which is otherwise + -- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null } + function json.null() + return json.null -- so json.null() will also return null ;-) + end + ----------------------------------------------------------------------------- + -- Internal, PRIVATE functions. + -- Following a Python-like convention, I have prefixed all these 'PRIVATE' + -- functions with an underscore. + ----------------------------------------------------------------------------- + + --- Scans an array from JSON into a Lua object + -- startPos begins at the start of the array. + -- Returns the array and the next starting position + -- @param s The string being scanned. + -- @param startPos The starting position for the scan. + -- @return table, int The scanned array as a table, and the position of the next character to scan. + function decode_scanArray(s,startPos) + local array = {} -- The return value + local stringLen = string.len(s) + assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) + startPos = startPos + 1 + -- Infinite loop for array elements + local index = 1 + repeat + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') + local curChar = string.sub(s,startPos,startPos) + if (curChar==']') then + return array, startPos+1 + end + if (curChar==',') then + startPos = decode_scanWhitespace(s,startPos+1) + end + assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') + local object + object, startPos = json.decode(s,startPos) + array[index] = object + index = index + 1 + until false + end + + --- Scans a comment and discards the comment. + -- Returns the position of the next character following the comment. + -- @param string s The JSON string to scan. + -- @param int startPos The starting position of the comment + function decode_scanComment(s, startPos) + assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) + local endPos = string.find(s,'*/',startPos+2) + assert(endPos~=nil, "Unterminated comment in string at " .. startPos) + return endPos+2 + end + + --- Scans for given constants: true, false or null + -- Returns the appropriate Lua type, and the position of the next character to read. + -- @param s The string being scanned. + -- @param startPos The position in the string at which to start scanning. + -- @return object, int The object (true, false or nil) and the position at which the next character should be + -- scanned. + function decode_scanConstant(s, startPos) + local consts = { ["true"] = true, ["false"] = false, ["null"] = nil } + local constNames = {"true","false","null"} + + for i,k in pairs(constNames) do + if string.sub(s,startPos, startPos + string.len(k) -1 )==k then + return consts[k], startPos + string.len(k) + end + end + assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) + end + + --- Scans a number from the JSON encoded string. + -- (in fact, also is able to scan numeric +- eqns, which is not + -- in the JSON spec.) + -- Returns the number, and the position of the next character + -- after the number. + -- @param s The string being scanned. + -- @param startPos The position at which to start scanning. + -- @return number, int The extracted number and the position of the next character to scan. + function decode_scanNumber(s,startPos) + local endPos = startPos+1 + local stringLen = string.len(s) + local acceptableChars = "+-0123456789.e" + while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) + and endPos<=stringLen + ) do + endPos = endPos + 1 + end + -- local stringValue = 'return ' .. string.sub(s, startPos, endPos - 1) + -- local stringEval = loadstring(stringValue) + -- assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) + local numberValue = string.sub(s, startPos, endPos - 1) + return numberValue, endPos + end + + --- Scans a JSON object into a Lua object. + -- startPos begins at the start of the object. + -- Returns the object and the next starting position. + -- @param s The string being scanned. + -- @param startPos The starting position of the scan. + -- @return table, int The scanned object as a table and the position of the next character to scan. + function decode_scanObject(s,startPos) + local object = {} + local stringLen = string.len(s) + local key, value + assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s) + startPos = startPos + 1 + repeat + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') + local curChar = string.sub(s,startPos,startPos) + if (curChar=='}') then + return object,startPos+1 + end + if (curChar==',') then + startPos = decode_scanWhitespace(s,startPos+1) + end + assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') + -- Scan the key + key, startPos = json.decode(s,startPos) + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) + startPos = decode_scanWhitespace(s,startPos+1) + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + value, startPos = json.decode(s,startPos) + object[key]=value + until false -- infinite loop while key-value pairs are found + end + + -- START SoniEx2 + -- Initialize some things used by decode_scanString + -- You know, for efficiency + local escapeSequences = { + ["\\t"] = "\t", + ["\\f"] = "\f", + ["\\r"] = "\r", + ["\\n"] = "\n", + ["\\b"] = "\b" + } + setmetatable(escapeSequences, {__index = function(t,k) + -- skip "\" aka strip escape + return string.sub(k,2) + end}) + -- END SoniEx2 + + --- Scans a JSON string from the opening inverted comma or single quote to the + -- end of the string. + -- Returns the string extracted as a Lua string, + -- and the position of the next non-string character + -- (after the closing inverted comma or single quote). + -- @param s The string being scanned. + -- @param startPos The starting position of the scan. + -- @return string, int The extracted string as a Lua string, and the next character to parse. + function decode_scanString(s,startPos) + assert(startPos, 'decode_scanString(..) called without start position') + local startChar = string.sub(s,startPos,startPos) + -- START SoniEx2 + -- PS: I don't think single quotes are valid JSON + assert(startChar == [["]] or startChar == [[']],'decode_scanString called for a non-string') + --assert(startPos, "String decoding failed: missing closing " .. startChar .. " for string at position " .. oldStart) + local t = {} + local i,j = startPos,startPos + while string.find(s, startChar, j+1) ~= j+1 do + local oldj = j + i,j = string.find(s, "\\.", j+1) + local x,y = string.find(s, startChar, oldj+1) + if not i or x < i then + i,j = x,y-1 + end + table.insert(t, string.sub(s, oldj+1, i-1)) + if string.sub(s, i, j) == "\\u" then + local a = string.sub(s,j+1,j+4) + j = j + 4 + local n = tonumber(a, 16) + assert(n, "String decoding failed: bad Unicode escape " .. a .. " at position " .. i .. " : " .. j) + -- math.floor(x/2^y) == lazy right shift + -- a % 2^b == bitwise_and(a, (2^b)-1) + -- 64 = 2^6 + -- 4096 = 2^12 (or 2^6 * 2^6) + local x + if n < 0x80 then + x = string.char(n % 0x80) + elseif n < 0x800 then + -- [110x xxxx] [10xx xxxx] + x = string.char(0xC0 + (math.floor(n/64) % 0x20), 0x80 + (n % 0x40)) + else + -- [1110 xxxx] [10xx xxxx] [10xx xxxx] + x = string.char(0xE0 + (math.floor(n/4096) % 0x10), 0x80 + (math.floor(n/64) % 0x40), 0x80 + (n % 0x40)) + end + table.insert(t, x) + else + table.insert(t, escapeSequences[string.sub(s, i, j)]) + end + end + table.insert(t,string.sub(j, j+1)) + assert(string.find(s, startChar, j+1), "String decoding failed: missing closing " .. startChar .. " at position " .. j .. "(for string at position " .. startPos .. ")") + return table.concat(t,""), j+2 + -- END SoniEx2 + end + + --- Scans a JSON string skipping all whitespace from the current start position. + -- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached. + -- @param s The string being scanned + -- @param startPos The starting position where we should begin removing whitespace. + -- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string + -- was reached. + function decode_scanWhitespace(s,startPos) + local whitespace=" \n\r\t" + local stringLen = string.len(s) + while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do + startPos = startPos + 1 + end + return startPos + end + + --- Encodes a string to be JSON-compatible. + -- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-) + -- @param s The string to return as a JSON encoded (i.e. backquoted string) + -- @return The string appropriately escaped. + + local escapeList = { + ['"'] = '\\"', + ['\\'] = '\\\\', + ['/'] = '\\/', + ['\b'] = '\\b', + ['\f'] = '\\f', + ['\n'] = '\\n', + ['\r'] = '\\r', + ['\t'] = '\\t' + } + + function json_private.encodeString(s) + local s = tostring(s) + return s:gsub(".", function(c) return escapeList[c] end) -- SoniEx2: 5.0 compat + end + + -- Determines whether the given Lua type is an array or a table / dictionary. + -- We consider any table an array if it has indexes 1..n for its n items, and no + -- other data in the table. + -- I think this method is currently a little 'flaky', but can't think of a good way around it yet... + -- @param t The table to evaluate as an array + -- @return boolean, number True if the table can be represented as an array, false otherwise. If true, + -- the second returned value is the maximum + -- number of indexed elements in the array. + function isArray(t) + -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable + -- (with the possible exception of 'n') + if (t == json.EMPTY_ARRAY) then return true, 0 end + if (t == json.EMPTY_OBJECT) then return false end + + local maxIndex = 0 + for k,v in pairs(t) do + if (type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair + if (not isEncodable(v)) then return false end -- All array elements must be encodable + maxIndex = math.max(maxIndex,k) + else + if (k=='n') then + if v ~= (t.n or #t) then return false end -- False if n does not hold the number of elements + else -- Else of (k=='n') + if isEncodable(v) then return false end + end -- End of (k~='n') + end -- End of k,v not an indexed pair + end -- End of loop across all pairs + return true, maxIndex + end + + --- Determines whether the given Lua object / table / variable can be JSON encoded. The only + -- types that are JSON encodable are: string, boolean, number, nil, table and json.null. + -- In this implementation, all other types are ignored. + -- @param o The object to examine. + -- @return boolean True if the object should be JSON encoded, false if it should be ignored. + function isEncodable(o) + local t = type(o) + return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or + (t=='function' and o==json.null) + end + return json +end + +-- Sourced from http://lua-users.org/wiki/BaseSixtyFour + +-- Lua 5.1+ base64 v3.0 (c) 2009 by Alex Kloss +-- licensed under the terms of the LGPL2 + +-- character table string +local base64CharTable='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + +-- encoding +function tools.base64encode(data) + return ((data:gsub('.', function(x) + local r,b='',x:byte() + for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end + return r; + end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) + if (#x < 6) then return '' end + local c=0 + for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end + return base64CharTable:sub(c+1,c+1) + end)..({ '', '==', '=' })[#data%3+1]) +end + +-- decoding +function tools.base64decode(data) + data = string.gsub(data, '[^'..base64CharTable..'=]', '') + return (data:gsub('.', function(x) + if (x == '=') then return '' end + local r,f='',(base64CharTable:find(x)-1) + for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end + return r; + end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) + if (#x ~= 8) then return '' end + local c=0 + for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end + return string.char(c) + end)) +end + +-- tools变量 +json = tools.createJson(); --json处理 this.printToConsole("load LuaPanda success", 1); +this.replaceCoroutineFuncs() return this; diff --git a/Debugger/debugger_lib/libpdebug.cpp b/Debugger/debugger_lib/libpdebug.cpp index 6b330a4..6decb74 100644 --- a/Debugger/debugger_lib/libpdebug.cpp +++ b/Debugger/debugger_lib/libpdebug.cpp @@ -31,12 +31,14 @@ const char* last_source; int ar_current_line = 0; int ar_def_line = 0; int ar_lastdef_line = 0; +int bp_twice_check_res = 1; +int lua_debugger_ver = 0; // luapanda.lua的版本,便于做向下兼容 struct path_transfer_node; struct breakpoint; // 路径缓存队列 getinfo -> format std::list getinfo_to_format_cache; // 存放断点map,key为source -std::map> all_breakpoint_map; +std::map > all_breakpoint_map; enum run_state { @@ -115,7 +117,7 @@ void print_all_breakpoint_map(lua_State *L, int print_level = 0) { if (print_level < logLevel) { return; } - std::map>::iterator iter1; + std::map >::iterator iter1; std::map::iterator iter2; std::string log_message = "[breakpoints in chook:]\n"; for (iter1 = all_breakpoint_map.begin(); iter1 != all_breakpoint_map.end(); ++iter1) { @@ -190,7 +192,7 @@ template int call_lua_function(lua_State *L, const char * lua_function_name, int retCount , ARGS... args){ lua_getglobal(L, LUA_DEBUGGER_NAME); if (!lua_istable(L, -1)) { - const char *err_msg = "[Debug Lib Error]:call_lua_function Get LUA_DEBUGGER_NAME error.\n"; + const char *err_msg = "[C Module Error]:call_lua_function Get LUA_DEBUGGER_NAME error.\n"; print_to_vscode(L, err_msg, 2); return -1; } @@ -198,7 +200,7 @@ int call_lua_function(lua_State *L, const char * lua_function_name, int retCount lua_getfield(L, -1, lua_function_name); if (!lua_isfunction(L, -1)) { char err_msg[100]; - snprintf(err_msg, sizeof(err_msg), "[Debug Lib Error]:call_lua_function Get lua function '%s' error\n.", lua_function_name); + snprintf(err_msg, sizeof(err_msg), "[C Module Error]:call_lua_function Get lua function '%s' error\n.", lua_function_name); print_to_vscode(L, err_msg, 2); return -1; } @@ -208,7 +210,7 @@ int call_lua_function(lua_State *L, const char * lua_function_name, int retCount if (err_code) { char err_msg[1024]; const char *lua_error = lua_tostring(L, -1); - snprintf(err_msg, sizeof(err_msg), "[Debug Lib Error]:call_lua_function Call '%s' error. ErrorCode: %d, ErrorMessage: %s.\n", lua_function_name, err_code, lua_error); + snprintf(err_msg, sizeof(err_msg), "[C Module Error]:call_lua_function Call '%s' error. ErrorCode: %d, ErrorMessage: %s.\n", lua_function_name, err_code, lua_error); print_to_vscode(L, err_msg, 2); lua_pop(L, 1); return err_code; @@ -240,6 +242,13 @@ extern "C" int get_last_source(lua_State *L) return 1; } +//同步luapanda.lua的版本号 +extern "C" int sync_lua_debugger_ver(lua_State *L) +{ + lua_debugger_ver = static_cast(luaL_checkinteger(L, 1)); + return 0; +} + //同步断点命中标识 extern "C" int sync_bp_hit(lua_State *L) { if(cur_hook_state == DISCONNECT_HOOK){ @@ -265,6 +274,13 @@ extern "C" int sync_tempfile_path(lua_State *L) { return 0; } + +//同步临时文件路径 +extern "C" int set_bp_twice_check_res(lua_State *L) { + bp_twice_check_res = luaL_checknumber(L, 1); + return 0; +} + //lua 获取版本号 extern "C" int sync_getLibVersion(lua_State *L) { lua_pushstring(L, HOOK_LIB_VERSION); @@ -351,7 +367,7 @@ const char* getPath(lua_State *L,const char* source){ debug_auto_stack _tt(L); if(source == nullptr){ - print_to_vscode(L, "[Debug Lib Error]: getPath Exception: source == nullptr", 2); + print_to_vscode(L, "[C Module Error]: getPath Exception: source == nullptr", 2); return ""; } @@ -376,19 +392,37 @@ const char* getPath(lua_State *L,const char* source){ return retSource; } +// 向 lua 中 checkRealHitBreakpoint 查询是否在缓存中,以判断是否真正命中断点 +const int checkRealHitBreakpoint(lua_State *L,const char* source, int line){ + debug_auto_stack _tt(L); + + if(source == nullptr){ + print_to_vscode(L, "[C Module Error]: checkRealHitBreakpoint Exception: source == nullptr", 2); + return 0; + } + + //若缓存中没有,到lua中转换 + int lua_ret = call_lua_function(L, "checkRealHitBreakpoint", 1 , source, line); + if (lua_ret != 0) { + return 0; + } + int realHit = lua_toboolean(L, -1); + return realHit; +} + //供lua调用,把断点列表同步给c端 extern "C" int sync_breakpoints(lua_State *L) { debug_auto_stack _tt(L); //取数组 lua_getglobal(L, LUA_DEBUGGER_NAME); //-1 LuaPanda if (!lua_istable(L, -1)) { - print_to_vscode(L, "[Debug Lib Error] debug_ishit_bk get LUA_DEBUGGER_NAME error", 2); + print_to_vscode(L, "[C Module Error] debug_ishit_bk get LUA_DEBUGGER_NAME error", 2); return -1; } lua_getfield(L, -1, "breaks"); if (!lua_istable(L, -1)) { - print_to_vscode(L, "[Debug Lib Error] debug_ishit_bk get breaks error", 2); + print_to_vscode(L, "[C Module Error] debug_ishit_bk get breaks error", 2); return -1; } @@ -402,52 +436,107 @@ extern "C" int sync_breakpoints(lua_State *L) { std::map file_breakpoint_map; lua_pushnil(L);//k,v, nil while (lua_next(L, -2)) { - //k,v,k,v - lua_getfield(L, -1, "line"); //k,v,k,v,line - int line = (int)lua_tointeger(L, -1); - lua_pop(L, 1); // line - - lua_getfield(L, -1, "type"); - int type = (int)lua_tointeger(L, -1); - lua_pop(L, 1); // type - - struct breakpoint bp; - switch (type) { - case CONDITION_BREAKPOINT: { - bp.type = CONDITION_BREAKPOINT; - - lua_getfield(L, -1, "condition"); - const char* condition = luaL_checkstring(L, -1); - lua_pop(L, 1); // condition - bp.info = condition; - break; + if(lua_debugger_ver >= 30150){ + lua_pushnil(L);//k,v, nil + while (lua_next(L, -2)) { + //k,v,k,v + lua_getfield(L, -1, "line"); //k,v,k,v,line + int line = (int)lua_tointeger(L, -1); + lua_pop(L, 1); // line + + lua_getfield(L, -1, "type"); + int type = (int)lua_tointeger(L, -1); + lua_pop(L, 1); // type + + struct breakpoint bp; + switch (type) { + case CONDITION_BREAKPOINT: { + bp.type = CONDITION_BREAKPOINT; + + lua_getfield(L, -1, "condition"); + const char* condition = luaL_checkstring(L, -1); + lua_pop(L, 1); // condition + bp.info = condition; + break; + } + + case LOG_POINT: { + bp.type = LOG_POINT; + + lua_getfield(L, -1, "logMessage"); + const char* log_message = luaL_checkstring(L, -1); + lua_pop(L, 1); // logMessage + bp.info = log_message; + break; + } + + case LINE_BREAKPOINT: + bp.type = LINE_BREAKPOINT; + + bp.info = std::to_string(line); + break; + + default: + print_to_vscode(L, "[C Module Error] Invalid breakpoint type!", 2); + return -1; + } + + file_breakpoint_map[line] = bp; + + lua_pop(L, 1);//value + //k,v,k } - - case LOG_POINT: { - bp.type = LOG_POINT; - - lua_getfield(L, -1, "logMessage"); - const char* log_message = luaL_checkstring(L, -1); - lua_pop(L, 1); // logMessage - bp.info = log_message; - break; + lua_pop(L, 1);//value + }else{ + // 兼容 < 3.1.5 版本的luapanda.lua + + //k,v,k,v + lua_getfield(L, -1, "line"); //k,v,k,v,line + int line = (int)lua_tointeger(L, -1); + lua_pop(L, 1); // line + + lua_getfield(L, -1, "type"); + int type = (int)lua_tointeger(L, -1); + lua_pop(L, 1); // type + + struct breakpoint bp; + switch (type) { + case CONDITION_BREAKPOINT: { + bp.type = CONDITION_BREAKPOINT; + + lua_getfield(L, -1, "condition"); + const char* condition = luaL_checkstring(L, -1); + lua_pop(L, 1); // condition + bp.info = condition; + break; + } + + case LOG_POINT: { + bp.type = LOG_POINT; + + lua_getfield(L, -1, "logMessage"); + const char* log_message = luaL_checkstring(L, -1); + lua_pop(L, 1); // logMessage + bp.info = log_message; + break; + } + + case LINE_BREAKPOINT: + bp.type = LINE_BREAKPOINT; + + bp.info = std::to_string(line); + break; + + default: + print_to_vscode(L, "[C Module Error] Invalid breakpoint type!", 2); + return -1; } - - case LINE_BREAKPOINT: - bp.type = LINE_BREAKPOINT; - - bp.info = std::to_string(line); - break; - - default: - print_to_vscode(L, "[Debug Lib Error] Invalid breakpoint type!", 2); - return -1; + + file_breakpoint_map[line] = bp; + + lua_pop(L, 1);//value + } - - file_breakpoint_map[line] = bp; - - lua_pop(L, 1);//value - //k,v,k } all_breakpoint_map[std::string(source)] = file_breakpoint_map; //k,v @@ -463,47 +552,60 @@ extern "C" int sync_breakpoints(lua_State *L) { //断点命中判断 int debug_ishit_bk(lua_State *L, const char * curPath, int current_line) { - print_to_vscode(L, "debug_ishit_bk\n"); debug_auto_stack _tt(L); - + // 获取标准路径[文件名.后缀] const char *standardPath = getPath(L, curPath); // 判断是否存在同名文件 - std::map>::const_iterator const_iter1 = all_breakpoint_map.find(std::string(standardPath)); + std::map >::const_iterator const_iter1 = all_breakpoint_map.find(std::string(standardPath)); if (const_iter1 == all_breakpoint_map.end()) { return 0; } + // c++ all_breakpoint_map 的数据结构保持不变,和lua不一样 // 根据是否存在相同行号 std::map::const_iterator const_iter2 = const_iter1->second.find(current_line); if (const_iter2 == const_iter1->second.end()) { return 0; } - // 条件断点 - if (const_iter2->second.type == CONDITION_BREAKPOINT) { - std::string condition = const_iter2->second.info; - int lua_ret = call_lua_function(L, "IsMeetCondition", 1, condition.c_str()); + if(lua_debugger_ver >= 30160){ + // luapanda.lua >= 3.1.6 才会调用 + // 初步命中,到lua层中检测是否真正命中,以及断点类型 + int lua_ret = call_lua_function(L, "isHitBreakpoint", 1, standardPath, curPath, current_line); if (lua_ret != 0) { + // 调用出错时,按命中处理 + return 1; + } + + int realHit = lua_toboolean(L, -1); + return realHit; + }else{ + // 兼容旧版本 + // 条件断点 + if (const_iter2->second.type == CONDITION_BREAKPOINT) { + std::string condition = const_iter2->second.info; + int lua_ret = call_lua_function(L, "IsMeetCondition", 1, condition.c_str()); + if (lua_ret != 0) { + return 0; + } + // if (!lua_isboolean(L, -1)) { + // print_to_vscode(L, "[Debug Lib Error] debug_ishit_bk process condition expression result error!", 2); + // return 0; + // } + int is_meet_condition = lua_toboolean(L, -1); + lua_pop(L, 1); + return is_meet_condition; + } + + // 记录点 + if (const_iter2->second.type == LOG_POINT) { + std::string log_message = "[log point output]: "; + log_message.append(const_iter2->second.info); + print_to_vscode(L, log_message.c_str() , 1); return 0; } - // if (!lua_isboolean(L, -1)) { - // print_to_vscode(L, "[Debug Lib Error] debug_ishit_bk process condition expression result error!", 2); - // return 0; - // } - int is_meet_condition = lua_toboolean(L, -1); - lua_pop(L, 1); - return is_meet_condition; - } - - // 记录点 - if (const_iter2->second.type == LOG_POINT) { - std::string log_message = "[log point output]: "; - log_message.append(const_iter2->second.info); - print_to_vscode(L, log_message.c_str() , 1); - return 0; + return 1; } - - return 1; } //判断字符串是否匹配[string " @@ -521,16 +623,32 @@ int breakpoint_process(lua_State *L, lua_Debug *ar){ int is_hit = 0; if (ar->event == LINE) { is_hit = debug_ishit_bk(L, ar->source, ar->currentline); + // 同名文件可能会命中假断点 folder1/a.lua 和 folder2/a.lua 截取文件名都是 a.lua, 可能导致命中混淆 + if(is_hit && lua_debugger_ver >= 30160){ + // luapanda.lua >= 3.1.6 版本才会调用 + is_hit = checkRealHitBreakpoint(L, ar->source, ar->currentline); + } if (is_hit == 1 || BPhit) { - BPhit = 0; - print_to_vscode(L, "bk HIT"); - + print_to_vscode(L, "[C Module] Breakpoint hit!"); + int record_stackdeep_counter = stackdeep_counter; + int record_cur_run_state = cur_run_state; stackdeep_counter = 0; sync_runstate_toLua(L, HIT_BREAKPOINT); + bp_twice_check_res = 1; //c层掌握 STEPOVER 计数器,状态机放在lua层,c主要去读(毕竟C作为lua的扩展) //通知lua层,lua层阻塞,发消息 - call_lua_function(L, "SendMsgWithStack", 0, "stopOnBreakpoint"); + if(BPhit){ + BPhit = 0; + call_lua_function(L, "SendMsgWithStack", 0, "stopOnCodeBreakpoint"); + }else{ + call_lua_function(L, "SendMsgWithStack", 0, "stopOnBreakpoint"); + if( bp_twice_check_res == 0 ){ + is_hit = 0; + stackdeep_counter = record_stackdeep_counter; + sync_runstate_toLua(L, record_cur_run_state); + } + } } } return is_hit; @@ -579,6 +697,7 @@ void step_process(lua_State *L, lua_Debug *ar){ } } +// 无需reconnect返回1 ,需要重连时返回0 int hook_process_reconnect(lua_State *L){ time_t currentSecs = time(static_cast(NULL)); if(cur_hook_state == DISCONNECT_HOOK){ @@ -652,7 +771,7 @@ int checkHasBreakpoint(lua_State *L, const char * src1, int current_line, int sl return LITE_HOOK; } - std::map>::iterator iter1; + std::map >::iterator iter1; for (iter1 = all_breakpoint_map.begin(); iter1 != all_breakpoint_map.end(); ++iter1) { if (iter1->first == std::string(src)) { // compare() @@ -776,6 +895,8 @@ static luaL_Reg libpdebug[] = { { "get_libhook_state", get_libhook_state }, { "get_last_source", get_last_source }, { "clear_pathcache", clear_pathcache }, + { "set_bp_twice_check_res", set_bp_twice_check_res }, + { "sync_lua_debugger_ver", sync_lua_debugger_ver }, { NULL, NULL } }; diff --git a/Debugger/debugger_lib/libpdebug.h b/Debugger/debugger_lib/libpdebug.h index 13b649b..b5a21c3 100644 --- a/Debugger/debugger_lib/libpdebug.h +++ b/Debugger/debugger_lib/libpdebug.h @@ -3,7 +3,7 @@ //1.使用源码编译,要打开宏USE_SOURCE_CODE. win下要设置LUA_INTEGER和lua版本号 #define LUA_DEBUGGER_NAME "LuaPanda" //debugger's name in LuaDebug.lua -#define HOOK_LIB_VERSION "2.3.0" //lib version +#define HOOK_LIB_VERSION "3.2.0" //lib version //#define USE_SOURCE_CODE //using source code to build #if !defined(USE_SOURCE_CODE) && defined(_WIN32) #define LUA_INTEGER long long //set LUA_INTEGER. In 501 is ptrdiff_t. 503 can set longlong(64bit) or int(32bit) diff --git a/Debugger/debugger_lib/plugins/mac/arm_64/503/libpdebug.so b/Debugger/debugger_lib/plugins/mac/arm_64/503/libpdebug.so new file mode 100755 index 0000000..a223e1e Binary files /dev/null and b/Debugger/debugger_lib/plugins/mac/arm_64/503/libpdebug.so differ diff --git a/Debugger/debugger_lib/plugins/mac/arm_64/504/libpdebug.so b/Debugger/debugger_lib/plugins/mac/arm_64/504/libpdebug.so new file mode 100755 index 0000000..1247e4f Binary files /dev/null and b/Debugger/debugger_lib/plugins/mac/arm_64/504/libpdebug.so differ diff --git a/Debugger/debugger_lib/plugins/mac/x86_64/501/libpdebug.so b/Debugger/debugger_lib/plugins/mac/x86_64/501/libpdebug.so index 5374e1c..317c74e 100755 Binary files a/Debugger/debugger_lib/plugins/mac/x86_64/501/libpdebug.so and b/Debugger/debugger_lib/plugins/mac/x86_64/501/libpdebug.so differ diff --git a/Debugger/debugger_lib/plugins/mac/x86_64/503/libpdebug.so b/Debugger/debugger_lib/plugins/mac/x86_64/503/libpdebug.so index cdba992..c845c63 100755 Binary files a/Debugger/debugger_lib/plugins/mac/x86_64/503/libpdebug.so and b/Debugger/debugger_lib/plugins/mac/x86_64/503/libpdebug.so differ diff --git a/Debugger/debugger_lib/plugins/win/x86/501/libpdebug.dll b/Debugger/debugger_lib/plugins/win/x86/501/libpdebug.dll index 4a27601..de9a5cf 100644 Binary files a/Debugger/debugger_lib/plugins/win/x86/501/libpdebug.dll and b/Debugger/debugger_lib/plugins/win/x86/501/libpdebug.dll differ diff --git a/Debugger/debugger_lib/plugins/win/x86/503/libpdebug.dll b/Debugger/debugger_lib/plugins/win/x86/503/libpdebug.dll index 8b9f257..91b736f 100644 Binary files a/Debugger/debugger_lib/plugins/win/x86/503/libpdebug.dll and b/Debugger/debugger_lib/plugins/win/x86/503/libpdebug.dll differ diff --git a/Debugger/debugger_lib/plugins/win/x86_64/501/libpdebug.dll b/Debugger/debugger_lib/plugins/win/x86_64/501/libpdebug.dll index 9f8b988..b14204e 100644 Binary files a/Debugger/debugger_lib/plugins/win/x86_64/501/libpdebug.dll and b/Debugger/debugger_lib/plugins/win/x86_64/501/libpdebug.dll differ diff --git a/Debugger/debugger_lib/plugins/win/x86_64/503/libpdebug.dll b/Debugger/debugger_lib/plugins/win/x86_64/503/libpdebug.dll index 49ca883..94f44a9 100644 Binary files a/Debugger/debugger_lib/plugins/win/x86_64/503/libpdebug.dll and b/Debugger/debugger_lib/plugins/win/x86_64/503/libpdebug.dll differ diff --git a/Debugger/debugger_lib/plugins/win/x86_64/504/libpdebug.dll b/Debugger/debugger_lib/plugins/win/x86_64/504/libpdebug.dll new file mode 100644 index 0000000..4c35aa2 Binary files /dev/null and b/Debugger/debugger_lib/plugins/win/x86_64/504/libpdebug.dll differ diff --git a/Docs/Development-instructions/how_to_join.md b/Docs/Development/how-to-join.md old mode 100644 new mode 100755 similarity index 71% rename from Docs/Development-instructions/how_to_join.md rename to Docs/Development/how-to-join.md index 6d9a0c3..b469453 --- a/Docs/Development-instructions/how_to_join.md +++ b/Docs/Development/how-to-join.md @@ -42,17 +42,61 @@ debugger 由两部分组成,分别是 lua 调试主体和 c 扩展库。使用 1. VSCode打开一个待调试工程,切换到调试界面,点击界面上的齿轮创建/查看配置。 2. 配置文件中有一项`logLevel` , 它表示日志级别。默认是1,为了展示所有日志现在改为0。 -3. 正常开始lua调试流程, 点击输出(OUTPUT), 其中 Debugger/log 日志选项卡可以查看Debugger打出的日志。 +3. 正常开始lua调试流程, 点击输出(OUTPUT), 其中 LuaPanda Debugger 日志选项卡可以查看Debugger打出的日志。 4. 如果需要在debugger中增加新日志,使用`LuaPanda.printToVSCode()`,但建立网络连接(vscode底部颜色条变为橙色)之后debugger日志才会打印在vscode中。 -5. 可以使用日志跟踪运行状态。但因为VSCode的异步打印机制,日志可能无法保证严格顺序。也可以参考`Adapter/log` ,日志信息也会在这里打印,这里的日志是严格排序的,可以对照[运行流程图](../static/work-flow.png)了解交互过程。 +5. 可以使用日志跟踪运行状态。但因为VSCode的异步打印机制,日志可能无法保证严格顺序。也可以参考`LuaPanda Adapter` ,日志信息也会在这里打印,这里的日志是严格排序的,可以对照[运行流程图](../Res/work-flow.png)了解交互过程。 -![debug_log](../static/debug_log.png) +![debug_log](../Res/debug_log.png) +## 编译相关库 ++ mac +lua: +``` +make macosx +sudo make install +``` +luasocket: +``` +//LUAV指定版本 +//LUAINC指定头文件位置 +make LUAV=5.4 LUAINC=-I/usr/local/include macosx +sudo make install +``` + +libpdebug: + +``` +g++ -shared -o libpdebug.so libpdebug.cpp -I/usr/local/include -L/usr/local/lib -llua +``` + + + ++ win + +mingw:https://nuwen.net/mingw.html + +lua: + +``` +make mingw +``` + +luasocket: + +修改 src/makefile 文件中的 lua 版本,mingw_base 及编译头文件,lua库的引用位置 + +``` +make mingw +``` + +libpdebug: + +使用 vs 编译,创建动态库项目 diff --git a/Docs/Development-instructions/project-description.md b/Docs/Development/project-description.md old mode 100644 new mode 100755 similarity index 91% rename from Docs/Development-instructions/project-description.md rename to Docs/Development/project-description.md index 40ffc52..fa59d50 --- a/Docs/Development-instructions/project-description.md +++ b/Docs/Development/project-description.md @@ -6,7 +6,7 @@ -![debug-arch](../static/debug-arch.png) +![debug-arch](../Res/debug-arch.png) @@ -35,8 +35,7 @@ │   ├── debugger_lib │   │ ├── libpdebug.h/cpp C调试库源码 │   │ └── plugins 打包好的调试库文件 -│   ├── LuaPanda.lua Debugger源码 -│   └── DebugTools.lua Debugger工具文件 +│   └── LuaPanda.lua Debugger源码 ├── Docs 项目文档 ├── LICENSE.txt 证书 ├── package.json 工程配置文件 @@ -44,7 +43,7 @@ ├── src VSCode Extension源码 │   ├── LogManager.ts 日志管理模块 │   ├── StatusBarManager.ts 状态栏管理模块 -│   ├── dataProcesser.ts adapter<->debugger之间数据收发 +│   ├── dataProcessor.ts adapter<->debugger之间数据收发 │   ├── debugAdapter.ts Adapter入口文件 │   ├── extension.ts VSCode插件启动文件 │   ├── luaDebug.ts 实现Adapter的主要功能 @@ -95,7 +94,7 @@ adapter->vscode: debugger收到命令 Note right of debugger :继续执行... ``` -( 若看不到流程图,请点击 [这里](../static/work-flow.png)) +( 若看不到流程图,请点击 [这里](../Res/work-flow.png)) 启动时,adapter把VSCode前端信息(包括初始化设置和断点)传给debugger,之后debugger会设置钩子,监控lua运行状态。lua每执行一行都会进入钩子函数中,钩子中会做断点判断,遇到断点处会暂停主线程lua的执行,并把变量信息和调用栈发给VSCode展示。 @@ -106,13 +105,13 @@ Note right of debugger :继续执行... 1. 下载工程后,在工程目录下运行 `npm install` 安装 npm 依赖,此操作要连外网或设置代理。 2. 编译: 把整个工程目录 `LuaPanda` 用 VSCode 打开, 点击菜单 terminal -> RunBuild Task, vscode会自动切换到console进行编译 -![run_build_task](../static/run_build_task.jpg) +![run_build_task](../Res/run_build_task.jpg) *每次修改代码后要先编译再运行 3. 打包: 点击菜单 terminal -> Run Task. 选择package, 就会在工程目录下打出VSIX包 -![compile_package](../static/compile_package.png) +![compile_package](../Res/compile_package.png) 4. 切换到 VSCode 的 debug 页卡,点击 run 按钮。会弹出 [扩展开发主机] 窗口。这个窗口中已经安装了编译好的插件,在[扩展开发主机]窗口中进行调试操作即可。 @@ -128,6 +127,6 @@ Note right of debugger :继续执行... **查看日志**:在VSCode的OUTPUT(输出)页卡中,分别展示Adapter和Debugger的日志。 -![debug_log](../static/debug_log.png) +![debug_log](../Res/debug_log.png) diff --git a/Docs/Manual/FAQ.md b/Docs/Manual/FAQ.md index cfe2bde..fbbedf5 100644 --- a/Docs/Manual/FAQ.md +++ b/Docs/Manual/FAQ.md @@ -26,7 +26,7 @@ ## 查看调试日志 -- 调试器自带日志模块,方便追踪问题。使用方法是切换到console的OUTPUT(输出)页卡, 选择 Adapter/log 或者 Debugger/log 就可以查看对应的日志。 +- 调试器自带日志模块,方便追踪问题。使用方法是切换到console的OUTPUT(输出)页卡, 选择 LuaPanda Adapter 或者 LuaPanda Debugger 就可以查看对应的日志。 - 通常看Adapter就可以展示Adapter和Debugger的交互信息。 - 如果需要更全面的日志,可以调整launch.json中的`logLevel:0`,再查看Debugger日志,可以输出每行执行到的文件信息。因日志较多,level设置为0可能会造成卡顿(0级日志主要用于调试器开发,使用时开1级日志就可以)。 @@ -77,21 +77,39 @@ format: cwd + getinfo ## 执行到断点处无法停止 -通常遇到的情况是stop on entry时可以停止,但是后续或者子文件的断点无法停止。 +通常遇到的情况是 stop on entry 或者 LuaPanda.BP() 时可以停止,但是后续或者子文件的断点无法停止。 这种情况是**断点路径**和**当前文件format路径**对比不一致导致的,因为用户环境多样,可使用下面方法定位问题: +首先确定在launch.json配置文件中正确配置了lua后缀。 + +![](../Res/Manual/luaext.png) + + + +如果用户设置的断点依然无法命中,可以按如下操作 + 1. 打开launch.json的stopOnEntry 2. 在断点未停的位置,保持断点并在源码中加入一行代码`LuaPanda.BP()`. -3. 再次运行项目,让项目运行并停止在`LuaPanda.BP()` (此处也可能会报错找不到文件,不要停止调试,进行下一步)。 -4. 在控制台输入`LuaPanda.doctor()`, 查看给出的路径建议中 filepath和getinfo是否一致,根据具体情况调整直到二者一致即可。 +3. 再次运行项目,让项目运行并停止在`LuaPanda.BP()` (此处也可能会报错找不到文件,不要停止调试器直接,进行下一步)。 +4. 在控制台输入`LuaPanda.doctor()`, 查看给出的路径建议中 Formated和Breakpoint是否一致,并根据提示进行调整。 +5. 如果 LuaPanda.lua 文件版本为3.2.0 ,在控制台输入`LuaPanda.testBreakpoint()`, 根据提示进行操作,最终确保 Formated 路径是 Breakpoint 路径的子集。 + +![](../Res/Manual/testbk.png) + + ## VSCode端无法和lua建立连接 -- 在无自定义主题的情况下,建立连接前VSCode下端状态栏为`蓝色`,建立连接后变为`橙色` -- 检查`require("LuaPanda").start("127.0.0.1",8818);`和`launch.json`中工程配置的端口号是否一致,并尝试重启VSCode。 ++ 可以看下调试控制台,第一行表示调试器的VScode插件正常启动,蓝框中的 [Connected] 部分表示成功建立了连接。可以依据此判断是否建立连接。 + +![](../Res/Manual/connected.png) + + + +- 如果无法建立连接,可以检查`require("LuaPanda").start("127.0.0.1",8818);`和`launch.json`中工程配置的端口号是否一致,并尝试重启VSCode。 @@ -99,4 +117,35 @@ format: cwd + getinfo - 手机和pc处于同一网段 - 手机端App集成luasocket -- 路径问题:真机中回传lua路径和pc是不同的,这时候需要lua在手机中的路径结构和pc中一致。以便调试器可以用cwd(工程文件夹) + getinfo(文件相对路径)找到对应文件。 \ No newline at end of file +- 路径问题:真机中回传lua路径和pc是不同的,这时候需要lua在手机中的路径结构和pc中一致。以便调试器可以用cwd(工程文件夹) + getinfo(文件相对路径)找到对应文件。 + + + +## 使用 xlua Demo 测试时断点不会停 + +使用 xlua 时,如果修改 launch.json 中的文件后缀,要重启 unity 。以避免修改的配置无法加载导致的断点不停。 + +## 版本说明和升级建议 + + 3.0.0 之后加入了 LuaPanda.lua 文件的自动更新提示,帮助把此文件保持到最新。升级原理是检测 VScode 打开工程中是否包含 LuaPanda 文件,并匹配文件中的版本号,如果落后于当前插件版本,则用插件中附带的最新版覆盖。升级过程无需网络,也不会对外发送和接收数据。 + + ![updateTips](../Res/updateTips.png?raw=true) + + 另外加入了配置页面,点击状态栏的LuaPanda图标即可打开。其中提供了一些常用配置方便用户修改。配置页面打开时读取 launch.json 中的对应数据,并在配置完成后把数据写回launch.json, 如果不想使用配置页面,直接修改 launch.json 中的项目可以达到同样的效果。 + + + +## 关于找不到`libpdebug`模块报错 + + `libpdebug.so(dll)` 是放置在VSCode插件中的调试器C扩展,会在调试器运行时自动加载,作用是加速调试。此模块未能加载时,调试器功能不会受到影响,仍可正常使用。 + + xlua允许用户重写文件加载函数`CustomLoader`,sluaunreal也提供类似方法`setLoadFileDelegate`。 + + 发生此问题的原因之一是用户重写的加载函数中没有加入对so/dll的处理,加载so/dll时会报找不到文件错误,但随后执行lua原生loader能够正确加载libpdebug。 + + 查看libpdebug.so是否加载的方式是在控制台输入`LuaPanda.getInfo()`, 返回信息中有 hookLib Ver 说明libpdebug已经加载。此时可以忽略报错或在文件加载函数函数中正确处理.so/dll。 + + +## 我仍想使用LuaPanda旧版本, 不希望自动升级 + +参见 `升级说明` 文档中继续使用旧版本的方法 \ No newline at end of file diff --git a/Docs/Manual/access-guidelines.md b/Docs/Manual/access-guidelines.md old mode 100644 new mode 100755 index f7dba05..efa0857 --- a/Docs/Manual/access-guidelines.md +++ b/Docs/Manual/access-guidelines.md @@ -1,67 +1,50 @@ -# 接入指引 +# 调试器接入指引 [TOC] -lua调试器依赖于 **luasocket** 和 **规范的路径**,需验证这两点后方可接入,下面详细介绍。 +# 准备工作 -### 第一步 项目需带有luasocket +### 第一步 验证项目带有luasocket 调试器需要 luasocket 使 debugger 和 VSCode 建立起通信。**目前lua框架: slua, slua-unreal, xlua 都已集成 luasocket**。 -**测试方法**:在项目lua中加入`require("socket.core");`,如果运行不报错,工程已经包含luasocket,测试通过。 +**测试方法**:在项目lua中加入代码`require("socket.core");`,如果运行不报错说明工程已经包含luasocket,测试通过。 ### 第二步 路径说明 -**2.3.0增加了自动路径模式。开启后调试器会自动处理路径。用户只需保证VSCode打开的目录中不存在同名lua文件即可。当开启自动路径模式后,可跳过本节继续接入。** - -launch.json文件中配置项 `"autoPathMode": true/false` 可以设置是否使用自动路径,无此配置项时默认关闭。 - -#### 关于自动路径配置和手动配置的说明: - -调试器需要获得当前执行文件的绝对路径,以便做断点匹配和命中断点后打开对应文件。但是通过getinfo从lua虚拟机获取的路径可能是一个相对路径 ,调试器要把它转化为绝对路径。 - -把相对路径转为绝对路径的方式有以下两种: - -1. 自动路径模式 - -自动路径处理,原理是VSCode端在启动时扫描当前工程中的lua文件,创建[文件路径表]。当需要绝对路径时,把从 getinfo 获得的非完整路径在[文件路径表]中查询 ,就可以获得绝对路径。这种路径策略易于配置使用,也有利于真机调试。因为[文件路径表]的查询key是文件名,所以需要用户保证工程中不存在同名lua文件,否则断点可能会指向错误。 - -2. 拼接路径模式 - -本模式下会使用 cwd+getinfo 和 VSCode 传来的断点路径作对比,完全一致才算命中。这种策略比较准确,不会受到同名文件的干扰,但是配置较麻烦。如果希望手动配置路径或了解处理的细节,请继续阅读。 - -调试器运行需要从lua虚拟机中获取当前文件信息。所以要求工程debug.getinfo或debug.traceback能输出较为规范的路径,就是**绝对路径**或者**对于固定位置的相对路径**。 - -**测试方法**:在代码中加入`print(debug.traceback("debug test"))`, 查看打印的堆栈。 - +**2.3.0 之后的版本增加了自动路径模式。用户可以不必关心路径问题。** +launch.json文件中配置项 `"autoPathMode": true/false` 可以设置是否使用自动路径,此配置项**默认开启(true)**。 + +
+建议用户使用自动路径模式,可继续下一步。也可以点我查看关于路径的原理介绍 +
+关于路径的说明:
+调试器需要获得当前执行文件的绝对路径,用来做断点匹配以及命中断点后打开对应文件。但是通过 getinfo 从lua虚拟机获取的路径可能是一个相对路径 (这里取决于lua虚拟机读文件时的传入路径) ,调试器要把它转化为绝对路径。
+把相对路径转为绝对路径的方式包括 自动路径模式 和 拼接路径模式
++ 自动路径模式(推荐)
+  自动路径处理的原理是 VSCode 端在启动时扫描当前工程中的 lua 文件,创建[文件路径表](一个map , key:文件名 value:路径)。当需要绝对路径时,把从 getinfo 获得的非完整路径在[文件路径表]中查询 ,就可以获得绝对路径。这种路径策略易于配置使用,也有利于真机调试。因为[文件路径表]的key是文件名,所以需要用户保证工程中不存在同名lua文件,否则断点可能会指向错误。
++ 拼接路径模式
+  本模式下会使用 cwd + getinfo 拼接并和 VSCode 传来的断点路径作对比,完全一致才算命中。这种策略比较准确,不会受到同名文件的干扰,但是配置较麻烦。如果希望手动配置路径或了解处理的细节,请继续阅读。
+  调试器运行需要从lua虚拟机中获取当前文件信息。所以要求工程debug.getinfo或debug.traceback能输出较为规范的路径,就是绝对路径或者对于固定位置的相对路径。
+使用拼接路径模式接入(不推荐使用, 仅做介绍):
+测试方法:在代码中加入`print(debug.traceback("debug test"))`, 查看打印的堆栈。
 如下打印出文件的绝对路径,可以进行下一步
-![absolute_path](../static/access_introduction/absolute_path.png)
-
+
 打印出的信息是一个相对路径(路径前可能带有@),这不会影响调试器工作,可进行下一步
-![relatively_path](../static/access_introduction/relatively_path.png)
-
+
 打印出的路径包含在[string ]中,只要是符合上面的要求,是一个可以定位的路径,可进行下一步
-![string_path](../static/access_introduction/string_path.png)
-
-
-
+
 如果仅输出了文件名,而这些文件其实不在同一个目录下,如下面的案例。此时需要调试器根据文件名定位文件路径,必须开启自动路径功能( launch.json 中设置 autoPathMode:true ),否则无法正常工作。
-
-![filename_path](../static/access_introduction/filename_path.png)
-
-
-
-**更新说明**:   lua中允许使用`require("a.b") `引用子目录中的文件,在标准lua库中,lua虚拟机会把路径转化为 a/b 传给调试器。但是有些开发框架没有做这样的转换,调试器会把收到的a.b当做一个文件处理。结果是导致调试进入require("a.b")的文件时,报找不到文件错误。
-
-这个问题的解决办法需要用户修改自身框架,保证`require("a.b") `调用文件时传给lua虚拟机的路径是a/b。或者参见  [issue #24](https://github.com/Tencent/LuaPanda/issues/24) , 在调试器中强转换路径中的. 为 /
-
+
+
+
-# 接入工作 +# 开始接入 ### 第一步 下载VSCode调试扩展 @@ -71,9 +54,9 @@ launch.json文件中配置项 `"autoPathMode": true/false` 可以设置是否使 ### 第二步 放入debugger 文件,并引用 -文件:`LuaPanda.lua`, `DebugTools.lua` +文件:`LuaPanda.lua` -下载位置:github 的 `Debugger` 目录下 +下载位置:github 项目的 `Debugger` 目录下 把以上两个文件放在lua代码可以引用到的位置,并在用户代码中引用: @@ -81,7 +64,7 @@ launch.json文件中配置项 `"autoPathMode": true/false` 可以设置是否使 require("LuaPanda").start("127.0.0.1",8818); ``` -*8818是默认端口号,如果需要修改,请同时修改launch.json的端口设置。 +*8818是默认端口号,如果需要修改,必须同时修改launch.json的端口设置。 @@ -93,11 +76,12 @@ require("LuaPanda").start("127.0.0.1",8818); ### 第二步 调试配置 -切换到VSCode的**调试选项卡**,点击齿轮,在弹出框中选择 LuaPanda (若无此选项说明以前用别的插件调试过lua , 要把先前用过的调试插件禁用)。会自动生成launch.json文件。 -![vscode_debug_ui](../static/access_introduction/vscode_debug_ui.png) +切换到VSCode的**调试选项卡**,点击齿轮,在弹出框中选择 LuaPanda (若无此选项说明以前用别的插件调试过lua , 要把先前用过的调试插件禁用)。之后会自动生成launch.json文件。 +![vscode_debug_ui](../Res/access_introduction/create-launchjson.png) -launch.json 配置项中要修改的主要是luaFileExtension, 改成lua文件使用的后缀就行。(比如xlua改为lua.txt, slua是txt)。**各配置项鼠标悬停会有提示**,可根据需要更改。 -![debug_config](../static/access_introduction/debug_config.png) +生成的 launch.json 文件的个配置项可以参考 [launch.json配置说明](launch-json-introduction.md) + +**【非常重要】launch.json 配置项中的修改主要是 luaFileExtension , 改成lua文件使用的后缀。**(比如xlua改为lua.txt, slua是txt)。**各配置项鼠标悬停会有提示**,可根据需要更改。 @@ -109,13 +93,13 @@ launch.json 配置项中要修改的主要是luaFileExtension, 改成lua文件 如果开始调试时弹出了如下错误 -![cannot_find_file](../static/access_introduction/cannot_find_file.png) +![cannot_find_file](../Res/access_introduction/cannot_find_file.png) 不要停止调试,在VSCode中找到报错中提到的文件,在其中任意位置打一个断点,之后在调试控制台中输入`LuaPanda.doctor()`。这是一个帮助用户检查错误的命令,可以进行路径分析,给出建议。 输出结果 -![doctor](../static/access_introduction/doctor.png) +![doctor](../Res/access_introduction/doctor.png) format是调试器拼接出的文件路径,filepath是文件真实存在的路径。 @@ -127,9 +111,7 @@ format是调试器拼接出的文件路径,filepath是文件真实存在的路 **先运行VSCode端,再运行Lua代码**: 点击调试选项卡左上角的绿色箭头,再运行unity/ue4工程。如果有stopOnEntry或是执行到断点处,就会自动停住。 - - -![debug_ui](../static/access_introduction/debug_ui.png) +![debug_ui](../Res/access_introduction/debug_ui.png) enjoy! @@ -142,7 +124,7 @@ LuaPanda 在PC上调试会默认使用 c hook,它是用c重写了debugger的 验证方式:停在断点处后,在调试控制台输入`LuaPanda.getInfo()`, 返回信息的BaseInfo会给出提示,如果c库已加载,还会给出版本号。 -![getinfo](../static/access_introduction/getinfo.png) +![getinfo](../Res/access_introduction/getinfo.png) + 如果提示c库未能正确加载,可以使用`LuaPanda.doctor()`命令查看详细信息 @@ -168,112 +150,53 @@ LuaPanda 在PC上调试会默认使用 c hook,它是用c重写了debugger的 Breaks Info: 断点列表 - ![getinfo_complete](../static/access_introduction/getinfo_complete.png) + ![getinfo_complete](../Res/access_introduction/getinfo_complete.png) + LuaPanda.doctor() 诊断工具,帮助用户诊断当前存在的问题。 - ![doctor_complete](../static/access_introduction/doctor_complete.png) + ![doctor_complete](../Res/access_introduction/doctor_complete.png) + LuaPanda.getBreaks() 打印所有断点信息(已包含在getInfo中) - ![get_breaks_complete](../static/access_introduction/get_breaks_complete.png) + ![get_breaks_complete](../Res/access_introduction/get_breaks_complete.png) ++ LuaPanda.testBreakpoint() + 测试断点,用于分析路径错误导致断点无法停止的情况。 + + 使用方法是 launch.json 中开启 stopOnEntry, 或者在代码中加入LuaPanda.BP()。运行调试器,当停止在 stopOnEntry 或者 LuaPanda.BP() 时在调试控制台输入 LuaPanda.testBreakpoint(),根据提示打一个断点后再次输入 LuaPanda.testBreakpoint()。此时系统会给出一些路径提示,帮助用户分析断点可能无法停止的原因。 + + # 调试器设置项说明 -调试器有几处设置项,这里做详细说明 +调试器有两处设置项,这里做详细说明 ### 1. VSCode端工程的launch.json文件 -点击VSCode调试页卡的齿轮就会自动打开launch.json文件。 - -```lua -{ - "version": "0.2.0", - "configurations": [ - { - //正常调试配置 - "type": "lua", - "request": "launch", - "internalConsoleOptions": "openOnFirstSessionStart", - "name": "LuaPanda", - "program": "${workspaceFolder}", - "cwd": "${workspaceFolder}", - "pathCaseSensitivity": true, //路径大小写敏感 - "docPathReplace": [], //路径映射 - "luaFileExtension": "", //lua文件后缀 - "connectionPort": 8818, //连接端口号 - "stopOnEntry": true, //建立连接后自动停止 - "useCHook": true, //使用C调试库 - "logLevel": 1 //日志等级,默认无需修改 - }, - { - //单文件调试配置 - "type": "lua", - "request": "launch", - "internalConsoleOptions": "neverOpen", - "name": "LuaPanda-DebugFile", //单文件调试,请不要修改 - "program": "${workspaceFolder}", - "cwd": "${workspaceFolder}", - "pathCaseSensitivity": true, - "luaPath": "", //lua.exe所在位置 - "luaFileExtension": "", - "connectionPort": 8818, - "stopOnEntry": true, - "useCHook": true, - "logLevel": 1 - } - ] -} -``` - -**cwd**:工作路径,${workspaceFolder}表示VSCode加载目录的路径。 - -**luaFileExtension**:lua文件后缀。不同框架中lua文件后缀可能不同, 比如 - -``` -slua:txt -xlua:lua.txt -sluaunreal:lua -``` - -此项为空会尝试从getinfo中自动获取,如果获取不到会报打开文件错误,仍需用户手动填写。 - -**pathCaseSensitivity**:路径大小写敏感。如文件名是Test.lua ,执行此文件时getinfo获得的文件名是test.lua , 就需要把这个选项设置为false。否则路径因大小写不一致会查找不到文件。 - -**docPathReplace**:路径映射。当执行的lua文件和VScode查看的lua文件位置不同时, 可以使用路径映射,主要用于真机调试。如 - -`docPathReplace": ["A", "B"]` - -我们把目录分为A:运行路径 和 B:观察路径 -前提: 用户保证运行路径A 和观察路径B 的代码是完全相同的,否则调试时行号会错乱 -具体实现: -调试器启动时会自动把 B 下的断点位置替换成 A, 以便运行时判断断点命中。 -命中断点后,用户把堆栈中 A 路径替换回 B ,便于VSCode展示文件给用户看 +点击VSCode调试页卡的齿轮就会自动生成或打开 launch.json 文件。此文件默认放置位置 `vscode打开文件夹/.vscode/launch.json` -**stopOnEntry**:调试器建立连接后立刻暂停,建议true。 +关于 launch.json 各项配置的详细含义,可以参考 [launch.json 配置说明](./launch-json-introduction.md) -**connectionPort**:连接端口号。此处请保持和`require("LuaPanda").start("127.0.0.1",8818);` 中一致,否则无法建立网络连接。 -**logLevel**:日志等级,默认1。0是在输出所有日志,但输出日志会降低执行效率,造成运行卡顿,仅在定位问题时修改。 -**useCHook**:是否使用C lib库,此项默认不显示,默认值为true。需要时可手动添加修改。true是自动尝试查找c库提升执行效率,false是不使用c库。 - - - -### 2. LuaPanda.lua 文件头部 +### 2. LuaPanda.lua 文件配置 ```lua -local openAttachMode = true; +--用户设置项 +local openAttachMode = true; --是否开启attach模式。attach模式开启后可以在任意时刻启动vscode连接调试。缺点是没有连接调试时也会略降低lua执行效率(会不断进行attach请求) local attachInterval = 1; --attach间隔时间(s) local customGetSocketInstance = nil; --支持用户实现一个自定义调用luasocket的函数,函数返回值必须是一个socket实例。例: function() return require("socket.core").tcp() end; local consoleLogLevel = 2; --打印在控制台(print)的日志等级 0 : all/ 1: info/ 2: error. -local connectTimeoutSec = 0.005; --等待连接超时时间, 单位s. 时间过长等待attach时会造成卡顿,时间过短可能无法连接。建议值0.005 - 0.05 +local connectTimeoutSec = 0.005; --lua进程作为Client时, 连接超时时间, 单位s. 时间过长等待attach时会造成卡顿,时间过短可能无法连接。建议值0.005 - 0.05 +local listeningTimeoutSec = 0.5; -- lua进程作为Server时,连接超时时间, 单位s. 时间过长等待attach时会造成卡顿,时间过短可能无法连接。建议值0.1 - 1 +local userDotInRequire = true; --兼容require中使用 require(a.b) 和 require(a/b) 的形式引用文件夹中的文件 +local traversalUserData = false; --如果可以的话(取决于userdata原表中的__pairs),展示userdata中的元素。 如果在调试器中展开userdata时有错误,请关闭此项. +--用户设置项END ``` **openAttachMode**: attach模式开启后可以在任意时刻启动vscode连接调试。缺点是不调试时也会略降低lua执行效率(会不断进行attach请求)。**所以请不要把调试器放在正式环境大量外发**。 @@ -286,11 +209,15 @@ local connectTimeoutSec = 0.005; --等待连接超时时间, 单位s. 时 **connectTimeoutSec**:连接超时时间,如出现调试器连接不上VSCode时,可以改长一些,但不建议超过0.05 +**listeningTimeoutSec**: vscode作为client,lua进程作为server时的超时时间 + +**traversalUserData**: 在调试时是否遍历 userdata ,默认关闭。 遍历ud的原理是查询ud的原表中是否有 \_\_pairs ,如果有,使用for k,v in pairs遍历ud 。 因这个操作在有些框架中会报错(有些框架给ud原表赋值了 \_\_pairs ,但可能给了一个错误的方法,导致遍历失败),所以默认是关闭的,有需要可以打开。 + # 调试器升级方法 通常VSCode插件版本是向下兼容的,只更新VSCode插件也可正常使用,但无法体验最新特性。 -更新方法参阅文档 [update](https://github.com/Tencent/LuaPanda/blob/master/Docs/Manual/update.md) +更新方法参阅文档 [升级和版本说明](./update.md) diff --git a/Docs/Manual/common-functions.md b/Docs/Manual/common-functions.md new file mode 100644 index 0000000..d8a27d4 --- /dev/null +++ b/Docs/Manual/common-functions.md @@ -0,0 +1,95 @@ +# 其他功能说明 + + + +## 多目标调试 (multi-target) + +有时我们希望同时调试多个lua虚拟机。最简单的办法是启动多个vscode窗口,打开不同的项目,分别和各虚拟机建立连接,唯一要注意的是不同的虚拟机需要设置不同的端口通信( 同时修改launch.json和 `require("LuaPanda").start(ip, port) `中的端口即可 ) + +如果希望在一个VSCode窗口中连接多个lua虚拟机,就要用到多目标调试。如下图,一个 VSCode 窗口同时和两个虚拟机建立了连接,Project1 和 Project2 分别独立运行,通过切换堆栈可以分别观察它们的状态。 + +![multi-target](../Res/Manual/common-functions/multi-target.png) + + + +要如何进行多目标调试呢? LuaPanda支持两种方式: + +#### 1. 多个项目放在同一个workspace中 + +![workspace](../Res/Manual/common-functions/workspace.png) + ++ 打开workspace, 发现存在两个项目 project1 和 project2. 切换到调试选项卡,可以分别给两个项目增加配置。 + +![workspace-config](../Res/Manual/common-functions/workspace-config.png) + + + ++ 如果两个项目之前单独调试过,那么配置文件应该是已存在的。分别创建两个项目的配置后,修改他们的配置名和端口号,使之不同,避免相互干扰。 + +![change-name-port](../Res/Manual/common-functions/change-name-port.png) + ++ 在vscode窗口中运行两个目标。分别在两个lua虚拟机的代码中引用调试器(**注意保持端口一致**),之后启动虚拟机,即可分别建立连接,开始调试。 + + + +#### 2. 多个项目放置在同一个工程中 + +这种情况下,一个文件夹中的 lua 代码分别运行在两个虚拟机,配置方法如下 + ++ 打开上层公共文件夹 + ++ 建立launch.json文件: 切换到debug选项卡,创建一份launch.json 配置文件 + ++ 修改 launch.json。 复制一份 LuaPanda 配置,并修改name和port, 使之不同,如下图 + +![one-folder](../Res/Manual/common-functions/one-folder.png) + + + ++ 在VScode中启动两个目标,在两个lua虚拟机运行的lua代码中分别引用调试器(**注意设置端口**),运行各lua虚拟机,即可开始调试。 + + + + + +## 独立文件调试说明(debugging independent file) + +### 什么是独立文件 + +我们这里的 **独立文件** 指的是不依赖于 `其他模块/特定运行环境/特定框架` 的 lua 文件。 + +即存在一个文件 a.lua 用户执行 + +``` +lua a.lua +``` + +注意:此模式下无需加入require("LuaPanda"), 调试器会自动引用。 + +![config_select](../Res/config-select.png?lastModify=1605749769) + +把调试选项切换至`LuaPanda-DebugIndependentFile`, **代码编辑窗口切换到待调试文件**,点击下图绿色箭头或按F5运行。 + +### 调试独立文件 + + + +VSCode 会启动一个新终端,调用 lua 命令来执行当前打开的lua代码。 + +![nodebug](../Res/nodebug.png?lastModify=1605749769) + +把代码编辑窗口切换到待执行文件(让待执行文件的窗口处于激活状态),然后如下图操作。 + +### 快速运行独立文件 + + + +如果希望使用运行独立文件,第一步是建立 launch.json ,可以[参阅文档](launch-json-introduction.md),这里不再重复。 + +就可以正确运行,并输出运行结果的文件。调试器执行独立文件也正是调用了上面的命令,这些被执行的命令可以从vscode的终端里看到。 + + + +## VSCode ssh远程调试 + +Vscode 有非常强大的远程插件机制,通过 Remote-SSH 插件就可以实现。远程插件这里不做详细介绍,LuaPanda 支持远程调试,唯一要注意的是打开的文件夹中不要包含中文或异常符号,在一些平台上(如 linux)会因为这些符号导致问题。 \ No newline at end of file diff --git a/Docs/Manual/debug-file.md b/Docs/Manual/debug-file.md deleted file mode 100644 index 15a0a6c..0000000 --- a/Docs/Manual/debug-file.md +++ /dev/null @@ -1,89 +0,0 @@ -# 单文件运行调试说明 - -[TOC] - -我们在做lua开发时,有时希望在一个独立文件中测试函数执行结果。单文件的执行/调试也是为了方便这种场景。 - -配置方法: - -### 1. 重建launch.json - -备份并删除工程现有的launch.json,点击VSCode调试界面的齿轮重新生成配置文件。 - -新生成的配置文件中新增了单文件执行/调试的选项。 - -```json -配置文件示例 -{ - "version": "0.2.0", - "configurations": [ - { - //正常调试配置 - "type": "lua", - "request": "launch", - "internalConsoleOptions": "openOnFirstSessionStart", - "name": "LuaPanda", - "program": "${workspaceFolder}", - "cwd": "${workspaceFolder}", - "pathCaseSensitivity": true,//路径大小写敏感 - "docPathReplace": [], //路径映射 - "luaFileExtension": "", //lua文件后缀 - "connectionPort": 8818, //连接端口号 - "stopOnEntry": true, //建立连接后自动停止 - "useCHook": true, //使用C调试库 - "logLevel": 1 //日志等级,默认无需修改 - }, - { - //单文件调试配置 - "type": "lua", - "request": "launch", - "internalConsoleOptions": "neverOpen", - "name": "LuaPanda-DebugFile",//单文件调试,请不要修改 - "program": "${workspaceFolder}", - "cwd": "${workspaceFolder}", - "pathCaseSensitivity": true, - "luaPath": "", //lua.exe所在位置 - "packagePath": "", //希望加入package.path的路径 - "luaFileExtension": "", - "connectionPort": 8818, - "stopOnEntry": true, - "useCHook": true, - "logLevel": 1 - } - ] -} -``` - - - -相对于旧的配置文件,增加了单文件调试配置(LuaPanda-DebugFile)选项,它可以用来调试单文件。 - -新增的配置项包括 -**luaPath**:lua命令路径,如果lua命令已经存在系统path中,可以不填。 - -**packagePath**:运行起lua文件时,希望加入package.path的路径。 - - - -### 2. 运行单文件 - -把代码编辑窗口切换到待执行文件,如下选择不调试情况下启动。 - -![nodebug](../static/nodebug.png) - -VSCode 会启动一个新终端,执行当前打开的lua代码。 - - - -### 3. 调试单文件 - -把调试选项切换至`LuaPanda-DebugFile`, **代码编辑窗口切换到待调试文件**,运行。 - -![config_select](../static/config-select.png) - -此模式下无需加入require("LuaPanda"), 调试器会自动引用。 - -注意:刚加载文件夹后,VSCode读取当前活动窗口会出错,出现如下错误提示再执行一次就可以了。 -``` -lua: cannot open extension-output-#5: No such file or directory -``` diff --git a/Docs/Manual/debug-on-phone.md b/Docs/Manual/debug-on-phone.md index aea1514..ba0708d 100644 --- a/Docs/Manual/debug-on-phone.md +++ b/Docs/Manual/debug-on-phone.md @@ -67,8 +67,34 @@ require("LuaPanda").start("pcIP",8818) ``` mac上无此现象。 + ++ 超时时间设置 + 之前经常有同学咨询一种情况:本机调试可以成功连接,但是真机调试无法建立起连接。这里有两种可能: + 1. 手机apk中是否集成了luasocket + + 2. 超时时间太短 + + 这里解释下第二点: + + luasocket 是同步连接,lua attach 使用轮询机制查询是否可以建立连接,如果每次等待连接比较久,会卡游戏进程。 + + LuaPanda.lua 文件中有三个设置项 + + ```lua + local attachInterval = 1; --attach间隔时间(s) + local connectTimeoutSec = 0.005; --lua进程作为Client时, 连接超时时间, 单位s. 时间过长等待attach时会造成卡顿,时间过短可能无法连接。建议值0.005 - 0.05 + local listeningTimeoutSec = 0.5; -- lua进程作为Server时,连接超时时间, 单位s. 时间过长等待attach时会造成卡顿,时间过短可能无法连接。建议值0.1 - 1 + ``` + + 分别表示 lua 部分每次查询 attach 连接的时间间隔,查询连接的超时时间。 + + 在localhost本机调试的时候,不存在网络延迟,所以超时时间被设置的非常短,减少对游戏帧数的影响。 + + 但是在网络调试的时候,过短的超时时间可能无法建立起连接。首次使用时可以尝试调大一些,后续再根据网络状况缩小,比如把connectTimeoutSec调整到0.5。 + + ### 真机调试时的路径说明 @@ -94,4 +120,40 @@ require("LuaPanda").start("pcIP",8818) "docPathReplace": ["/data/data/com.project.test/"," C:/GameProcect/Assets/"] ``` -就可以完成路径映射,把运行环境的/data/data/com.project.test/script/test.lua映射到观察环境C:/GameProcect/Assets/script/test.lua \ No newline at end of file +就可以完成路径映射,把运行环境的/data/data/com.project.test/script/test.lua映射到观察环境C:/GameProcect/Assets/script/test.lua + + + +### 反转 client - server + +调试器分为 vscode 插件 和 LuaPanda.lua 两部分。通常情况下 VScode 插件作为 server 端,lua 进程作为 client. + +这种配置会造成问题,当运行 VScode 的 pc 处于内网时,client 通过 ip 是无法连接的(上面的安卓反向代理方法不受影响)。 + +为了解决以上情况。调试器支持 c-s 反转,具体使用方法是 + +1. 确认 VScode 插件 以及 LuaPanda.lua 都升级到3.2.0版本 + +2. 在 launch.json 中 加入如下配置 + +``` +"VSCodeAsClient": true, +"connectionIP": "127.0.0.1" +``` + +这里的 ip 填写要连接的手机 ip. 之后尝试在 VScode 中运行,会提示 + +``` +[Connecting] 调试器 VSCode Client 已启动,正在尝试连接。 TargetName:LuaPanda Port:8818 +``` + +3. 修改 lua 代码的 require 调用 + +``` +require("LuaPanda").startServer("0.0.0.0", port) +``` + +这里的port 要和要上面 TargetName:LuaPanda Port:8818 这里的 Port 保持一致,之后若连接正常即可调试 + + + diff --git a/Docs/Manual/feature-introduction.md b/Docs/Manual/feature-introduction.md old mode 100644 new mode 100755 index be1ba4b..97ffa09 --- a/Docs/Manual/feature-introduction.md +++ b/Docs/Manual/feature-introduction.md @@ -2,60 +2,85 @@ [TOC] -LuaPanda 是一个基于VSCode扩展的lua调试器,设计目标是简单易用,支持多种开发框架。因当前项目大量使用lua进行开发,为了能够提升定位修复lua问题的效率,我们自己动手开发了这个工具。 +### lua 代码辅助 -LuaPanda 使用了lua + C 双架构。调试器主体使用lua开发(可独立运行),另外有一个高性能的C扩展库,兼顾了C的高效以及lua的灵活性。 +经常使用VSCode做lua开发,所以花时间开发了lua代码辅助功能。 -lua适合的场景 +目前实现的主要功能: -- 动态下发,避免游戏打包后无法调试的。适合发布后使用。 +- 自动补全(auto completion) +- 代码片段(snippet completion) +- 定义跳转(definition) +- 引用分析 (find reference) +- 类型推断 (type inference) +- 生成注释 (comment generation) +- 代码诊断(linting):依赖 [luacheck](https://github.com/mpeterv/luacheck) +- 代码格式化 (formatting) : 依赖 [lua-fmt](https://github.com/trixnz/lua-fmt) -C 扩展适合的场景 -- 效率高,适合开发期调试。 -调试器的IDE使用VSCode,下面是调试界面。 +​ 功能展示: 代码提示和定义跳转 -![debugui](../static/feature-introduction/debugui.png) +![](../Res/feature-introduction/codeDefAndCompleting.gif) -LuaPanda由两部分组成,分别是 Debugger Extension 和 debugger 调试器。架构可以参考下图 -(图片引自 https://code.visualstudio.com/api/extension-guides/debugger-extension) +​ 功能展示: 生成注释 -![debug-arch2](../static/feature-introduction/debug-arch2.png) +​ ![](../Res/feature-introduction/generateComments.gif) -Debugger Extension 是一个 VScode 扩展。Debugger是 Lua 实现的调试器。另外LuaPanda还提供一个可选的C调试库,运行时会自动引用,使用时不必关心。 +使用代码提示时注意两点,无需其他配置 +1. 使用 VScode 打开的包含lua文件的文件夹,而不是单个lua文件。 +2. VScode 文件后缀和类型关联正确,如果 lua 文件后缀是 txt 或者是 lua.txt 都要正确关联lua类型,插件才能生效。 -# 特性 + + +### lua 代码调试 + +调试器总体分为两部分,分别是[VSCode 插件]和 运行于lua进程的[调试器主体]。二者建立网络连接通过 tcp 通信,支持真机/远程调试,所以需要用户项目中包含 luasocket。 -以下是支持的特性 +调试器总体架构可以参考下图。左侧的 VSCode 是 IDE ,中间的 Debug Adapter 表示调试器的 VSCode 插件,它和IDE通信遵循DAP协议。最右侧的 debugger 是调试器运行在 lua 中的部分,也就是 luapanda.lua文件,它捕获lua 运行状态,在触发命中断点等事件时,通知 Debug Adapter。 -- 支持单步调试,断点调试,协程调试 -- 支持lua5.1- 5.3, 支持 win/mac 平台,支持 slua/xlua/slua-unreal 等框架 -- 在断点处可以监视和运行表达式,返回结果 -- 可以根据断点密集程度调整 hook 频率,有较好的效率 -- 支持 attach 模式,lua 运行过程中可随时建立连接 -- 使用 lua / C 双调试引擎。lua 部分可动态下发,避免打包后无法调试。C 部分效率高,适合开发期调试。 +![debug-arch2](../Res/feature-introduction/debug-arch2.png) +*图片来源 https://code.visualstudio.com/api/extension-guides/debugger-extension* + +和其他调试器不同的是 : LuaPanda 的 debugger 部分使用了 lua + C 双架构。主体使用 lua 开发(可独立运行),另外有一个高性能的C扩展库,兼顾了C的高效以及lua的灵活性。C 扩展库会根据场景自动尝试加载,即使加载不了也不会影响调试,用户可以不关注。 + +lua 调试器适用的场景 + +- 动态下发,避免游戏打包后无法调试的。适合发布后使用。 + +C 模块适合的场景 + +- 效率高,适合开发期调试。 + +调试器的 IDE 使用VSCode,下面是调试界面。 + +![debugui](../Res/feature-introduction/debugui.png) + + + +# 特性 + ### 多平台的支持 Mac console + lua 5.1 -![debugon-console](../static/feature-introduction/debugon-console.png) +![debugon-console](../Res/feature-introduction/debugon-console.png) Win slua-unreal + lua5.3 -![debugon-slua-ue](../static/feature-introduction/debugon-slua-ue.png) +![debugon-slua-ue](../Res/feature-introduction/debugon-slua-ue.png) ### 展示元表 和 upvalue 可以显示table的成员数目和元表,function的upvalue。 -![show-metatable](../static/feature-introduction/show-metatable.png) +![show-metatable](../Res/feature-introduction/show-metatable.png) @@ -63,11 +88,11 @@ Win slua-unreal + lua5.3 在变量监控区可以输入并监控表达式 -![REPL-watch](../static/feature-introduction/REPL-watch.png) +![REPL-watch](../Res/feature-introduction/REPL-watch.png) 调试控制台,可以在断点处输入表达式,执行函数,或者输入变量名观察它的值 -![debug-console](../static/feature-introduction/debug-console.png) +![debug-console](../Res/feature-introduction/debug-console.png) @@ -76,7 +101,7 @@ Win slua-unreal + lua5.3 通常的调试流程是先运行vscode端,再开始执行lua工程。 attach模式支持先执行lua工程,在希望调试的时候运行调试器,建立连接,开始调试。 -![attach_mode](../static/feature-introduction/attach_mode.GIF) +![attach_mode](../Res/feature-introduction/attach_mode.GIF) @@ -84,13 +109,13 @@ attach模式支持先执行lua工程,在希望调试的时候运行调试器 在 VSCode 行号前点击鼠标右键可选择普通断点,条件断点和记录点。 -![add_condition_bk](../static/feature-introduction/add_condition_bk.png) +![add_condition_bk](../Res/feature-introduction/add_condition_bk.png) 若用户输入的条件是 `a == 2` , 调试器会执行表达式,并获取执行结果。注意执行结果 nil 和 false 为假,其他都为真。 -记录点在被执行时会打印日志。日志输出在:`DebugConsole - OUTPUT - Debugger/log` +记录点在被执行时会打印日志。日志输出在:`DebugConsole - OUTPUT - LuaPanda Debugger` -![print_log](../static/feature-introduction/print_log.png) +![print_log](../Res/feature-introduction/print_log.png) @@ -98,7 +123,7 @@ attach模式支持先执行lua工程,在希望调试的时候运行调试器 断点处允许用户修改变量的值, 用户也可以通过调试控制台给变量赋值。 -![企业微信截图_84fc8535-8733-4b04-9518-64cee91b2439](https://github.com/Tencent/LuaPanda/blob/dev/Docs/static/feature-introduction/set-var-value.gif?raw=true) +![企业微信截图_84fc8535-8733-4b04-9518-64cee91b2439](../Res/feature-introduction/set-var-value.gif?raw=true) @@ -106,7 +131,6 @@ attach模式支持先执行lua工程,在希望调试的时候运行调试器 使用单文件调试,可以在工程中很方便的调试单个lua文件。 -![debug-file](https://github.com/Tencent/LuaPanda/blob/master/Docs/static/debug-file.GIF?raw=true) +![debug-file](../Res/debug-file.GIF?raw=true) -详细配置请查看 [单文件调试说明](https://github.com/Tencent/LuaPanda/blob/master/Docs/Manual/debug-file.md) diff --git a/Docs/Manual/launch-json-introduction.md b/Docs/Manual/launch-json-introduction.md new file mode 100755 index 0000000..f6cc3f3 --- /dev/null +++ b/Docs/Manual/launch-json-introduction.md @@ -0,0 +1,96 @@ +# launch.json 配置说明 + +[TOC] + +launch.json 指的是存在于被调试项目的 .vscode/launch.json 文件,它保存调试器的运行配置,这里主要介绍各项配置项的含义,方便大家根据需要求改。 + +另外需要注意的是,调试器不同版本生成的 launch.json 文件可能会有所不同,如果升级后遇到问题,可以删除launch.json文件并重新生成。 + +下面是 3.2.0 版本的 launch.json + +```lua +{ + "version": "0.2.0", + "configurations": [ + { + "type": "lua", + "request": "launch", + "tag": "normal", + "name": "LuaPanda", + "cwd": "${workspaceFolder}", + "luaFileExtension": "", + "connectionPort": 8818, + "stopOnEntry": true, + "useCHook": true, + "autoPathMode": true, + }, + { + "type": "lua", + "request": "launch", + "tag": "independent_file", + "name": "LuaPanda-IndependentFile", + "luaPath": "", + "packagePath": [], + "luaFileExtension": "", + "connectionPort": 8820, + "stopOnEntry": true, + "useCHook": true, + } + ] +} +``` + +launch.json 包含两个模式 + ++ LuaPanda 自适应模式 ++ LuaPanda-IndependentFile 单文件调试模式 + +我们定义的自适应模式是一种最频繁使用的模式。有些调试器分为 launch 以及 attach 模式,我们理解 lua 是一种脚本语言,常嵌入 c# , c++ 中被调用,通常在使用 lua 调试器时,用户会手动启动 unity / unreal,调试器无需再拉起这些被调试程序。如果有启动 vscode 调试器拉起一个二进制程序的需求,可以关注下面的 program 选项。 + +必要配置 + +| 项目 | 默认值 | 意义 | +| ---------------- | -------------------- | ------------------------------------------------------------ | +| type | "lua" | 插件适用于lua语言,**请勿修改** | +| request | "launch" | 因为我们使用自适应模式,保持launch不必修改 | +| tag | "normal" | 可以选择normal(通用模式),attach(附加模式),区别是启动时是否自动拉起program配置指向的程序。**不建议修改** | +| name | "LuaPanda" | 展示在VScode运行按钮旁的目标名,3.2.0 后可根据需要自行修改 | +| cwd | "${workspaceFolder}" | 被调试的包含lua目录,${workspaceFolder} 指 VScode 打开的目录,通常不用修改。即使要修改,也请用 "\${workspaceFolder}/path" 这样的相对路径 | +| luaFileExtension | "" | **重要设置:** 用户设置的lua文件的后缀,如 txt , lua.txt 等 | +| connectionPort | 8818 | 默认端口号,如果连接无问题,可以不用修改。如果改了这里,请同步修改`require("LuaPanda").start(ip, port)`中的端口号 | +| stopOnEntry | true | 调试器建立连接后立刻停止。接入调试器时建议设置true, 稳定使用后可根据用户需要设置成 false | +| useCHook | true | 运行时尝试加载 c 模块,这个模块作用是加速运行,加载不成功也不会影响调试效果 | +| autoPathMode | true | 是否使用自动路径模式。**强烈建议 true** | +| program | "" | 可以填入一个二进制文件路径,启动调试器会自动运行这个文件 | + + +扩展功能的可选配置 + +| 项目 | 默认值 | 意义 | +| ----------------------- | ----------- | ------------------------------------------------------------ | +| isNeedB64EncodeStr | true | 对传输的字符串使用 base64 加密,避免一些异常字符干扰协议解析 | +| pathCaseSensitivity | false | 路径大小写敏感。默认 false 可兼容 getInfo获取的路径大小写,无需修改 | +| updateTips | true | 当检查到项目中的 lua 文件比较旧时,提示用户升级 | +| logLevel | 1 | 日志等级,开发调试器时可能会使用0级,大量日志会降低运行效率。正常使用请勿修改 | +| distinguishSameNameFile | false | 调试器默认不做同名文件区分 , 请不要在同名文件中打断点(此时仅依靠文件名进行文件区分)。如需要调试器区分同名文件,可尝试设置为 true,此时会执行较为严格的路径模式。 | +| truncatedOPath | "" | 路径裁剪,**通常无需修改**。配合 distinguishSameNameFile: true 模式使用。裁减掉 getinfo 的一部分路径,用剩余的路径进行断点匹配 | +| VSCodeAsClient | false | 反转 VScode 和 lua 进程的 C/S | +| connectionIP | "127.0.0.1" | 配合 VSCodeAsClient: true 模式使用,要连接的 lua 进程所在ip | + + + ++ LuaPanda-IndependentFile 模式的配置 + +LuaPanda-IndependentFile 我们称之为"独立文件模式" , 它的作用是打开一个新的终端,并 lua 命令运行当前 VSCode 活动窗口中的 lua 代码,并连接调试器,对其进行调试。 + +这个模式的目的是方便进行 lua 开发时,测试一些独立的文件 / 函数运行状况。 + +使用时请确保系统中安装了 lua 命令行二进制文件 以及 luasocket。测试方法是打开一个终端,运行 lua 看是否报错,之后尝试 `require("socket.core")` 不报错即可使用。 + +单文件模式有一些独立的配置: + +| 配置项 | 默认值 | 建议 | +| ----------- | ------ | ------------------------------------------------------------ | +| luaPath | "" | Lua 可执行文件的路径,如果已经加入系统path路径,可以不用修改 | +| packagePath | [] | 可以加入用户自己的packagepath路径,比如["./?.lua", "../?.lua" ] | + diff --git a/Docs/Manual/lua-intellisense.md b/Docs/Manual/lua-intellisense.md new file mode 100644 index 0000000..df1d58f --- /dev/null +++ b/Docs/Manual/lua-intellisense.md @@ -0,0 +1,26 @@ +# Lua 原生符号感知 + +在编写lua代码时,分析插件可以感知lua代码中的符号,用作定义跳转和代码补全。但是无法感知c++/c#导出符号,给使用带来不便。 + +我们尝试分析 c# / c++ 的导出符号文件,提取其中的符号转换为lua代码,并把这部分自动生成的代码放置在一个预读区。插件启动时会读取这些生成的符号,在用户输入时进行代码提示。 + +预读区设置在用户当前打开文件夹的.vscode/LuaPanda/IntelliSenseRes/路径下。以后用户每次用VSCode打开这个文件夹,都可以自动加载这些预读文件。如果用户不再需要这些文件,直接把.vscode/LuaPanda/IntelliSenseRes/目录下的内容删除就行。 + +### 使用方法 + +插件完全启动后可以点击VScode状态栏的LuaPanda按钮,拉起可视化设置界面,在最后一项“用于IntelliSense的文件夹路径”中填入导出符号的文件路径 + +比如slua-unreal demo工程可以填写 + +E:\sluaunreal\Source (E:\sluaunreal替换为sluaunreal所在路径) + +或者 + +D:\Epic Games\UE_4.22\Engine\Source\Runtime\Engine\Public (D:\Epic Games\UE_4.22\替换为UE所在路径) + +点击生成文件按钮,稍后提示成功后。点开 .vscode/LuaPanda/IntelliSenseRes/UECppinterface 就可以看到生成的文件,之后输入代码时即可看到这些新生成的提示。 + + + +*slua 对应的原生接口文件在 `项目路径\Assets\Slua\LuaObject` 目录下 + diff --git a/Docs/Manual/quick-use.md b/Docs/Manual/quick-use.md old mode 100644 new mode 100755 index 090ae07..bf50f92 --- a/Docs/Manual/quick-use.md +++ b/Docs/Manual/quick-use.md @@ -1,6 +1,20 @@ -# 快速试用 +# 调试器快速试用 -注:VSCode的调试插件机制是: 当使用某个插件调试过一种语言时,会导致该语言的其他调试插件无法生效,请先禁用之前的调试插件并重新启动VSCode。 +### 代码辅助工具 + +代码辅助工具无需配置,注意以下几点就可以 + +1. 使用 VScode 打开的包含lua文件的文件夹,而不是单个lua文件。 +2. VScode 文件后缀和类型关联正确,如果 lua 文件后缀是 txt 或者是 lua.txt 都要正确关联lua类型,插件才能生效。 +3. 本文件 lua 代码中不要有 lua error, 会影响符号的生成。 + +完成以上配置后, 打开 VSCode 文件列表(explorer)页签 ,展开左下角的大纲(outline),如果有信息说明是可以进行调试的。 + + + +### lua 调试器 + +注意:VSCode的调试插件机制是: 当使用某个插件调试过一种语言时,会导致该语言的其他调试插件无法生效,请先禁用之前的调试插件并重新启动VSCode。 以下是各框架下快速试用调试器的方法,开始之前请先到 VSCode 扩展商店下载安装 `LuaPanda` 调试插件。 @@ -28,7 +42,7 @@ 1. **下载 slua 工程** 下载 slua 工程源码 https://github.com/pangweiwei/slua 2. **slua 工程设置** 使用 Unity 打开 slua 工程,切换工程平台到 Android/iOS , 点击菜单 Slua -> All -> Make,选择 `Slua/Editor/example/Circle` 场景。 -3. **放入调试文件** 把github中 /Debugger 下的 `LuaPanda.lua, DebugTools.lua` 两个文件拷贝到slua工程 `Slua/Resources/` 目录下, 并修改文件后缀为 `.txt` +3. **放入调试文件** 把github中 /Debugger 下的 `LuaPanda.lua` 文件拷贝到slua工程 `Slua/Resources/` 目录下, 并修改文件后缀为 `.txt` 4. **配置工程** VSCode 打开 `Slua/Resources/` 目录,点击 VSCode 调试选项卡下的齿轮图标,选择 LuaPanda。把配置项 luaFileExtension 值修改为 "txt"。 5. **开始调试** 在 `Slua/Resources/circle/circle.txt` 中加入代码 `require("LuaPanda").start("127.0.0.1",8818)`. VSCode切换到调试选项卡,配置项选择`LuaPanda`,点击 VSCode 调试的绿色箭头,再运行Unity,在加入 require 的位置后会自动停止。也可以打断点调试 @@ -37,7 +51,7 @@ ### xlua 1. **下载 xlua 工程** https://github.com/Tencent/xLua -2. **放入调试文件** 把github中 /Debugger 下的 `LuaPanda.lua, DebugTools.lua` 两个文件拷贝到xlua工程 `\XLua\Examples\07_AsyncTest\Resources` 目录下, 并修改后缀为 `.lua.txt` +2. **放入调试文件** 把github中 /Debugger 下的 `LuaPanda.lua` 文件拷贝到xlua工程 `\XLua\Examples\07_AsyncTest\Resources` 目录下, 并修改后缀为 `.lua.txt` 3. **配置工程** 把`\XLua\Examples\07_AsyncTest\Resources` 文件夹放入 VSCode , 点击 VSCode 调试选项卡下的齿轮图标,选择 LuaPanda。把配置项 luaFileExtension 值修改为 "lua.txt" 4. **开始调试** 在`\XLua\Examples\07_AsyncTest\Resources\async_test.lua.txt` 中加入`require("LuaPanda").start("127.0.0.1",8818)` 。VSCode切换到调试选项卡,配置项选择`LuaPanda`, 点击 VSCode 的开始调试箭头,运行Unity,在加入 require 的位置后会自动停止。也可以打断点调试。 @@ -46,19 +60,38 @@ ### slua-unreal 1. **下载slua-unreal工程** https://github.com/Tencent/sluaunreal -2. **放入调试文件** 把github中 /Debugger 下的 `LuaPanda.lua, DebugTools.lua` 两个文件拷贝到slua-unreal 工程`sluaunreal/Content/Lua/`目录下 +2. **放入调试文件** 把github中 /Debugger 下的 `LuaPanda.lua` 文件拷贝到slua-unreal 工程`sluaunreal/Content/Lua/`目录下 3. **配置工程** 把`sluaunreal/Content`文件夹放入 VSCode , 点击 VSCode 调试选项卡下的齿轮图标,选择 LuaPanda。 4. **开始调试** 在执行的lua代码中加入`require("LuaPanda").start("127.0.0.1",8818)` 。VSCode切换到调试选项卡,配置项选择`LuaPanda`, 点击 VSCode 的开始调试箭头,再运行ue4,在加入 require 的位置后会自动停止。之后可以打断点调试。 -#### cocos2dx +### unlua + +目前unlua默认不集成luasocket,需要安装调试依赖的luasocket库,之后再进行调试接入。 + +1. **安装luasocket** + + luasocket源码推荐使用 https://github.com/diegonehab/luasocket 。我们用此源码编译了luasocket库文件并放在项目的/luasocketBin/下。 + + mac下可以选择 **源码编译/插件管理工具安装/拷贝库文件** 的方式,把`socket和mime文件夹`部署到/usr/local/lib/lua/5.3/ 目录下,运行时可以自动被引用到, 部署完成后调用`require("socket.core");` 验证是否有报错。 + + win下可以选择 **源码编译/拷贝库文件** 的方式,把luascoket拷贝到自定义位置,并在lua代码中修改package.cpath,使库文件可以被引用到。比如部署在c:/luasocket下,cpath要修改为`package.cpath = package.cpath .. ";c:/luasocket/?.dll";`, 并用 `require("socket.core");` 验证是否有报错。 +2. **放入调试器文件** 把github中 /Debugger 下的 `LuaPanda.lua` 文件拷贝到unlua工程 `unlua/Content/Script/`下,和UnLua.lua 文件同级 +3. **配置工程** 把`Script`文件夹放入 VSCode , 点击 VSCode 调试选项卡下的齿轮图标,选择 LuaPanda。打开生成的 `.vscode/launch.json` 文件, **调整其中的stopOnEntry为 false。** +4. **开始调试** 在执行的lua代码中加入`require("LuaPanda").start("127.0.0.1",8818)` 。VSCode切换到调试选项卡,配置项选择`LuaPanda`, 点击 VSCode 的开始调试箭头,再运行ue4,在加入 require 的位置后会自动停止。之后可以打断点调试。 + + + +### cocos2dx 可能存在的问题:LuaPanda目前支持标准lua虚拟机,cocos2dx集成的是luajit,可能会在调试函数尾调用时出现跳步的情况,后续完整支持luajit后会解决此问题。 1. 下载cocos2dx并创建新工程 -2. **放入调试器文件** 把github中 /Debugger 下的 `LuaPanda.lua, DebugTools.lua` 两个文件拷贝到cocos2dx工程 /src下,和main.lua 文件同级 +2. **放入调试器文件** 把github中 /Debugger 下的 `LuaPanda.lua` 文件拷贝到cocos2dx工程 /src下,和main.lua 文件同级 3. **配置工程** 把`src`文件夹拖入 VSCode , 点击 VSCode 调试选项卡下的齿轮图标,选择 LuaPanda。 -4. **开始调试** 在main.lua文件 `require "cocos.init"` 行之前加入代码 `require("LuaPanda").start("127.0.0.1",8818)` 。VSCode切换到调试选项卡,配置项选择`LuaPanda`, 点击 VSCode 的开始调试箭头,再运行cocos2dx工程,在加入 require 的位置后会自动停止。之后可以打断点调试。 \ No newline at end of file +4. **开始调试** 在main.lua文件 `require "cocos.init"` 行之前加入代码 `require("LuaPanda").start("127.0.0.1",8818)` 。VSCode切换到调试选项卡,配置项选择`LuaPanda`, 点击 VSCode 的开始调试箭头,再运行cocos2dx工程,在加入 require 的位置后会自动停止。之后可以打断点调试。 + +### tolua + +群里有同学测试了 tolua 上的调试,文档请参考这里 +https://github.com/Arthur-qi/LuaPandaTutorialForToLua/blob/master/LuaPandaTutorialForToLua-LuaFramework_UGUI.txt diff --git a/Docs/Manual/update.md b/Docs/Manual/update.md old mode 100644 new mode 100755 index 646e99d..0252ca1 --- a/Docs/Manual/update.md +++ b/Docs/Manual/update.md @@ -1,4 +1,24 @@ -# 升级说明 +# 升级和版本说明 + +如果希望使用某一旧版本而不进行升级,可以参考 FAQ 文档。 + +### 自动升级 + +**VSCode 插件升级** :当出现如下升级提示时,点击 update 即可把插件升级到最新版本。 + +![vscodeExt-update](../Res/Manual/update/vscodeExt-update.png) + + + +**LuaPanda 文件升级**:当在 vscode 中启动调试器时,会检测当前打开项目中的 Luapanda.lua 版本,并自动弹出以下提示。如果用户自己修改过 Luapanda.lua 文件,请做好备份,以免文件被覆盖。 + +![luapanda-file-update](../Res/Manual/update/luapanda-file-update.png) + +**重建 launch.json**: 升级后如果调试出现问题,建议重建 launch.json 文件。具体方法是删除 工程下 .vsode/launch.json ,再按如下步骤重建。 + +![create-launchjson](../Res/access_introduction/create-launchjson.png) + +### 手动升级 调试器框架分为三部分 @@ -8,14 +28,41 @@ + lua文件。 下载地址:https://github.com/Tencent/LuaPanda/releases - LuaPanda.lua,DebugTools.lua + `Debugger/LuaPanda.lua` - 需要用户手动下载最新的文件,替换工程中原有文件。 + 如果 VScode 插件检测到项目中 LuaPanda.lua 文件比较旧,会弹出升级提示。如果用户对 LuaPanda.lua 文件内容做过修改,请先备份在升级,避免升级后出现异常影响使用。 + + 也可以自己到 github上手动下载最新的 LuaPanda.lua,替换工程中原有文件。 + 调试器c库 : plibdebug - plibdebug默认放置在VSCode 扩展中,无需用户手动更新。如果使用源码编译的方式,需要更新代码重新编译。 + plibdebug 默认放置在 VSCode 扩展中,无需用户手动更新。 + + 它作为一个加速插件,不会对调试器功能产生影响。仅是把耗时部分放在c中执行,提高执行效率。 + + 如果使用源码编译的方式,需要更新代码重新编译。源码位置 `Debugger/debugger_lib` + + + +综上,通常用户只需要关注 LuaPanda.lua 文件,保持最新即可。 + + + +### 使用旧版本 + +如果升级到新版本遇到了文件,希望继续使用旧版本调试器。可以按以下操作 + ++ 下载指定版本的 VScode 扩展 + +![download_specific_ver](../Res/download_specific_ver.png) + +​ 进入商店找到 LuaPanda 点击旁边的齿轮,选择 install another version ,选择需要的版本号就行。 ++ 下载对应的lua文件 + tag 保存位置 https://github.com/Tencent/LuaPanda/releases -通常用户只要手动更新lua文件即可。 \ No newline at end of file + 下载对应版本的包,并把其中的 `Debugger/LuaPanda.lua` 放置在项目中 + ++ 重新配置一下 launch.json , 主要注意其中的 "luaFileExtension": "lua.txt" 按实际情况配置好后缀 ++ 最后不要忘记在代码中引用 `require("LuaPanda").start()` diff --git a/Docs/Development-instructions/debugger-principle.md b/Docs/Principle/debugger-principle.md old mode 100644 new mode 100755 similarity index 94% rename from Docs/Development-instructions/debugger-principle.md rename to Docs/Principle/debugger-principle.md index a438cc7..f283986 --- a/Docs/Development-instructions/debugger-principle.md +++ b/Docs/Principle/debugger-principle.md @@ -6,11 +6,11 @@ LuaPanda 是一个基于VSCode的lua代码调试器,主要由三部分组成。 -![](../static/debugger-principle/mode_pic.png) +![](../Res/debugger-principle/mode_pic.png) + 调试器前端是VScode扩展,作用是通知VScode展示信息和进行数据交换。 -+ 调试器后端是debugger,它负责调试主体功能的实现,使用lua开发。 ++ 调试器后端是一个lua模块,它负责调试主体功能的实现,使用lua开发。 + 另外提供了一个可选的,用来提高调试效率的c-hook扩展模块。这个模块依赖于debugger,不能独立使用。 @@ -58,7 +58,7 @@ hook状态分级的目标是希望减少hook次数,但是不能影响判断的 lua支持三种hook状态,lcr(line , call , return)。最初的方案三种状态全都要进行hook -![](../static/debugger-principle/state-pic.png) +![](../Res/debugger-principle/state-pic.png) @@ -69,7 +69,7 @@ lua支持三种hook状态,lcr(line , call , return)。最初的方案三种状 再假如用户设置了断点,但是当前执行的函数中没有断点,那么只需要在发生函数切换时(call,return)hook, 并根据新函数中是否有断点改变hook状态. 根据上面思想,我们建立了hook状态机,把hook分成以下状态 -![](../static/debugger-principle/state-table.png) +![](../Res/debugger-principle/state-table.png) 每次用户打断点,以及发生函数 call 和 return 时,都要重新检查,根据断点情况切换到对应状态,以免漏过断点。 @@ -94,7 +94,7 @@ debug.sethook(this.debug_hook, "r", 1000000); 调试到断点处,用户可以输入变量或者表达式,观察执行结果。如下 -![](../static/debugger-principle/dostring-pic.png) +![](../Res/debugger-principle/dostring-pic.png) loc_num是局部变量,用户在断点处希望查看loc_num+1的值。 diff --git a/Docs/README.md b/Docs/README.md index 2081478..a0ed888 100644 --- a/Docs/README.md +++ b/Docs/README.md @@ -1,28 +1,46 @@ -## 文档说明 - -LuaPanda的文档全部集中在这个目录下,根据类型分为 - -+ 使用介绍 - - 放在`Manual`目录下 - - [项目介绍](./Manual/feature-introduction.md) - - [接入指引](./Manual/access-guidelines.md) - - [快速试用](./Manual/quick-use.md) - - [FAQ](./Manual/FAQ.md) - - - -+ 调试器开发相关 - - 放在`Development-instructions`目录下 - - [工程说明](./Development-instructions/project-description.md) - - [开发指引](./Development-instructions/how_to_join.md) - -`static`中放一些静态图片资源,供各文档引用 - +## 文档说明 + +LuaPanda的文档全部集中在这个目录下,根据类型分为 + ++ 使用介绍 + + 放在`Manual`目录下 + + [项目介绍](./Manual/feature-introduction.md) + + [快速试用指引](./Manual/quick-use.md) + + [调试器接入指引](./Manual/access-guidelines.md) + + [launch.json 配置项介绍](./Manual/launch-json-introduction.md) + + [Lua 原生符号感知](./Manual/lua-intellisense.md) + + [真机调试](./Manual/debug-on-phone.md) + + [其他调试能力](./Manual/common-functions.md) + + [升级说明](./Manual/update.md) + + [FAQ](./Manual/FAQ.md) + + + ++ 调试器开发相关 + + 放在`Development`目录下 + + [工程说明](./Development/project-description.md) + + [开发指引](./Development/how-to-join.md) + + + ++ 原理介绍 + + 放在`Principle`目录下 + + [一些特性的实现方案](./Principle/debugger-principle.md) + +`Res`中放一些静态图片资源,供各文档引用 + diff --git a/Docs/Res/Manual/common-functions/change-name-port.png b/Docs/Res/Manual/common-functions/change-name-port.png new file mode 100644 index 0000000..deaf017 Binary files /dev/null and b/Docs/Res/Manual/common-functions/change-name-port.png differ diff --git a/Docs/Res/Manual/common-functions/multi-target.png b/Docs/Res/Manual/common-functions/multi-target.png new file mode 100644 index 0000000..cf076aa Binary files /dev/null and b/Docs/Res/Manual/common-functions/multi-target.png differ diff --git a/Docs/Res/Manual/common-functions/one-folder.png b/Docs/Res/Manual/common-functions/one-folder.png new file mode 100644 index 0000000..16a90bd Binary files /dev/null and b/Docs/Res/Manual/common-functions/one-folder.png differ diff --git a/Docs/Res/Manual/common-functions/workspace-config.png b/Docs/Res/Manual/common-functions/workspace-config.png new file mode 100644 index 0000000..eea28a2 Binary files /dev/null and b/Docs/Res/Manual/common-functions/workspace-config.png differ diff --git a/Docs/Res/Manual/common-functions/workspace.png b/Docs/Res/Manual/common-functions/workspace.png new file mode 100644 index 0000000..c8b72ab Binary files /dev/null and b/Docs/Res/Manual/common-functions/workspace.png differ diff --git a/Docs/Res/Manual/connected.png b/Docs/Res/Manual/connected.png new file mode 100644 index 0000000..3ed7258 Binary files /dev/null and b/Docs/Res/Manual/connected.png differ diff --git a/Docs/Res/Manual/luaext.png b/Docs/Res/Manual/luaext.png new file mode 100644 index 0000000..38465d3 Binary files /dev/null and b/Docs/Res/Manual/luaext.png differ diff --git a/Docs/Res/Manual/testbk.png b/Docs/Res/Manual/testbk.png new file mode 100644 index 0000000..5df21c5 Binary files /dev/null and b/Docs/Res/Manual/testbk.png differ diff --git a/Docs/Res/Manual/update/luapanda-file-update.png b/Docs/Res/Manual/update/luapanda-file-update.png new file mode 100644 index 0000000..06f85b7 Binary files /dev/null and b/Docs/Res/Manual/update/luapanda-file-update.png differ diff --git a/Docs/Res/Manual/update/vscodeExt-update.png b/Docs/Res/Manual/update/vscodeExt-update.png new file mode 100644 index 0000000..2ceaaf2 Binary files /dev/null and b/Docs/Res/Manual/update/vscodeExt-update.png differ diff --git a/Docs/static/access_introduction/absolute_path.png b/Docs/Res/access_introduction/absolute_path.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/absolute_path.png rename to Docs/Res/access_introduction/absolute_path.png diff --git a/Docs/static/access_introduction/cannot_find_file.png b/Docs/Res/access_introduction/cannot_find_file.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/cannot_find_file.png rename to Docs/Res/access_introduction/cannot_find_file.png diff --git a/Docs/Res/access_introduction/create-launchjson.png b/Docs/Res/access_introduction/create-launchjson.png new file mode 100644 index 0000000..ae18fbc Binary files /dev/null and b/Docs/Res/access_introduction/create-launchjson.png differ diff --git a/Docs/static/access_introduction/debug_config.png b/Docs/Res/access_introduction/debug_config.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/debug_config.png rename to Docs/Res/access_introduction/debug_config.png diff --git a/Docs/static/access_introduction/debug_ui.png b/Docs/Res/access_introduction/debug_ui.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/debug_ui.png rename to Docs/Res/access_introduction/debug_ui.png diff --git a/Docs/static/access_introduction/doctor.png b/Docs/Res/access_introduction/doctor.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/doctor.png rename to Docs/Res/access_introduction/doctor.png diff --git a/Docs/static/access_introduction/doctor_complete.png b/Docs/Res/access_introduction/doctor_complete.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/doctor_complete.png rename to Docs/Res/access_introduction/doctor_complete.png diff --git a/Docs/static/access_introduction/filename_path.png b/Docs/Res/access_introduction/filename_path.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/filename_path.png rename to Docs/Res/access_introduction/filename_path.png diff --git a/Docs/static/access_introduction/get_breaks_complete.png b/Docs/Res/access_introduction/get_breaks_complete.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/get_breaks_complete.png rename to Docs/Res/access_introduction/get_breaks_complete.png diff --git a/Docs/static/access_introduction/getinfo.png b/Docs/Res/access_introduction/getinfo.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/getinfo.png rename to Docs/Res/access_introduction/getinfo.png diff --git a/Docs/static/access_introduction/getinfo_complete.png b/Docs/Res/access_introduction/getinfo_complete.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/getinfo_complete.png rename to Docs/Res/access_introduction/getinfo_complete.png diff --git a/Docs/static/access_introduction/relatively_path.png b/Docs/Res/access_introduction/relatively_path.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/relatively_path.png rename to Docs/Res/access_introduction/relatively_path.png diff --git a/Docs/static/access_introduction/sluaunreal_socket.png b/Docs/Res/access_introduction/sluaunreal_socket.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/sluaunreal_socket.png rename to Docs/Res/access_introduction/sluaunreal_socket.png diff --git a/Docs/static/access_introduction/string_path.png b/Docs/Res/access_introduction/string_path.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/string_path.png rename to Docs/Res/access_introduction/string_path.png diff --git a/Docs/static/access_introduction/vscode_debug_ui.png b/Docs/Res/access_introduction/vscode_debug_ui.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/access_introduction/vscode_debug_ui.png rename to Docs/Res/access_introduction/vscode_debug_ui.png diff --git a/Docs/static/compile_package.png b/Docs/Res/compile_package.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/compile_package.png rename to Docs/Res/compile_package.png diff --git a/Docs/static/config-select.png b/Docs/Res/config-select.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/config-select.png rename to Docs/Res/config-select.png diff --git a/Docs/static/debug-arch.png b/Docs/Res/debug-arch.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/debug-arch.png rename to Docs/Res/debug-arch.png diff --git a/Docs/static/debug-file.GIF b/Docs/Res/debug-file.GIF old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/debug-file.GIF rename to Docs/Res/debug-file.GIF diff --git a/Docs/static/debug_log.png b/Docs/Res/debug_log.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/debug_log.png rename to Docs/Res/debug_log.png diff --git a/Docs/static/debugger-principle/dostring-pic.png b/Docs/Res/debugger-principle/dostring-pic.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/debugger-principle/dostring-pic.png rename to Docs/Res/debugger-principle/dostring-pic.png diff --git a/Docs/static/debugger-principle/mode_pic.png b/Docs/Res/debugger-principle/mode_pic.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/debugger-principle/mode_pic.png rename to Docs/Res/debugger-principle/mode_pic.png diff --git a/Docs/static/debugger-principle/state-pic.png b/Docs/Res/debugger-principle/state-pic.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/debugger-principle/state-pic.png rename to Docs/Res/debugger-principle/state-pic.png diff --git a/Docs/static/debugger-principle/state-table.png b/Docs/Res/debugger-principle/state-table.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/debugger-principle/state-table.png rename to Docs/Res/debugger-principle/state-table.png diff --git a/Docs/Res/download_specific_ver.png b/Docs/Res/download_specific_ver.png new file mode 100644 index 0000000..f63b37d Binary files /dev/null and b/Docs/Res/download_specific_ver.png differ diff --git a/Docs/static/feature-introduction/REPL-watch.png b/Docs/Res/feature-introduction/REPL-watch.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/feature-introduction/REPL-watch.png rename to Docs/Res/feature-introduction/REPL-watch.png diff --git a/Docs/static/feature-introduction/add_condition_bk.png b/Docs/Res/feature-introduction/add_condition_bk.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/feature-introduction/add_condition_bk.png rename to Docs/Res/feature-introduction/add_condition_bk.png diff --git a/Docs/static/feature-introduction/attach_mode.GIF b/Docs/Res/feature-introduction/attach_mode.GIF old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/feature-introduction/attach_mode.GIF rename to Docs/Res/feature-introduction/attach_mode.GIF diff --git a/Docs/Res/feature-introduction/codeDefAndCompleting.gif b/Docs/Res/feature-introduction/codeDefAndCompleting.gif new file mode 100755 index 0000000..ea74820 Binary files /dev/null and b/Docs/Res/feature-introduction/codeDefAndCompleting.gif differ diff --git a/Docs/static/feature-introduction/debug-arch2.png b/Docs/Res/feature-introduction/debug-arch2.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/feature-introduction/debug-arch2.png rename to Docs/Res/feature-introduction/debug-arch2.png diff --git a/Docs/static/feature-introduction/debug-console.png b/Docs/Res/feature-introduction/debug-console.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/feature-introduction/debug-console.png rename to Docs/Res/feature-introduction/debug-console.png diff --git a/Docs/static/feature-introduction/debugon-console.png b/Docs/Res/feature-introduction/debugon-console.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/feature-introduction/debugon-console.png rename to Docs/Res/feature-introduction/debugon-console.png diff --git a/Docs/static/feature-introduction/debugon-slua-ue.png b/Docs/Res/feature-introduction/debugon-slua-ue.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/feature-introduction/debugon-slua-ue.png rename to Docs/Res/feature-introduction/debugon-slua-ue.png diff --git a/Docs/static/feature-introduction/debugui.png b/Docs/Res/feature-introduction/debugui.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/feature-introduction/debugui.png rename to Docs/Res/feature-introduction/debugui.png diff --git a/Docs/Res/feature-introduction/generateComments.gif b/Docs/Res/feature-introduction/generateComments.gif new file mode 100755 index 0000000..11cfb45 Binary files /dev/null and b/Docs/Res/feature-introduction/generateComments.gif differ diff --git a/Docs/static/feature-introduction/print_log.png b/Docs/Res/feature-introduction/print_log.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/feature-introduction/print_log.png rename to Docs/Res/feature-introduction/print_log.png diff --git a/Docs/static/feature-introduction/set-var-value.gif b/Docs/Res/feature-introduction/set-var-value.gif old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/feature-introduction/set-var-value.gif rename to Docs/Res/feature-introduction/set-var-value.gif diff --git a/Docs/static/feature-introduction/show-metatable.png b/Docs/Res/feature-introduction/show-metatable.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/feature-introduction/show-metatable.png rename to Docs/Res/feature-introduction/show-metatable.png diff --git a/Docs/static/nodebug.png b/Docs/Res/nodebug.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/nodebug.png rename to Docs/Res/nodebug.png diff --git a/Docs/static/run_build_task.jpg b/Docs/Res/run_build_task.jpg old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/run_build_task.jpg rename to Docs/Res/run_build_task.jpg diff --git a/Docs/Res/settingpage.png b/Docs/Res/settingpage.png new file mode 100755 index 0000000..c33e3e4 Binary files /dev/null and b/Docs/Res/settingpage.png differ diff --git a/Docs/Res/updateTips.png b/Docs/Res/updateTips.png new file mode 100755 index 0000000..9dfa81f Binary files /dev/null and b/Docs/Res/updateTips.png differ diff --git a/Docs/static/vsix-install.png b/Docs/Res/vsix-install.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/vsix-install.png rename to Docs/Res/vsix-install.png diff --git a/Docs/static/work-flow.png b/Docs/Res/work-flow.png old mode 100644 new mode 100755 similarity index 100% rename from Docs/static/work-flow.png rename to Docs/Res/work-flow.png diff --git a/lua504Test/mac/.vscode/launch.json b/lua504Test/mac/.vscode/launch.json new file mode 100644 index 0000000..b52f05d --- /dev/null +++ b/lua504Test/mac/.vscode/launch.json @@ -0,0 +1,39 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lua", + "request": "launch", + "name": "LuaPanda", + "program": "", + "cwd": "${workspaceFolder}", + "luaFileExtension": "lua", + "connectionPort": 8818, + "pathCaseSensitivity": true, + "stopOnEntry": true, + "useCHook": false, + "autoPathMode": true, + "logLevel": 1 + }, + { + "type": "lua", + "request": "launch", + "name": "LuaPanda-Attach" + }, + { + "type": "lua", + "request": "launch", + "name": "LuaPanda-DebugFile", + "luaPath": "", + "packagePath": [], + "luaFileExtension": "", + "connectionPort": 8818, + "stopOnEntry": true, + "useCHook": true, + "logLevel": 1 + } + ] +} \ No newline at end of file diff --git a/lua504Test/mac/LuaPanda.lua b/lua504Test/mac/LuaPanda.lua new file mode 100644 index 0000000..22dbe72 --- /dev/null +++ b/lua504Test/mac/LuaPanda.lua @@ -0,0 +1,3205 @@ +--[[ +Tencent is pleased to support the open source community by making LuaPanda available. +Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +https://opensource.org/licenses/BSD-3-Clause +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +API: + LuaPanda.printToVSCode(logStr, printLevel, type) + 打印日志到VSCode Output下Debugger/log中 + @printLevel: debug(0)/info(1)/error(2) 这里的日志等级需高于launch.json中配置等级日志才能输出 (可选参数,默认0) + @type: 0:VSCode output console 1:VSCode tip (可选参数,默认0) + + LuaPanda.BP() + 强制打断点,可以在协程中使用。建议使用以下写法: + local ret = LuaPanda and LuaPanda.BP and LuaPanda.BP(); + 如果成功加入断点ret返回true,否则是nil + + LuaPanda.getInfo() + 返回获取调试器信息。包括版本号,是否使用lib库,系统是否支持loadstring(load方法)。返回值类型string, 推荐在调试控制台中使用。 + + LuaPanda.doctor() + 返回对当前环境的诊断信息,提示可能存在的问题。返回值类型string, 推荐在调试控制台中使用。 + + LuaPanda.getCWD() + 用户可以调用或在调试控制台中输出这个函数,返回帮助设置CWD的路径。比如 + cwd: F:/1/2/3/4/5 + getinfo: @../../../../../unreal_10/slua-unreal_1018/Content//Lua/TestArray.lua + format: f:/unreal_10/slua-unreal_1018/Content/Lua/TestArray.lua + cwd是vscode传来的配置路径。getinfo是通过getinfo获取到的正在运行的文件路径。format是经过 cwd + getinfo 整合后的格式化路径。 + format是传给VSCode的最终路径。 + 如果format路径和文件真实路径不符,导致VSCode找不到文件,通过调整工程中launch.json的cwd,使format路径和真实路径一致。 + 返回值类型string, 推荐在调试控制台中使用。 + + LuaPanda.getBreaks() + 获取断点信息,返回值类型string, 推荐在调试控制台中使用。 + + LuaPanda.serializeTable(table) + 把table序列化为字符串,返回值类型是string。 +]] + +--用户设置项 +local openAttachMode = true; --是否开启attach模式。attach模式开启后可以在任意时刻启动vscode连接调试。缺点是没有连接调试时也会略降低lua执行效率(会不断进行attach请求) +local attachInterval = 1; --attach间隔时间(s) +local customGetSocketInstance = nil; --支持用户实现一个自定义调用luasocket的函数,函数返回值必须是一个socket实例。例: function() return require("socket.core").tcp() end; +local consoleLogLevel = 2; --打印在控制台(print)的日志等级 0 : all/ 1: info/ 2: error. +local connectTimeoutSec = 0.005; --等待连接超时时间, 单位s. 时间过长等待attach时会造成卡顿,时间过短可能无法连接。建议值0.005 - 0.05 +--用户设置项END + +local debuggerVer = "3.1.0"; --debugger版本号 +LuaPanda = {}; +local this = LuaPanda; +local tools = {}; --引用的开源工具,包括json解析和table展开工具等 +this.tools = tools; +this.curStackId = 0; +--json处理 +local json; +--hook状态列表 +local hookState = { + DISCONNECT_HOOK = 0, --断开连接 + LITE_HOOK = 1, --全局无断点 + MID_HOOK = 2, --全局有断点,本文件无断点 + ALL_HOOK = 3, --本文件有断点 +}; +--运行状态列表 +local runState = { + DISCONNECT = 0, --未连接 + WAIT_CMD = 1, --已连接,等待命令 + STOP_ON_ENTRY = 2, --初始状态 + RUN = 3, + STEPOVER = 4, + STEPIN = 5, + STEPOUT = 6, + STEPOVER_STOP = 7, + STEPIN_STOP = 8, + STEPOUT_STOP = 9, + HIT_BREAKPOINT = 10 +}; + +local TCPSplitChar = "|*|"; --json协议分隔符,请不要修改 +local MAX_TIMEOUT_SEC = 3600 * 24; --网络最大超时等待时间 +--当前运行状态 +local currentRunState; +local currentHookState; +--断点信息 +local breaks = {}; --保存断点的数组 +this.breaks = breaks; --供hookLib调用 +local recCallbackId = ""; +--VSCode端传过来的配置,在VSCode端的launch配置,传过来并赋值 +local luaFileExtension = ""; --脚本后缀 +local cwd = ""; --工作路径 +local DebuggerFileName = ""; --Debugger文件名(原始,未经path处理), 函数中会自动获取 +local DebuggerToolsName = ""; +local lastRunFunction = {}; --上一个执行过的函数。在有些复杂场景下(find,getcomponent)一行会挺两次 +local currentCallStack = {}; --获取当前调用堆栈信息 +local hitBP = false; --BP()中的强制断点命中标记 +local TempFilePath_luaString = ""; --VSCode端配置的临时文件存放路径 +local connectHost; --记录连接端IP +local connectPort; --记录连接端口号 +local sock; --tcp socket +local OSType; --VSCode识别出的系统类型,也可以自行设置。Windows_NT | Linux | Darwin +local clibPath; --chook库在VScode端的路径,也可自行设置。 +local hookLib; --chook库的引用实例 +local adapterVer; --VScode传来的adapter版本号 +--标记位 +local logLevel = 1; --日志等级all/info/error. 此设置对应的是VSCode端设置的日志等级. +local variableRefIdx = 1; --变量索引 +local variableRefTab = {}; --变量记录table +local lastRunFilePath = ""; --最后执行的文件路径 +local pathCaseSensitivity = true; --路径是否发大小写敏感,这个选项接收VScode设置,请勿在此处更改 +local recvMsgQueue = {}; --接收的消息队列 +local coroutinePool = {}; --保存用户协程的队列 +local winDiskSymbolUpper = false;--设置win下盘符的大小写。以此确保从VSCode中传入的断点路径,cwd和从lua虚拟机获得的文件路径盘符大小写一致 +local isNeedB64EncodeStr = false;-- 记录是否使用base64编码字符串 +local loadclibErrReason = 'launch.json文件的配置项useCHook被设置为false.'; +local OSTypeErrTip = ""; +local pathErrTip = "" +local winDiskSymbolTip = ""; +local isAbsolutePath = false; +local stopOnEntry; --用户在VSCode端设置的是否打开stopOnEntry +local userSetUseClib; --用户在VSCode端设置的是否是用clib库 +local autoPathMode = false; +--Step控制标记位 +local stepOverCounter = 0; --STEPOVER over计数器 +local stepOutCounter = 0; --STEPOVER out计数器 +local HOOK_LEVEL = 3; --调用栈偏移量,使用clib时为3,lua中不再使用此变量,而是通过函数getSpecificFunctionStackLevel获取 +local isUseLoadstring = 0; +local debugger_loadString; +--临时变量 +local coroutineCreate; --用来记录lua原始的coroutine.create函数 +local stopConnectTime = 0; --用来临时记录stop断开连接的时间 +local isInMainThread; +local receiveMsgTimer = 0; +local formatPathCache = {}; -- getinfo -> format +local isUserSetClibPath = false; --用户是否在本文件中自设了clib路径 +--5.1/5.3兼容 +if _VERSION == "Lua 5.1" then + debugger_loadString = loadstring; +else + debugger_loadString = load; +end + +--用户在控制台输入信息的环境变量 +local env = setmetatable({ }, { + __index = function( _ , varName ) + local ret = this.getWatchedVariable( varName, _G.LuaPanda.curStackId , false); + return ret; + end, + + __newindex = function( _ , varName, newValue ) + this.setVariableValue( varName, _G.LuaPanda.curStackId, newValue); + end +}); + +----------------------------------------------------------------------------- +-- 流程 +----------------------------------------------------------------------------- + +-- 启动调试器 +-- @host adapter端ip, 默认127.0.0.1 +-- @port adapter端port ,默认8818 +function this.start(host, port) + host = tostring(host or "127.0.0.1") ; + port = tonumber(port) or 8818; + this.printToConsole("Debugger start. connect host:" .. host .. " port:".. tostring(port), 1); + if sock ~= nil then + this.printToConsole("[Warning] 调试器已经启动,请不要再次调用start()" , 1); + return; + end + + --尝试初次连接 + this.changeRunState(runState.DISCONNECT); + if not this.reGetSock() then + this.printToConsole("[Error] Start debugger but get Socket fail , please install luasocket!", 2); + return; + end + connectHost = host; + connectPort = port; + local sockSuccess = sock and sock:connect(connectHost, connectPort); + if sockSuccess ~= nil then + this.printToConsole("first connect success!"); + this.connectSuccess(); + else + this.printToConsole("first connect failed!"); + this.changeHookState(hookState.DISCONNECT_HOOK); + end +end + +-- 连接成功,开始初始化 +function this.connectSuccess() + this.changeRunState(runState.WAIT_CMD); + this.printToConsole("connectSuccess", 1); + --设置初始状态 + local ret = this.debugger_wait_msg(); + + --获取debugger文件路径 + if DebuggerFileName == "" then + local info = debug.getinfo(1, "S") + for k,v in pairs(info) do + if k == "source" then + DebuggerFileName = v; + this.printToVSCode("DebuggerFileName:" .. tostring(DebuggerFileName)); + + if hookLib ~= nil then + hookLib.sync_debugger_path(DebuggerFileName); + end + end + end + end + if DebuggerToolsName == "" then + DebuggerToolsName = tools.getFileSource(); + if hookLib ~= nil then + hookLib.sync_tools_path(DebuggerToolsName); + end + end + + if ret == false then + this.printToVSCode("[debugger error]初始化未完成, 建立连接但接收初始化消息失败。请更换端口重试", 2); + return; + end + this.printToVSCode("debugger init success", 1); + + this.changeHookState(hookState.ALL_HOOK); + if hookLib == nil then + --协程调试 + if coroutineCreate == nil and type(coroutine.create) == "function" then + this.printToConsole("change coroutine.create"); + coroutineCreate = coroutine.create; + coroutine.create = function(...) + local co = coroutineCreate(...) + table.insert(coroutinePool, co); + --运行状态下,创建协程即启动hook + this.changeCoroutineHookState(); + return co; + end + else + this.printToConsole("restart coroutine"); + this.changeCoroutineHookState(); + end + end + +end + +--重置数据 +function this.clearData() + OSType = nil; + clibPath = nil; + -- reset breaks + breaks = {}; + formatPathCache = {}; + this.breaks = breaks; + if hookLib ~= nil then + hookLib.sync_breakpoints(); --清空断点信息 + hookLib.clear_pathcache(); --清空路径缓存 + end +end + +--断开连接 +function this.disconnect() + this.printToConsole("Debugger disconnect", 1); + this.clearData() + this.changeHookState( hookState.DISCONNECT_HOOK ); + stopConnectTime = os.time(); + this.changeRunState(runState.DISCONNECT); + + if sock ~= nil then + sock:close(); + end + + if connectPort == nil or connectHost == nil then + --异常情况处理, 在调用LuaPanda.start()前首先调用了LuaPanda.disconnect() + this.printToConsole("[Warning] User call LuaPanda.disconnect() before set debug ip & port, please call LuaPanda.start() first!", 2); + return; + end + + this.reGetSock(); +end + +----------------------------------------------------------------------------- +-- 调试器通用方法 +----------------------------------------------------------------------------- +-- 返回断点信息 +function this.getBreaks() + return breaks; +end + +-- 返回路径相关信息 +-- cwd:配置的工程路径 | info["source"]:通过 debug.getinfo 获得执行文件的路径 | format:格式化后的文件路径 +function this.getCWD() + local ly = this.getSpecificFunctionStackLevel(lastRunFunction.func); + if type(ly) ~= "number" then + ly = 2; + end + local runSource = lastRunFunction["source"]; + if runSource == nil and hookLib ~= nil then + runSource = this.getPath(tostring(hookLib.get_last_source())); + end + local info = debug.getinfo(ly, "S"); + return "cwd: "..cwd .."\ngetinfo: ".. info["source"] .. "\nformat: " .. tostring(runSource) ; +end + +--返回版本号等配置 +function this.getBaseInfo() + local strTable = {}; + local jitVer = ""; + if jit and jit.version then + jitVer = "," .. tostring(jit.version); + end + + strTable[#strTable + 1] = "Lua Ver:" .. _VERSION .. jitVer .." | adapterVer:" .. tostring(adapterVer) .. " | Debugger Ver:" .. tostring(debuggerVer); + local moreInfoStr = ""; + if hookLib ~= nil then + local clibVer, forluaVer = hookLib.sync_getLibVersion(); + local clibStr = forluaVer ~= nil and tostring(clibVer) .. " for " .. tostring(math.ceil(forluaVer)) or tostring(clibVer); + strTable[#strTable + 1] = " | hookLib Ver:" .. clibStr; + moreInfoStr = moreInfoStr .. "说明: 已加载 libpdebug 库."; + else + moreInfoStr = moreInfoStr .. "说明: 未能加载 libpdebug 库。原因请使用 LuaPanda.doctor() 查看"; + end + + local outputIsUseLoadstring = false + if type(isUseLoadstring) == "number" and isUseLoadstring == 1 then + outputIsUseLoadstring = true; + end + + strTable[#strTable + 1] = " | supportREPL:".. tostring(outputIsUseLoadstring); + strTable[#strTable + 1] = " | useBase64EncodeString:".. tostring(isNeedB64EncodeStr); + strTable[#strTable + 1] = " | codeEnv:" .. tostring(OSType) .. '\n'; + strTable[#strTable + 1] = moreInfoStr; + if OSTypeErrTip ~= nil and OSTypeErrTip ~= '' then + strTable[#strTable + 1] = '\n' ..OSTypeErrTip; + end + return table.concat(strTable); +end + +--自动诊断当前环境的错误,并输出信息 +function this.doctor() + local strTable = {}; + if debuggerVer ~= adapterVer then + strTable[#strTable + 1] = "\n- 建议更新版本\nLuaPanda VSCode插件版本是" .. adapterVer .. ", LuaPanda.lua文件版本是" .. debuggerVer .. "。建议检查并更新到最新版本。"; + strTable[#strTable + 1] = "\n更新方式 : https://github.com/Tencent/LuaPanda/blob/master/Docs/Manual/update.md"; + strTable[#strTable + 1] = "\nRelease版本: https://github.com/Tencent/LuaPanda/releases"; + end + --plibdebug + if hookLib == nil then + strTable[#strTable + 1] = "\n\n- libpdebug 库没有加载\n"; + if userSetUseClib then + --用户允许使用clib插件 + if isUserSetClibPath == true then + --用户自设了clib地址 + strTable[#strTable + 1] = "用户使用 LuaPanda.lua 中 clibPath 变量指定了 plibdebug 的位置: " .. clibPath; + if this.tryRequireClib("libpdebug", clibPath) then + strTable[#strTable + 1] = "\n引用成功"; + else + strTable[#strTable + 1] = "\n引用错误:" .. loadclibErrReason; + end + else + --使用默认clib地址 + local clibExt, platform; + if OSType == "Darwin" then clibExt = "/?.so;"; platform = "mac"; + elseif OSType == "Linux" then clibExt = "/?.so;"; platform = "linux"; + else clibExt = "/?.dll;"; platform = "win"; end + local lua_ver; + if _VERSION == "Lua 5.1" then + lua_ver = "501"; + else + lua_ver = "503"; + end + local x86Path = clibPath .. platform .."/x86/".. lua_ver .. clibExt; + local x64Path = clibPath .. platform .."/x86_64/".. lua_ver .. clibExt; + + strTable[#strTable + 1] = "尝试引用x64库: ".. x64Path; + if this.tryRequireClib("libpdebug", x64Path) then + strTable[#strTable + 1] = "\n引用成功"; + else + strTable[#strTable + 1] = "\n引用错误:" .. loadclibErrReason; + strTable[#strTable + 1] = "\n尝试引用x86库: ".. x86Path; + if this.tryRequireClib("libpdebug", x86Path) then + strTable[#strTable + 1] = "\n引用成功"; + else + strTable[#strTable + 1] = "\n引用错误:" .. loadclibErrReason; + end + end + end + else + strTable[#strTable + 1] = "原因是" .. loadclibErrReason; + end + end + + --path + --尝试直接读当前getinfo指向的文件,看能否找到。如果能,提示正确,如果找不到,给出提示,建议玩家在这个文件中打一个断点 + --检查断点,文件和当前文件的不同,给出建议 + local runSource = lastRunFilePath; + if hookLib ~= nil then + runSource = this.getPath(tostring(hookLib.get_last_source())); + end + + -- 在精确路径模式下的路径错误检测 + if not autoPathMode and runSource and runSource ~= "" then + -- 读文件 + local isFileExist = this.fileExists(runSource); + if not isFileExist then + strTable[#strTable + 1] = "\n\n- 路径存在问题\n"; + --解析路径,得到文件名,到断点路径中查这个文件名 + local pathArray = this.stringSplit(runSource, '/'); + --如果pathArray和断点能匹配上 + local fileMatch= false; + for key, _ in pairs(this.getBreaks()) do + if string.find(key, pathArray[#pathArray], 1, true) then + --和断点匹配了 + fileMatch = true; + -- retStr = retStr .. "\n请对比如下路径:\n"; + strTable[#strTable + 1] = this.getCWD(); + strTable[#strTable + 1] = "\nfilepath: " .. key; + if isAbsolutePath then + strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的是绝对路径,format使用getinfo路径。"; + else + strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的是相对路径,调试器运行依赖的绝对路径(format)是来源于cwd+getinfo拼接。"; + end + strTable[#strTable + 1] = "\nfilepath是VSCode通过获取到的文件正确路径 , 对比format和filepath,调整launch.json中CWD,或改变VSCode打开文件夹的位置。使format和filepath一致即可。\n如果format和filepath路径仅大小写不一致,设置launch.json中 pathCaseSensitivity:false 可忽略路径大小写"; + end + end + + if fileMatch == false then + --未能和断点匹配 + strTable[#strTable + 1] = "\n找不到文件:" .. runSource .. ", 请检查路径是否正确。\n或者在VSCode文件" .. pathArray[#pathArray] .. "中打一个断点后,再执行一次doctor命令,查看路径分析结果。"; + end + end + end + + --日志等级对性能的影响 + if logLevel < 1 or consoleLogLevel < 1 then + strTable[#strTable + 1] = "\n\n- 日志等级\n"; + if logLevel < 1 then + strTable[#strTable + 1] = "当前日志等级是" .. logLevel .. ", 会产生大量日志,降低调试速度。建议调整launch.json中logLevel:1"; + end + if consoleLogLevel < 1 then + strTable[#strTable + 1] = "当前console日志等级是" .. consoleLogLevel .. ", 过低的日志等级会降低调试速度,建议调整LuaPanda.lua文件头部consoleLogLevel=2"; + end + end + + if #strTable == 0 then + strTable[#strTable + 1] = "未检测出问题"; + end + return table.concat(strTable); +end + +function this.fileExists(path) + local f=io.open(path,"r"); + if f~= nil then io.close(f) return true else return false end + end + +--返回一些信息,帮助用户定位问题 +function this.getInfo() + --用户设置项 + local strTable = {}; + strTable[#strTable + 1] = "\n- Base Info: \n"; + strTable[#strTable + 1] = this.getBaseInfo(); + --已经加载C库,x86/64 未能加载,原因 + strTable[#strTable + 1] = "\n\n- User Setting: \n"; + strTable[#strTable + 1] = "stopOnEntry:" .. tostring(stopOnEntry) .. ' | '; + -- strTable[#strTable + 1] = "luaFileExtension:" .. luaFileExtension .. ' | '; + strTable[#strTable + 1] = "logLevel:" .. logLevel .. ' | ' ; + strTable[#strTable + 1] = "consoleLogLevel:" .. consoleLogLevel .. ' | '; + strTable[#strTable + 1] = "pathCaseSensitivity:" .. tostring(pathCaseSensitivity) .. ' | '; + strTable[#strTable + 1] = "attachMode:".. tostring(openAttachMode).. ' | '; + strTable[#strTable + 1] = "autoPathMode:".. tostring(autoPathMode).. ' | '; + + if userSetUseClib then + strTable[#strTable + 1] = "useCHook:true"; + else + strTable[#strTable + 1] = "useCHook:false"; + end + + if logLevel == 0 or consoleLogLevel == 0 then + strTable[#strTable + 1] = "\n说明:日志等级过低,会影响执行效率。请调整logLevel和consoleLogLevel值 >= 1"; + end + + strTable[#strTable + 1] = "\n\n- Path Info: \n"; + strTable[#strTable + 1] = "clibPath: " .. tostring(clibPath) .. '\n'; + strTable[#strTable + 1] = "debugger: " .. this.getPath(DebuggerFileName) .. '\n'; + strTable[#strTable + 1] = this.getCWD(); + + if not autoPathMode then + if isAbsolutePath then + strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的是绝对路径,format使用getinfo路径。" .. winDiskSymbolTip; + else + strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的路径(getinfo)是相对路径,调试器运行依赖的绝对路径(format)是来源于cwd+getinfo拼接。如format路径错误请尝试调整cwd或改变VSCode打开文件夹的位置。也可以在format对应的文件下打一个断点,调整直到format和Breaks Info中断点路径完全一致。" .. winDiskSymbolTip; + end + else + strTable[#strTable + 1] = "\n说明:已开启autoPathMode自动路径模式,调试器会根据getinfo获得的文件名自动查找文件位置,请确保VSCode打开的工程中不存在同名lua文件。"; + end + + if pathErrTip ~= nil and pathErrTip ~= '' then + strTable[#strTable + 1] = '\n' .. pathErrTip; + end + + strTable[#strTable + 1] = "\n\n- Breaks Info: \n"; + strTable[#strTable + 1] = this.serializeTable(this.getBreaks(), "breaks"); + return table.concat(strTable); +end + +--判断是否在协程中 +function this.isInMain() + return isInMainThread; +end + +--添加路径,尝试引用库。完成后把cpath还原,返回引用结果true/false +-- @libName 库名 +-- path lib的cpath路径 +function this.tryRequireClib(libName , libPath) + this.printToVSCode("tryRequireClib search : [" .. libName .. "] in "..libPath); + local savedCpath = package.cpath; + package.cpath = package.cpath .. ';' .. libPath; + this.printToVSCode("package.cpath:" .. package.cpath); + local status, err = pcall(function() hookLib = require(libName) end); + if status then + if type(hookLib) == "table" and this.getTableMemberNum(hookLib) > 0 then + this.printToVSCode("tryRequireClib success : [" .. libName .. "] in "..libPath); + package.cpath = savedCpath; + return true; + else + loadclibErrReason = "tryRequireClib fail : require success, but member function num <= 0; [" .. libName .. "] in "..libPath; + this.printToVSCode(loadclibErrReason); + hookLib = nil; + package.cpath = savedCpath; + return false; + end + else + -- 此处考虑到tryRequireClib会被调用两次,日志级别设置为0,防止输出不必要的信息。 + loadclibErrReason = err; + this.printToVSCode("[Require clib error]: " .. err, 0); + end + package.cpath = savedCpath; + return false +end +------------------------字符串处理------------------------- +-- 倒序查找字符串 a.b/c查找/ , 返回4 +-- @str 被查找的长串 +-- @subPattern 查找的子串, 也可以是pattern +-- @plain plane text / pattern +-- @return 未找到目标串返回nil. 否则返回倒序找到的字串位置 +function this.revFindString(str, subPattern, plain) + local revStr = string.reverse(str); + local _, idx = string.find(revStr, subPattern, 1, plain); + if idx == nil then return nil end; + return string.len(revStr) - idx + 1; +end + +-- 反序裁剪字符串 如:print(subString("a.b/c", "/"))输出c +-- @return 未找到目标串返回nil. 否则返回被裁剪后的字符串 +function this.revSubString(str, subStr, plain) + local idx = this.revFindString(str, subStr, plain) + if idx == nil then return nil end; + return string.sub(str, idx + 1, str.length) +end + +-- 把字符串按reps分割成并放入table +-- @str 目标串 +-- @reps 分割符。注意这个分隔符是一个pattern +function this.stringSplit( str, separator ) + local retStrTable = {} + string.gsub(str, '[^' .. separator ..']+', function ( word ) + table.insert(retStrTable, word) + end) + return retStrTable; +end + +-- 保存CallbackId(通信序列号) +function this.setCallbackId( id ) + if id ~= nil and id ~= "0" then + recCallbackId = tostring(id); + end +end + +-- 读取CallbackId(通信序列号)。读取后记录值将被置空 +function this.getCallbackId() + if recCallbackId == nil then + recCallbackId = "0"; + end + local id = recCallbackId; + recCallbackId = "0"; + return id; +end + +-- reference from https://www.lua.org/pil/20.1.html +function this.trim (s) + return (string.gsub(s, "^%s*(.-)%s*$", "%1")) +end + +--返回table中成员数量(数字key和非数字key之和) +-- @t 目标table +-- @return 元素数量 +function this.getTableMemberNum(t) + local retNum = 0; + if type(t) ~= "table" then + this.printToVSCode("[debugger Error] getTableMemberNum get "..tostring(type(t)), 2) + return retNum; + end + for k,v in pairs(t) do + retNum = retNum + 1; + end + return retNum; +end + +-- 生成一个消息Table +function this.getMsgTable(cmd ,callbackId) + callbackId = callbackId or 0; + local msgTable = {}; + msgTable["cmd"] = cmd; + msgTable["callbackId"] = callbackId; + msgTable["info"] = {}; + return msgTable; +end + +function this.serializeTable(tab, name) + local sTable = tools.serializeTable(tab, name); + return sTable; +end +------------------------日志打印相关------------------------- +-- 把日志打印在VSCode端 +-- @str: 日志内容 +-- @printLevel: all(0)/info(1)/error(2) +-- @type: 0:vscode console 1:vscode tip +function this.printToVSCode(str, printLevel, type) + type = type or 0; + printLevel = printLevel or 0; + if currentRunState == runState.DISCONNECT or logLevel > printLevel then + return; + end + + local sendTab = {}; + sendTab["callbackId"] = "0"; + if type == 0 then + sendTab["cmd"] = "log"; + else + sendTab["cmd"] = "tip"; + end + sendTab["info"] = {}; + sendTab["info"]["logInfo"] = tostring(str); + this.sendMsg(sendTab); +end + +-- 把日志打印在控制台 +-- @str: 日志内容 +-- @printLevel: all(0)/info(1)/error(2) +function this.printToConsole(str, printLevel) + printLevel = printLevel or 0; + if consoleLogLevel > printLevel then + return; + end + print("[LuaPanda] ".. tostring(str)); +end + +----------------------------------------------------------------------------- +-- 提升兼容性方法 +----------------------------------------------------------------------------- +--生成平台无关的路径。 +--return:nil(error)/path +function this.genUnifiedPath(path) + if path == "" or path == nil then + return ""; + end + --大小写不敏感时,路径全部转为小写 + if pathCaseSensitivity == false then + path = string.lower(path); + end + --统一路径全部替换成/ + path = string.gsub(path, [[\]], "/"); + --处理 /../ /./ + local pathTab = this.stringSplit(path, '/'); + local newPathTab = {}; + for k, v in ipairs(pathTab) do + if v == '.' then + --continue + elseif v == ".." and #newPathTab >= 1 and newPathTab[#newPathTab]:sub(2,2) ~= ':' then + --newPathTab有元素,最后一项不是X: + table.remove(newPathTab); + else + table.insert(newPathTab, v); + end + end + --重新拼合后如果是mac路径第一位是/ + local newpath = table.concat(newPathTab, '/'); + if path:sub(1,1) == '/' then + newpath = '/'.. newpath; + end + + --win下按照winDiskSymbolUpper的设置修改盘符大小 + if "Windows_NT" == OSType then + if winDiskSymbolUpper then + newpath = newpath:gsub("^%a:", string.upper); + winDiskSymbolTip = "路径中Windows盘符已转为大写。" + else + newpath = newpath:gsub("^%a:", string.lower); + winDiskSymbolTip = "路径中Windows盘符已转为小写。" + end + end + + return newpath; +end + +function this.getCacheFormatPath(source) + if source == nil then return formatPathCache end; + return formatPathCache[source]; +end + +function this.setCacheFormatPath(source, dest) + formatPathCache[source] = dest; +end +----------------------------------------------------------------------------- +-- 内存相关 +----------------------------------------------------------------------------- +function this.sendLuaMemory() + local luaMem = collectgarbage("count"); + local sendTab = {}; + sendTab["callbackId"] = "0"; + sendTab["cmd"] = "refreshLuaMemory"; + sendTab["info"] = {}; + sendTab["info"]["memInfo"] = tostring(luaMem); + this.sendMsg(sendTab); +end + +----------------------------------------------------------------------------- +-- 网络相关方法 +----------------------------------------------------------------------------- +-- 刷新socket +-- @return true/false 刷新成功/失败 +function this.reGetSock() + if sock ~= nil then + pcall(function() sock:close() end); + end + --call ue4 luasocket + sock = lua_extension and lua_extension.luasocket and lua_extension.luasocket().tcp(); + if sock == nil then + --call u3d luasocket + if pcall(function() sock = require("socket.core").tcp(); end) then + this.printToConsole("reGetSock success"); + sock:settimeout(connectTimeoutSec); + else + --call custom function to get socket + if customGetSocketInstance and pcall( function() sock = customGetSocketInstance(); end ) then + this.printToConsole("reGetSock custom success"); + sock:settimeout(connectTimeoutSec); + else + this.printToConsole("[Error] reGetSock fail", 2); + return false; + end + end + else + --set ue4 luasocket + this.printToConsole("reGetSock ue4 success"); + sock:settimeout(connectTimeoutSec); + end + return true; +end + +-- 定时(以函数return为时机) 进行attach连接 +function this.reConnect() + if currentHookState == hookState.DISCONNECT_HOOK then + if os.time() - stopConnectTime < attachInterval then + this.printToConsole("Reconnect time less than 1s"); + this.printToConsole("os.time:".. os.time() .. " | stopConnectTime:" ..stopConnectTime); + return 1; + end + + if sock == nil then + this.reGetSock(); + end + + local sockSuccess, status = sock:connect(connectHost, connectPort); + if sockSuccess == 1 or status == "already connected" then + this.printToConsole("reconnect success"); + this.connectSuccess(); + else + this.printToConsole("reconnect failed . retCode:" .. tostring(sockSuccess) .. " status:" .. status); + stopConnectTime = os.time(); + end + return 1; + end + return 0; +end + +-- 向adapter发消息 +-- @sendTab 消息体table +function this.sendMsg( sendTab ) + if isNeedB64EncodeStr and sendTab["info"] ~= nil then + for _, v in ipairs(sendTab["info"]) do + if v["type"] == "string" then + v["value"] = tools.base64encode(v["value"]) + end + end + end + + local sendStr = json.encode(sendTab); + if currentRunState == runState.DISCONNECT then + this.printToConsole("[debugger error] disconnect but want sendMsg:" .. sendStr, 2); + this.disconnect(); + return; + end + + local succ,err; + if pcall(function() succ,err = sock:send(sendStr..TCPSplitChar.."\n"); end) then + if succ == nil then + if err == "closed" then + this.disconnect(); + end + end + end +end + +-- 处理 收到的消息 +-- @dataStr 接收的消息json +function this.dataProcess( dataStr ) + this.printToVSCode("debugger get:"..dataStr); + local dataTable = json.decode(dataStr); + if dataTable == nil then + this.printToVSCode("[error] Json is error", 2); + return; + end + + if dataTable.callbackId ~= "0" then + this.setCallbackId(dataTable.callbackId); + end + + if dataTable.cmd == "continue" then + this.changeRunState(runState.RUN); + local msgTab = this.getMsgTable("continue", this.getCallbackId()); + this.sendMsg(msgTab); + + elseif dataTable.cmd == "stopOnStep" then + this.changeRunState(runState.STEPOVER); + local msgTab = this.getMsgTable("stopOnStep", this.getCallbackId()); + this.sendMsg(msgTab); + this.changeHookState(hookState.ALL_HOOK); + + elseif dataTable.cmd == "stopOnStepIn" then + this.changeRunState(runState.STEPIN); + local msgTab = this.getMsgTable("stopOnStepIn", this.getCallbackId()); + this.sendMsg(msgTab); + this.changeHookState(hookState.ALL_HOOK); + + elseif dataTable.cmd == "stopOnStepOut" then + this.changeRunState(runState.STEPOUT); + local msgTab = this.getMsgTable("stopOnStepOut", this.getCallbackId()); + this.sendMsg(msgTab); + this.changeHookState(hookState.ALL_HOOK); + + elseif dataTable.cmd == "setBreakPoint" then + this.printToVSCode("dataTable.cmd == setBreakPoint"); + local bkPath = dataTable.info.path; + bkPath = this.genUnifiedPath(bkPath); + if autoPathMode then + -- 自动路径模式下,仅保留文件名 + bkPath = this.getFilenameFromPath(bkPath); + end + this.printToVSCode("setBreakPoint path:"..tostring(bkPath)); + breaks[bkPath] = dataTable.info.bks; + + -- 当v为空时,从断点列表中去除文件 + for k, v in pairs(breaks) do + if next(v) == nil then + breaks[k] = nil; + end + end + + --sync breaks to c + if hookLib ~= nil then + hookLib.sync_breakpoints(); + end + + if currentRunState ~= runState.WAIT_CMD then + if hookLib == nil then + local fileBP, G_BP =this.checkHasBreakpoint(lastRunFilePath); + if fileBP == false then + if G_BP == true then + this.changeHookState(hookState.MID_HOOK); + else + this.changeHookState(hookState.LITE_HOOK); + end + else + this.changeHookState(hookState.ALL_HOOK); + end + end + else + local msgTab = this.getMsgTable("setBreakPoint", this.getCallbackId()); + this.sendMsg(msgTab); + return; + end + --其他时机收到breaks消息 + local msgTab = this.getMsgTable("setBreakPoint", this.getCallbackId()); + this.sendMsg(msgTab); + -- 打印调试信息 + this.printToVSCode("LuaPanda.getInfo()\n" .. this.getInfo()) + this.debugger_wait_msg(); + elseif dataTable.cmd == "setVariable" then + if currentRunState == runState.STOP_ON_ENTRY or + currentRunState == runState.HIT_BREAKPOINT or + currentRunState == runState.STEPOVER_STOP or + currentRunState == runState.STEPIN_STOP or + currentRunState == runState.STEPOUT_STOP then + local msgTab = this.getMsgTable("setVariable", this.getCallbackId()); + local varRefNum = tonumber(dataTable.info.varRef); + local newValue = tostring(dataTable.info.newValue); + local needFindVariable = true; --如果变量是基础类型,直接赋值,needFindVariable = false; 如果变量是引用类型,needFindVariable = true + local varName = tostring(dataTable.info.varName); + -- 根据首末含有" ' 判断 newValue 是否是字符串 + local first_chr = string.sub(newValue, 1, 1); + local end_chr = string.sub(newValue, -1, -1); + if first_chr == end_chr then + if first_chr == "'" or first_chr == '"' then + newValue = string.sub(newValue, 2, -2); + needFindVariable = false; + end + end + --数字,nil,false,true的处理 + if newValue == "nil" and needFindVariable == true then newValue = nil; needFindVariable = false; + elseif newValue == "true" and needFindVariable == true then newValue = true; needFindVariable = false; + elseif newValue == "false" and needFindVariable == true then newValue = false; needFindVariable = false; + elseif tonumber(newValue) and needFindVariable == true then newValue = tonumber(newValue); needFindVariable = false; + end + + -- 如果新值是基础类型,则不需遍历 + if dataTable.info.stackId ~= nil and tonumber(dataTable.info.stackId) ~= nil and tonumber(dataTable.info.stackId) > 1 then + this.curStackId = tonumber(dataTable.info.stackId); + else + this.printToVSCode("未能获取到堆栈层级,默认使用 this.curStackId;") + end + + if varRefNum < 10000 then + -- 如果修改的是一个 引用变量,那么可直接赋值。但还是要走变量查询过程。查找和赋值过程都需要steakId。 目前给引用变量赋值Object,steak可能有问题 + msgTab.info = this.createSetValueRetTable(varName, newValue, needFindVariable, this.curStackId, variableRefTab[varRefNum]); + else + -- 如果修改的是一个基础类型 + local setLimit; --设置检索变量的限定区域 + if varRefNum >= 10000 and varRefNum < 20000 then setLimit = "local"; + elseif varRefNum >= 20000 and varRefNum < 30000 then setLimit = "global"; + elseif varRefNum >= 30000 then setLimit = "upvalue"; + end + msgTab.info = this.createSetValueRetTable(varName, newValue, needFindVariable, this.curStackId, nil, setLimit); + end + + this.sendMsg(msgTab); + this.debugger_wait_msg(); + end + + elseif dataTable.cmd == "getVariable" then + --仅在停止时处理消息,其他时刻收到此消息,丢弃 + if currentRunState == runState.STOP_ON_ENTRY or + currentRunState == runState.HIT_BREAKPOINT or + currentRunState == runState.STEPOVER_STOP or + currentRunState == runState.STEPIN_STOP or + currentRunState == runState.STEPOUT_STOP then + --发送变量给游戏,并保持之前的状态,等待再次接收数据 + --dataTable.info.varRef 10000~20000局部变量 + -- 20000~30000全局变量 + -- 30000~ upvalue + -- 1000~2000局部变量的查询,2000~3000全局,3000~4000upvalue + local msgTab = this.getMsgTable("getVariable", this.getCallbackId()); + local varRefNum = tonumber(dataTable.info.varRef); + if varRefNum < 10000 then + --查询变量, 此时忽略 stackId + local varTable = this.getVariableRef(dataTable.info.varRef, true); + msgTab.info = varTable; + elseif varRefNum >= 10000 and varRefNum < 20000 then + --局部变量 + if dataTable.info.stackId ~= nil and tonumber(dataTable.info.stackId) > 1 then + this.curStackId = tonumber(dataTable.info.stackId); + if type(currentCallStack[this.curStackId - 1]) ~= "table" or type(currentCallStack[this.curStackId - 1].func) ~= "function" then + local str = "getVariable getLocal currentCallStack " .. this.curStackId - 1 .. " Error\n" .. this.serializeTable(currentCallStack, "currentCallStack"); + this.printToVSCode(str, 2); + msgTab.info = {}; + else + local stackId = this.getSpecificFunctionStackLevel(currentCallStack[this.curStackId - 1].func); --去除偏移量 + local varTable = this.getVariable(stackId, true); + msgTab.info = varTable; + end + end + + elseif varRefNum >= 20000 and varRefNum < 30000 then + --全局变量 + local varTable = this.getGlobalVariable(); + msgTab.info = varTable; + elseif varRefNum >= 30000 then + --upValue + if dataTable.info.stackId ~= nil and tonumber(dataTable.info.stackId) > 1 then + this.curStackId = tonumber(dataTable.info.stackId); + if type(currentCallStack[this.curStackId - 1]) ~= "table" or type(currentCallStack[this.curStackId - 1].func) ~= "function" then + local str = "getVariable getUpvalue currentCallStack " .. this.curStackId - 1 .. " Error\n" .. this.serializeTable(currentCallStack, "currentCallStack"); + this.printToVSCode(str, 2); + msgTab.info = {}; + else + local varTable = this.getUpValueVariable(currentCallStack[this.curStackId - 1 ].func, true); + msgTab.info = varTable; + end + end + end + this.sendMsg(msgTab); + this.debugger_wait_msg(); + end + elseif dataTable.cmd == "initSuccess" then + --初始化会传过来一些变量,这里记录这些变量 + --Base64 + if dataTable.info.isNeedB64EncodeStr == "true" then + isNeedB64EncodeStr = true; + else + isNeedB64EncodeStr = false; + end + --path + luaFileExtension = dataTable.info.luaFileExtension + local TempFilePath = dataTable.info.TempFilePath; + if TempFilePath:sub(-1, -1) == [[\]] or TempFilePath:sub(-1, -1) == [[/]] then + TempFilePath = TempFilePath:sub(1, -2); + end + TempFilePath_luaString = TempFilePath; + cwd = this.genUnifiedPath(dataTable.info.cwd); + --logLevel + logLevel = tonumber(dataTable.info.logLevel) or 1; + --autoPathMode + if dataTable.info.autoPathMode == "true" then + autoPathMode = true; + else + autoPathMode = false; + end + + if dataTable.info.pathCaseSensitivity == "true" then + pathCaseSensitivity = true; + else + pathCaseSensitivity = false; + end + + --OS type + if nil == OSType then + --用户未主动设置OSType, 接收VSCode传来的数据 + if type(dataTable.info.OSType) == "string" then + OSType = dataTable.info.OSType; + else + OSType = "Windows_NT"; + OSTypeErrTip = "未能检测出OSType, 可能是node os库未能加载,系统使用默认设置Windows_NT" + end + else + --用户自设OSType, 使用用户的设置 + end + + --检测用户是否自设了clib路径 + isUserSetClibPath = false; + if nil == clibPath then + --用户未设置clibPath, 接收VSCode传来的数据 + if type(dataTable.info.clibPath) == "string" then + clibPath = dataTable.info.clibPath; + else + clibPath = ""; + pathErrTip = "未能正确获取libpdebug库所在位置, 可能无法加载libpdebug库。"; + end + else + --用户自设clibPath + isUserSetClibPath = true; + end + + --查找c++的hook库是否存在 + if tostring(dataTable.info.useCHook) == "true" then + userSetUseClib = true; --用户确定使用clib + if isUserSetClibPath == true then --如果用户自设了clib路径 + if luapanda_chook ~= nil then + hookLib = luapanda_chook; + else + if not(this.tryRequireClib("libpdebug", clibPath)) then + this.printToVSCode("Require clib failed, use Lua to continue debug, use LuaPanda.doctor() for more information.", 1); + end + end + else + local clibExt, platform; + if OSType == "Darwin" then clibExt = "/?.so;"; platform = "mac"; + elseif OSType == "Linux" then clibExt = "/?.so;"; platform = "linux"; + else clibExt = "/?.dll;"; platform = "win"; end + + local lua_ver; + if _VERSION == "Lua 5.1" then + lua_ver = "501"; + else + lua_ver = "503"; + end + + local x86Path = clibPath.. platform .."/x86/".. lua_ver .. clibExt; + local x64Path = clibPath.. platform .."/x86_64/".. lua_ver .. clibExt; + + if luapanda_chook ~= nil then + hookLib = luapanda_chook; + else + if not(this.tryRequireClib("libpdebug", x64Path) or this.tryRequireClib("libpdebug", x86Path)) then + this.printToVSCode("Require clib failed, use Lua to continue debug, use LuaPanda.doctor() for more information.", 1); + end + end + end + else + userSetUseClib = false; + end + + --adapter版本信息 + adapterVer = tostring(dataTable.info.adapterVersion); + local msgTab = this.getMsgTable("initSuccess", this.getCallbackId()); + --回传是否使用了lib,是否有loadstring函数 + local isUseHookLib = 0; + if hookLib ~= nil then + isUseHookLib = 1; + --同步数据给c hook + -- hookLib.sync_config(logLevel, pathCaseSensitivity and 1 or 0, autoPathMode and 1 or 0); + hookLib.sync_config(logLevel, pathCaseSensitivity and 1 or 0); + hookLib.sync_tempfile_path(TempFilePath_luaString); + hookLib.sync_cwd(cwd); + hookLib.sync_file_ext(luaFileExtension); + end + --detect LoadString + isUseLoadstring = 0; + if debugger_loadString ~= nil and type(debugger_loadString) == "function" then + if(pcall(debugger_loadString("return 0"))) then + isUseLoadstring = 1; + end + end + local tab = { debuggerVer = tostring(debuggerVer) , UseHookLib = tostring(isUseHookLib) , UseLoadstring = tostring(isUseLoadstring), isNeedB64EncodeStr = tostring(isNeedB64EncodeStr) }; + msgTab.info = tab; + this.sendMsg(msgTab); + --上面getBK中会判断当前状态是否WAIT_CMD, 所以最后再切换状态。 + stopOnEntry = dataTable.info.stopOnEntry; + if dataTable.info.stopOnEntry == "true" then + this.changeRunState(runState.STOP_ON_ENTRY); --停止在STOP_ON_ENTRY再接收breaks消息 + else + this.debugger_wait_msg(1); --等待1s bk消息 如果收到或超时(没有断点)就开始运行 + this.changeRunState(runState.RUN); + end + + elseif dataTable.cmd == "getWatchedVariable" then + local msgTab = this.getMsgTable("getWatchedVariable", this.getCallbackId()); + local stackId = tonumber(dataTable.info.stackId); + --loadstring系统函数, watch插件加载 + if isUseLoadstring == 1 then + --使用loadstring + this.curStackId = stackId; + local retValue = this.processWatchedExp(dataTable.info); + msgTab.info = retValue + this.sendMsg(msgTab); + this.debugger_wait_msg(); + return; + else + --旧的查找方式 + local wv = this.getWatchedVariable(dataTable.info.varName, stackId, true); + if wv ~= nil then + msgTab.info = wv; + end + this.sendMsg(msgTab); + this.debugger_wait_msg(); + end + elseif dataTable.cmd == "stopRun" then + --停止hook,已不在处理任何断点信息,也就不会产生日志等。发送消息后等待前端主动断开连接 + local msgTab = this.getMsgTable("stopRun", this.getCallbackId()); + this.sendMsg(msgTab); + this.disconnect(); + elseif "LuaGarbageCollect" == dataTable.cmd then + this.printToVSCode("collect garbage!"); + collectgarbage("collect"); + --回收后刷一下内存 + this.sendLuaMemory(); + this.debugger_wait_msg(); + elseif "runREPLExpression" == dataTable.cmd then + this.curStackId = tonumber(dataTable.info.stackId); + local retValue = this.processExp(dataTable.info); + local msgTab = this.getMsgTable("runREPLExpression", this.getCallbackId()); + msgTab.info = retValue + this.sendMsg(msgTab); + this.debugger_wait_msg(); + else + end +end + +-- 变量赋值的处理函数。基本逻辑是先从当前栈帧(curStackId)中取 newValue 代表的变量,找到之后再把找到的值通过setVariableValue写回。 +-- @varName 被设置值的变量名 +-- @newValue 新值的名字,它是一个string +-- @needFindVariable 是否需要查找引用变量。(用户输入的是否是一个Object) +-- @curStackId 当前栈帧(查找和变量赋值用) +-- @assigndVar 被直接赋值(省去查找过程) +-- @setLimit 赋值时的限制范围(local upvalue global) +function this.createSetValueRetTable(varName, newValue, needFindVariable, curStackId, assigndVar , setLimit) + local info; + local getVarRet; + -- needFindVariable == true,则使用getWatchedVariable处理(可选, 用来支持 a = b (b为变量的情况))。 + if needFindVariable == false then + getVarRet = {}; + getVarRet[1] = {variablesReference = 0, value = newValue, name = varName, type = type(newValue)}; + else + getVarRet = this.getWatchedVariable( tostring(newValue), curStackId, true); + end + if getVarRet ~= nil then + -- newValue赋变量真实值 + local realVarValue; + local displayVarValue = getVarRet[1].value; + if needFindVariable == true then + if tonumber(getVarRet[1].variablesReference) > 0 then + realVarValue = variableRefTab[tonumber(getVarRet[1].variablesReference)]; + else + if getVarRet[1].type == 'number' then realVarValue = tonumber(getVarRet[1].value) end + if getVarRet[1].type == 'string' then + realVarValue = tostring(getVarRet[1].value); + local first_chr = string.sub(realVarValue, 1, 1); + local end_chr = string.sub(realVarValue, -1, -1); + if first_chr == end_chr then + if first_chr == "'" or first_chr == '"' then + realVarValue = string.sub(realVarValue, 2, -2); + displayVarValue = realVarValue; + end + end + end + if getVarRet[1].type == 'boolean' then + if getVarRet[1].value == "true" then + realVarValue = true; + else + realVarValue = false; + end + end + if getVarRet[1].type == 'nil' then realVarValue = nil end + end + else + realVarValue = getVarRet[1].value; + end + + local setVarRet; + if type(assigndVar) ~= table then + setVarRet = this.setVariableValue( varName, curStackId, realVarValue, setLimit ); + else + assigndVar[varName] = realVarValue; + setVarRet = true; + end + + if getVarRet[1].type == "string" then + displayVarValue = '"' .. displayVarValue .. '"'; + end + + if setVarRet ~= false and setVarRet ~= nil then + local retTip = "变量 ".. varName .." 赋值成功"; + info = { success = "true", name = getVarRet[1].name , type = getVarRet[1].type , value = displayVarValue, variablesReference = tostring(getVarRet[1].variablesReference), tip = retTip}; + else + info = { success = "false", type = type(realVarValue), value = displayVarValue, tip = "找不到要设置的变量"}; + end + + else + info = { success = "false", type = nil, value = nil, tip = "输入的值无意义"}; + end + return info +end + +--接收消息 +--这里维护一个接收消息队列,因为Lua端未做隔断符保护,变量赋值时请注意其中不要包含隔断符 |*| +-- @timeoutSec 超时时间 +-- @return boolean 成功/失败 +function this.receiveMessage( timeoutSec ) + timeoutSec = timeoutSec or MAX_TIMEOUT_SEC; + sock:settimeout(timeoutSec); + --如果队列中还有消息,直接取出来交给dataProcess处理 + if #recvMsgQueue > 0 then + local saved_cmd = recvMsgQueue[1]; + table.remove(recvMsgQueue, 1); + this.dataProcess(saved_cmd); + return true; + end + + if currentRunState == runState.DISCONNECT then + this.disconnect(); + return false; + end + + if sock == nil then + this.printToConsole("[debugger error]接收信息失败 | reason: socket == nil", 2); + return; + end + local response, err = sock:receive(); + if response == nil then + if err == "closed" then + this.printToConsole("[debugger error]接收信息失败 | reason:"..err, 2); + this.disconnect(); + end + return false; + else + + --判断是否是一条消息,分拆 + local proc_response = string.sub(response, 1, -1 * (TCPSplitChar:len() + 1 )); + local match_res = string.find(proc_response, TCPSplitChar, 1, true); + if match_res == nil then + --单条 + this.dataProcess(proc_response); + else + --有粘包 + repeat + --待处理命令 + local str1 = string.sub(proc_response, 1, match_res - 1); + table.insert(recvMsgQueue, str1); + --剩余匹配 + local str2 = string.sub(proc_response, match_res + TCPSplitChar:len() , -1); + match_res = string.find(str2, TCPSplitChar, 1, true); + until not match_res + this.receiveMessage(); + end + return true; + end +end + +--这里不用循环,在外面处理完消息会在调用回来 +-- @timeoutSec 等待时间s +-- @entryFlag 入口标记,用来标识是从哪里调入的 +function this.debugger_wait_msg(timeoutSec) + timeoutSec = timeoutSec or MAX_TIMEOUT_SEC; + + if currentRunState == runState.WAIT_CMD then + local ret = this.receiveMessage(timeoutSec); + return ret; + end + + if currentRunState == runState.STEPOVER or + currentRunState == runState.STEPIN or + currentRunState == runState.STEPOUT or + currentRunState == runState.RUN then + this.receiveMessage(0); + return + end + + if currentRunState == runState.STEPOVER_STOP or + currentRunState == runState.STEPIN_STOP or + currentRunState == runState.STEPOUT_STOP or + currentRunState == runState.HIT_BREAKPOINT or + currentRunState == runState.STOP_ON_ENTRY + then + this.sendLuaMemory(); + this.receiveMessage(MAX_TIMEOUT_SEC); + return + end +end + +----------------------------------------------------------------------------- +-- 调试器核心方法 +----------------------------------------------------------------------------- + +------------------------堆栈管理------------------------- + + +--getStackTable需要建立stackTable,保存每层的lua函数实例(用来取upvalue),保存函数展示层级和ly的关系(便于根据前端传来的stackId查局部变量) +-- @level 要获取的层级 +function this.getStackTable( level ) + local functionLevel = 0 + if hookLib ~= nil then + functionLevel = level or HOOK_LEVEL; + else + functionLevel = level or this.getSpecificFunctionStackLevel(lastRunFunction.func); + end + local stackTab = {}; + local userFuncSteakLevel = 0; --用户函数的steaklevel + repeat + local info = debug.getinfo(functionLevel, "SlLnf") + if info == nil then + break; + end + if info.source == "=[C]" then + break; + end + + local ss = {}; + ss.file = this.getPath(info); + ss.name = "文件名"; --这里要做截取 + ss.line = tostring(info.currentline); + --使用hookLib时,堆栈有偏移量,这里统一调用栈顶编号2 + local ssindex = functionLevel - 3; + if hookLib ~= nil then + ssindex = ssindex + 2; + end + ss.index = tostring(ssindex); + table.insert(stackTab,ss); + --把数据存入currentCallStack + local callStackInfo = {}; + callStackInfo.name = ss.file; + callStackInfo.line = ss.line; + callStackInfo.func = info.func; --保存的function + callStackInfo.realLy = functionLevel; --真实堆栈层functionLevel(仅debug时用) + table.insert(currentCallStack, callStackInfo); + + --level赋值 + if userFuncSteakLevel == 0 then + userFuncSteakLevel = functionLevel; + end + functionLevel = functionLevel + 1; + until info == nil + return stackTab, userFuncSteakLevel; +end + +--这个方法是根据工程中的cwd和luaFileExtension修改 +-- @info getInfo获取的包含调用信息table +function this.getPath( info ) + local filePath = info; + if type(info) == "table" then + filePath = info.source; + end + --尝试从Cache中获取路径 + local cachePath = this.getCacheFormatPath(filePath); + if cachePath~= nil and type(cachePath) == "string" then + return cachePath; + end + + -- originalPath是getInfo的原始路径,后面用来填充缓存key + local originalPath = filePath; + + --后缀处理 + if luaFileExtension ~= "" then + --判断后缀中是否包含%1等魔法字符.用于从lua虚拟机获取到的路径含.的情况 + if string.find(luaFileExtension, "%%%d") then + filePath = string.gsub(filePath, "%.[%w%.]+$", luaFileExtension); + else + filePath = string.gsub(filePath, "%.[%w%.]+$", ""); + filePath = filePath .. "." .. luaFileExtension; + end + end + + --如果路径头部有@,去除 + if filePath:sub(1,1) == '@' then + filePath = filePath:sub(2); + end + + if not autoPathMode then + --绝对路径和相对路径的处理 | 若在Mac下以/开头,或者在Win下以*:开头,说明是绝对路径,不需要再拼。 + if filePath:sub(1,1) == [[/]] or filePath:sub(1,2):match("^%a:") then + isAbsolutePath = true; + else + isAbsolutePath = false; + if cwd ~= "" then + --查看filePath中是否包含cwd + local matchRes = string.find(filePath, cwd, 1, true); + if matchRes == nil then + filePath = cwd.."/"..filePath; + end + end + end + end + filePath = this.genUnifiedPath(filePath); + + if autoPathMode then + -- 自动路径模式下,只保留文件名 + filePath = this.getFilenameFromPath(filePath) + end + --放入Cache中缓存 + this.setCacheFormatPath(originalPath, filePath); + return filePath; +end + +--从路径中获取文件名 +function this.getFilenameFromPath(path) + if path == nil then + return ''; + end + + return string.match(path, "([^/]*)$"); +end + +--获取当前函数的堆栈层级 +--原理是向上查找,遇到DebuggerFileName就调过。但是可能存在代码段和C导致不确定性。目前使用getSpecificFunctionStackLevel代替。 +function this.getCurrentFunctionStackLevel() + -- print(debug.traceback("===getCurrentFunctionStackLevel Stack trace===")) + local funclayer = 2; + repeat + local info = debug.getinfo(funclayer, "S"); --通过name来判断 + if info ~= nil then + local matchRes = ((info.source == DebuggerFileName) or (info.source == DebuggerToolsName)); + if matchRes == false then + return (funclayer - 1); + end + end + funclayer = funclayer + 1; + until not info + return 0; +end + +--获取指定函数的堆栈层级 +--通常用来获取最后一个用户函数的层级,用法是从currentCallStack取用户点击的栈,再使用本函数取对应层级。 +-- @func 被获取层级的function +function this.getSpecificFunctionStackLevel( func ) + local funclayer = 2; + repeat + local info = debug.getinfo(funclayer, "f"); --通过name来判断 + if info ~= nil then + if info.func == func then + return (funclayer - 1); + end + end + funclayer = funclayer + 1; + until not info + return 0; +end + +--检查当前堆栈是否是Lua +-- @checkLayer 指定的栈层 +function this.checkCurrentLayerisLua( checkLayer ) + local info = debug.getinfo(checkLayer, "S"); + if info == nil then + return nil; + end + info.source = this.genUnifiedPath(info.source); + if info ~= nil then + for k,v in pairs(info) do + if k == "what" then + if v == "C" then + return false; + else + return true; + end + end + end + end + return nil; +end + + +------------------------断点处理------------------------- +-- 参数info是当前堆栈信息 +-- @info getInfo获取的当前调用信息 +function this.isHitBreakpoint( info ) + local curLine = tostring(info.currentline); + local breakpointPath = info.source; + local isPathHit = false; + + if breaks[breakpointPath] then + isPathHit = true; + end + + if isPathHit then + for k,v in ipairs(breaks[breakpointPath]) do + if tostring(v["line"]) == tostring(curLine) then + -- type是TS中的枚举类型,其定义在BreakPoint.tx文件中 + --[[ + enum BreakpointType { + conditionBreakpoint = 0, + logPoint, + lineBreakpoint + } + ]] + + if v["type"] == "0" then + -- condition breakpoint + -- 注意此处不要使用尾调用,否则会影响调用栈,导致Lua5.3和Lua5.1中调用栈层级不同 + local conditionRet = this.IsMeetCondition(v["condition"]); + return conditionRet; + elseif v["type"] == "1" then + -- log point + this.printToVSCode("[log point output]: " .. v["logMessage"], 1); + else + -- line breakpoint + return true; + end + end + end + end + return false; +end + +-- 条件断点处理函数 +-- 返回true表示条件成立 +-- @conditionExp 条件表达式 +function this.IsMeetCondition(conditionExp) + -- 判断条件之前更新堆栈信息 + currentCallStack = {}; + variableRefTab = {}; + variableRefIdx = 1; + this.getStackTable(); + this.curStackId = 2; --在用户空间最上层执行 + + local conditionExpTable = {["varName"] = conditionExp} + local retTable = this.processWatchedExp(conditionExpTable) + + local isMeetCondition = false; + local function HandleResult() + if retTable[1]["isSuccess"] == "true" then + if retTable[1]["value"] == "nil" or (retTable[1]["value"] == "false" and retTable[1]["type"] == "boolean") then + isMeetCondition = false; + else + isMeetCondition = true; + end + else + isMeetCondition = false; + end + end + + xpcall(HandleResult, function() isMeetCondition = false; end) + return isMeetCondition; +end + +--加入断点函数 +function this.BP() + this.printToConsole("BP()"); + if hookLib == nil then + if currentHookState == hookState.DISCONNECT_HOOK then + this.printToConsole("BP() but NO HOOK"); + return; + end + + local co, isMain = coroutine.running(); + if _VERSION == "Lua 5.1" then + if co == nil then + isMain = true; + else + isMain = false; + end + end + + if isMain == true then + this.printToConsole("BP() in main"); + else + this.printToConsole("BP() in coroutine"); + debug.sethook(co, this.debug_hook, "lrc"); + end + hitBP = true; + else + if hookLib.get_libhook_state() == hookState.DISCONNECT_HOOK then + this.printToConsole("BP() but NO C HOOK"); + return; + end + + --clib, set hitBP + hookLib.sync_bp_hit(1); + end + this.changeHookState(hookState.ALL_HOOK); + return true; +end + +-- 检查当前文件中是否有断点 +-- 如果填写参数fileName 返回fileName中有无断点, 全局有无断点 +-- fileName为空,返回全局是否有断点 +function this.checkHasBreakpoint(fileName) + local hasBk = false; + --有无全局断点 + if next(breaks) == nil then + hasBk = false; + else + hasBk = true; + end + --当前文件中是否有断点 + if fileName ~= nil then + return breaks[fileName] ~= nil, hasBk; + else + return hasBk; + end +end + +function this.checkfuncHasBreakpoint(sLine, eLine, fileName) + if breaks[fileName] == nil then + return false; + end + sLine = tonumber(sLine); + eLine = tonumber(eLine); + + --起始行号>结束行号,或者sLine = eLine = 0 + if sLine >= eLine then + return true; + end + + if #breaks[fileName] <= 0 then + return false; + else + for k,v in ipairs(breaks[fileName]) do + if tonumber(v.line) > sLine and tonumber(v.line) <= eLine then + return true; + end + end + end + return false; +end +------------------------HOOK模块------------------------- +-- 钩子函数 +-- @event 执行状态(call,return,line) +-- @line 行号 +function this.debug_hook(event, line) + if this.reConnect() == 1 then return; end + + if logLevel == 0 then + local logTable = {"-----enter debug_hook-----\n", "event:", event, " line:", tostring(line), " currentHookState:",currentHookState," currentRunState:", currentRunState}; + local logString = table.concat(logTable); + this.printToVSCode(logString); + end + + --litehook 仅非阻塞接收断点 + if currentHookState == hookState.LITE_HOOK then + local ti = os.time(); + if ti - receiveMsgTimer > 1 then + this.debugger_wait_msg(0); + receiveMsgTimer = ti; + end + return; + end + + --运行中 + local info; + local co, isMain = coroutine.running(); + if _VERSION == "Lua 5.1" then + if co == nil then + isMain = true; + else + isMain = false; + end + end + isInMainThread = isMain; + if isMain == true then + info = debug.getinfo(2, "Slf") + else + info = debug.getinfo(co, 2, "Slf") + end + info.event = event; + + this.real_hook_process(info); +end + +function this.real_hook_process(info) + local jumpFlag = false; + local event = info.event; + + --如果当前行在Debugger中,不做处理 + local matchRes = ((info.source == DebuggerFileName) or (info.source == DebuggerToolsName)); + if matchRes == true then + return; + end + + --即使MID hook在C中, 或者是Run或者单步时也接收消息 + if currentRunState == runState.RUN or + currentRunState == runState.STEPOVER or + currentRunState == runState.STEPIN or + currentRunState == runState.STEPOUT then + local ti = os.time(); + if ti - receiveMsgTimer > 1 then + this.debugger_wait_msg(0); + receiveMsgTimer = ti; + end + end + + --不处理C函数 + if info.source == "=[C]" then + this.printToVSCode("current method is C"); + return; + end + + --不处理 slua "temp buffer" + if info.source == "temp buffer" then + this.printToVSCode("current method is in temp buffer"); + return; + end + + --不处理 xlua "chunk" + if info.source == "chunk" then + this.printToVSCode("current method is in chunk"); + return; + end + + --lua 代码段的处理,目前暂不调试代码段。 + if info.short_src:match("%[string \"") then + --当shortSrc中出现[string时]。要检查一下source, 区别是路径还是代码段. 方法是看路径中有没有\t \n ; + if info.source:match("[\n;=]") then + --是代码段,调过 + this.printToVSCode("hook jump Code String!"); + jumpFlag = true; + end + end + + --标准路径处理 + if jumpFlag == false then + info.source = this.getPath(info); + end + --本次执行的函数和上次执行的函数作对比,防止在一行停留两次 + if lastRunFunction["currentline"] == info["currentline"] and lastRunFunction["source"] == info["source"] and lastRunFunction["func"] == info["func"] and lastRunFunction["event"] == event then + this.printToVSCode("run twice"); + end + --记录最后一次调用信息 + if jumpFlag == false then + lastRunFunction = info; + lastRunFunction["event"] = event; + lastRunFilePath = info.source; + end + --输出函数信息到前台 + if logLevel == 0 and jumpFlag == false then + local logTable = {"[lua hook] event:", tostring(event), " currentRunState:",tostring(currentRunState)," currentHookState:",tostring(currentHookState)," jumpFlag:", tostring(jumpFlag)}; + for k,v in pairs(info) do + table.insert(logTable, tostring(k)); + table.insert(logTable, ":"); + table.insert(logTable, tostring(v)); + table.insert(logTable, " "); + end + local logString = table.concat(logTable); + this.printToVSCode(logString); + end + + --仅在line时做断点判断。进了断点之后不再进入本次STEP类型的判断,用Aflag做标记 + local isHit = false; + if tostring(event) == "line" and jumpFlag == false then + if currentRunState == runState.RUN or currentRunState == runState.STEPOVER or currentRunState == runState.STEPIN or currentRunState == runState.STEPOUT then + --断点判断 + isHit = this.isHitBreakpoint(info) or hitBP; + if isHit == true then + this.printToVSCode(" + HitBreakpoint true"); + hitBP = false; --hitBP是断点硬性命中标记 + --计数器清0 + stepOverCounter = 0; + stepOutCounter = 0; + this.changeRunState(runState.HIT_BREAKPOINT); + --发消息并等待 + this.SendMsgWithStack("stopOnBreakpoint"); + end + end + end + + if isHit == true then + return; + end + + if currentRunState == runState.STEPOVER then + -- line stepOverCounter!= 0 不作操作 + -- line stepOverCounter == 0 停止 + if event == "line" and stepOverCounter <= 0 and jumpFlag == false then + stepOverCounter = 0; + this.changeRunState(runState.STEPOVER_STOP) + this.SendMsgWithStack("stopOnStep"); + elseif event == "return" or event == "tail return" then + --5.1中是tail return + if stepOverCounter ~= 0 then + stepOverCounter = stepOverCounter - 1; + end + elseif event == "call" then + stepOverCounter = stepOverCounter + 1; + end + elseif currentRunState == runState.STOP_ON_ENTRY then + --在Lua入口点处直接停住 + if event == "line" and jumpFlag == false then + --初始化内存分析的变量 + -- MemProfiler.getSystemVar(); + --这里要判断一下是Lua的入口点,否则停到 + this.SendMsgWithStack("stopOnEntry"); + end + elseif currentRunState == runState.STEPIN then + if event == "line" and jumpFlag == false then + this.changeRunState(runState.STEPIN_STOP) + this.SendMsgWithStack("stopOnStepIn"); + end + elseif currentRunState == runState.STEPOUT then + --line 不做操作 + --in 计数器+1 + --out 计数器-1 + if jumpFlag == false then + if stepOutCounter <= -1 then + stepOutCounter = 0; + this.changeRunState(runState.STEPOUT_STOP) + this.SendMsgWithStack("stopOnStepOut"); + end + end + + if event == "return" or event == "tail return" then + stepOutCounter = stepOutCounter - 1; + elseif event == "call" then + stepOutCounter = stepOutCounter + 1; + end + end + + --在RUN时检查并改变状态 + if hookLib == nil then + if currentRunState == runState.RUN and jumpFlag == false and currentHookState ~= hookState.DISCONNECT_HOOK then + local fileBP, G_BP = this.checkHasBreakpoint(lastRunFilePath); + if fileBP == false then + --文件无断点 + if G_BP == true then + this.changeHookState(hookState.MID_HOOK); + else + this.changeHookState(hookState.LITE_HOOK); + end + else + --文件有断点, 判断函数内是否有断点 + local funHasBP = this.checkfuncHasBreakpoint(lastRunFunction.linedefined, lastRunFunction.lastlinedefined, lastRunFilePath); + if funHasBP then + --函数定义范围内 + this.changeHookState(hookState.ALL_HOOK); + else + this.changeHookState(hookState.MID_HOOK); + end + end + + --MID_HOOK状态下,return需要在下一次hook检查文件(return时,还是当前文件,检查文件时状态无法转换) + if (event == "return" or event == "tail return") and currentHookState == hookState.MID_HOOK then + this.changeHookState(hookState.ALL_HOOK); + end + end + end +end + +-- 向Vscode发送标准通知消息,cmdStr是消息类型 +-- @cmdStr 命令字 +function this.SendMsgWithStack(cmdStr) + local msgTab = this.getMsgTable(cmdStr); + local userFuncLevel = 0; + msgTab["stack"] , userFuncLevel= this.getStackTable(); + if userFuncLevel ~= 0 then + lastRunFunction["func"] = debug.getinfo( (userFuncLevel - 1) , 'f').func; + end + this.sendMsg(msgTab); + this.debugger_wait_msg(); +end + +-- hook状态改变 +-- @s 目标状态 +function this.changeHookState( s ) + if hookLib == nil and currentHookState == s then + return; + end + + this.printToConsole("change hook state :"..s) + if s ~= hookState.DISCONNECT_HOOK then + this.printToVSCode("change hook state : "..s) + end + + currentHookState = s; + if s == hookState.DISCONNECT_HOOK then + --为了实现通用attach模式,require即开始hook,利用r作为时机发起连接 + if openAttachMode == true then + if hookLib then hookLib.lua_set_hookstate(hookState.DISCONNECT_HOOK); else debug.sethook(this.debug_hook, "r", 1000000); end + else + if hookLib then hookLib.endHook(); else debug.sethook(); end + end + elseif s == hookState.LITE_HOOK then + if hookLib then hookLib.lua_set_hookstate(hookState.LITE_HOOK); else debug.sethook(this.debug_hook, "r"); end + elseif s == hookState.MID_HOOK then + if hookLib then hookLib.lua_set_hookstate(hookState.MID_HOOK); else debug.sethook(this.debug_hook, "rc"); end + elseif s == hookState.ALL_HOOK then + if hookLib then hookLib.lua_set_hookstate(hookState.ALL_HOOK); else debug.sethook(this.debug_hook, "lrc");end + end + --coroutine + if hookLib == nil then + this.changeCoroutineHookState(); + end +end + +-- 运行状态机,状态变更 +-- @s 目标状态 +-- @isFromHooklib 1:从libc库中发来的状态改变 | 0:lua发来的状态改变 +function this.changeRunState(s , isFromHooklib) + local msgFrom; + if isFromHooklib == 1 then + msgFrom = "libc"; + else + msgFrom = "lua"; + end + + --WAIT_CMD状态会等待接收消息,以下两个状态下不能发消息 + this.printToConsole("changeRunState :"..s.. " | from:"..msgFrom); + if s ~= runState.DISCONNECT and s ~= runState.WAIT_CMD then + this.printToVSCode("changeRunState :"..s.." | from:"..msgFrom); + end + + if hookLib ~= nil and isFromHooklib ~= 1 then + hookLib.lua_set_runstate(s); + end + currentRunState = s; + --状态切换时,清除记录栈信息的状态 + currentCallStack = {}; + variableRefTab = {}; + variableRefIdx = 1; +end + +-- 修改协程状态 +-- @s hook标志位 +function this.changeCoroutineHookState(s) + s = s or currentHookState; + this.printToConsole("change [Coroutine] HookState: "..tostring(s)); + for k ,co in pairs(coroutinePool) do + if coroutine.status(co) == "dead" then + table.remove(coroutinePool, k) + else + if s == hookState.DISCONNECT_HOOK then + if openAttachMode == true then + debug.sethook(co, this.debug_hook, "r", 1000000); + else + debug.sethook(co, this.debug_hook, ""); + end + elseif s == hookState.LITE_HOOK then debug.sethook(co , this.debug_hook, "r"); + elseif s == hookState.MID_HOOK then debug.sethook(co , this.debug_hook, "rc"); + elseif s == hookState.ALL_HOOK then debug.sethook(co , this.debug_hook, "lrc"); + end + end + end +end +-------------------------变量处理相关----------------------------- + +--清空REPL的env环境 +function this.clearEnv() + if this.getTableMemberNum(env) > 0 then + --清空env table + env = setmetatable({}, getmetatable(env)); + end +end + +--返回REPL的env环境 +function this.showEnv() + return env; +end + +-- 用户观察table的查找函数。用tableVarName作为key去查逐层级查找realVar是否匹配 +-- @tableVarName 是用户观察的变量名,已经按层级被解析成table。比如用户输出a.b.c,tableVarName是 a = { b = { c } } +-- @realVar 是待查询 table +-- @return 返回查到的table。没查到返回nil +function this.findTableVar( tableVarName, realVar) + if type(tableVarName) ~= "table" or type(realVar) ~= "table" then + return nil; + end + + local layer = 2; + local curVar = realVar; + local jumpOutFlag = false; + repeat + if tableVarName[layer] ~= nil then + --这里优先展示数字key,比如a{"1" = "aa", [1] = "bb"} 会展示[1]的值 + local tmpCurVar = nil; + xpcall(function() tmpCurVar = curVar[tonumber(tableVarName[layer])]; end , function() tmpCurVar = nil end ); + if tmpCurVar == nil then + xpcall(function() curVar = curVar[tostring(tableVarName[layer])]; end , function() curVar = nil end ); + else + curVar = tmpCurVar; + end + layer = layer + 1; + if curVar == nil then + return nil; + end + else + --找到 + jumpOutFlag = true; + end + until(jumpOutFlag == true) + return curVar; +end + +-- 根据传入信息生成返回的变量信息 +-- @variableName 变量名 +-- @variableIns 变量实例 +-- @return 包含变量信息的格式化table +function this.createWatchedVariableInfo(variableName, variableIns) + local var = {}; + var.name = variableName; + var.type = tostring(type(variableIns)); + xpcall(function() var.value = tostring(variableIns) end , function() var.value = tostring(type(variableIns)) .. " [value can't trans to string]" end ); + var.variablesReference = "0"; --这个地方必须用“0”, 以免variableRefTab[0]出错 + + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = variableIns; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(variableIns); + var.value = memberNum .." Members ".. var.value; + end + elseif var.type == "string" then + var.value = '"' ..variableIns.. '"'; + end + return var; +end + +-- 设置 global 变量 +-- @varName 被修改的变量名 +-- @newValue 新的值 +function this.setGlobal(varName, newValue) + _G[varName] = newValue; + this.printToVSCode("[setVariable success] 已设置 _G.".. varName .. " = " .. tostring(newValue) ); + return true; +end + +-- 设置 upvalue 变量 +-- @varName 被修改的变量名 +-- @newValue 新的值 +-- @stackId 变量所在stack栈层 +-- @tableVarName 变量名拆分成的数组 +function this.setUpvalue(varName, newValue, stackId, tableVarName) + local ret = false; + local upTable = this.getUpValueVariable(currentCallStack[stackId - 1 ].func, true); + for i, realVar in ipairs(upTable) do + if realVar.name == varName then + if #tableVarName > 0 and type(realVar) == "table" then + --处理a.b.c的table类型 + local findRes = this.findTableVar(tableVarName, variableRefTab[realVar.variablesReference]); + if findRes ~= nil then + --命中 + local setVarRet = debug.setupvalue (currentCallStack[stackId - 1 ].func, i, newValue); + if setVarRet == varName then + this.printToConsole("[setVariable success1] 已设置 upvalue ".. varName .. " = " .. tostring(newValue) ); + ret = true; + else + this.printToConsole("[setVariable error1] 未能设置 upvalue ".. varName .. " = " .. tostring(newValue).." , 返回结果: ".. tostring(setVarRet)); + end + return ret; + end + else + --命中 + local setVarRet = debug.setupvalue (currentCallStack[stackId - 1 ].func, i, newValue); + if setVarRet == varName then + this.printToConsole("[setVariable success] 已设置 upvalue ".. varName .. " = " .. tostring(newValue) ); + ret = true; + else + this.printToConsole("[setVariable error] 未能设置 upvalue ".. varName .. " = " .. tostring(newValue).." , 返回结果: ".. tostring(setVarRet)); + end + return ret; + end + end + end + return ret; +end + +-- 设置local 变量 +-- @varName 被修改的变量名 +-- @newValue 新的值 +-- @tableVarName 变量名拆分成的数组 +function this.setLocal( varName, newValue, tableVarName, stackId) + local istackId = tonumber(stackId); + local offset = (istackId and istackId - 2) or 0; + local layerVarTab, ly = this.getVariable(nil , true, offset); + local ret = false; + for i, realVar in ipairs(layerVarTab) do + if realVar.name == varName then + if #tableVarName > 0 and type(realVar) == "table" then + --处理a.b.c的table类型 + local findRes = this.findTableVar(tableVarName, variableRefTab[realVar.variablesReference]); + if findRes ~= nil then + --命中 + local setVarRet = debug.setlocal(ly , layerVarTab[i].index, newValue); + if setVarRet == varName then + this.printToConsole("[setVariable success1] 已设置 local ".. varName .. " = " .. tostring(newValue) ); + ret = true; + else + this.printToConsole("[setVariable error1] 未能设置 local ".. varName .. " = " .. tostring(newValue).." , 返回结果: ".. tostring(setVarRet)); + end + return ret; + end + else + + local setVarRet = debug.setlocal(ly , layerVarTab[i].index, newValue); + + if setVarRet == varName then + this.printToConsole("[setVariable success] 已设置 local ".. varName .. " = " .. tostring(newValue) ); + ret = true; + else + this.printToConsole("[setVariable error] 未能设置 local ".. varName .. " = " .. tostring(newValue) .." , 返回结果: ".. tostring(setVarRet)); + end + return ret; + end + end + end + return ret; +end + + +-- 设置变量的值 +-- @varName 被修改的变量名 +-- @curStackId 调用栈层级(仅在固定栈层查找) +-- @newValue 新的值 +-- @limit 限制符, 10000表示仅在局部变量查找 ,20000 global, 30000 upvalue +function this.setVariableValue (varName, stackId, newValue , limit) + this.printToConsole("setVariableValue | varName:" .. tostring(varName) .. " stackId:".. tostring(stackId) .." newValue:" .. tostring(newValue) .." limit:"..tostring(limit) ) + if tostring(varName) == nil or tostring(varName) == "" then + --赋值错误 + this.printToConsole("[setVariable Error] 被赋值的变量名为空", 2 ); + this.printToVSCode("[setVariable Error] 被赋值的变量名为空", 2 ); + return false; + end + + --支持a.b.c形式。切割varName + local tableVarName = {}; + if varName:match('%.') then + tableVarName = this.stringSplit(varName , '%.'); + if type(tableVarName) ~= "table" or #tableVarName < 1 then + return false; + end + varName = tableVarName[1]; + end + + if limit == "local" then + local ret = this.setLocal( varName, newValue, tableVarName, stackId); + return ret; + elseif limit == "upvalue" then + local ret = this.setUpvalue(varName, newValue, stackId, tableVarName); + return ret + elseif limit == "global" then + local ret = this.setGlobal(varName, newValue); + return ret; + else + local ret = this.setLocal( varName, newValue, tableVarName, stackId) or this.setUpvalue(varName, newValue, stackId, tableVarName) or this.setGlobal(varName, newValue); + this.printToConsole("set Value res :".. tostring(ret)); + return ret; + end +end + +-- 按照local -> upvalue -> _G 顺序查找观察变量 +-- @varName 用户输入的变量名 +-- @stackId 调用栈层级(仅在固定栈层查找) +-- @isFormatVariable 是否把变量格式化为VSCode接收的形式 +-- @return 查到返回信息,查不到返回nil +function this.getWatchedVariable( varName , stackId , isFormatVariable ) + this.printToConsole("getWatchedVariable | varName:" .. tostring(varName) .. " stackId:".. tostring(stackId) .." isFormatVariable:" .. tostring(isFormatVariable) ) + if tostring(varName) == nil or tostring(varName) == "" then + return nil; + end + + if type(currentCallStack[stackId - 1]) ~= "table" or type(currentCallStack[stackId - 1].func) ~= "function" then + local str = "getWatchedVariable currentCallStack " .. stackId - 1 .. " Error\n" .. this.serializeTable(currentCallStack, "currentCallStack"); + this.printToVSCode(str, 2); + return nil; + end + + --orgname 记录原名字. 用来处理a.b.c的形式 + local orgname = varName; + --支持a.b.c形式。切割varName + local tableVarName = {}; + if varName:match('%.') then + tableVarName = this.stringSplit(varName , '%.'); + if type(tableVarName) ~= "table" or #tableVarName < 1 then + return nil; + end + varName = tableVarName[1]; + end + --用来返回,带有查到变量的table + local varTab = {}; + local ly = this.getSpecificFunctionStackLevel(currentCallStack[stackId - 1].func); + + local layerVarTab = this.getVariable(ly, isFormatVariable); + local upTable = this.getUpValueVariable(currentCallStack[stackId - 1 ].func, isFormatVariable); + local travelTab = {}; + table.insert(travelTab, layerVarTab); + table.insert(travelTab, upTable); + for _, layerVarTab in ipairs(travelTab) do + for i,realVar in ipairs(layerVarTab) do + if realVar.name == varName then + if #tableVarName > 0 and type(realVar) == "table" then + --处理a.b.c的table类型 + local findRes = this.findTableVar(tableVarName, variableRefTab[realVar.variablesReference]); + if findRes ~= nil then + --命中 + if isFormatVariable then + local var = this.createWatchedVariableInfo( orgname , findRes ); + table.insert(varTab, var); + return varTab; + else + return findRes.value; + end + end + else + --命中 + if isFormatVariable then + table.insert(varTab, realVar); + return varTab; + else + return realVar.value; + end + end + end + end + end + + --在全局变量_G中查找 + if _G[varName] ~= nil then + --命中 + if #tableVarName > 0 and type(_G[varName]) == "table" then + local findRes = this.findTableVar(tableVarName, _G[varName]); + if findRes ~= nil then + if isFormatVariable then + local var = this.createWatchedVariableInfo( orgname , findRes ); + table.insert(varTab, var); + return varTab; + else + return findRes; + end + end + else + if isFormatVariable then + local var = this.createWatchedVariableInfo( varName , _G[varName] ); + table.insert(varTab, var); + return varTab; + else + return _G[varName]; + end + end + end + this.printToConsole("getWatchedVariable not find variable"); + return nil; +end + +-- 查询引用变量 +-- @refStr 变量记录id(variableRefTab索引) +-- @return 格式化的变量信息table +function this.getVariableRef( refStr ) + local varRef = tonumber(refStr); + local varTab = {}; + + if tostring(type(variableRefTab[varRef])) == "table" then + for n,v in pairs(variableRefTab[varRef]) do + local var = {}; + if type(n) == "string" then + var.name = '"' .. tostring(n) .. '"'; + else + var.name = tostring(n); + end + var.type = tostring(type(v)); + xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .. " [value can't trans to string]" end ); + var.variablesReference = "0"; + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = v; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(v); + var.value = memberNum .." Members ".. ( var.value or '' ); + end + elseif var.type == "string" then + var.value = '"' ..v.. '"'; + end + table.insert(varTab, var); + end + --获取一下mtTable + local mtTab = getmetatable(variableRefTab[varRef]); + if mtTab ~= nil and type(mtTab) == "table" then + local var = {}; + var.name = "_Metatable_"; + var.type = tostring(type(mtTab)); + xpcall(function() var.value = "元表 "..tostring(mtTab); end , function() var.value = "元表 [value can't trans to string]" end ); + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = mtTab; + variableRefIdx = variableRefIdx + 1; + table.insert(varTab, var); + end + elseif tostring(type(variableRefTab[varRef])) == "function" then + --取upvalue + varTab = this.getUpValueVariable(variableRefTab[varRef], true); + elseif tostring(type(variableRefTab[varRef])) == "userdata" then + --取mt table + local udMtTable = getmetatable(variableRefTab[varRef]); + if udMtTable ~= nil and type(udMtTable) == "table" then + local var = {}; + var.name = "_Metatable_"; + var.type = tostring(type(udMtTable)); + xpcall(function() var.value = "元表 "..tostring(udMtTable); end , function() var.value = "元表 [value can't trans to string]" end ); + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = udMtTable; + variableRefIdx = variableRefIdx + 1; + table.insert(varTab, var); + + if udMtTable.__pairs ~= nil and type(udMtTable.__pairs) == "function" then + for n,v in pairs(variableRefTab[varRef]) do + local var = {}; + var.name = tostring(n); + var.type = tostring(type(v)); + xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .. " [value can't trans to string]" end ); + var.variablesReference = "0"; + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = v; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(v); + var.value = memberNum .." Members ".. ( var.value or '' ); + end + elseif var.type == "string" then + var.value = '"' ..v.. '"'; + end + table.insert(varTab, var); + end + end + end + end + return varTab; +end + +-- 获取全局变量。方法和内存管理中获取全局变量的方法一样 +-- @return 格式化的信息, 若未找到返回空table +function this.getGlobalVariable( ... ) + --成本比较高,这里只能遍历_G中的所有变量,并去除系统变量,再返回给客户端 + local varTab = {}; + for k,v in pairs(_G) do + local var = {}; + var.name = tostring(k); + var.type = tostring(type(v)); + xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .." [value can't trans to string]" end ); + var.variablesReference = "0"; + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = v; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(v); + var.value = memberNum .." Members ".. ( var.value or '' ); + end + elseif var.type == "string" then + var.value = '"' ..v.. '"'; + end + table.insert(varTab, var); + end + return varTab; +end + +-- 获取upValues +-- @isFormatVariable true返回[值] true返回[格式化的数据] +function this.getUpValueVariable( checkFunc , isFormatVariable) + local isGetValue = true; + if isFormatVariable == true then + isGetValue = false; + end + + --通过Debug获取当前函数的Func + checkFunc = checkFunc or lastRunFunction.func; + + local varTab = {}; + if checkFunc == nil then + return varTab; + end + local i = 1 + repeat + local n, v = debug.getupvalue(checkFunc, i) + if n then + + local var = {}; + var.name = n; + var.type = tostring(type(v)); + var.variablesReference = "0"; + + if isGetValue == false then + xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .. " [value can't trans to string]" end ); + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = v; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(v); + var.value = memberNum .." Members ".. ( var.value or '' ); + end + elseif var.type == "string" then + var.value = '"' ..v.. '"'; + end + else + var.value = v; + end + + table.insert(varTab, var); + i = i + 1 + end + until not n + return varTab; +end + +-- 获取局部变量 checkLayer是要查询的层级,如果不设置则查询当前层级 +-- @isFormatVariable 是否取值,true:取值的tostring +function this.getVariable( checkLayer, isFormatVariable , offset) + local isGetValue = true; + if isFormatVariable == true then + isGetValue = false; + end + + local ly = 0; + if checkLayer ~= nil and type(checkLayer) == "number" then ly = checkLayer + 1; + else ly = this.getSpecificFunctionStackLevel(lastRunFunction.func); end + + if ly == 0 then + this.printToVSCode("[error]获取层次失败!", 2); + return; + end + local varTab = {}; + local stacklayer = ly; + local k = 1; + + if type(offset) == 'number' then + stacklayer = stacklayer + offset; + end + + repeat + local n, v = debug.getlocal(stacklayer, k) + if n == nil then + break; + end + + --(*temporary)是系统变量,过滤掉。这里假设(*temporary)仅出现在最后 + if "(*temporary)" ~= tostring(n) then + local var = {}; + var.name = n; + var.type = tostring(type(v)); + var.variablesReference = "0"; + var.index = k; + + if isGetValue == false then + xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .. " [value can't trans to string]" end ); + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = v; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(v); + var.value = memberNum .." Members ".. ( var.value or '' ); + end + elseif var.type == "string" then + var.value = '"' ..v.. '"'; + end + else + var.value = v; + end + + local sameIdx = this.checkSameNameVar(varTab, var); + if sameIdx ~= 0 then + varTab[sameIdx] = var; + else + table.insert(varTab, var); + end + end + k = k + 1 + until n == nil + return varTab, stacklayer - 1; +end + +--检查变量列表中的同名变量 +function this.checkSameNameVar(varTab, var) + for k , v in pairs(varTab) do + if v.name == var.name then + return k; + end + end + return 0; +end + +-- 执行表达式 +function this.processExp(msgTable) + local retString; + local var = {}; + var.isSuccess = "true"; + if msgTable ~= nil then + local expression = this.trim(tostring(msgTable.Expression)); + local isCmd = false; + if isCmd == false then + --兼容旧版p 命令 + if expression:find("p ", 1, true) == 1 then + expression = expression:sub(3); + end + + local expressionWithReturn = "return " .. expression; + local f = debugger_loadString(expressionWithReturn) or debugger_loadString(expression); + --判断结果,如果表达式错误会返回nil + if type(f) == "function" then + if _VERSION == "Lua 5.1" then + setfenv(f , env); + else + debug.setupvalue(f, 1, env); + end + --表达式要有错误处理 + xpcall(function() retString = f() end , function() retString = "输入错误指令。\n + 请检查指令是否正确\n + 指令仅能在[暂停在断点时]输入, 请不要在程序持续运行时输入"; var.isSuccess = false; end) + else + retString = "指令执行错误。\n + 请检查指令是否正确\n + 可以直接输入表达式,执行函数或变量名,并观察执行结果"; + var.isSuccess = false; + end + end + end + + var.name = "Exp"; + var.type = tostring(type(retString)); + xpcall(function() var.value = tostring(retString) end , function(e) var.value = tostring(type(retString)) .. " [value can't trans to string] ".. e; var.isSuccess = false; end); + var.variablesReference = "0"; + if var.type == "table" or var.type == "function" or var.type == "userdata" then + variableRefTab[variableRefIdx] = retString; + var.variablesReference = variableRefIdx; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(retString); + var.value = memberNum .." Members ".. var.value; + end + elseif var.type == "string" then + var.value = '"' ..retString.. '"'; + end + --string执行完毕后清空env环境 + this.clearEnv(); + local retTab = {} + table.insert(retTab ,var); + return retTab; +end + +--执行变量观察表达式 +function this.processWatchedExp(msgTable) + local retString; + local expression = "return ".. tostring(msgTable.varName) + this.printToConsole("processWatchedExp | expression: " .. expression); + local f = debugger_loadString(expression); + local var = {}; + var.isSuccess = "true"; + --判断结果,如果表达式错误会返回nil + if type(f) == "function" then + --表达式正确 + if _VERSION == "Lua 5.1" then + setfenv(f , env); + else + debug.setupvalue(f, 1, env); + end + xpcall(function() retString = f() end , function() retString = "输入了错误的变量信息"; var.isSuccess = "false"; end) + else + retString = "未能找到变量的值"; + var.isSuccess = "false"; + end + + var.name = msgTable.varName; + var.type = tostring(type(retString)); + xpcall(function() var.value = tostring(retString) end , function() var.value = tostring(type(retString)) .. " [value can't trans to string]"; var.isSuccess = "false"; end ); + var.variablesReference = "0"; + + if var.type == "table" or var.type == "function" or var.type == "userdata" then + variableRefTab[variableRefIdx] = retString; + var.variablesReference = variableRefIdx; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(retString); + var.value = memberNum .." Members ".. var.value; + end + elseif var.type == "string" then + var.value = '"' ..retString.. '"'; + end + + local retTab = {} + table.insert(retTab ,var); + return retTab; +end + + +function tools.getFileSource() + local info = debug.getinfo(1, "S") + for k,v in pairs(info) do + if k == "source" then + return v; + end + end +end + +--序列化并打印table +function tools.printTable(t, name ,indent) + local str = (tools.show(t, name, indent)); + print(str); +end + +--序列化并返回table +function tools.serializeTable(t, name, indent) + local str = (tools.show(t, name, indent)) + return str +end + +--[[ +Author: Julio Manuel Fernandez-Diaz +Date: January 12, 2007 +Modified slightly by RiciLake to avoid the unnecessary table traversal in tablecount() +Formats tables with cycles recursively to any depth. +The output is returned as a string. +References to other tables are shown as values. +Self references are indicated. +The string returned is "Lua code", which can be procesed +(in the case in which indent is composed by spaces or "--"). +Userdata and function keys and values are shown as strings, +which logically are exactly not equivalent to the original code. +This routine can serve for pretty formating tables with +proper indentations, apart from printing them: +print(table.show(t, "t")) -- a typical use +Heavily based on "Saving tables with cycles", PIL2, p. 113. +Arguments: +t is the table. +name is the name of the table (optional) +indent is a first indentation (optional). +--]] +function tools.show(t, name, indent) + local cart -- a container + local autoref -- for self references + + local function isemptytable(t) return next(t) == nil end + + local function basicSerialize (o) + local so = tostring(o) + if type(o) == "function" then + local info = debug.getinfo(o, "S") + -- info.name is nil because o is not a calling level + if info.what == "C" then + return string.format("%q", so .. ", C function") + else + -- the information is defined through lines + return string.format("%q", so .. ", defined in (" .. + info.linedefined .. "-" .. info.lastlinedefined .. + ")" .. info.source) + end + elseif type(o) == "number" or type(o) == "boolean" then + return so + else + return string.format("%q", so) + end + end + + local function addtocart (value, name, indent, saved, field) + indent = indent or "" + saved = saved or {} + field = field or name + + cart = cart .. indent .. field + + if type(value) ~= "table" then + cart = cart .. " = " .. basicSerialize(value) .. ";\n" + else + if saved[value] then + cart = cart .. " = {}; -- " .. saved[value] + .. " (self reference)\n" + autoref = autoref .. name .. " = " .. saved[value] .. ";\n" + else + saved[value] = name + --if tablecount(value) == 0 then + if isemptytable(value) then + cart = cart .. " = {};\n" + else + cart = cart .. " = {\n" + for k, v in pairs(value) do + k = basicSerialize(k) + local fname = string.format("%s[%s]", name, k) + field = string.format("[%s]", k) + -- three spaces between levels + addtocart(v, fname, indent .. " ", saved, field) + end + cart = cart .. indent .. "};\n" + end + end + end + end + + name = name or "PRINT_Table" + if type(t) ~= "table" then + return name .. " = " .. basicSerialize(t) + end + cart, autoref = "", "" + addtocart(t, name, indent) + return cart .. autoref +end + +----------------------------------------------------------------------------- +-- JSON4Lua: JSON encoding / decoding support for the Lua language. +-- json Module. +-- Author: Craig Mason-Jones +-- Homepage: http://github.com/craigmj/json4lua/ +-- Version: 1.0.0 +-- This module is released under the MIT License (MIT). +-- Please see LICENCE.txt for details. +-- +-- USAGE: +-- This module exposes two functions: +-- json.encode(o) +-- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string. +-- json.decode(json_string) +-- Returns a Lua object populated with the data encoded in the JSON string json_string. +-- +-- REQUIREMENTS: +-- compat-5.1 if using Lua 5.0 +-- +-- CHANGELOG +-- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix). +-- Fixed Lua 5.1 compatibility issues. +-- Introduced json.null to have null values in associative arrays. +-- json.encode() performance improvement (more than 50%) through table.concat rather than .. +-- Introduced decode ability to ignore /**/ comments in the JSON string. +-- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays. +----------------------------------------------------------------------------- + +function tools.createJson() + ----------------------------------------------------------------------------- + -- Imports and dependencies + ----------------------------------------------------------------------------- + local math = require('math') + local string = require("string") + local table = require("table") + + ----------------------------------------------------------------------------- + -- Module declaration + ----------------------------------------------------------------------------- + local json = {} -- Public namespace + local json_private = {} -- Private namespace + + -- Public constants + json.EMPTY_ARRAY={} + json.EMPTY_OBJECT={} + + -- Public functions + + -- Private functions + local decode_scanArray + local decode_scanComment + local decode_scanConstant + local decode_scanNumber + local decode_scanObject + local decode_scanString + local decode_scanWhitespace + local encodeString + local isArray + local isEncodable + + ----------------------------------------------------------------------------- + -- PUBLIC FUNCTIONS + ----------------------------------------------------------------------------- + --- Encodes an arbitrary Lua object / variable. + -- @param v The Lua object / variable to be JSON encoded. + -- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode) + function json.encode (v) + -- Handle nil values + if v==nil then + return "null" + end + + local vtype = type(v) + + -- Handle strings + if vtype=='string' then + return '"' .. json_private.encodeString(v) .. '"' -- Need to handle encoding in string + end + + -- Handle booleans + if vtype=='number' or vtype=='boolean' then + return tostring(v) + end + + -- Handle tables + if vtype=='table' then + local rval = {} + -- Consider arrays separately + local bArray, maxCount = isArray(v) + if bArray then + for i = 1,maxCount do + table.insert(rval, json.encode(v[i])) + end + else -- An object, not an array + for i,j in pairs(v) do + if isEncodable(i) and isEncodable(j) then + table.insert(rval, '"' .. json_private.encodeString(i) .. '":' .. json.encode(j)) + end + end + end + if bArray then + return '[' .. table.concat(rval,',') ..']' + else + return '{' .. table.concat(rval,',') .. '}' + end + end + + -- Handle null values + if vtype=='function' and v==json.null then + return 'null' + end + + assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. tostring(v)) + end + + + --- Decodes a JSON string and returns the decoded value as a Lua data structure / value. + -- @param s The string to scan. + -- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1. + -- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil, + -- and the position of the first character after + -- the scanned JSON object. + function json.decode(s, startPos) + startPos = startPos and startPos or 1 + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') + local curChar = string.sub(s,startPos,startPos) + -- Object + if curChar=='{' then + return decode_scanObject(s,startPos) + end + -- Array + if curChar=='[' then + return decode_scanArray(s,startPos) + end + -- Number + if string.find("+-0123456789.e", curChar, 1, true) then + return decode_scanNumber(s,startPos) + end + -- String + if curChar==[["]] or curChar==[[']] then + return decode_scanString(s,startPos) + end + if string.sub(s,startPos,startPos+1)=='/*' then + return json.decode(s, decode_scanComment(s,startPos)) + end + -- Otherwise, it must be a constant + return decode_scanConstant(s,startPos) + end + + --- The null function allows one to specify a null value in an associative array (which is otherwise + -- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null } + function json.null() + return json.null -- so json.null() will also return null ;-) + end + ----------------------------------------------------------------------------- + -- Internal, PRIVATE functions. + -- Following a Python-like convention, I have prefixed all these 'PRIVATE' + -- functions with an underscore. + ----------------------------------------------------------------------------- + + --- Scans an array from JSON into a Lua object + -- startPos begins at the start of the array. + -- Returns the array and the next starting position + -- @param s The string being scanned. + -- @param startPos The starting position for the scan. + -- @return table, int The scanned array as a table, and the position of the next character to scan. + function decode_scanArray(s,startPos) + local array = {} -- The return value + local stringLen = string.len(s) + assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) + startPos = startPos + 1 + -- Infinite loop for array elements + local index = 1 + repeat + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') + local curChar = string.sub(s,startPos,startPos) + if (curChar==']') then + return array, startPos+1 + end + if (curChar==',') then + startPos = decode_scanWhitespace(s,startPos+1) + end + assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') + local object + object, startPos = json.decode(s,startPos) + array[index] = object + index = index + 1 + until false + end + + --- Scans a comment and discards the comment. + -- Returns the position of the next character following the comment. + -- @param string s The JSON string to scan. + -- @param int startPos The starting position of the comment + function decode_scanComment(s, startPos) + assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) + local endPos = string.find(s,'*/',startPos+2) + assert(endPos~=nil, "Unterminated comment in string at " .. startPos) + return endPos+2 + end + + --- Scans for given constants: true, false or null + -- Returns the appropriate Lua type, and the position of the next character to read. + -- @param s The string being scanned. + -- @param startPos The position in the string at which to start scanning. + -- @return object, int The object (true, false or nil) and the position at which the next character should be + -- scanned. + function decode_scanConstant(s, startPos) + local consts = { ["true"] = true, ["false"] = false, ["null"] = nil } + local constNames = {"true","false","null"} + + for i,k in pairs(constNames) do + if string.sub(s,startPos, startPos + string.len(k) -1 )==k then + return consts[k], startPos + string.len(k) + end + end + assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) + end + + --- Scans a number from the JSON encoded string. + -- (in fact, also is able to scan numeric +- eqns, which is not + -- in the JSON spec.) + -- Returns the number, and the position of the next character + -- after the number. + -- @param s The string being scanned. + -- @param startPos The position at which to start scanning. + -- @return number, int The extracted number and the position of the next character to scan. + function decode_scanNumber(s,startPos) + local endPos = startPos+1 + local stringLen = string.len(s) + local acceptableChars = "+-0123456789.e" + while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) + and endPos<=stringLen + ) do + endPos = endPos + 1 + end + -- local stringValue = 'return ' .. string.sub(s, startPos, endPos - 1) + -- local stringEval = loadstring(stringValue) + -- assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) + local numberValue = string.sub(s, startPos, endPos - 1) + return numberValue, endPos + end + + --- Scans a JSON object into a Lua object. + -- startPos begins at the start of the object. + -- Returns the object and the next starting position. + -- @param s The string being scanned. + -- @param startPos The starting position of the scan. + -- @return table, int The scanned object as a table and the position of the next character to scan. + function decode_scanObject(s,startPos) + local object = {} + local stringLen = string.len(s) + local key, value + assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s) + startPos = startPos + 1 + repeat + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') + local curChar = string.sub(s,startPos,startPos) + if (curChar=='}') then + return object,startPos+1 + end + if (curChar==',') then + startPos = decode_scanWhitespace(s,startPos+1) + end + assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') + -- Scan the key + key, startPos = json.decode(s,startPos) + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) + startPos = decode_scanWhitespace(s,startPos+1) + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + value, startPos = json.decode(s,startPos) + object[key]=value + until false -- infinite loop while key-value pairs are found + end + + -- START SoniEx2 + -- Initialize some things used by decode_scanString + -- You know, for efficiency + local escapeSequences = { + ["\\t"] = "\t", + ["\\f"] = "\f", + ["\\r"] = "\r", + ["\\n"] = "\n", + ["\\b"] = "\b" + } + setmetatable(escapeSequences, {__index = function(t,k) + -- skip "\" aka strip escape + return string.sub(k,2) + end}) + -- END SoniEx2 + + --- Scans a JSON string from the opening inverted comma or single quote to the + -- end of the string. + -- Returns the string extracted as a Lua string, + -- and the position of the next non-string character + -- (after the closing inverted comma or single quote). + -- @param s The string being scanned. + -- @param startPos The starting position of the scan. + -- @return string, int The extracted string as a Lua string, and the next character to parse. + function decode_scanString(s,startPos) + assert(startPos, 'decode_scanString(..) called without start position') + local startChar = string.sub(s,startPos,startPos) + -- START SoniEx2 + -- PS: I don't think single quotes are valid JSON + assert(startChar == [["]] or startChar == [[']],'decode_scanString called for a non-string') + --assert(startPos, "String decoding failed: missing closing " .. startChar .. " for string at position " .. oldStart) + local t = {} + local i,j = startPos,startPos + while string.find(s, startChar, j+1) ~= j+1 do + local oldj = j + i,j = string.find(s, "\\.", j+1) + local x,y = string.find(s, startChar, oldj+1) + if not i or x < i then + i,j = x,y-1 + end + table.insert(t, string.sub(s, oldj+1, i-1)) + if string.sub(s, i, j) == "\\u" then + local a = string.sub(s,j+1,j+4) + j = j + 4 + local n = tonumber(a, 16) + assert(n, "String decoding failed: bad Unicode escape " .. a .. " at position " .. i .. " : " .. j) + -- math.floor(x/2^y) == lazy right shift + -- a % 2^b == bitwise_and(a, (2^b)-1) + -- 64 = 2^6 + -- 4096 = 2^12 (or 2^6 * 2^6) + local x + if n < 0x80 then + x = string.char(n % 0x80) + elseif n < 0x800 then + -- [110x xxxx] [10xx xxxx] + x = string.char(0xC0 + (math.floor(n/64) % 0x20), 0x80 + (n % 0x40)) + else + -- [1110 xxxx] [10xx xxxx] [10xx xxxx] + x = string.char(0xE0 + (math.floor(n/4096) % 0x10), 0x80 + (math.floor(n/64) % 0x40), 0x80 + (n % 0x40)) + end + table.insert(t, x) + else + table.insert(t, escapeSequences[string.sub(s, i, j)]) + end + end + table.insert(t,string.sub(j, j+1)) + assert(string.find(s, startChar, j+1), "String decoding failed: missing closing " .. startChar .. " at position " .. j .. "(for string at position " .. startPos .. ")") + return table.concat(t,""), j+2 + -- END SoniEx2 + end + + --- Scans a JSON string skipping all whitespace from the current start position. + -- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached. + -- @param s The string being scanned + -- @param startPos The starting position where we should begin removing whitespace. + -- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string + -- was reached. + function decode_scanWhitespace(s,startPos) + local whitespace=" \n\r\t" + local stringLen = string.len(s) + while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do + startPos = startPos + 1 + end + return startPos + end + + --- Encodes a string to be JSON-compatible. + -- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-) + -- @param s The string to return as a JSON encoded (i.e. backquoted string) + -- @return The string appropriately escaped. + + local escapeList = { + ['"'] = '\\"', + ['\\'] = '\\\\', + ['/'] = '\\/', + ['\b'] = '\\b', + ['\f'] = '\\f', + ['\n'] = '\\n', + ['\r'] = '\\r', + ['\t'] = '\\t' + } + + function json_private.encodeString(s) + local s = tostring(s) + return s:gsub(".", function(c) return escapeList[c] end) -- SoniEx2: 5.0 compat + end + + -- Determines whether the given Lua type is an array or a table / dictionary. + -- We consider any table an array if it has indexes 1..n for its n items, and no + -- other data in the table. + -- I think this method is currently a little 'flaky', but can't think of a good way around it yet... + -- @param t The table to evaluate as an array + -- @return boolean, number True if the table can be represented as an array, false otherwise. If true, + -- the second returned value is the maximum + -- number of indexed elements in the array. + function isArray(t) + -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable + -- (with the possible exception of 'n') + if (t == json.EMPTY_ARRAY) then return true, 0 end + if (t == json.EMPTY_OBJECT) then return false end + + local maxIndex = 0 + for k,v in pairs(t) do + if (type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair + if (not isEncodable(v)) then return false end -- All array elements must be encodable + maxIndex = math.max(maxIndex,k) + else + if (k=='n') then + if v ~= (t.n or #t) then return false end -- False if n does not hold the number of elements + else -- Else of (k=='n') + if isEncodable(v) then return false end + end -- End of (k~='n') + end -- End of k,v not an indexed pair + end -- End of loop across all pairs + return true, maxIndex + end + + --- Determines whether the given Lua object / table / variable can be JSON encoded. The only + -- types that are JSON encodable are: string, boolean, number, nil, table and json.null. + -- In this implementation, all other types are ignored. + -- @param o The object to examine. + -- @return boolean True if the object should be JSON encoded, false if it should be ignored. + function isEncodable(o) + local t = type(o) + return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or + (t=='function' and o==json.null) + end + return json +end + +-- Sourced from http://lua-users.org/wiki/BaseSixtyFour + +-- Lua 5.1+ base64 v3.0 (c) 2009 by Alex Kloss +-- licensed under the terms of the LGPL2 + +-- character table string +local base64CharTable='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + +-- encoding +function tools.base64encode(data) + return ((data:gsub('.', function(x) + local r,b='',x:byte() + for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end + return r; + end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) + if (#x < 6) then return '' end + local c=0 + for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end + return base64CharTable:sub(c+1,c+1) + end)..({ '', '==', '=' })[#data%3+1]) +end + +-- decoding +function tools.base64decode(data) + data = string.gsub(data, '[^'..base64CharTable..'=]', '') + return (data:gsub('.', function(x) + if (x == '=') then return '' end + local r,f='',(base64CharTable:find(x)-1) + for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end + return r; + end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) + if (#x ~= 8) then return '' end + local c=0 + for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end + return string.char(c) + end)) +end + +-- tools变量 +json = tools.createJson(); --json处理 +this.printToConsole("load LuaPanda success", 1); +return this; diff --git a/lua504Test/mac/lua504 b/lua504Test/mac/lua504 new file mode 100755 index 0000000..934a81c Binary files /dev/null and b/lua504Test/mac/lua504 differ diff --git a/lua504Test/mac/socket/core.so b/lua504Test/mac/socket/core.so new file mode 100755 index 0000000..05974fd Binary files /dev/null and b/lua504Test/mac/socket/core.so differ diff --git a/lua504Test/mac/test.lua b/lua504Test/mac/test.lua new file mode 100644 index 0000000..44f2abe --- /dev/null +++ b/lua504Test/mac/test.lua @@ -0,0 +1,7 @@ +-- this is a file for test lua 504 + +require("LuaPanda").start() + +local a = 4 +local b = a + 7 +print(b) diff --git a/lua504Test/readme.md b/lua504Test/readme.md new file mode 100644 index 0000000..1f8393a --- /dev/null +++ b/lua504Test/readme.md @@ -0,0 +1,25 @@ +# Lua5.4 测试用例 + +本文件夹中提供 win / mac 下 lua 5.4 测试用例,其中的 lua 可执行程序及对应的 luasocket 使用 [Lua 5.4.1](https://www.lua.org/ftp/lua-5.4.1.tar.gz) , [luasocket](https://github.com/diegonehab/luasocket) 源码编译出的。 + +win 和 mac 都是x64版本 , 如果需要x86后续我再补充。 + + + +测试方法: + +vscode 安装好 LuaPanda 插件,把 win 或 mac 文件夹拖入 vscode 。如下图点击 Run,之后打开终端 + +windows 输入 `./lua504.exe ./test.lua` + +mac 输入`./lua504 ./test.lua` + +即可开始调试,可自行在 test.lua 加入测试代码,如有问题,欢迎 issue 反馈 + + + +![test504-1](./res/test504-1.png) + + + +注意:5.4 版本调试时请在 launch.json中关闭 useCHook 选项(本测试用例中已关闭) \ No newline at end of file diff --git a/lua504Test/res/test504-1.png b/lua504Test/res/test504-1.png new file mode 100644 index 0000000..61e2fb7 Binary files /dev/null and b/lua504Test/res/test504-1.png differ diff --git a/lua504Test/win/.vscode/launch.json b/lua504Test/win/.vscode/launch.json new file mode 100644 index 0000000..b52f05d --- /dev/null +++ b/lua504Test/win/.vscode/launch.json @@ -0,0 +1,39 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lua", + "request": "launch", + "name": "LuaPanda", + "program": "", + "cwd": "${workspaceFolder}", + "luaFileExtension": "lua", + "connectionPort": 8818, + "pathCaseSensitivity": true, + "stopOnEntry": true, + "useCHook": false, + "autoPathMode": true, + "logLevel": 1 + }, + { + "type": "lua", + "request": "launch", + "name": "LuaPanda-Attach" + }, + { + "type": "lua", + "request": "launch", + "name": "LuaPanda-DebugFile", + "luaPath": "", + "packagePath": [], + "luaFileExtension": "", + "connectionPort": 8818, + "stopOnEntry": true, + "useCHook": true, + "logLevel": 1 + } + ] +} \ No newline at end of file diff --git a/lua504Test/win/LuaPanda.lua b/lua504Test/win/LuaPanda.lua new file mode 100644 index 0000000..22dbe72 --- /dev/null +++ b/lua504Test/win/LuaPanda.lua @@ -0,0 +1,3205 @@ +--[[ +Tencent is pleased to support the open source community by making LuaPanda available. +Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +https://opensource.org/licenses/BSD-3-Clause +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +API: + LuaPanda.printToVSCode(logStr, printLevel, type) + 打印日志到VSCode Output下Debugger/log中 + @printLevel: debug(0)/info(1)/error(2) 这里的日志等级需高于launch.json中配置等级日志才能输出 (可选参数,默认0) + @type: 0:VSCode output console 1:VSCode tip (可选参数,默认0) + + LuaPanda.BP() + 强制打断点,可以在协程中使用。建议使用以下写法: + local ret = LuaPanda and LuaPanda.BP and LuaPanda.BP(); + 如果成功加入断点ret返回true,否则是nil + + LuaPanda.getInfo() + 返回获取调试器信息。包括版本号,是否使用lib库,系统是否支持loadstring(load方法)。返回值类型string, 推荐在调试控制台中使用。 + + LuaPanda.doctor() + 返回对当前环境的诊断信息,提示可能存在的问题。返回值类型string, 推荐在调试控制台中使用。 + + LuaPanda.getCWD() + 用户可以调用或在调试控制台中输出这个函数,返回帮助设置CWD的路径。比如 + cwd: F:/1/2/3/4/5 + getinfo: @../../../../../unreal_10/slua-unreal_1018/Content//Lua/TestArray.lua + format: f:/unreal_10/slua-unreal_1018/Content/Lua/TestArray.lua + cwd是vscode传来的配置路径。getinfo是通过getinfo获取到的正在运行的文件路径。format是经过 cwd + getinfo 整合后的格式化路径。 + format是传给VSCode的最终路径。 + 如果format路径和文件真实路径不符,导致VSCode找不到文件,通过调整工程中launch.json的cwd,使format路径和真实路径一致。 + 返回值类型string, 推荐在调试控制台中使用。 + + LuaPanda.getBreaks() + 获取断点信息,返回值类型string, 推荐在调试控制台中使用。 + + LuaPanda.serializeTable(table) + 把table序列化为字符串,返回值类型是string。 +]] + +--用户设置项 +local openAttachMode = true; --是否开启attach模式。attach模式开启后可以在任意时刻启动vscode连接调试。缺点是没有连接调试时也会略降低lua执行效率(会不断进行attach请求) +local attachInterval = 1; --attach间隔时间(s) +local customGetSocketInstance = nil; --支持用户实现一个自定义调用luasocket的函数,函数返回值必须是一个socket实例。例: function() return require("socket.core").tcp() end; +local consoleLogLevel = 2; --打印在控制台(print)的日志等级 0 : all/ 1: info/ 2: error. +local connectTimeoutSec = 0.005; --等待连接超时时间, 单位s. 时间过长等待attach时会造成卡顿,时间过短可能无法连接。建议值0.005 - 0.05 +--用户设置项END + +local debuggerVer = "3.1.0"; --debugger版本号 +LuaPanda = {}; +local this = LuaPanda; +local tools = {}; --引用的开源工具,包括json解析和table展开工具等 +this.tools = tools; +this.curStackId = 0; +--json处理 +local json; +--hook状态列表 +local hookState = { + DISCONNECT_HOOK = 0, --断开连接 + LITE_HOOK = 1, --全局无断点 + MID_HOOK = 2, --全局有断点,本文件无断点 + ALL_HOOK = 3, --本文件有断点 +}; +--运行状态列表 +local runState = { + DISCONNECT = 0, --未连接 + WAIT_CMD = 1, --已连接,等待命令 + STOP_ON_ENTRY = 2, --初始状态 + RUN = 3, + STEPOVER = 4, + STEPIN = 5, + STEPOUT = 6, + STEPOVER_STOP = 7, + STEPIN_STOP = 8, + STEPOUT_STOP = 9, + HIT_BREAKPOINT = 10 +}; + +local TCPSplitChar = "|*|"; --json协议分隔符,请不要修改 +local MAX_TIMEOUT_SEC = 3600 * 24; --网络最大超时等待时间 +--当前运行状态 +local currentRunState; +local currentHookState; +--断点信息 +local breaks = {}; --保存断点的数组 +this.breaks = breaks; --供hookLib调用 +local recCallbackId = ""; +--VSCode端传过来的配置,在VSCode端的launch配置,传过来并赋值 +local luaFileExtension = ""; --脚本后缀 +local cwd = ""; --工作路径 +local DebuggerFileName = ""; --Debugger文件名(原始,未经path处理), 函数中会自动获取 +local DebuggerToolsName = ""; +local lastRunFunction = {}; --上一个执行过的函数。在有些复杂场景下(find,getcomponent)一行会挺两次 +local currentCallStack = {}; --获取当前调用堆栈信息 +local hitBP = false; --BP()中的强制断点命中标记 +local TempFilePath_luaString = ""; --VSCode端配置的临时文件存放路径 +local connectHost; --记录连接端IP +local connectPort; --记录连接端口号 +local sock; --tcp socket +local OSType; --VSCode识别出的系统类型,也可以自行设置。Windows_NT | Linux | Darwin +local clibPath; --chook库在VScode端的路径,也可自行设置。 +local hookLib; --chook库的引用实例 +local adapterVer; --VScode传来的adapter版本号 +--标记位 +local logLevel = 1; --日志等级all/info/error. 此设置对应的是VSCode端设置的日志等级. +local variableRefIdx = 1; --变量索引 +local variableRefTab = {}; --变量记录table +local lastRunFilePath = ""; --最后执行的文件路径 +local pathCaseSensitivity = true; --路径是否发大小写敏感,这个选项接收VScode设置,请勿在此处更改 +local recvMsgQueue = {}; --接收的消息队列 +local coroutinePool = {}; --保存用户协程的队列 +local winDiskSymbolUpper = false;--设置win下盘符的大小写。以此确保从VSCode中传入的断点路径,cwd和从lua虚拟机获得的文件路径盘符大小写一致 +local isNeedB64EncodeStr = false;-- 记录是否使用base64编码字符串 +local loadclibErrReason = 'launch.json文件的配置项useCHook被设置为false.'; +local OSTypeErrTip = ""; +local pathErrTip = "" +local winDiskSymbolTip = ""; +local isAbsolutePath = false; +local stopOnEntry; --用户在VSCode端设置的是否打开stopOnEntry +local userSetUseClib; --用户在VSCode端设置的是否是用clib库 +local autoPathMode = false; +--Step控制标记位 +local stepOverCounter = 0; --STEPOVER over计数器 +local stepOutCounter = 0; --STEPOVER out计数器 +local HOOK_LEVEL = 3; --调用栈偏移量,使用clib时为3,lua中不再使用此变量,而是通过函数getSpecificFunctionStackLevel获取 +local isUseLoadstring = 0; +local debugger_loadString; +--临时变量 +local coroutineCreate; --用来记录lua原始的coroutine.create函数 +local stopConnectTime = 0; --用来临时记录stop断开连接的时间 +local isInMainThread; +local receiveMsgTimer = 0; +local formatPathCache = {}; -- getinfo -> format +local isUserSetClibPath = false; --用户是否在本文件中自设了clib路径 +--5.1/5.3兼容 +if _VERSION == "Lua 5.1" then + debugger_loadString = loadstring; +else + debugger_loadString = load; +end + +--用户在控制台输入信息的环境变量 +local env = setmetatable({ }, { + __index = function( _ , varName ) + local ret = this.getWatchedVariable( varName, _G.LuaPanda.curStackId , false); + return ret; + end, + + __newindex = function( _ , varName, newValue ) + this.setVariableValue( varName, _G.LuaPanda.curStackId, newValue); + end +}); + +----------------------------------------------------------------------------- +-- 流程 +----------------------------------------------------------------------------- + +-- 启动调试器 +-- @host adapter端ip, 默认127.0.0.1 +-- @port adapter端port ,默认8818 +function this.start(host, port) + host = tostring(host or "127.0.0.1") ; + port = tonumber(port) or 8818; + this.printToConsole("Debugger start. connect host:" .. host .. " port:".. tostring(port), 1); + if sock ~= nil then + this.printToConsole("[Warning] 调试器已经启动,请不要再次调用start()" , 1); + return; + end + + --尝试初次连接 + this.changeRunState(runState.DISCONNECT); + if not this.reGetSock() then + this.printToConsole("[Error] Start debugger but get Socket fail , please install luasocket!", 2); + return; + end + connectHost = host; + connectPort = port; + local sockSuccess = sock and sock:connect(connectHost, connectPort); + if sockSuccess ~= nil then + this.printToConsole("first connect success!"); + this.connectSuccess(); + else + this.printToConsole("first connect failed!"); + this.changeHookState(hookState.DISCONNECT_HOOK); + end +end + +-- 连接成功,开始初始化 +function this.connectSuccess() + this.changeRunState(runState.WAIT_CMD); + this.printToConsole("connectSuccess", 1); + --设置初始状态 + local ret = this.debugger_wait_msg(); + + --获取debugger文件路径 + if DebuggerFileName == "" then + local info = debug.getinfo(1, "S") + for k,v in pairs(info) do + if k == "source" then + DebuggerFileName = v; + this.printToVSCode("DebuggerFileName:" .. tostring(DebuggerFileName)); + + if hookLib ~= nil then + hookLib.sync_debugger_path(DebuggerFileName); + end + end + end + end + if DebuggerToolsName == "" then + DebuggerToolsName = tools.getFileSource(); + if hookLib ~= nil then + hookLib.sync_tools_path(DebuggerToolsName); + end + end + + if ret == false then + this.printToVSCode("[debugger error]初始化未完成, 建立连接但接收初始化消息失败。请更换端口重试", 2); + return; + end + this.printToVSCode("debugger init success", 1); + + this.changeHookState(hookState.ALL_HOOK); + if hookLib == nil then + --协程调试 + if coroutineCreate == nil and type(coroutine.create) == "function" then + this.printToConsole("change coroutine.create"); + coroutineCreate = coroutine.create; + coroutine.create = function(...) + local co = coroutineCreate(...) + table.insert(coroutinePool, co); + --运行状态下,创建协程即启动hook + this.changeCoroutineHookState(); + return co; + end + else + this.printToConsole("restart coroutine"); + this.changeCoroutineHookState(); + end + end + +end + +--重置数据 +function this.clearData() + OSType = nil; + clibPath = nil; + -- reset breaks + breaks = {}; + formatPathCache = {}; + this.breaks = breaks; + if hookLib ~= nil then + hookLib.sync_breakpoints(); --清空断点信息 + hookLib.clear_pathcache(); --清空路径缓存 + end +end + +--断开连接 +function this.disconnect() + this.printToConsole("Debugger disconnect", 1); + this.clearData() + this.changeHookState( hookState.DISCONNECT_HOOK ); + stopConnectTime = os.time(); + this.changeRunState(runState.DISCONNECT); + + if sock ~= nil then + sock:close(); + end + + if connectPort == nil or connectHost == nil then + --异常情况处理, 在调用LuaPanda.start()前首先调用了LuaPanda.disconnect() + this.printToConsole("[Warning] User call LuaPanda.disconnect() before set debug ip & port, please call LuaPanda.start() first!", 2); + return; + end + + this.reGetSock(); +end + +----------------------------------------------------------------------------- +-- 调试器通用方法 +----------------------------------------------------------------------------- +-- 返回断点信息 +function this.getBreaks() + return breaks; +end + +-- 返回路径相关信息 +-- cwd:配置的工程路径 | info["source"]:通过 debug.getinfo 获得执行文件的路径 | format:格式化后的文件路径 +function this.getCWD() + local ly = this.getSpecificFunctionStackLevel(lastRunFunction.func); + if type(ly) ~= "number" then + ly = 2; + end + local runSource = lastRunFunction["source"]; + if runSource == nil and hookLib ~= nil then + runSource = this.getPath(tostring(hookLib.get_last_source())); + end + local info = debug.getinfo(ly, "S"); + return "cwd: "..cwd .."\ngetinfo: ".. info["source"] .. "\nformat: " .. tostring(runSource) ; +end + +--返回版本号等配置 +function this.getBaseInfo() + local strTable = {}; + local jitVer = ""; + if jit and jit.version then + jitVer = "," .. tostring(jit.version); + end + + strTable[#strTable + 1] = "Lua Ver:" .. _VERSION .. jitVer .." | adapterVer:" .. tostring(adapterVer) .. " | Debugger Ver:" .. tostring(debuggerVer); + local moreInfoStr = ""; + if hookLib ~= nil then + local clibVer, forluaVer = hookLib.sync_getLibVersion(); + local clibStr = forluaVer ~= nil and tostring(clibVer) .. " for " .. tostring(math.ceil(forluaVer)) or tostring(clibVer); + strTable[#strTable + 1] = " | hookLib Ver:" .. clibStr; + moreInfoStr = moreInfoStr .. "说明: 已加载 libpdebug 库."; + else + moreInfoStr = moreInfoStr .. "说明: 未能加载 libpdebug 库。原因请使用 LuaPanda.doctor() 查看"; + end + + local outputIsUseLoadstring = false + if type(isUseLoadstring) == "number" and isUseLoadstring == 1 then + outputIsUseLoadstring = true; + end + + strTable[#strTable + 1] = " | supportREPL:".. tostring(outputIsUseLoadstring); + strTable[#strTable + 1] = " | useBase64EncodeString:".. tostring(isNeedB64EncodeStr); + strTable[#strTable + 1] = " | codeEnv:" .. tostring(OSType) .. '\n'; + strTable[#strTable + 1] = moreInfoStr; + if OSTypeErrTip ~= nil and OSTypeErrTip ~= '' then + strTable[#strTable + 1] = '\n' ..OSTypeErrTip; + end + return table.concat(strTable); +end + +--自动诊断当前环境的错误,并输出信息 +function this.doctor() + local strTable = {}; + if debuggerVer ~= adapterVer then + strTable[#strTable + 1] = "\n- 建议更新版本\nLuaPanda VSCode插件版本是" .. adapterVer .. ", LuaPanda.lua文件版本是" .. debuggerVer .. "。建议检查并更新到最新版本。"; + strTable[#strTable + 1] = "\n更新方式 : https://github.com/Tencent/LuaPanda/blob/master/Docs/Manual/update.md"; + strTable[#strTable + 1] = "\nRelease版本: https://github.com/Tencent/LuaPanda/releases"; + end + --plibdebug + if hookLib == nil then + strTable[#strTable + 1] = "\n\n- libpdebug 库没有加载\n"; + if userSetUseClib then + --用户允许使用clib插件 + if isUserSetClibPath == true then + --用户自设了clib地址 + strTable[#strTable + 1] = "用户使用 LuaPanda.lua 中 clibPath 变量指定了 plibdebug 的位置: " .. clibPath; + if this.tryRequireClib("libpdebug", clibPath) then + strTable[#strTable + 1] = "\n引用成功"; + else + strTable[#strTable + 1] = "\n引用错误:" .. loadclibErrReason; + end + else + --使用默认clib地址 + local clibExt, platform; + if OSType == "Darwin" then clibExt = "/?.so;"; platform = "mac"; + elseif OSType == "Linux" then clibExt = "/?.so;"; platform = "linux"; + else clibExt = "/?.dll;"; platform = "win"; end + local lua_ver; + if _VERSION == "Lua 5.1" then + lua_ver = "501"; + else + lua_ver = "503"; + end + local x86Path = clibPath .. platform .."/x86/".. lua_ver .. clibExt; + local x64Path = clibPath .. platform .."/x86_64/".. lua_ver .. clibExt; + + strTable[#strTable + 1] = "尝试引用x64库: ".. x64Path; + if this.tryRequireClib("libpdebug", x64Path) then + strTable[#strTable + 1] = "\n引用成功"; + else + strTable[#strTable + 1] = "\n引用错误:" .. loadclibErrReason; + strTable[#strTable + 1] = "\n尝试引用x86库: ".. x86Path; + if this.tryRequireClib("libpdebug", x86Path) then + strTable[#strTable + 1] = "\n引用成功"; + else + strTable[#strTable + 1] = "\n引用错误:" .. loadclibErrReason; + end + end + end + else + strTable[#strTable + 1] = "原因是" .. loadclibErrReason; + end + end + + --path + --尝试直接读当前getinfo指向的文件,看能否找到。如果能,提示正确,如果找不到,给出提示,建议玩家在这个文件中打一个断点 + --检查断点,文件和当前文件的不同,给出建议 + local runSource = lastRunFilePath; + if hookLib ~= nil then + runSource = this.getPath(tostring(hookLib.get_last_source())); + end + + -- 在精确路径模式下的路径错误检测 + if not autoPathMode and runSource and runSource ~= "" then + -- 读文件 + local isFileExist = this.fileExists(runSource); + if not isFileExist then + strTable[#strTable + 1] = "\n\n- 路径存在问题\n"; + --解析路径,得到文件名,到断点路径中查这个文件名 + local pathArray = this.stringSplit(runSource, '/'); + --如果pathArray和断点能匹配上 + local fileMatch= false; + for key, _ in pairs(this.getBreaks()) do + if string.find(key, pathArray[#pathArray], 1, true) then + --和断点匹配了 + fileMatch = true; + -- retStr = retStr .. "\n请对比如下路径:\n"; + strTable[#strTable + 1] = this.getCWD(); + strTable[#strTable + 1] = "\nfilepath: " .. key; + if isAbsolutePath then + strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的是绝对路径,format使用getinfo路径。"; + else + strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的是相对路径,调试器运行依赖的绝对路径(format)是来源于cwd+getinfo拼接。"; + end + strTable[#strTable + 1] = "\nfilepath是VSCode通过获取到的文件正确路径 , 对比format和filepath,调整launch.json中CWD,或改变VSCode打开文件夹的位置。使format和filepath一致即可。\n如果format和filepath路径仅大小写不一致,设置launch.json中 pathCaseSensitivity:false 可忽略路径大小写"; + end + end + + if fileMatch == false then + --未能和断点匹配 + strTable[#strTable + 1] = "\n找不到文件:" .. runSource .. ", 请检查路径是否正确。\n或者在VSCode文件" .. pathArray[#pathArray] .. "中打一个断点后,再执行一次doctor命令,查看路径分析结果。"; + end + end + end + + --日志等级对性能的影响 + if logLevel < 1 or consoleLogLevel < 1 then + strTable[#strTable + 1] = "\n\n- 日志等级\n"; + if logLevel < 1 then + strTable[#strTable + 1] = "当前日志等级是" .. logLevel .. ", 会产生大量日志,降低调试速度。建议调整launch.json中logLevel:1"; + end + if consoleLogLevel < 1 then + strTable[#strTable + 1] = "当前console日志等级是" .. consoleLogLevel .. ", 过低的日志等级会降低调试速度,建议调整LuaPanda.lua文件头部consoleLogLevel=2"; + end + end + + if #strTable == 0 then + strTable[#strTable + 1] = "未检测出问题"; + end + return table.concat(strTable); +end + +function this.fileExists(path) + local f=io.open(path,"r"); + if f~= nil then io.close(f) return true else return false end + end + +--返回一些信息,帮助用户定位问题 +function this.getInfo() + --用户设置项 + local strTable = {}; + strTable[#strTable + 1] = "\n- Base Info: \n"; + strTable[#strTable + 1] = this.getBaseInfo(); + --已经加载C库,x86/64 未能加载,原因 + strTable[#strTable + 1] = "\n\n- User Setting: \n"; + strTable[#strTable + 1] = "stopOnEntry:" .. tostring(stopOnEntry) .. ' | '; + -- strTable[#strTable + 1] = "luaFileExtension:" .. luaFileExtension .. ' | '; + strTable[#strTable + 1] = "logLevel:" .. logLevel .. ' | ' ; + strTable[#strTable + 1] = "consoleLogLevel:" .. consoleLogLevel .. ' | '; + strTable[#strTable + 1] = "pathCaseSensitivity:" .. tostring(pathCaseSensitivity) .. ' | '; + strTable[#strTable + 1] = "attachMode:".. tostring(openAttachMode).. ' | '; + strTable[#strTable + 1] = "autoPathMode:".. tostring(autoPathMode).. ' | '; + + if userSetUseClib then + strTable[#strTable + 1] = "useCHook:true"; + else + strTable[#strTable + 1] = "useCHook:false"; + end + + if logLevel == 0 or consoleLogLevel == 0 then + strTable[#strTable + 1] = "\n说明:日志等级过低,会影响执行效率。请调整logLevel和consoleLogLevel值 >= 1"; + end + + strTable[#strTable + 1] = "\n\n- Path Info: \n"; + strTable[#strTable + 1] = "clibPath: " .. tostring(clibPath) .. '\n'; + strTable[#strTable + 1] = "debugger: " .. this.getPath(DebuggerFileName) .. '\n'; + strTable[#strTable + 1] = this.getCWD(); + + if not autoPathMode then + if isAbsolutePath then + strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的是绝对路径,format使用getinfo路径。" .. winDiskSymbolTip; + else + strTable[#strTable + 1] = "\n说明:从lua虚拟机获取到的路径(getinfo)是相对路径,调试器运行依赖的绝对路径(format)是来源于cwd+getinfo拼接。如format路径错误请尝试调整cwd或改变VSCode打开文件夹的位置。也可以在format对应的文件下打一个断点,调整直到format和Breaks Info中断点路径完全一致。" .. winDiskSymbolTip; + end + else + strTable[#strTable + 1] = "\n说明:已开启autoPathMode自动路径模式,调试器会根据getinfo获得的文件名自动查找文件位置,请确保VSCode打开的工程中不存在同名lua文件。"; + end + + if pathErrTip ~= nil and pathErrTip ~= '' then + strTable[#strTable + 1] = '\n' .. pathErrTip; + end + + strTable[#strTable + 1] = "\n\n- Breaks Info: \n"; + strTable[#strTable + 1] = this.serializeTable(this.getBreaks(), "breaks"); + return table.concat(strTable); +end + +--判断是否在协程中 +function this.isInMain() + return isInMainThread; +end + +--添加路径,尝试引用库。完成后把cpath还原,返回引用结果true/false +-- @libName 库名 +-- path lib的cpath路径 +function this.tryRequireClib(libName , libPath) + this.printToVSCode("tryRequireClib search : [" .. libName .. "] in "..libPath); + local savedCpath = package.cpath; + package.cpath = package.cpath .. ';' .. libPath; + this.printToVSCode("package.cpath:" .. package.cpath); + local status, err = pcall(function() hookLib = require(libName) end); + if status then + if type(hookLib) == "table" and this.getTableMemberNum(hookLib) > 0 then + this.printToVSCode("tryRequireClib success : [" .. libName .. "] in "..libPath); + package.cpath = savedCpath; + return true; + else + loadclibErrReason = "tryRequireClib fail : require success, but member function num <= 0; [" .. libName .. "] in "..libPath; + this.printToVSCode(loadclibErrReason); + hookLib = nil; + package.cpath = savedCpath; + return false; + end + else + -- 此处考虑到tryRequireClib会被调用两次,日志级别设置为0,防止输出不必要的信息。 + loadclibErrReason = err; + this.printToVSCode("[Require clib error]: " .. err, 0); + end + package.cpath = savedCpath; + return false +end +------------------------字符串处理------------------------- +-- 倒序查找字符串 a.b/c查找/ , 返回4 +-- @str 被查找的长串 +-- @subPattern 查找的子串, 也可以是pattern +-- @plain plane text / pattern +-- @return 未找到目标串返回nil. 否则返回倒序找到的字串位置 +function this.revFindString(str, subPattern, plain) + local revStr = string.reverse(str); + local _, idx = string.find(revStr, subPattern, 1, plain); + if idx == nil then return nil end; + return string.len(revStr) - idx + 1; +end + +-- 反序裁剪字符串 如:print(subString("a.b/c", "/"))输出c +-- @return 未找到目标串返回nil. 否则返回被裁剪后的字符串 +function this.revSubString(str, subStr, plain) + local idx = this.revFindString(str, subStr, plain) + if idx == nil then return nil end; + return string.sub(str, idx + 1, str.length) +end + +-- 把字符串按reps分割成并放入table +-- @str 目标串 +-- @reps 分割符。注意这个分隔符是一个pattern +function this.stringSplit( str, separator ) + local retStrTable = {} + string.gsub(str, '[^' .. separator ..']+', function ( word ) + table.insert(retStrTable, word) + end) + return retStrTable; +end + +-- 保存CallbackId(通信序列号) +function this.setCallbackId( id ) + if id ~= nil and id ~= "0" then + recCallbackId = tostring(id); + end +end + +-- 读取CallbackId(通信序列号)。读取后记录值将被置空 +function this.getCallbackId() + if recCallbackId == nil then + recCallbackId = "0"; + end + local id = recCallbackId; + recCallbackId = "0"; + return id; +end + +-- reference from https://www.lua.org/pil/20.1.html +function this.trim (s) + return (string.gsub(s, "^%s*(.-)%s*$", "%1")) +end + +--返回table中成员数量(数字key和非数字key之和) +-- @t 目标table +-- @return 元素数量 +function this.getTableMemberNum(t) + local retNum = 0; + if type(t) ~= "table" then + this.printToVSCode("[debugger Error] getTableMemberNum get "..tostring(type(t)), 2) + return retNum; + end + for k,v in pairs(t) do + retNum = retNum + 1; + end + return retNum; +end + +-- 生成一个消息Table +function this.getMsgTable(cmd ,callbackId) + callbackId = callbackId or 0; + local msgTable = {}; + msgTable["cmd"] = cmd; + msgTable["callbackId"] = callbackId; + msgTable["info"] = {}; + return msgTable; +end + +function this.serializeTable(tab, name) + local sTable = tools.serializeTable(tab, name); + return sTable; +end +------------------------日志打印相关------------------------- +-- 把日志打印在VSCode端 +-- @str: 日志内容 +-- @printLevel: all(0)/info(1)/error(2) +-- @type: 0:vscode console 1:vscode tip +function this.printToVSCode(str, printLevel, type) + type = type or 0; + printLevel = printLevel or 0; + if currentRunState == runState.DISCONNECT or logLevel > printLevel then + return; + end + + local sendTab = {}; + sendTab["callbackId"] = "0"; + if type == 0 then + sendTab["cmd"] = "log"; + else + sendTab["cmd"] = "tip"; + end + sendTab["info"] = {}; + sendTab["info"]["logInfo"] = tostring(str); + this.sendMsg(sendTab); +end + +-- 把日志打印在控制台 +-- @str: 日志内容 +-- @printLevel: all(0)/info(1)/error(2) +function this.printToConsole(str, printLevel) + printLevel = printLevel or 0; + if consoleLogLevel > printLevel then + return; + end + print("[LuaPanda] ".. tostring(str)); +end + +----------------------------------------------------------------------------- +-- 提升兼容性方法 +----------------------------------------------------------------------------- +--生成平台无关的路径。 +--return:nil(error)/path +function this.genUnifiedPath(path) + if path == "" or path == nil then + return ""; + end + --大小写不敏感时,路径全部转为小写 + if pathCaseSensitivity == false then + path = string.lower(path); + end + --统一路径全部替换成/ + path = string.gsub(path, [[\]], "/"); + --处理 /../ /./ + local pathTab = this.stringSplit(path, '/'); + local newPathTab = {}; + for k, v in ipairs(pathTab) do + if v == '.' then + --continue + elseif v == ".." and #newPathTab >= 1 and newPathTab[#newPathTab]:sub(2,2) ~= ':' then + --newPathTab有元素,最后一项不是X: + table.remove(newPathTab); + else + table.insert(newPathTab, v); + end + end + --重新拼合后如果是mac路径第一位是/ + local newpath = table.concat(newPathTab, '/'); + if path:sub(1,1) == '/' then + newpath = '/'.. newpath; + end + + --win下按照winDiskSymbolUpper的设置修改盘符大小 + if "Windows_NT" == OSType then + if winDiskSymbolUpper then + newpath = newpath:gsub("^%a:", string.upper); + winDiskSymbolTip = "路径中Windows盘符已转为大写。" + else + newpath = newpath:gsub("^%a:", string.lower); + winDiskSymbolTip = "路径中Windows盘符已转为小写。" + end + end + + return newpath; +end + +function this.getCacheFormatPath(source) + if source == nil then return formatPathCache end; + return formatPathCache[source]; +end + +function this.setCacheFormatPath(source, dest) + formatPathCache[source] = dest; +end +----------------------------------------------------------------------------- +-- 内存相关 +----------------------------------------------------------------------------- +function this.sendLuaMemory() + local luaMem = collectgarbage("count"); + local sendTab = {}; + sendTab["callbackId"] = "0"; + sendTab["cmd"] = "refreshLuaMemory"; + sendTab["info"] = {}; + sendTab["info"]["memInfo"] = tostring(luaMem); + this.sendMsg(sendTab); +end + +----------------------------------------------------------------------------- +-- 网络相关方法 +----------------------------------------------------------------------------- +-- 刷新socket +-- @return true/false 刷新成功/失败 +function this.reGetSock() + if sock ~= nil then + pcall(function() sock:close() end); + end + --call ue4 luasocket + sock = lua_extension and lua_extension.luasocket and lua_extension.luasocket().tcp(); + if sock == nil then + --call u3d luasocket + if pcall(function() sock = require("socket.core").tcp(); end) then + this.printToConsole("reGetSock success"); + sock:settimeout(connectTimeoutSec); + else + --call custom function to get socket + if customGetSocketInstance and pcall( function() sock = customGetSocketInstance(); end ) then + this.printToConsole("reGetSock custom success"); + sock:settimeout(connectTimeoutSec); + else + this.printToConsole("[Error] reGetSock fail", 2); + return false; + end + end + else + --set ue4 luasocket + this.printToConsole("reGetSock ue4 success"); + sock:settimeout(connectTimeoutSec); + end + return true; +end + +-- 定时(以函数return为时机) 进行attach连接 +function this.reConnect() + if currentHookState == hookState.DISCONNECT_HOOK then + if os.time() - stopConnectTime < attachInterval then + this.printToConsole("Reconnect time less than 1s"); + this.printToConsole("os.time:".. os.time() .. " | stopConnectTime:" ..stopConnectTime); + return 1; + end + + if sock == nil then + this.reGetSock(); + end + + local sockSuccess, status = sock:connect(connectHost, connectPort); + if sockSuccess == 1 or status == "already connected" then + this.printToConsole("reconnect success"); + this.connectSuccess(); + else + this.printToConsole("reconnect failed . retCode:" .. tostring(sockSuccess) .. " status:" .. status); + stopConnectTime = os.time(); + end + return 1; + end + return 0; +end + +-- 向adapter发消息 +-- @sendTab 消息体table +function this.sendMsg( sendTab ) + if isNeedB64EncodeStr and sendTab["info"] ~= nil then + for _, v in ipairs(sendTab["info"]) do + if v["type"] == "string" then + v["value"] = tools.base64encode(v["value"]) + end + end + end + + local sendStr = json.encode(sendTab); + if currentRunState == runState.DISCONNECT then + this.printToConsole("[debugger error] disconnect but want sendMsg:" .. sendStr, 2); + this.disconnect(); + return; + end + + local succ,err; + if pcall(function() succ,err = sock:send(sendStr..TCPSplitChar.."\n"); end) then + if succ == nil then + if err == "closed" then + this.disconnect(); + end + end + end +end + +-- 处理 收到的消息 +-- @dataStr 接收的消息json +function this.dataProcess( dataStr ) + this.printToVSCode("debugger get:"..dataStr); + local dataTable = json.decode(dataStr); + if dataTable == nil then + this.printToVSCode("[error] Json is error", 2); + return; + end + + if dataTable.callbackId ~= "0" then + this.setCallbackId(dataTable.callbackId); + end + + if dataTable.cmd == "continue" then + this.changeRunState(runState.RUN); + local msgTab = this.getMsgTable("continue", this.getCallbackId()); + this.sendMsg(msgTab); + + elseif dataTable.cmd == "stopOnStep" then + this.changeRunState(runState.STEPOVER); + local msgTab = this.getMsgTable("stopOnStep", this.getCallbackId()); + this.sendMsg(msgTab); + this.changeHookState(hookState.ALL_HOOK); + + elseif dataTable.cmd == "stopOnStepIn" then + this.changeRunState(runState.STEPIN); + local msgTab = this.getMsgTable("stopOnStepIn", this.getCallbackId()); + this.sendMsg(msgTab); + this.changeHookState(hookState.ALL_HOOK); + + elseif dataTable.cmd == "stopOnStepOut" then + this.changeRunState(runState.STEPOUT); + local msgTab = this.getMsgTable("stopOnStepOut", this.getCallbackId()); + this.sendMsg(msgTab); + this.changeHookState(hookState.ALL_HOOK); + + elseif dataTable.cmd == "setBreakPoint" then + this.printToVSCode("dataTable.cmd == setBreakPoint"); + local bkPath = dataTable.info.path; + bkPath = this.genUnifiedPath(bkPath); + if autoPathMode then + -- 自动路径模式下,仅保留文件名 + bkPath = this.getFilenameFromPath(bkPath); + end + this.printToVSCode("setBreakPoint path:"..tostring(bkPath)); + breaks[bkPath] = dataTable.info.bks; + + -- 当v为空时,从断点列表中去除文件 + for k, v in pairs(breaks) do + if next(v) == nil then + breaks[k] = nil; + end + end + + --sync breaks to c + if hookLib ~= nil then + hookLib.sync_breakpoints(); + end + + if currentRunState ~= runState.WAIT_CMD then + if hookLib == nil then + local fileBP, G_BP =this.checkHasBreakpoint(lastRunFilePath); + if fileBP == false then + if G_BP == true then + this.changeHookState(hookState.MID_HOOK); + else + this.changeHookState(hookState.LITE_HOOK); + end + else + this.changeHookState(hookState.ALL_HOOK); + end + end + else + local msgTab = this.getMsgTable("setBreakPoint", this.getCallbackId()); + this.sendMsg(msgTab); + return; + end + --其他时机收到breaks消息 + local msgTab = this.getMsgTable("setBreakPoint", this.getCallbackId()); + this.sendMsg(msgTab); + -- 打印调试信息 + this.printToVSCode("LuaPanda.getInfo()\n" .. this.getInfo()) + this.debugger_wait_msg(); + elseif dataTable.cmd == "setVariable" then + if currentRunState == runState.STOP_ON_ENTRY or + currentRunState == runState.HIT_BREAKPOINT or + currentRunState == runState.STEPOVER_STOP or + currentRunState == runState.STEPIN_STOP or + currentRunState == runState.STEPOUT_STOP then + local msgTab = this.getMsgTable("setVariable", this.getCallbackId()); + local varRefNum = tonumber(dataTable.info.varRef); + local newValue = tostring(dataTable.info.newValue); + local needFindVariable = true; --如果变量是基础类型,直接赋值,needFindVariable = false; 如果变量是引用类型,needFindVariable = true + local varName = tostring(dataTable.info.varName); + -- 根据首末含有" ' 判断 newValue 是否是字符串 + local first_chr = string.sub(newValue, 1, 1); + local end_chr = string.sub(newValue, -1, -1); + if first_chr == end_chr then + if first_chr == "'" or first_chr == '"' then + newValue = string.sub(newValue, 2, -2); + needFindVariable = false; + end + end + --数字,nil,false,true的处理 + if newValue == "nil" and needFindVariable == true then newValue = nil; needFindVariable = false; + elseif newValue == "true" and needFindVariable == true then newValue = true; needFindVariable = false; + elseif newValue == "false" and needFindVariable == true then newValue = false; needFindVariable = false; + elseif tonumber(newValue) and needFindVariable == true then newValue = tonumber(newValue); needFindVariable = false; + end + + -- 如果新值是基础类型,则不需遍历 + if dataTable.info.stackId ~= nil and tonumber(dataTable.info.stackId) ~= nil and tonumber(dataTable.info.stackId) > 1 then + this.curStackId = tonumber(dataTable.info.stackId); + else + this.printToVSCode("未能获取到堆栈层级,默认使用 this.curStackId;") + end + + if varRefNum < 10000 then + -- 如果修改的是一个 引用变量,那么可直接赋值。但还是要走变量查询过程。查找和赋值过程都需要steakId。 目前给引用变量赋值Object,steak可能有问题 + msgTab.info = this.createSetValueRetTable(varName, newValue, needFindVariable, this.curStackId, variableRefTab[varRefNum]); + else + -- 如果修改的是一个基础类型 + local setLimit; --设置检索变量的限定区域 + if varRefNum >= 10000 and varRefNum < 20000 then setLimit = "local"; + elseif varRefNum >= 20000 and varRefNum < 30000 then setLimit = "global"; + elseif varRefNum >= 30000 then setLimit = "upvalue"; + end + msgTab.info = this.createSetValueRetTable(varName, newValue, needFindVariable, this.curStackId, nil, setLimit); + end + + this.sendMsg(msgTab); + this.debugger_wait_msg(); + end + + elseif dataTable.cmd == "getVariable" then + --仅在停止时处理消息,其他时刻收到此消息,丢弃 + if currentRunState == runState.STOP_ON_ENTRY or + currentRunState == runState.HIT_BREAKPOINT or + currentRunState == runState.STEPOVER_STOP or + currentRunState == runState.STEPIN_STOP or + currentRunState == runState.STEPOUT_STOP then + --发送变量给游戏,并保持之前的状态,等待再次接收数据 + --dataTable.info.varRef 10000~20000局部变量 + -- 20000~30000全局变量 + -- 30000~ upvalue + -- 1000~2000局部变量的查询,2000~3000全局,3000~4000upvalue + local msgTab = this.getMsgTable("getVariable", this.getCallbackId()); + local varRefNum = tonumber(dataTable.info.varRef); + if varRefNum < 10000 then + --查询变量, 此时忽略 stackId + local varTable = this.getVariableRef(dataTable.info.varRef, true); + msgTab.info = varTable; + elseif varRefNum >= 10000 and varRefNum < 20000 then + --局部变量 + if dataTable.info.stackId ~= nil and tonumber(dataTable.info.stackId) > 1 then + this.curStackId = tonumber(dataTable.info.stackId); + if type(currentCallStack[this.curStackId - 1]) ~= "table" or type(currentCallStack[this.curStackId - 1].func) ~= "function" then + local str = "getVariable getLocal currentCallStack " .. this.curStackId - 1 .. " Error\n" .. this.serializeTable(currentCallStack, "currentCallStack"); + this.printToVSCode(str, 2); + msgTab.info = {}; + else + local stackId = this.getSpecificFunctionStackLevel(currentCallStack[this.curStackId - 1].func); --去除偏移量 + local varTable = this.getVariable(stackId, true); + msgTab.info = varTable; + end + end + + elseif varRefNum >= 20000 and varRefNum < 30000 then + --全局变量 + local varTable = this.getGlobalVariable(); + msgTab.info = varTable; + elseif varRefNum >= 30000 then + --upValue + if dataTable.info.stackId ~= nil and tonumber(dataTable.info.stackId) > 1 then + this.curStackId = tonumber(dataTable.info.stackId); + if type(currentCallStack[this.curStackId - 1]) ~= "table" or type(currentCallStack[this.curStackId - 1].func) ~= "function" then + local str = "getVariable getUpvalue currentCallStack " .. this.curStackId - 1 .. " Error\n" .. this.serializeTable(currentCallStack, "currentCallStack"); + this.printToVSCode(str, 2); + msgTab.info = {}; + else + local varTable = this.getUpValueVariable(currentCallStack[this.curStackId - 1 ].func, true); + msgTab.info = varTable; + end + end + end + this.sendMsg(msgTab); + this.debugger_wait_msg(); + end + elseif dataTable.cmd == "initSuccess" then + --初始化会传过来一些变量,这里记录这些变量 + --Base64 + if dataTable.info.isNeedB64EncodeStr == "true" then + isNeedB64EncodeStr = true; + else + isNeedB64EncodeStr = false; + end + --path + luaFileExtension = dataTable.info.luaFileExtension + local TempFilePath = dataTable.info.TempFilePath; + if TempFilePath:sub(-1, -1) == [[\]] or TempFilePath:sub(-1, -1) == [[/]] then + TempFilePath = TempFilePath:sub(1, -2); + end + TempFilePath_luaString = TempFilePath; + cwd = this.genUnifiedPath(dataTable.info.cwd); + --logLevel + logLevel = tonumber(dataTable.info.logLevel) or 1; + --autoPathMode + if dataTable.info.autoPathMode == "true" then + autoPathMode = true; + else + autoPathMode = false; + end + + if dataTable.info.pathCaseSensitivity == "true" then + pathCaseSensitivity = true; + else + pathCaseSensitivity = false; + end + + --OS type + if nil == OSType then + --用户未主动设置OSType, 接收VSCode传来的数据 + if type(dataTable.info.OSType) == "string" then + OSType = dataTable.info.OSType; + else + OSType = "Windows_NT"; + OSTypeErrTip = "未能检测出OSType, 可能是node os库未能加载,系统使用默认设置Windows_NT" + end + else + --用户自设OSType, 使用用户的设置 + end + + --检测用户是否自设了clib路径 + isUserSetClibPath = false; + if nil == clibPath then + --用户未设置clibPath, 接收VSCode传来的数据 + if type(dataTable.info.clibPath) == "string" then + clibPath = dataTable.info.clibPath; + else + clibPath = ""; + pathErrTip = "未能正确获取libpdebug库所在位置, 可能无法加载libpdebug库。"; + end + else + --用户自设clibPath + isUserSetClibPath = true; + end + + --查找c++的hook库是否存在 + if tostring(dataTable.info.useCHook) == "true" then + userSetUseClib = true; --用户确定使用clib + if isUserSetClibPath == true then --如果用户自设了clib路径 + if luapanda_chook ~= nil then + hookLib = luapanda_chook; + else + if not(this.tryRequireClib("libpdebug", clibPath)) then + this.printToVSCode("Require clib failed, use Lua to continue debug, use LuaPanda.doctor() for more information.", 1); + end + end + else + local clibExt, platform; + if OSType == "Darwin" then clibExt = "/?.so;"; platform = "mac"; + elseif OSType == "Linux" then clibExt = "/?.so;"; platform = "linux"; + else clibExt = "/?.dll;"; platform = "win"; end + + local lua_ver; + if _VERSION == "Lua 5.1" then + lua_ver = "501"; + else + lua_ver = "503"; + end + + local x86Path = clibPath.. platform .."/x86/".. lua_ver .. clibExt; + local x64Path = clibPath.. platform .."/x86_64/".. lua_ver .. clibExt; + + if luapanda_chook ~= nil then + hookLib = luapanda_chook; + else + if not(this.tryRequireClib("libpdebug", x64Path) or this.tryRequireClib("libpdebug", x86Path)) then + this.printToVSCode("Require clib failed, use Lua to continue debug, use LuaPanda.doctor() for more information.", 1); + end + end + end + else + userSetUseClib = false; + end + + --adapter版本信息 + adapterVer = tostring(dataTable.info.adapterVersion); + local msgTab = this.getMsgTable("initSuccess", this.getCallbackId()); + --回传是否使用了lib,是否有loadstring函数 + local isUseHookLib = 0; + if hookLib ~= nil then + isUseHookLib = 1; + --同步数据给c hook + -- hookLib.sync_config(logLevel, pathCaseSensitivity and 1 or 0, autoPathMode and 1 or 0); + hookLib.sync_config(logLevel, pathCaseSensitivity and 1 or 0); + hookLib.sync_tempfile_path(TempFilePath_luaString); + hookLib.sync_cwd(cwd); + hookLib.sync_file_ext(luaFileExtension); + end + --detect LoadString + isUseLoadstring = 0; + if debugger_loadString ~= nil and type(debugger_loadString) == "function" then + if(pcall(debugger_loadString("return 0"))) then + isUseLoadstring = 1; + end + end + local tab = { debuggerVer = tostring(debuggerVer) , UseHookLib = tostring(isUseHookLib) , UseLoadstring = tostring(isUseLoadstring), isNeedB64EncodeStr = tostring(isNeedB64EncodeStr) }; + msgTab.info = tab; + this.sendMsg(msgTab); + --上面getBK中会判断当前状态是否WAIT_CMD, 所以最后再切换状态。 + stopOnEntry = dataTable.info.stopOnEntry; + if dataTable.info.stopOnEntry == "true" then + this.changeRunState(runState.STOP_ON_ENTRY); --停止在STOP_ON_ENTRY再接收breaks消息 + else + this.debugger_wait_msg(1); --等待1s bk消息 如果收到或超时(没有断点)就开始运行 + this.changeRunState(runState.RUN); + end + + elseif dataTable.cmd == "getWatchedVariable" then + local msgTab = this.getMsgTable("getWatchedVariable", this.getCallbackId()); + local stackId = tonumber(dataTable.info.stackId); + --loadstring系统函数, watch插件加载 + if isUseLoadstring == 1 then + --使用loadstring + this.curStackId = stackId; + local retValue = this.processWatchedExp(dataTable.info); + msgTab.info = retValue + this.sendMsg(msgTab); + this.debugger_wait_msg(); + return; + else + --旧的查找方式 + local wv = this.getWatchedVariable(dataTable.info.varName, stackId, true); + if wv ~= nil then + msgTab.info = wv; + end + this.sendMsg(msgTab); + this.debugger_wait_msg(); + end + elseif dataTable.cmd == "stopRun" then + --停止hook,已不在处理任何断点信息,也就不会产生日志等。发送消息后等待前端主动断开连接 + local msgTab = this.getMsgTable("stopRun", this.getCallbackId()); + this.sendMsg(msgTab); + this.disconnect(); + elseif "LuaGarbageCollect" == dataTable.cmd then + this.printToVSCode("collect garbage!"); + collectgarbage("collect"); + --回收后刷一下内存 + this.sendLuaMemory(); + this.debugger_wait_msg(); + elseif "runREPLExpression" == dataTable.cmd then + this.curStackId = tonumber(dataTable.info.stackId); + local retValue = this.processExp(dataTable.info); + local msgTab = this.getMsgTable("runREPLExpression", this.getCallbackId()); + msgTab.info = retValue + this.sendMsg(msgTab); + this.debugger_wait_msg(); + else + end +end + +-- 变量赋值的处理函数。基本逻辑是先从当前栈帧(curStackId)中取 newValue 代表的变量,找到之后再把找到的值通过setVariableValue写回。 +-- @varName 被设置值的变量名 +-- @newValue 新值的名字,它是一个string +-- @needFindVariable 是否需要查找引用变量。(用户输入的是否是一个Object) +-- @curStackId 当前栈帧(查找和变量赋值用) +-- @assigndVar 被直接赋值(省去查找过程) +-- @setLimit 赋值时的限制范围(local upvalue global) +function this.createSetValueRetTable(varName, newValue, needFindVariable, curStackId, assigndVar , setLimit) + local info; + local getVarRet; + -- needFindVariable == true,则使用getWatchedVariable处理(可选, 用来支持 a = b (b为变量的情况))。 + if needFindVariable == false then + getVarRet = {}; + getVarRet[1] = {variablesReference = 0, value = newValue, name = varName, type = type(newValue)}; + else + getVarRet = this.getWatchedVariable( tostring(newValue), curStackId, true); + end + if getVarRet ~= nil then + -- newValue赋变量真实值 + local realVarValue; + local displayVarValue = getVarRet[1].value; + if needFindVariable == true then + if tonumber(getVarRet[1].variablesReference) > 0 then + realVarValue = variableRefTab[tonumber(getVarRet[1].variablesReference)]; + else + if getVarRet[1].type == 'number' then realVarValue = tonumber(getVarRet[1].value) end + if getVarRet[1].type == 'string' then + realVarValue = tostring(getVarRet[1].value); + local first_chr = string.sub(realVarValue, 1, 1); + local end_chr = string.sub(realVarValue, -1, -1); + if first_chr == end_chr then + if first_chr == "'" or first_chr == '"' then + realVarValue = string.sub(realVarValue, 2, -2); + displayVarValue = realVarValue; + end + end + end + if getVarRet[1].type == 'boolean' then + if getVarRet[1].value == "true" then + realVarValue = true; + else + realVarValue = false; + end + end + if getVarRet[1].type == 'nil' then realVarValue = nil end + end + else + realVarValue = getVarRet[1].value; + end + + local setVarRet; + if type(assigndVar) ~= table then + setVarRet = this.setVariableValue( varName, curStackId, realVarValue, setLimit ); + else + assigndVar[varName] = realVarValue; + setVarRet = true; + end + + if getVarRet[1].type == "string" then + displayVarValue = '"' .. displayVarValue .. '"'; + end + + if setVarRet ~= false and setVarRet ~= nil then + local retTip = "变量 ".. varName .." 赋值成功"; + info = { success = "true", name = getVarRet[1].name , type = getVarRet[1].type , value = displayVarValue, variablesReference = tostring(getVarRet[1].variablesReference), tip = retTip}; + else + info = { success = "false", type = type(realVarValue), value = displayVarValue, tip = "找不到要设置的变量"}; + end + + else + info = { success = "false", type = nil, value = nil, tip = "输入的值无意义"}; + end + return info +end + +--接收消息 +--这里维护一个接收消息队列,因为Lua端未做隔断符保护,变量赋值时请注意其中不要包含隔断符 |*| +-- @timeoutSec 超时时间 +-- @return boolean 成功/失败 +function this.receiveMessage( timeoutSec ) + timeoutSec = timeoutSec or MAX_TIMEOUT_SEC; + sock:settimeout(timeoutSec); + --如果队列中还有消息,直接取出来交给dataProcess处理 + if #recvMsgQueue > 0 then + local saved_cmd = recvMsgQueue[1]; + table.remove(recvMsgQueue, 1); + this.dataProcess(saved_cmd); + return true; + end + + if currentRunState == runState.DISCONNECT then + this.disconnect(); + return false; + end + + if sock == nil then + this.printToConsole("[debugger error]接收信息失败 | reason: socket == nil", 2); + return; + end + local response, err = sock:receive(); + if response == nil then + if err == "closed" then + this.printToConsole("[debugger error]接收信息失败 | reason:"..err, 2); + this.disconnect(); + end + return false; + else + + --判断是否是一条消息,分拆 + local proc_response = string.sub(response, 1, -1 * (TCPSplitChar:len() + 1 )); + local match_res = string.find(proc_response, TCPSplitChar, 1, true); + if match_res == nil then + --单条 + this.dataProcess(proc_response); + else + --有粘包 + repeat + --待处理命令 + local str1 = string.sub(proc_response, 1, match_res - 1); + table.insert(recvMsgQueue, str1); + --剩余匹配 + local str2 = string.sub(proc_response, match_res + TCPSplitChar:len() , -1); + match_res = string.find(str2, TCPSplitChar, 1, true); + until not match_res + this.receiveMessage(); + end + return true; + end +end + +--这里不用循环,在外面处理完消息会在调用回来 +-- @timeoutSec 等待时间s +-- @entryFlag 入口标记,用来标识是从哪里调入的 +function this.debugger_wait_msg(timeoutSec) + timeoutSec = timeoutSec or MAX_TIMEOUT_SEC; + + if currentRunState == runState.WAIT_CMD then + local ret = this.receiveMessage(timeoutSec); + return ret; + end + + if currentRunState == runState.STEPOVER or + currentRunState == runState.STEPIN or + currentRunState == runState.STEPOUT or + currentRunState == runState.RUN then + this.receiveMessage(0); + return + end + + if currentRunState == runState.STEPOVER_STOP or + currentRunState == runState.STEPIN_STOP or + currentRunState == runState.STEPOUT_STOP or + currentRunState == runState.HIT_BREAKPOINT or + currentRunState == runState.STOP_ON_ENTRY + then + this.sendLuaMemory(); + this.receiveMessage(MAX_TIMEOUT_SEC); + return + end +end + +----------------------------------------------------------------------------- +-- 调试器核心方法 +----------------------------------------------------------------------------- + +------------------------堆栈管理------------------------- + + +--getStackTable需要建立stackTable,保存每层的lua函数实例(用来取upvalue),保存函数展示层级和ly的关系(便于根据前端传来的stackId查局部变量) +-- @level 要获取的层级 +function this.getStackTable( level ) + local functionLevel = 0 + if hookLib ~= nil then + functionLevel = level or HOOK_LEVEL; + else + functionLevel = level or this.getSpecificFunctionStackLevel(lastRunFunction.func); + end + local stackTab = {}; + local userFuncSteakLevel = 0; --用户函数的steaklevel + repeat + local info = debug.getinfo(functionLevel, "SlLnf") + if info == nil then + break; + end + if info.source == "=[C]" then + break; + end + + local ss = {}; + ss.file = this.getPath(info); + ss.name = "文件名"; --这里要做截取 + ss.line = tostring(info.currentline); + --使用hookLib时,堆栈有偏移量,这里统一调用栈顶编号2 + local ssindex = functionLevel - 3; + if hookLib ~= nil then + ssindex = ssindex + 2; + end + ss.index = tostring(ssindex); + table.insert(stackTab,ss); + --把数据存入currentCallStack + local callStackInfo = {}; + callStackInfo.name = ss.file; + callStackInfo.line = ss.line; + callStackInfo.func = info.func; --保存的function + callStackInfo.realLy = functionLevel; --真实堆栈层functionLevel(仅debug时用) + table.insert(currentCallStack, callStackInfo); + + --level赋值 + if userFuncSteakLevel == 0 then + userFuncSteakLevel = functionLevel; + end + functionLevel = functionLevel + 1; + until info == nil + return stackTab, userFuncSteakLevel; +end + +--这个方法是根据工程中的cwd和luaFileExtension修改 +-- @info getInfo获取的包含调用信息table +function this.getPath( info ) + local filePath = info; + if type(info) == "table" then + filePath = info.source; + end + --尝试从Cache中获取路径 + local cachePath = this.getCacheFormatPath(filePath); + if cachePath~= nil and type(cachePath) == "string" then + return cachePath; + end + + -- originalPath是getInfo的原始路径,后面用来填充缓存key + local originalPath = filePath; + + --后缀处理 + if luaFileExtension ~= "" then + --判断后缀中是否包含%1等魔法字符.用于从lua虚拟机获取到的路径含.的情况 + if string.find(luaFileExtension, "%%%d") then + filePath = string.gsub(filePath, "%.[%w%.]+$", luaFileExtension); + else + filePath = string.gsub(filePath, "%.[%w%.]+$", ""); + filePath = filePath .. "." .. luaFileExtension; + end + end + + --如果路径头部有@,去除 + if filePath:sub(1,1) == '@' then + filePath = filePath:sub(2); + end + + if not autoPathMode then + --绝对路径和相对路径的处理 | 若在Mac下以/开头,或者在Win下以*:开头,说明是绝对路径,不需要再拼。 + if filePath:sub(1,1) == [[/]] or filePath:sub(1,2):match("^%a:") then + isAbsolutePath = true; + else + isAbsolutePath = false; + if cwd ~= "" then + --查看filePath中是否包含cwd + local matchRes = string.find(filePath, cwd, 1, true); + if matchRes == nil then + filePath = cwd.."/"..filePath; + end + end + end + end + filePath = this.genUnifiedPath(filePath); + + if autoPathMode then + -- 自动路径模式下,只保留文件名 + filePath = this.getFilenameFromPath(filePath) + end + --放入Cache中缓存 + this.setCacheFormatPath(originalPath, filePath); + return filePath; +end + +--从路径中获取文件名 +function this.getFilenameFromPath(path) + if path == nil then + return ''; + end + + return string.match(path, "([^/]*)$"); +end + +--获取当前函数的堆栈层级 +--原理是向上查找,遇到DebuggerFileName就调过。但是可能存在代码段和C导致不确定性。目前使用getSpecificFunctionStackLevel代替。 +function this.getCurrentFunctionStackLevel() + -- print(debug.traceback("===getCurrentFunctionStackLevel Stack trace===")) + local funclayer = 2; + repeat + local info = debug.getinfo(funclayer, "S"); --通过name来判断 + if info ~= nil then + local matchRes = ((info.source == DebuggerFileName) or (info.source == DebuggerToolsName)); + if matchRes == false then + return (funclayer - 1); + end + end + funclayer = funclayer + 1; + until not info + return 0; +end + +--获取指定函数的堆栈层级 +--通常用来获取最后一个用户函数的层级,用法是从currentCallStack取用户点击的栈,再使用本函数取对应层级。 +-- @func 被获取层级的function +function this.getSpecificFunctionStackLevel( func ) + local funclayer = 2; + repeat + local info = debug.getinfo(funclayer, "f"); --通过name来判断 + if info ~= nil then + if info.func == func then + return (funclayer - 1); + end + end + funclayer = funclayer + 1; + until not info + return 0; +end + +--检查当前堆栈是否是Lua +-- @checkLayer 指定的栈层 +function this.checkCurrentLayerisLua( checkLayer ) + local info = debug.getinfo(checkLayer, "S"); + if info == nil then + return nil; + end + info.source = this.genUnifiedPath(info.source); + if info ~= nil then + for k,v in pairs(info) do + if k == "what" then + if v == "C" then + return false; + else + return true; + end + end + end + end + return nil; +end + + +------------------------断点处理------------------------- +-- 参数info是当前堆栈信息 +-- @info getInfo获取的当前调用信息 +function this.isHitBreakpoint( info ) + local curLine = tostring(info.currentline); + local breakpointPath = info.source; + local isPathHit = false; + + if breaks[breakpointPath] then + isPathHit = true; + end + + if isPathHit then + for k,v in ipairs(breaks[breakpointPath]) do + if tostring(v["line"]) == tostring(curLine) then + -- type是TS中的枚举类型,其定义在BreakPoint.tx文件中 + --[[ + enum BreakpointType { + conditionBreakpoint = 0, + logPoint, + lineBreakpoint + } + ]] + + if v["type"] == "0" then + -- condition breakpoint + -- 注意此处不要使用尾调用,否则会影响调用栈,导致Lua5.3和Lua5.1中调用栈层级不同 + local conditionRet = this.IsMeetCondition(v["condition"]); + return conditionRet; + elseif v["type"] == "1" then + -- log point + this.printToVSCode("[log point output]: " .. v["logMessage"], 1); + else + -- line breakpoint + return true; + end + end + end + end + return false; +end + +-- 条件断点处理函数 +-- 返回true表示条件成立 +-- @conditionExp 条件表达式 +function this.IsMeetCondition(conditionExp) + -- 判断条件之前更新堆栈信息 + currentCallStack = {}; + variableRefTab = {}; + variableRefIdx = 1; + this.getStackTable(); + this.curStackId = 2; --在用户空间最上层执行 + + local conditionExpTable = {["varName"] = conditionExp} + local retTable = this.processWatchedExp(conditionExpTable) + + local isMeetCondition = false; + local function HandleResult() + if retTable[1]["isSuccess"] == "true" then + if retTable[1]["value"] == "nil" or (retTable[1]["value"] == "false" and retTable[1]["type"] == "boolean") then + isMeetCondition = false; + else + isMeetCondition = true; + end + else + isMeetCondition = false; + end + end + + xpcall(HandleResult, function() isMeetCondition = false; end) + return isMeetCondition; +end + +--加入断点函数 +function this.BP() + this.printToConsole("BP()"); + if hookLib == nil then + if currentHookState == hookState.DISCONNECT_HOOK then + this.printToConsole("BP() but NO HOOK"); + return; + end + + local co, isMain = coroutine.running(); + if _VERSION == "Lua 5.1" then + if co == nil then + isMain = true; + else + isMain = false; + end + end + + if isMain == true then + this.printToConsole("BP() in main"); + else + this.printToConsole("BP() in coroutine"); + debug.sethook(co, this.debug_hook, "lrc"); + end + hitBP = true; + else + if hookLib.get_libhook_state() == hookState.DISCONNECT_HOOK then + this.printToConsole("BP() but NO C HOOK"); + return; + end + + --clib, set hitBP + hookLib.sync_bp_hit(1); + end + this.changeHookState(hookState.ALL_HOOK); + return true; +end + +-- 检查当前文件中是否有断点 +-- 如果填写参数fileName 返回fileName中有无断点, 全局有无断点 +-- fileName为空,返回全局是否有断点 +function this.checkHasBreakpoint(fileName) + local hasBk = false; + --有无全局断点 + if next(breaks) == nil then + hasBk = false; + else + hasBk = true; + end + --当前文件中是否有断点 + if fileName ~= nil then + return breaks[fileName] ~= nil, hasBk; + else + return hasBk; + end +end + +function this.checkfuncHasBreakpoint(sLine, eLine, fileName) + if breaks[fileName] == nil then + return false; + end + sLine = tonumber(sLine); + eLine = tonumber(eLine); + + --起始行号>结束行号,或者sLine = eLine = 0 + if sLine >= eLine then + return true; + end + + if #breaks[fileName] <= 0 then + return false; + else + for k,v in ipairs(breaks[fileName]) do + if tonumber(v.line) > sLine and tonumber(v.line) <= eLine then + return true; + end + end + end + return false; +end +------------------------HOOK模块------------------------- +-- 钩子函数 +-- @event 执行状态(call,return,line) +-- @line 行号 +function this.debug_hook(event, line) + if this.reConnect() == 1 then return; end + + if logLevel == 0 then + local logTable = {"-----enter debug_hook-----\n", "event:", event, " line:", tostring(line), " currentHookState:",currentHookState," currentRunState:", currentRunState}; + local logString = table.concat(logTable); + this.printToVSCode(logString); + end + + --litehook 仅非阻塞接收断点 + if currentHookState == hookState.LITE_HOOK then + local ti = os.time(); + if ti - receiveMsgTimer > 1 then + this.debugger_wait_msg(0); + receiveMsgTimer = ti; + end + return; + end + + --运行中 + local info; + local co, isMain = coroutine.running(); + if _VERSION == "Lua 5.1" then + if co == nil then + isMain = true; + else + isMain = false; + end + end + isInMainThread = isMain; + if isMain == true then + info = debug.getinfo(2, "Slf") + else + info = debug.getinfo(co, 2, "Slf") + end + info.event = event; + + this.real_hook_process(info); +end + +function this.real_hook_process(info) + local jumpFlag = false; + local event = info.event; + + --如果当前行在Debugger中,不做处理 + local matchRes = ((info.source == DebuggerFileName) or (info.source == DebuggerToolsName)); + if matchRes == true then + return; + end + + --即使MID hook在C中, 或者是Run或者单步时也接收消息 + if currentRunState == runState.RUN or + currentRunState == runState.STEPOVER or + currentRunState == runState.STEPIN or + currentRunState == runState.STEPOUT then + local ti = os.time(); + if ti - receiveMsgTimer > 1 then + this.debugger_wait_msg(0); + receiveMsgTimer = ti; + end + end + + --不处理C函数 + if info.source == "=[C]" then + this.printToVSCode("current method is C"); + return; + end + + --不处理 slua "temp buffer" + if info.source == "temp buffer" then + this.printToVSCode("current method is in temp buffer"); + return; + end + + --不处理 xlua "chunk" + if info.source == "chunk" then + this.printToVSCode("current method is in chunk"); + return; + end + + --lua 代码段的处理,目前暂不调试代码段。 + if info.short_src:match("%[string \"") then + --当shortSrc中出现[string时]。要检查一下source, 区别是路径还是代码段. 方法是看路径中有没有\t \n ; + if info.source:match("[\n;=]") then + --是代码段,调过 + this.printToVSCode("hook jump Code String!"); + jumpFlag = true; + end + end + + --标准路径处理 + if jumpFlag == false then + info.source = this.getPath(info); + end + --本次执行的函数和上次执行的函数作对比,防止在一行停留两次 + if lastRunFunction["currentline"] == info["currentline"] and lastRunFunction["source"] == info["source"] and lastRunFunction["func"] == info["func"] and lastRunFunction["event"] == event then + this.printToVSCode("run twice"); + end + --记录最后一次调用信息 + if jumpFlag == false then + lastRunFunction = info; + lastRunFunction["event"] = event; + lastRunFilePath = info.source; + end + --输出函数信息到前台 + if logLevel == 0 and jumpFlag == false then + local logTable = {"[lua hook] event:", tostring(event), " currentRunState:",tostring(currentRunState)," currentHookState:",tostring(currentHookState)," jumpFlag:", tostring(jumpFlag)}; + for k,v in pairs(info) do + table.insert(logTable, tostring(k)); + table.insert(logTable, ":"); + table.insert(logTable, tostring(v)); + table.insert(logTable, " "); + end + local logString = table.concat(logTable); + this.printToVSCode(logString); + end + + --仅在line时做断点判断。进了断点之后不再进入本次STEP类型的判断,用Aflag做标记 + local isHit = false; + if tostring(event) == "line" and jumpFlag == false then + if currentRunState == runState.RUN or currentRunState == runState.STEPOVER or currentRunState == runState.STEPIN or currentRunState == runState.STEPOUT then + --断点判断 + isHit = this.isHitBreakpoint(info) or hitBP; + if isHit == true then + this.printToVSCode(" + HitBreakpoint true"); + hitBP = false; --hitBP是断点硬性命中标记 + --计数器清0 + stepOverCounter = 0; + stepOutCounter = 0; + this.changeRunState(runState.HIT_BREAKPOINT); + --发消息并等待 + this.SendMsgWithStack("stopOnBreakpoint"); + end + end + end + + if isHit == true then + return; + end + + if currentRunState == runState.STEPOVER then + -- line stepOverCounter!= 0 不作操作 + -- line stepOverCounter == 0 停止 + if event == "line" and stepOverCounter <= 0 and jumpFlag == false then + stepOverCounter = 0; + this.changeRunState(runState.STEPOVER_STOP) + this.SendMsgWithStack("stopOnStep"); + elseif event == "return" or event == "tail return" then + --5.1中是tail return + if stepOverCounter ~= 0 then + stepOverCounter = stepOverCounter - 1; + end + elseif event == "call" then + stepOverCounter = stepOverCounter + 1; + end + elseif currentRunState == runState.STOP_ON_ENTRY then + --在Lua入口点处直接停住 + if event == "line" and jumpFlag == false then + --初始化内存分析的变量 + -- MemProfiler.getSystemVar(); + --这里要判断一下是Lua的入口点,否则停到 + this.SendMsgWithStack("stopOnEntry"); + end + elseif currentRunState == runState.STEPIN then + if event == "line" and jumpFlag == false then + this.changeRunState(runState.STEPIN_STOP) + this.SendMsgWithStack("stopOnStepIn"); + end + elseif currentRunState == runState.STEPOUT then + --line 不做操作 + --in 计数器+1 + --out 计数器-1 + if jumpFlag == false then + if stepOutCounter <= -1 then + stepOutCounter = 0; + this.changeRunState(runState.STEPOUT_STOP) + this.SendMsgWithStack("stopOnStepOut"); + end + end + + if event == "return" or event == "tail return" then + stepOutCounter = stepOutCounter - 1; + elseif event == "call" then + stepOutCounter = stepOutCounter + 1; + end + end + + --在RUN时检查并改变状态 + if hookLib == nil then + if currentRunState == runState.RUN and jumpFlag == false and currentHookState ~= hookState.DISCONNECT_HOOK then + local fileBP, G_BP = this.checkHasBreakpoint(lastRunFilePath); + if fileBP == false then + --文件无断点 + if G_BP == true then + this.changeHookState(hookState.MID_HOOK); + else + this.changeHookState(hookState.LITE_HOOK); + end + else + --文件有断点, 判断函数内是否有断点 + local funHasBP = this.checkfuncHasBreakpoint(lastRunFunction.linedefined, lastRunFunction.lastlinedefined, lastRunFilePath); + if funHasBP then + --函数定义范围内 + this.changeHookState(hookState.ALL_HOOK); + else + this.changeHookState(hookState.MID_HOOK); + end + end + + --MID_HOOK状态下,return需要在下一次hook检查文件(return时,还是当前文件,检查文件时状态无法转换) + if (event == "return" or event == "tail return") and currentHookState == hookState.MID_HOOK then + this.changeHookState(hookState.ALL_HOOK); + end + end + end +end + +-- 向Vscode发送标准通知消息,cmdStr是消息类型 +-- @cmdStr 命令字 +function this.SendMsgWithStack(cmdStr) + local msgTab = this.getMsgTable(cmdStr); + local userFuncLevel = 0; + msgTab["stack"] , userFuncLevel= this.getStackTable(); + if userFuncLevel ~= 0 then + lastRunFunction["func"] = debug.getinfo( (userFuncLevel - 1) , 'f').func; + end + this.sendMsg(msgTab); + this.debugger_wait_msg(); +end + +-- hook状态改变 +-- @s 目标状态 +function this.changeHookState( s ) + if hookLib == nil and currentHookState == s then + return; + end + + this.printToConsole("change hook state :"..s) + if s ~= hookState.DISCONNECT_HOOK then + this.printToVSCode("change hook state : "..s) + end + + currentHookState = s; + if s == hookState.DISCONNECT_HOOK then + --为了实现通用attach模式,require即开始hook,利用r作为时机发起连接 + if openAttachMode == true then + if hookLib then hookLib.lua_set_hookstate(hookState.DISCONNECT_HOOK); else debug.sethook(this.debug_hook, "r", 1000000); end + else + if hookLib then hookLib.endHook(); else debug.sethook(); end + end + elseif s == hookState.LITE_HOOK then + if hookLib then hookLib.lua_set_hookstate(hookState.LITE_HOOK); else debug.sethook(this.debug_hook, "r"); end + elseif s == hookState.MID_HOOK then + if hookLib then hookLib.lua_set_hookstate(hookState.MID_HOOK); else debug.sethook(this.debug_hook, "rc"); end + elseif s == hookState.ALL_HOOK then + if hookLib then hookLib.lua_set_hookstate(hookState.ALL_HOOK); else debug.sethook(this.debug_hook, "lrc");end + end + --coroutine + if hookLib == nil then + this.changeCoroutineHookState(); + end +end + +-- 运行状态机,状态变更 +-- @s 目标状态 +-- @isFromHooklib 1:从libc库中发来的状态改变 | 0:lua发来的状态改变 +function this.changeRunState(s , isFromHooklib) + local msgFrom; + if isFromHooklib == 1 then + msgFrom = "libc"; + else + msgFrom = "lua"; + end + + --WAIT_CMD状态会等待接收消息,以下两个状态下不能发消息 + this.printToConsole("changeRunState :"..s.. " | from:"..msgFrom); + if s ~= runState.DISCONNECT and s ~= runState.WAIT_CMD then + this.printToVSCode("changeRunState :"..s.." | from:"..msgFrom); + end + + if hookLib ~= nil and isFromHooklib ~= 1 then + hookLib.lua_set_runstate(s); + end + currentRunState = s; + --状态切换时,清除记录栈信息的状态 + currentCallStack = {}; + variableRefTab = {}; + variableRefIdx = 1; +end + +-- 修改协程状态 +-- @s hook标志位 +function this.changeCoroutineHookState(s) + s = s or currentHookState; + this.printToConsole("change [Coroutine] HookState: "..tostring(s)); + for k ,co in pairs(coroutinePool) do + if coroutine.status(co) == "dead" then + table.remove(coroutinePool, k) + else + if s == hookState.DISCONNECT_HOOK then + if openAttachMode == true then + debug.sethook(co, this.debug_hook, "r", 1000000); + else + debug.sethook(co, this.debug_hook, ""); + end + elseif s == hookState.LITE_HOOK then debug.sethook(co , this.debug_hook, "r"); + elseif s == hookState.MID_HOOK then debug.sethook(co , this.debug_hook, "rc"); + elseif s == hookState.ALL_HOOK then debug.sethook(co , this.debug_hook, "lrc"); + end + end + end +end +-------------------------变量处理相关----------------------------- + +--清空REPL的env环境 +function this.clearEnv() + if this.getTableMemberNum(env) > 0 then + --清空env table + env = setmetatable({}, getmetatable(env)); + end +end + +--返回REPL的env环境 +function this.showEnv() + return env; +end + +-- 用户观察table的查找函数。用tableVarName作为key去查逐层级查找realVar是否匹配 +-- @tableVarName 是用户观察的变量名,已经按层级被解析成table。比如用户输出a.b.c,tableVarName是 a = { b = { c } } +-- @realVar 是待查询 table +-- @return 返回查到的table。没查到返回nil +function this.findTableVar( tableVarName, realVar) + if type(tableVarName) ~= "table" or type(realVar) ~= "table" then + return nil; + end + + local layer = 2; + local curVar = realVar; + local jumpOutFlag = false; + repeat + if tableVarName[layer] ~= nil then + --这里优先展示数字key,比如a{"1" = "aa", [1] = "bb"} 会展示[1]的值 + local tmpCurVar = nil; + xpcall(function() tmpCurVar = curVar[tonumber(tableVarName[layer])]; end , function() tmpCurVar = nil end ); + if tmpCurVar == nil then + xpcall(function() curVar = curVar[tostring(tableVarName[layer])]; end , function() curVar = nil end ); + else + curVar = tmpCurVar; + end + layer = layer + 1; + if curVar == nil then + return nil; + end + else + --找到 + jumpOutFlag = true; + end + until(jumpOutFlag == true) + return curVar; +end + +-- 根据传入信息生成返回的变量信息 +-- @variableName 变量名 +-- @variableIns 变量实例 +-- @return 包含变量信息的格式化table +function this.createWatchedVariableInfo(variableName, variableIns) + local var = {}; + var.name = variableName; + var.type = tostring(type(variableIns)); + xpcall(function() var.value = tostring(variableIns) end , function() var.value = tostring(type(variableIns)) .. " [value can't trans to string]" end ); + var.variablesReference = "0"; --这个地方必须用“0”, 以免variableRefTab[0]出错 + + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = variableIns; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(variableIns); + var.value = memberNum .." Members ".. var.value; + end + elseif var.type == "string" then + var.value = '"' ..variableIns.. '"'; + end + return var; +end + +-- 设置 global 变量 +-- @varName 被修改的变量名 +-- @newValue 新的值 +function this.setGlobal(varName, newValue) + _G[varName] = newValue; + this.printToVSCode("[setVariable success] 已设置 _G.".. varName .. " = " .. tostring(newValue) ); + return true; +end + +-- 设置 upvalue 变量 +-- @varName 被修改的变量名 +-- @newValue 新的值 +-- @stackId 变量所在stack栈层 +-- @tableVarName 变量名拆分成的数组 +function this.setUpvalue(varName, newValue, stackId, tableVarName) + local ret = false; + local upTable = this.getUpValueVariable(currentCallStack[stackId - 1 ].func, true); + for i, realVar in ipairs(upTable) do + if realVar.name == varName then + if #tableVarName > 0 and type(realVar) == "table" then + --处理a.b.c的table类型 + local findRes = this.findTableVar(tableVarName, variableRefTab[realVar.variablesReference]); + if findRes ~= nil then + --命中 + local setVarRet = debug.setupvalue (currentCallStack[stackId - 1 ].func, i, newValue); + if setVarRet == varName then + this.printToConsole("[setVariable success1] 已设置 upvalue ".. varName .. " = " .. tostring(newValue) ); + ret = true; + else + this.printToConsole("[setVariable error1] 未能设置 upvalue ".. varName .. " = " .. tostring(newValue).." , 返回结果: ".. tostring(setVarRet)); + end + return ret; + end + else + --命中 + local setVarRet = debug.setupvalue (currentCallStack[stackId - 1 ].func, i, newValue); + if setVarRet == varName then + this.printToConsole("[setVariable success] 已设置 upvalue ".. varName .. " = " .. tostring(newValue) ); + ret = true; + else + this.printToConsole("[setVariable error] 未能设置 upvalue ".. varName .. " = " .. tostring(newValue).." , 返回结果: ".. tostring(setVarRet)); + end + return ret; + end + end + end + return ret; +end + +-- 设置local 变量 +-- @varName 被修改的变量名 +-- @newValue 新的值 +-- @tableVarName 变量名拆分成的数组 +function this.setLocal( varName, newValue, tableVarName, stackId) + local istackId = tonumber(stackId); + local offset = (istackId and istackId - 2) or 0; + local layerVarTab, ly = this.getVariable(nil , true, offset); + local ret = false; + for i, realVar in ipairs(layerVarTab) do + if realVar.name == varName then + if #tableVarName > 0 and type(realVar) == "table" then + --处理a.b.c的table类型 + local findRes = this.findTableVar(tableVarName, variableRefTab[realVar.variablesReference]); + if findRes ~= nil then + --命中 + local setVarRet = debug.setlocal(ly , layerVarTab[i].index, newValue); + if setVarRet == varName then + this.printToConsole("[setVariable success1] 已设置 local ".. varName .. " = " .. tostring(newValue) ); + ret = true; + else + this.printToConsole("[setVariable error1] 未能设置 local ".. varName .. " = " .. tostring(newValue).." , 返回结果: ".. tostring(setVarRet)); + end + return ret; + end + else + + local setVarRet = debug.setlocal(ly , layerVarTab[i].index, newValue); + + if setVarRet == varName then + this.printToConsole("[setVariable success] 已设置 local ".. varName .. " = " .. tostring(newValue) ); + ret = true; + else + this.printToConsole("[setVariable error] 未能设置 local ".. varName .. " = " .. tostring(newValue) .." , 返回结果: ".. tostring(setVarRet)); + end + return ret; + end + end + end + return ret; +end + + +-- 设置变量的值 +-- @varName 被修改的变量名 +-- @curStackId 调用栈层级(仅在固定栈层查找) +-- @newValue 新的值 +-- @limit 限制符, 10000表示仅在局部变量查找 ,20000 global, 30000 upvalue +function this.setVariableValue (varName, stackId, newValue , limit) + this.printToConsole("setVariableValue | varName:" .. tostring(varName) .. " stackId:".. tostring(stackId) .." newValue:" .. tostring(newValue) .." limit:"..tostring(limit) ) + if tostring(varName) == nil or tostring(varName) == "" then + --赋值错误 + this.printToConsole("[setVariable Error] 被赋值的变量名为空", 2 ); + this.printToVSCode("[setVariable Error] 被赋值的变量名为空", 2 ); + return false; + end + + --支持a.b.c形式。切割varName + local tableVarName = {}; + if varName:match('%.') then + tableVarName = this.stringSplit(varName , '%.'); + if type(tableVarName) ~= "table" or #tableVarName < 1 then + return false; + end + varName = tableVarName[1]; + end + + if limit == "local" then + local ret = this.setLocal( varName, newValue, tableVarName, stackId); + return ret; + elseif limit == "upvalue" then + local ret = this.setUpvalue(varName, newValue, stackId, tableVarName); + return ret + elseif limit == "global" then + local ret = this.setGlobal(varName, newValue); + return ret; + else + local ret = this.setLocal( varName, newValue, tableVarName, stackId) or this.setUpvalue(varName, newValue, stackId, tableVarName) or this.setGlobal(varName, newValue); + this.printToConsole("set Value res :".. tostring(ret)); + return ret; + end +end + +-- 按照local -> upvalue -> _G 顺序查找观察变量 +-- @varName 用户输入的变量名 +-- @stackId 调用栈层级(仅在固定栈层查找) +-- @isFormatVariable 是否把变量格式化为VSCode接收的形式 +-- @return 查到返回信息,查不到返回nil +function this.getWatchedVariable( varName , stackId , isFormatVariable ) + this.printToConsole("getWatchedVariable | varName:" .. tostring(varName) .. " stackId:".. tostring(stackId) .." isFormatVariable:" .. tostring(isFormatVariable) ) + if tostring(varName) == nil or tostring(varName) == "" then + return nil; + end + + if type(currentCallStack[stackId - 1]) ~= "table" or type(currentCallStack[stackId - 1].func) ~= "function" then + local str = "getWatchedVariable currentCallStack " .. stackId - 1 .. " Error\n" .. this.serializeTable(currentCallStack, "currentCallStack"); + this.printToVSCode(str, 2); + return nil; + end + + --orgname 记录原名字. 用来处理a.b.c的形式 + local orgname = varName; + --支持a.b.c形式。切割varName + local tableVarName = {}; + if varName:match('%.') then + tableVarName = this.stringSplit(varName , '%.'); + if type(tableVarName) ~= "table" or #tableVarName < 1 then + return nil; + end + varName = tableVarName[1]; + end + --用来返回,带有查到变量的table + local varTab = {}; + local ly = this.getSpecificFunctionStackLevel(currentCallStack[stackId - 1].func); + + local layerVarTab = this.getVariable(ly, isFormatVariable); + local upTable = this.getUpValueVariable(currentCallStack[stackId - 1 ].func, isFormatVariable); + local travelTab = {}; + table.insert(travelTab, layerVarTab); + table.insert(travelTab, upTable); + for _, layerVarTab in ipairs(travelTab) do + for i,realVar in ipairs(layerVarTab) do + if realVar.name == varName then + if #tableVarName > 0 and type(realVar) == "table" then + --处理a.b.c的table类型 + local findRes = this.findTableVar(tableVarName, variableRefTab[realVar.variablesReference]); + if findRes ~= nil then + --命中 + if isFormatVariable then + local var = this.createWatchedVariableInfo( orgname , findRes ); + table.insert(varTab, var); + return varTab; + else + return findRes.value; + end + end + else + --命中 + if isFormatVariable then + table.insert(varTab, realVar); + return varTab; + else + return realVar.value; + end + end + end + end + end + + --在全局变量_G中查找 + if _G[varName] ~= nil then + --命中 + if #tableVarName > 0 and type(_G[varName]) == "table" then + local findRes = this.findTableVar(tableVarName, _G[varName]); + if findRes ~= nil then + if isFormatVariable then + local var = this.createWatchedVariableInfo( orgname , findRes ); + table.insert(varTab, var); + return varTab; + else + return findRes; + end + end + else + if isFormatVariable then + local var = this.createWatchedVariableInfo( varName , _G[varName] ); + table.insert(varTab, var); + return varTab; + else + return _G[varName]; + end + end + end + this.printToConsole("getWatchedVariable not find variable"); + return nil; +end + +-- 查询引用变量 +-- @refStr 变量记录id(variableRefTab索引) +-- @return 格式化的变量信息table +function this.getVariableRef( refStr ) + local varRef = tonumber(refStr); + local varTab = {}; + + if tostring(type(variableRefTab[varRef])) == "table" then + for n,v in pairs(variableRefTab[varRef]) do + local var = {}; + if type(n) == "string" then + var.name = '"' .. tostring(n) .. '"'; + else + var.name = tostring(n); + end + var.type = tostring(type(v)); + xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .. " [value can't trans to string]" end ); + var.variablesReference = "0"; + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = v; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(v); + var.value = memberNum .." Members ".. ( var.value or '' ); + end + elseif var.type == "string" then + var.value = '"' ..v.. '"'; + end + table.insert(varTab, var); + end + --获取一下mtTable + local mtTab = getmetatable(variableRefTab[varRef]); + if mtTab ~= nil and type(mtTab) == "table" then + local var = {}; + var.name = "_Metatable_"; + var.type = tostring(type(mtTab)); + xpcall(function() var.value = "元表 "..tostring(mtTab); end , function() var.value = "元表 [value can't trans to string]" end ); + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = mtTab; + variableRefIdx = variableRefIdx + 1; + table.insert(varTab, var); + end + elseif tostring(type(variableRefTab[varRef])) == "function" then + --取upvalue + varTab = this.getUpValueVariable(variableRefTab[varRef], true); + elseif tostring(type(variableRefTab[varRef])) == "userdata" then + --取mt table + local udMtTable = getmetatable(variableRefTab[varRef]); + if udMtTable ~= nil and type(udMtTable) == "table" then + local var = {}; + var.name = "_Metatable_"; + var.type = tostring(type(udMtTable)); + xpcall(function() var.value = "元表 "..tostring(udMtTable); end , function() var.value = "元表 [value can't trans to string]" end ); + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = udMtTable; + variableRefIdx = variableRefIdx + 1; + table.insert(varTab, var); + + if udMtTable.__pairs ~= nil and type(udMtTable.__pairs) == "function" then + for n,v in pairs(variableRefTab[varRef]) do + local var = {}; + var.name = tostring(n); + var.type = tostring(type(v)); + xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .. " [value can't trans to string]" end ); + var.variablesReference = "0"; + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = v; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(v); + var.value = memberNum .." Members ".. ( var.value or '' ); + end + elseif var.type == "string" then + var.value = '"' ..v.. '"'; + end + table.insert(varTab, var); + end + end + end + end + return varTab; +end + +-- 获取全局变量。方法和内存管理中获取全局变量的方法一样 +-- @return 格式化的信息, 若未找到返回空table +function this.getGlobalVariable( ... ) + --成本比较高,这里只能遍历_G中的所有变量,并去除系统变量,再返回给客户端 + local varTab = {}; + for k,v in pairs(_G) do + local var = {}; + var.name = tostring(k); + var.type = tostring(type(v)); + xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .." [value can't trans to string]" end ); + var.variablesReference = "0"; + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = v; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(v); + var.value = memberNum .." Members ".. ( var.value or '' ); + end + elseif var.type == "string" then + var.value = '"' ..v.. '"'; + end + table.insert(varTab, var); + end + return varTab; +end + +-- 获取upValues +-- @isFormatVariable true返回[值] true返回[格式化的数据] +function this.getUpValueVariable( checkFunc , isFormatVariable) + local isGetValue = true; + if isFormatVariable == true then + isGetValue = false; + end + + --通过Debug获取当前函数的Func + checkFunc = checkFunc or lastRunFunction.func; + + local varTab = {}; + if checkFunc == nil then + return varTab; + end + local i = 1 + repeat + local n, v = debug.getupvalue(checkFunc, i) + if n then + + local var = {}; + var.name = n; + var.type = tostring(type(v)); + var.variablesReference = "0"; + + if isGetValue == false then + xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .. " [value can't trans to string]" end ); + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = v; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(v); + var.value = memberNum .." Members ".. ( var.value or '' ); + end + elseif var.type == "string" then + var.value = '"' ..v.. '"'; + end + else + var.value = v; + end + + table.insert(varTab, var); + i = i + 1 + end + until not n + return varTab; +end + +-- 获取局部变量 checkLayer是要查询的层级,如果不设置则查询当前层级 +-- @isFormatVariable 是否取值,true:取值的tostring +function this.getVariable( checkLayer, isFormatVariable , offset) + local isGetValue = true; + if isFormatVariable == true then + isGetValue = false; + end + + local ly = 0; + if checkLayer ~= nil and type(checkLayer) == "number" then ly = checkLayer + 1; + else ly = this.getSpecificFunctionStackLevel(lastRunFunction.func); end + + if ly == 0 then + this.printToVSCode("[error]获取层次失败!", 2); + return; + end + local varTab = {}; + local stacklayer = ly; + local k = 1; + + if type(offset) == 'number' then + stacklayer = stacklayer + offset; + end + + repeat + local n, v = debug.getlocal(stacklayer, k) + if n == nil then + break; + end + + --(*temporary)是系统变量,过滤掉。这里假设(*temporary)仅出现在最后 + if "(*temporary)" ~= tostring(n) then + local var = {}; + var.name = n; + var.type = tostring(type(v)); + var.variablesReference = "0"; + var.index = k; + + if isGetValue == false then + xpcall(function() var.value = tostring(v) end , function() var.value = tostring(type(v)) .. " [value can't trans to string]" end ); + if var.type == "table" or var.type == "function" or var.type == "userdata" then + var.variablesReference = variableRefIdx; + variableRefTab[variableRefIdx] = v; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(v); + var.value = memberNum .." Members ".. ( var.value or '' ); + end + elseif var.type == "string" then + var.value = '"' ..v.. '"'; + end + else + var.value = v; + end + + local sameIdx = this.checkSameNameVar(varTab, var); + if sameIdx ~= 0 then + varTab[sameIdx] = var; + else + table.insert(varTab, var); + end + end + k = k + 1 + until n == nil + return varTab, stacklayer - 1; +end + +--检查变量列表中的同名变量 +function this.checkSameNameVar(varTab, var) + for k , v in pairs(varTab) do + if v.name == var.name then + return k; + end + end + return 0; +end + +-- 执行表达式 +function this.processExp(msgTable) + local retString; + local var = {}; + var.isSuccess = "true"; + if msgTable ~= nil then + local expression = this.trim(tostring(msgTable.Expression)); + local isCmd = false; + if isCmd == false then + --兼容旧版p 命令 + if expression:find("p ", 1, true) == 1 then + expression = expression:sub(3); + end + + local expressionWithReturn = "return " .. expression; + local f = debugger_loadString(expressionWithReturn) or debugger_loadString(expression); + --判断结果,如果表达式错误会返回nil + if type(f) == "function" then + if _VERSION == "Lua 5.1" then + setfenv(f , env); + else + debug.setupvalue(f, 1, env); + end + --表达式要有错误处理 + xpcall(function() retString = f() end , function() retString = "输入错误指令。\n + 请检查指令是否正确\n + 指令仅能在[暂停在断点时]输入, 请不要在程序持续运行时输入"; var.isSuccess = false; end) + else + retString = "指令执行错误。\n + 请检查指令是否正确\n + 可以直接输入表达式,执行函数或变量名,并观察执行结果"; + var.isSuccess = false; + end + end + end + + var.name = "Exp"; + var.type = tostring(type(retString)); + xpcall(function() var.value = tostring(retString) end , function(e) var.value = tostring(type(retString)) .. " [value can't trans to string] ".. e; var.isSuccess = false; end); + var.variablesReference = "0"; + if var.type == "table" or var.type == "function" or var.type == "userdata" then + variableRefTab[variableRefIdx] = retString; + var.variablesReference = variableRefIdx; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(retString); + var.value = memberNum .." Members ".. var.value; + end + elseif var.type == "string" then + var.value = '"' ..retString.. '"'; + end + --string执行完毕后清空env环境 + this.clearEnv(); + local retTab = {} + table.insert(retTab ,var); + return retTab; +end + +--执行变量观察表达式 +function this.processWatchedExp(msgTable) + local retString; + local expression = "return ".. tostring(msgTable.varName) + this.printToConsole("processWatchedExp | expression: " .. expression); + local f = debugger_loadString(expression); + local var = {}; + var.isSuccess = "true"; + --判断结果,如果表达式错误会返回nil + if type(f) == "function" then + --表达式正确 + if _VERSION == "Lua 5.1" then + setfenv(f , env); + else + debug.setupvalue(f, 1, env); + end + xpcall(function() retString = f() end , function() retString = "输入了错误的变量信息"; var.isSuccess = "false"; end) + else + retString = "未能找到变量的值"; + var.isSuccess = "false"; + end + + var.name = msgTable.varName; + var.type = tostring(type(retString)); + xpcall(function() var.value = tostring(retString) end , function() var.value = tostring(type(retString)) .. " [value can't trans to string]"; var.isSuccess = "false"; end ); + var.variablesReference = "0"; + + if var.type == "table" or var.type == "function" or var.type == "userdata" then + variableRefTab[variableRefIdx] = retString; + var.variablesReference = variableRefIdx; + variableRefIdx = variableRefIdx + 1; + if var.type == "table" then + local memberNum = this.getTableMemberNum(retString); + var.value = memberNum .." Members ".. var.value; + end + elseif var.type == "string" then + var.value = '"' ..retString.. '"'; + end + + local retTab = {} + table.insert(retTab ,var); + return retTab; +end + + +function tools.getFileSource() + local info = debug.getinfo(1, "S") + for k,v in pairs(info) do + if k == "source" then + return v; + end + end +end + +--序列化并打印table +function tools.printTable(t, name ,indent) + local str = (tools.show(t, name, indent)); + print(str); +end + +--序列化并返回table +function tools.serializeTable(t, name, indent) + local str = (tools.show(t, name, indent)) + return str +end + +--[[ +Author: Julio Manuel Fernandez-Diaz +Date: January 12, 2007 +Modified slightly by RiciLake to avoid the unnecessary table traversal in tablecount() +Formats tables with cycles recursively to any depth. +The output is returned as a string. +References to other tables are shown as values. +Self references are indicated. +The string returned is "Lua code", which can be procesed +(in the case in which indent is composed by spaces or "--"). +Userdata and function keys and values are shown as strings, +which logically are exactly not equivalent to the original code. +This routine can serve for pretty formating tables with +proper indentations, apart from printing them: +print(table.show(t, "t")) -- a typical use +Heavily based on "Saving tables with cycles", PIL2, p. 113. +Arguments: +t is the table. +name is the name of the table (optional) +indent is a first indentation (optional). +--]] +function tools.show(t, name, indent) + local cart -- a container + local autoref -- for self references + + local function isemptytable(t) return next(t) == nil end + + local function basicSerialize (o) + local so = tostring(o) + if type(o) == "function" then + local info = debug.getinfo(o, "S") + -- info.name is nil because o is not a calling level + if info.what == "C" then + return string.format("%q", so .. ", C function") + else + -- the information is defined through lines + return string.format("%q", so .. ", defined in (" .. + info.linedefined .. "-" .. info.lastlinedefined .. + ")" .. info.source) + end + elseif type(o) == "number" or type(o) == "boolean" then + return so + else + return string.format("%q", so) + end + end + + local function addtocart (value, name, indent, saved, field) + indent = indent or "" + saved = saved or {} + field = field or name + + cart = cart .. indent .. field + + if type(value) ~= "table" then + cart = cart .. " = " .. basicSerialize(value) .. ";\n" + else + if saved[value] then + cart = cart .. " = {}; -- " .. saved[value] + .. " (self reference)\n" + autoref = autoref .. name .. " = " .. saved[value] .. ";\n" + else + saved[value] = name + --if tablecount(value) == 0 then + if isemptytable(value) then + cart = cart .. " = {};\n" + else + cart = cart .. " = {\n" + for k, v in pairs(value) do + k = basicSerialize(k) + local fname = string.format("%s[%s]", name, k) + field = string.format("[%s]", k) + -- three spaces between levels + addtocart(v, fname, indent .. " ", saved, field) + end + cart = cart .. indent .. "};\n" + end + end + end + end + + name = name or "PRINT_Table" + if type(t) ~= "table" then + return name .. " = " .. basicSerialize(t) + end + cart, autoref = "", "" + addtocart(t, name, indent) + return cart .. autoref +end + +----------------------------------------------------------------------------- +-- JSON4Lua: JSON encoding / decoding support for the Lua language. +-- json Module. +-- Author: Craig Mason-Jones +-- Homepage: http://github.com/craigmj/json4lua/ +-- Version: 1.0.0 +-- This module is released under the MIT License (MIT). +-- Please see LICENCE.txt for details. +-- +-- USAGE: +-- This module exposes two functions: +-- json.encode(o) +-- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string. +-- json.decode(json_string) +-- Returns a Lua object populated with the data encoded in the JSON string json_string. +-- +-- REQUIREMENTS: +-- compat-5.1 if using Lua 5.0 +-- +-- CHANGELOG +-- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix). +-- Fixed Lua 5.1 compatibility issues. +-- Introduced json.null to have null values in associative arrays. +-- json.encode() performance improvement (more than 50%) through table.concat rather than .. +-- Introduced decode ability to ignore /**/ comments in the JSON string. +-- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays. +----------------------------------------------------------------------------- + +function tools.createJson() + ----------------------------------------------------------------------------- + -- Imports and dependencies + ----------------------------------------------------------------------------- + local math = require('math') + local string = require("string") + local table = require("table") + + ----------------------------------------------------------------------------- + -- Module declaration + ----------------------------------------------------------------------------- + local json = {} -- Public namespace + local json_private = {} -- Private namespace + + -- Public constants + json.EMPTY_ARRAY={} + json.EMPTY_OBJECT={} + + -- Public functions + + -- Private functions + local decode_scanArray + local decode_scanComment + local decode_scanConstant + local decode_scanNumber + local decode_scanObject + local decode_scanString + local decode_scanWhitespace + local encodeString + local isArray + local isEncodable + + ----------------------------------------------------------------------------- + -- PUBLIC FUNCTIONS + ----------------------------------------------------------------------------- + --- Encodes an arbitrary Lua object / variable. + -- @param v The Lua object / variable to be JSON encoded. + -- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode) + function json.encode (v) + -- Handle nil values + if v==nil then + return "null" + end + + local vtype = type(v) + + -- Handle strings + if vtype=='string' then + return '"' .. json_private.encodeString(v) .. '"' -- Need to handle encoding in string + end + + -- Handle booleans + if vtype=='number' or vtype=='boolean' then + return tostring(v) + end + + -- Handle tables + if vtype=='table' then + local rval = {} + -- Consider arrays separately + local bArray, maxCount = isArray(v) + if bArray then + for i = 1,maxCount do + table.insert(rval, json.encode(v[i])) + end + else -- An object, not an array + for i,j in pairs(v) do + if isEncodable(i) and isEncodable(j) then + table.insert(rval, '"' .. json_private.encodeString(i) .. '":' .. json.encode(j)) + end + end + end + if bArray then + return '[' .. table.concat(rval,',') ..']' + else + return '{' .. table.concat(rval,',') .. '}' + end + end + + -- Handle null values + if vtype=='function' and v==json.null then + return 'null' + end + + assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. tostring(v)) + end + + + --- Decodes a JSON string and returns the decoded value as a Lua data structure / value. + -- @param s The string to scan. + -- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1. + -- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil, + -- and the position of the first character after + -- the scanned JSON object. + function json.decode(s, startPos) + startPos = startPos and startPos or 1 + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') + local curChar = string.sub(s,startPos,startPos) + -- Object + if curChar=='{' then + return decode_scanObject(s,startPos) + end + -- Array + if curChar=='[' then + return decode_scanArray(s,startPos) + end + -- Number + if string.find("+-0123456789.e", curChar, 1, true) then + return decode_scanNumber(s,startPos) + end + -- String + if curChar==[["]] or curChar==[[']] then + return decode_scanString(s,startPos) + end + if string.sub(s,startPos,startPos+1)=='/*' then + return json.decode(s, decode_scanComment(s,startPos)) + end + -- Otherwise, it must be a constant + return decode_scanConstant(s,startPos) + end + + --- The null function allows one to specify a null value in an associative array (which is otherwise + -- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null } + function json.null() + return json.null -- so json.null() will also return null ;-) + end + ----------------------------------------------------------------------------- + -- Internal, PRIVATE functions. + -- Following a Python-like convention, I have prefixed all these 'PRIVATE' + -- functions with an underscore. + ----------------------------------------------------------------------------- + + --- Scans an array from JSON into a Lua object + -- startPos begins at the start of the array. + -- Returns the array and the next starting position + -- @param s The string being scanned. + -- @param startPos The starting position for the scan. + -- @return table, int The scanned array as a table, and the position of the next character to scan. + function decode_scanArray(s,startPos) + local array = {} -- The return value + local stringLen = string.len(s) + assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) + startPos = startPos + 1 + -- Infinite loop for array elements + local index = 1 + repeat + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') + local curChar = string.sub(s,startPos,startPos) + if (curChar==']') then + return array, startPos+1 + end + if (curChar==',') then + startPos = decode_scanWhitespace(s,startPos+1) + end + assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') + local object + object, startPos = json.decode(s,startPos) + array[index] = object + index = index + 1 + until false + end + + --- Scans a comment and discards the comment. + -- Returns the position of the next character following the comment. + -- @param string s The JSON string to scan. + -- @param int startPos The starting position of the comment + function decode_scanComment(s, startPos) + assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) + local endPos = string.find(s,'*/',startPos+2) + assert(endPos~=nil, "Unterminated comment in string at " .. startPos) + return endPos+2 + end + + --- Scans for given constants: true, false or null + -- Returns the appropriate Lua type, and the position of the next character to read. + -- @param s The string being scanned. + -- @param startPos The position in the string at which to start scanning. + -- @return object, int The object (true, false or nil) and the position at which the next character should be + -- scanned. + function decode_scanConstant(s, startPos) + local consts = { ["true"] = true, ["false"] = false, ["null"] = nil } + local constNames = {"true","false","null"} + + for i,k in pairs(constNames) do + if string.sub(s,startPos, startPos + string.len(k) -1 )==k then + return consts[k], startPos + string.len(k) + end + end + assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) + end + + --- Scans a number from the JSON encoded string. + -- (in fact, also is able to scan numeric +- eqns, which is not + -- in the JSON spec.) + -- Returns the number, and the position of the next character + -- after the number. + -- @param s The string being scanned. + -- @param startPos The position at which to start scanning. + -- @return number, int The extracted number and the position of the next character to scan. + function decode_scanNumber(s,startPos) + local endPos = startPos+1 + local stringLen = string.len(s) + local acceptableChars = "+-0123456789.e" + while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) + and endPos<=stringLen + ) do + endPos = endPos + 1 + end + -- local stringValue = 'return ' .. string.sub(s, startPos, endPos - 1) + -- local stringEval = loadstring(stringValue) + -- assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) + local numberValue = string.sub(s, startPos, endPos - 1) + return numberValue, endPos + end + + --- Scans a JSON object into a Lua object. + -- startPos begins at the start of the object. + -- Returns the object and the next starting position. + -- @param s The string being scanned. + -- @param startPos The starting position of the scan. + -- @return table, int The scanned object as a table and the position of the next character to scan. + function decode_scanObject(s,startPos) + local object = {} + local stringLen = string.len(s) + local key, value + assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s) + startPos = startPos + 1 + repeat + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') + local curChar = string.sub(s,startPos,startPos) + if (curChar=='}') then + return object,startPos+1 + end + if (curChar==',') then + startPos = decode_scanWhitespace(s,startPos+1) + end + assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') + -- Scan the key + key, startPos = json.decode(s,startPos) + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) + startPos = decode_scanWhitespace(s,startPos+1) + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + value, startPos = json.decode(s,startPos) + object[key]=value + until false -- infinite loop while key-value pairs are found + end + + -- START SoniEx2 + -- Initialize some things used by decode_scanString + -- You know, for efficiency + local escapeSequences = { + ["\\t"] = "\t", + ["\\f"] = "\f", + ["\\r"] = "\r", + ["\\n"] = "\n", + ["\\b"] = "\b" + } + setmetatable(escapeSequences, {__index = function(t,k) + -- skip "\" aka strip escape + return string.sub(k,2) + end}) + -- END SoniEx2 + + --- Scans a JSON string from the opening inverted comma or single quote to the + -- end of the string. + -- Returns the string extracted as a Lua string, + -- and the position of the next non-string character + -- (after the closing inverted comma or single quote). + -- @param s The string being scanned. + -- @param startPos The starting position of the scan. + -- @return string, int The extracted string as a Lua string, and the next character to parse. + function decode_scanString(s,startPos) + assert(startPos, 'decode_scanString(..) called without start position') + local startChar = string.sub(s,startPos,startPos) + -- START SoniEx2 + -- PS: I don't think single quotes are valid JSON + assert(startChar == [["]] or startChar == [[']],'decode_scanString called for a non-string') + --assert(startPos, "String decoding failed: missing closing " .. startChar .. " for string at position " .. oldStart) + local t = {} + local i,j = startPos,startPos + while string.find(s, startChar, j+1) ~= j+1 do + local oldj = j + i,j = string.find(s, "\\.", j+1) + local x,y = string.find(s, startChar, oldj+1) + if not i or x < i then + i,j = x,y-1 + end + table.insert(t, string.sub(s, oldj+1, i-1)) + if string.sub(s, i, j) == "\\u" then + local a = string.sub(s,j+1,j+4) + j = j + 4 + local n = tonumber(a, 16) + assert(n, "String decoding failed: bad Unicode escape " .. a .. " at position " .. i .. " : " .. j) + -- math.floor(x/2^y) == lazy right shift + -- a % 2^b == bitwise_and(a, (2^b)-1) + -- 64 = 2^6 + -- 4096 = 2^12 (or 2^6 * 2^6) + local x + if n < 0x80 then + x = string.char(n % 0x80) + elseif n < 0x800 then + -- [110x xxxx] [10xx xxxx] + x = string.char(0xC0 + (math.floor(n/64) % 0x20), 0x80 + (n % 0x40)) + else + -- [1110 xxxx] [10xx xxxx] [10xx xxxx] + x = string.char(0xE0 + (math.floor(n/4096) % 0x10), 0x80 + (math.floor(n/64) % 0x40), 0x80 + (n % 0x40)) + end + table.insert(t, x) + else + table.insert(t, escapeSequences[string.sub(s, i, j)]) + end + end + table.insert(t,string.sub(j, j+1)) + assert(string.find(s, startChar, j+1), "String decoding failed: missing closing " .. startChar .. " at position " .. j .. "(for string at position " .. startPos .. ")") + return table.concat(t,""), j+2 + -- END SoniEx2 + end + + --- Scans a JSON string skipping all whitespace from the current start position. + -- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached. + -- @param s The string being scanned + -- @param startPos The starting position where we should begin removing whitespace. + -- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string + -- was reached. + function decode_scanWhitespace(s,startPos) + local whitespace=" \n\r\t" + local stringLen = string.len(s) + while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do + startPos = startPos + 1 + end + return startPos + end + + --- Encodes a string to be JSON-compatible. + -- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-) + -- @param s The string to return as a JSON encoded (i.e. backquoted string) + -- @return The string appropriately escaped. + + local escapeList = { + ['"'] = '\\"', + ['\\'] = '\\\\', + ['/'] = '\\/', + ['\b'] = '\\b', + ['\f'] = '\\f', + ['\n'] = '\\n', + ['\r'] = '\\r', + ['\t'] = '\\t' + } + + function json_private.encodeString(s) + local s = tostring(s) + return s:gsub(".", function(c) return escapeList[c] end) -- SoniEx2: 5.0 compat + end + + -- Determines whether the given Lua type is an array or a table / dictionary. + -- We consider any table an array if it has indexes 1..n for its n items, and no + -- other data in the table. + -- I think this method is currently a little 'flaky', but can't think of a good way around it yet... + -- @param t The table to evaluate as an array + -- @return boolean, number True if the table can be represented as an array, false otherwise. If true, + -- the second returned value is the maximum + -- number of indexed elements in the array. + function isArray(t) + -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable + -- (with the possible exception of 'n') + if (t == json.EMPTY_ARRAY) then return true, 0 end + if (t == json.EMPTY_OBJECT) then return false end + + local maxIndex = 0 + for k,v in pairs(t) do + if (type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair + if (not isEncodable(v)) then return false end -- All array elements must be encodable + maxIndex = math.max(maxIndex,k) + else + if (k=='n') then + if v ~= (t.n or #t) then return false end -- False if n does not hold the number of elements + else -- Else of (k=='n') + if isEncodable(v) then return false end + end -- End of (k~='n') + end -- End of k,v not an indexed pair + end -- End of loop across all pairs + return true, maxIndex + end + + --- Determines whether the given Lua object / table / variable can be JSON encoded. The only + -- types that are JSON encodable are: string, boolean, number, nil, table and json.null. + -- In this implementation, all other types are ignored. + -- @param o The object to examine. + -- @return boolean True if the object should be JSON encoded, false if it should be ignored. + function isEncodable(o) + local t = type(o) + return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or + (t=='function' and o==json.null) + end + return json +end + +-- Sourced from http://lua-users.org/wiki/BaseSixtyFour + +-- Lua 5.1+ base64 v3.0 (c) 2009 by Alex Kloss +-- licensed under the terms of the LGPL2 + +-- character table string +local base64CharTable='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + +-- encoding +function tools.base64encode(data) + return ((data:gsub('.', function(x) + local r,b='',x:byte() + for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end + return r; + end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) + if (#x < 6) then return '' end + local c=0 + for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end + return base64CharTable:sub(c+1,c+1) + end)..({ '', '==', '=' })[#data%3+1]) +end + +-- decoding +function tools.base64decode(data) + data = string.gsub(data, '[^'..base64CharTable..'=]', '') + return (data:gsub('.', function(x) + if (x == '=') then return '' end + local r,f='',(base64CharTable:find(x)-1) + for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end + return r; + end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) + if (#x ~= 8) then return '' end + local c=0 + for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end + return string.char(c) + end)) +end + +-- tools变量 +json = tools.createJson(); --json处理 +this.printToConsole("load LuaPanda success", 1); +return this; diff --git a/lua504Test/win/lua504.exe b/lua504Test/win/lua504.exe new file mode 100644 index 0000000..a68b486 Binary files /dev/null and b/lua504Test/win/lua504.exe differ diff --git a/lua504Test/win/socket/core.dll b/lua504Test/win/socket/core.dll new file mode 100644 index 0000000..090acdb Binary files /dev/null and b/lua504Test/win/socket/core.dll differ diff --git a/lua504Test/win/test.lua b/lua504Test/win/test.lua new file mode 100644 index 0000000..44f2abe --- /dev/null +++ b/lua504Test/win/test.lua @@ -0,0 +1,7 @@ +-- this is a file for test lua 504 + +require("LuaPanda").start() + +local a = 4 +local b = a + 7 +print(b) diff --git a/luasocketBin/503/mac_arm64/mime/core.so b/luasocketBin/503/mac_arm64/mime/core.so new file mode 100755 index 0000000..f7838f7 Binary files /dev/null and b/luasocketBin/503/mac_arm64/mime/core.so differ diff --git a/luasocketBin/503/mac_arm64/socket/core.so b/luasocketBin/503/mac_arm64/socket/core.so new file mode 100755 index 0000000..4d0a976 Binary files /dev/null and b/luasocketBin/503/mac_arm64/socket/core.so differ diff --git a/luasocketBin/503/mac_x64/mime/core.so b/luasocketBin/503/mac_x64/mime/core.so new file mode 100755 index 0000000..d8ce467 Binary files /dev/null and b/luasocketBin/503/mac_x64/mime/core.so differ diff --git a/luasocketBin/503/mac_x64/socket/core.so b/luasocketBin/503/mac_x64/socket/core.so new file mode 100755 index 0000000..24aa004 Binary files /dev/null and b/luasocketBin/503/mac_x64/socket/core.so differ diff --git a/luasocketBin/503/win_x64/mime/core.dll b/luasocketBin/503/win_x64/mime/core.dll new file mode 100644 index 0000000..f77b90f Binary files /dev/null and b/luasocketBin/503/win_x64/mime/core.dll differ diff --git a/luasocketBin/503/win_x64/socket/core.dll b/luasocketBin/503/win_x64/socket/core.dll new file mode 100644 index 0000000..f225706 Binary files /dev/null and b/luasocketBin/503/win_x64/socket/core.dll differ diff --git a/luasocketBin/503/win_x86/mime/core.dll b/luasocketBin/503/win_x86/mime/core.dll new file mode 100644 index 0000000..65f2973 Binary files /dev/null and b/luasocketBin/503/win_x86/mime/core.dll differ diff --git a/luasocketBin/503/win_x86/socket/core.dll b/luasocketBin/503/win_x86/socket/core.dll new file mode 100644 index 0000000..dfe6cd7 Binary files /dev/null and b/luasocketBin/503/win_x86/socket/core.dll differ diff --git a/luasocketBin/504/mac_arm64/mime/core.so b/luasocketBin/504/mac_arm64/mime/core.so new file mode 100755 index 0000000..6b441dd Binary files /dev/null and b/luasocketBin/504/mac_arm64/mime/core.so differ diff --git a/luasocketBin/504/mac_arm64/socket/core.so b/luasocketBin/504/mac_arm64/socket/core.so new file mode 100755 index 0000000..6be194a Binary files /dev/null and b/luasocketBin/504/mac_arm64/socket/core.so differ diff --git a/luasocketBin/504/win_x64/mime/mime.dll b/luasocketBin/504/win_x64/mime/mime.dll new file mode 100644 index 0000000..1f18ec9 Binary files /dev/null and b/luasocketBin/504/win_x64/mime/mime.dll differ diff --git a/luasocketBin/504/win_x64/socket/core.dll b/luasocketBin/504/win_x64/socket/core.dll new file mode 100644 index 0000000..d512b37 Binary files /dev/null and b/luasocketBin/504/win_x64/socket/core.dll differ diff --git a/luasocketBin/readme.md b/luasocketBin/readme.md new file mode 100644 index 0000000..dfabecf --- /dev/null +++ b/luasocketBin/readme.md @@ -0,0 +1,8 @@ +本目录下的luasocket二进制文件使用 https://github.com/diegonehab/luasocket 源码编译,区分win和mac平台。 + +1. 部署时把socket和mime两个文件夹拷贝到用户指定的文件夹下,如 `c:/luasocket`。如果这个文件夹中的库不能被lua自动引用,要修改package.cpath,比如 `package.cpath = package.cpath .. ";c:/luasocket/?.dll"`. + +2. 最后在lua中用 `require("socket.core");` 验证,如无module 'socket.core' not found: 报错,则部署成功。 + +注意调试最好部署在开发环境,不要发布到正式环境。 + diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 80ea915..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3587 +0,0 @@ -{ - "name": "luapanda", - "version": "2.2.3", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@types/mocha": { - "version": "2.2.48", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", - "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", - "dev": true - }, - "@types/node": { - "version": "7.0.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.55.tgz", - "integrity": "sha512-diCxfWNT4g2UM9Y+BPgy4s3egcZ2qOXc0mXLauvbsBUq9SBKQfh0SmuEUEhJVFZt/p6UDsjg1s2EgfM6OSlp4g==", - "dev": true - }, - "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" - } - }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" - } - }, - "ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true - }, - "append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", - "dev": true, - "requires": { - "buffer-equal": "^1.0.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true - }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true - }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "await-notify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/await-notify/-/await-notify-1.0.1.tgz", - "integrity": "sha1-C0gTOyLlJBgeEVV2ZRhfKi885Hw=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true - }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "dev": true, - "requires": { - "inherits": "~2.0.0" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", - "dev": true - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true - }, - "buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", - "dev": true - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "cheerio": { - "version": "1.0.0-rc.3", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", - "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", - "dev": true, - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.1", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash": "^4.15.0", - "parse5": "^3.0.1" - } - }, - "clone": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", - "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dev": true, - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-assign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-1.0.0.tgz", - "integrity": "sha1-sJJ0O+hCfcYh6gBnzex+cN0Z83s=", - "dev": true, - "requires": { - "is-obj": "^1.0.0" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=", - "dev": true - }, - "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", - "dev": true - }, - "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "dev": true, - "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", - "dev": true - }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, - "requires": { - "readable-stream": "~1.1.9" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "dev": true, - "requires": { - "kind-of": "^1.1.0" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "dev": true, - "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "first-chunk-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", - "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=", - "dev": true - }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, - "fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "glob-stream": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", - "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", - "dev": true, - "requires": { - "extend": "^3.0.0", - "glob": "^5.0.3", - "glob-parent": "^3.0.0", - "micromatch": "^2.3.7", - "ordered-read-streams": "^0.3.0", - "through2": "^0.6.0", - "to-absolute-glob": "^0.1.1", - "unique-stream": "^2.0.2" - }, - "dependencies": { - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "dev": true, - "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - } - } - }, - "glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", - "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", - "dev": true - }, - "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", - "dev": true - }, - "gulp-chmod": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/gulp-chmod/-/gulp-chmod-2.0.0.tgz", - "integrity": "sha1-AMOQuSigeZslGsz2MaoJ4BzGKZw=", - "dev": true, - "requires": { - "deep-assign": "^1.0.0", - "stat-mode": "^0.2.0", - "through2": "^2.0.0" - } - }, - "gulp-filter": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-5.1.0.tgz", - "integrity": "sha1-oF4Rr/sHz33PQafeHLe2OsN4PnM=", - "dev": true, - "requires": { - "multimatch": "^2.0.0", - "plugin-error": "^0.1.2", - "streamfilter": "^1.0.5" - } - }, - "gulp-gunzip": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulp-gunzip/-/gulp-gunzip-1.0.0.tgz", - "integrity": "sha1-FbdBFF6Dqcb1CIYkG1fMWHHxUak=", - "dev": true, - "requires": { - "through2": "~0.6.5", - "vinyl": "~0.4.6" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "dev": true, - "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - } - } - }, - "gulp-remote-src": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/gulp-remote-src/-/gulp-remote-src-0.4.4.tgz", - "integrity": "sha512-mo7lGgZmNXyTbcUzfjSnUVkx1pnqqiwv/pPaIrYdTO77hq0WNTxXLAzQdoYOnyJ0mfVLNmNl9AGqWLiAzTPMMA==", - "dev": true, - "requires": { - "event-stream": "3.3.4", - "node.extend": "~1.1.2", - "request": "^2.88.0", - "through2": "~2.0.3", - "vinyl": "~2.0.1" - }, - "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "vinyl": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.0.2.tgz", - "integrity": "sha1-CjcT2NTpIhxY8QyhbAEWyeJe2nw=", - "dev": true, - "requires": { - "clone": "^1.0.0", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "is-stream": "^1.1.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - } - } - }, - "gulp-sourcemaps": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz", - "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", - "dev": true, - "requires": { - "convert-source-map": "^1.1.1", - "graceful-fs": "^4.1.2", - "strip-bom": "^2.0.0", - "through2": "^2.0.0", - "vinyl": "^1.0.0" - }, - "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, - "vinyl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", - "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", - "dev": true, - "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - } - } - } - }, - "gulp-symdest": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/gulp-symdest/-/gulp-symdest-1.1.1.tgz", - "integrity": "sha512-UHd3MokfIN7SrFdsbV5uZTwzBpL0ZSTu7iq98fuDqBGZ0dlHxgbQBJwfd6qjCW83snkQ3Hz9IY4sMRMz2iTq7w==", - "dev": true, - "requires": { - "event-stream": "3.3.4", - "mkdirp": "^0.5.1", - "queue": "^3.1.0", - "vinyl-fs": "^2.4.3" - } - }, - "gulp-untar": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/gulp-untar/-/gulp-untar-0.0.6.tgz", - "integrity": "sha1-1r3v3n6ajgVMnxYjhaB4LEvnQAA=", - "dev": true, - "requires": { - "event-stream": "~3.3.4", - "gulp-util": "~3.0.8", - "streamifier": "~0.1.1", - "tar": "^2.2.1", - "through2": "~2.0.3" - } - }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "dev": true, - "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - } - } - } - }, - "gulp-vinyl-zip": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/gulp-vinyl-zip/-/gulp-vinyl-zip-2.1.2.tgz", - "integrity": "sha512-wJn09jsb8PyvUeyFF7y7ImEJqJwYy40BqL9GKfJs6UGpaGW9A+N68Q+ajsIpb9AeR6lAdjMbIdDPclIGo1/b7Q==", - "dev": true, - "requires": { - "event-stream": "3.3.4", - "queue": "^4.2.1", - "through2": "^2.0.3", - "vinyl": "^2.0.2", - "vinyl-fs": "^3.0.3", - "yauzl": "^2.2.1", - "yazl": "^2.2.1" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", - "dev": true, - "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - } - }, - "is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", - "dev": true - }, - "ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } - }, - "queue": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/queue/-/queue-4.5.1.tgz", - "integrity": "sha512-AMD7w5hRXcFSb8s9u38acBZ+309u6GsiibP4/0YacJeaurRshogB7v/ZcVPxP5gD5+zIw6ixRHdutiYUJfwKHw==", - "dev": true, - "requires": { - "inherits": "~2.0.0" - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", - "dev": true, - "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - } - }, - "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - }, - "vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", - "dev": true, - "requires": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" - } - } - } - }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", - "dev": true, - "requires": { - "glogg": "^1.0.0" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", - "dev": true - }, - "is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - }, - "is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", - "dev": true - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "requires": { - "is-unc-path": "^1.0.0" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "requires": { - "unc-path-regex": "^0.1.2" - } - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-valid-glob": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz", - "integrity": "sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", - "dev": true - }, - "lazystream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", - "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", - "dev": true, - "requires": { - "readable-stream": "^2.0.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", - "dev": true, - "requires": { - "flush-write-stream": "^1.0.2" - } - }, - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "requires": { - "lodash._root": "^3.0.0" - } - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true, - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "dev": true, - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" - } - }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", - "dev": true - }, - "markdown-it": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz", - "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "entities": "~1.1.1", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", - "dev": true - }, - "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "dev": true, - "requires": { - "mime-db": "1.40.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.1.tgz", - "integrity": "sha512-SpwyojlnE/WRBNGtvJSNfllfm5PqEDFxcWluSIgLeSBJtXG4DmoX2NNAeEA7rP5kK+79VgtVq8nG6HskaL1ykg==", - "dev": true, - "requires": { - "browser-stdout": "1.3.0", - "commander": "2.11.0", - "debug": "3.1.0", - "diff": "3.3.1", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.3", - "he": "1.1.1", - "mkdirp": "0.5.1", - "supports-color": "4.4.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "multimatch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", - "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", - "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "minimatch": "^3.0.0" - } - }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, - "requires": { - "duplexer2": "0.0.2" - } - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "node.extend": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-1.1.8.tgz", - "integrity": "sha512-L/dvEBwyg3UowwqOUTyDsGBU6kjBQOpOhshio9V3i3BMPv5YUb9+mWNN8MK0IbWqT0AqaTSONZf0aTuMMahWgA==", - "dev": true, - "requires": { - "has": "^1.0.3", - "is": "^3.2.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "requires": { - "once": "^1.3.2" - } - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, - "requires": { - "boolbase": "~1.0.0" - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "ordered-read-streams": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", - "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", - "dev": true, - "requires": { - "is-stream": "^1.0.1", - "readable-stream": "^2.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, - "parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true - }, - "parse-semver": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", - "integrity": "sha1-mkr9bfBj3Egm+T+6SpnPIj9mbLg=", - "dev": true, - "requires": { - "semver": "^5.1.0" - } - }, - "parse5": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", - "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-reader": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-reader/-/path-reader-1.1.0.tgz", - "integrity": "sha512-RT7wPCyH2AqncAMi4L1h+LQAvMBrZU8cnypGqgLzuIfIINT7w6OWm7KLH1tT8AQ+6p6tuujgSm3K90EMmRDTAA==", - "requires": { - "minimatch": "^3.0.4" - } - }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "requires": { - "through": "~2.3" - } - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", - "dev": true, - "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - } - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "psl": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", - "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==", - "dev": true - }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", - "dev": true - }, - "queue": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/queue/-/queue-3.1.0.tgz", - "integrity": "sha1-bEnQHwCeIlZ4h4nyv/rGuLmZBYU=", - "dev": true, - "requires": { - "inherits": "~2.0.0" - } - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", - "dev": true, - "requires": { - "mute-stream": "~0.0.4" - } - }, - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, - "remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - } - }, - "remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", - "dev": true, - "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", - "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", - "dev": true, - "requires": { - "value-or-function": "^3.0.0" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", - "dev": true - }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", - "dev": true, - "requires": { - "through": "2" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stat-mode": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", - "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", - "dev": true - }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dev": true, - "requires": { - "duplexer": "~0.1.1" - } - }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true - }, - "streamfilter": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-1.0.7.tgz", - "integrity": "sha512-Gk6KZM+yNA1JpW0KzlZIhjo3EaBJDkYfXtYSbOwNIQ7Zd6006E6+sCFlW1NDvFG/vnXhKmw6TJJgiEQg/8lXfQ==", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "streamifier": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", - "integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=", - "dev": true - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-bom-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", - "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", - "dev": true, - "requires": { - "first-chunk-stream": "^1.0.0", - "strip-bom": "^2.0.0" - } - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "dev": true, - "requires": { - "has-flag": "^2.0.0" - } - }, - "tar": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", - "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", - "dev": true, - "requires": { - "block-stream": "*", - "fstream": "^1.0.12", - "inherits": "2" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "through2-filter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", - "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", - "dev": true, - "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", - "dev": true - }, - "tmp": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", - "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - }, - "to-absolute-glob": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz", - "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", - "dev": true, - "requires": { - "through2": "^2.0.3" - } - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } - } - }, - "tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", - "dev": true - }, - "tslint": { - "version": "5.9.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz", - "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.22.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^3.2.0", - "glob": "^7.1.1", - "js-yaml": "^3.7.0", - "minimatch": "^3.0.4", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.12.1" - }, - "dependencies": { - "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", - "dev": true - } - } - }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "tunnel": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz", - "integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "typed-rest-client": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-0.9.0.tgz", - "integrity": "sha1-92jMDcP06VDwbgSCXDaz54NKofI=", - "dev": true, - "requires": { - "tunnel": "0.0.4", - "underscore": "1.8.3" - }, - "dependencies": { - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", - "dev": true - } - } - }, - "typescript": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz", - "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=", - "dev": true - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true - }, - "underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", - "dev": true - }, - "unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", - "dev": true, - "requires": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - }, - "dependencies": { - "through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - } - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-join": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz", - "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=", - "dev": true - }, - "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true - }, - "vali-date": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", - "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=", - "dev": true - }, - "value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vinyl": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", - "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", - "dev": true, - "requires": { - "clone": "^0.2.0", - "clone-stats": "^0.0.1" - } - }, - "vinyl-fs": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", - "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", - "dev": true, - "requires": { - "duplexify": "^3.2.0", - "glob-stream": "^5.3.2", - "graceful-fs": "^4.0.0", - "gulp-sourcemaps": "1.6.0", - "is-valid-glob": "^0.3.0", - "lazystream": "^1.0.0", - "lodash.isequal": "^4.0.0", - "merge-stream": "^1.0.0", - "mkdirp": "^0.5.0", - "object-assign": "^4.0.0", - "readable-stream": "^2.0.4", - "strip-bom": "^2.0.0", - "strip-bom-stream": "^1.0.0", - "through2": "^2.0.0", - "through2-filter": "^2.0.0", - "vali-date": "^1.0.0", - "vinyl": "^1.0.0" - }, - "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "vinyl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", - "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", - "dev": true, - "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - } - } - } - }, - "vinyl-source-stream": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vinyl-source-stream/-/vinyl-source-stream-1.1.2.tgz", - "integrity": "sha1-YrU6E1YQqJbpjKlr7jqH8Aio54A=", - "dev": true, - "requires": { - "through2": "^2.0.3", - "vinyl": "^0.4.3" - } - }, - "vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", - "dev": true, - "requires": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - } - } - }, - "vsce": { - "version": "1.37.5", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.37.5.tgz", - "integrity": "sha512-AIFKhrdhp/sJ2eYM/Nq1xzJPxo/IHemStkQsNIvbY/pyB8gADvrSayUfw4eLt6ffyKa+6+OeExyDvAg6L3X4Pw==", - "dev": true, - "requires": { - "cheerio": "^1.0.0-rc.1", - "commander": "^2.8.1", - "denodeify": "^1.2.1", - "glob": "^7.0.6", - "lodash": "^4.15.0", - "markdown-it": "^8.3.1", - "mime": "^1.3.4", - "minimatch": "^3.0.3", - "osenv": "^0.1.3", - "parse-semver": "^1.1.1", - "read": "^1.0.7", - "semver": "^5.1.0", - "tmp": "0.0.29", - "url-join": "^1.1.0", - "vso-node-api": "6.1.2-preview", - "yauzl": "^2.3.1", - "yazl": "^2.2.2" - } - }, - "vscode": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.10.tgz", - "integrity": "sha512-MvFXXSGuhw0Q6GC6dQrnRc0ES+63wpttGIoYGBMQnoS9JFCCNC/rWfX0lBCHJyuKL2Q8CYg0ROsMEHbHVwEtVw==", - "dev": true, - "requires": { - "glob": "^7.1.2", - "gulp-chmod": "^2.0.0", - "gulp-filter": "^5.0.1", - "gulp-gunzip": "1.0.0", - "gulp-remote-src": "^0.4.3", - "gulp-symdest": "^1.1.0", - "gulp-untar": "^0.0.6", - "gulp-vinyl-zip": "^2.1.0", - "mocha": "^4.0.1", - "request": "^2.83.0", - "semver": "^5.4.1", - "source-map-support": "^0.5.0", - "url-parse": "^1.1.9", - "vinyl-source-stream": "^1.1.0" - }, - "dependencies": { - "mocha": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", - "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", - "dev": true, - "requires": { - "browser-stdout": "1.3.0", - "commander": "2.11.0", - "debug": "3.1.0", - "diff": "3.3.1", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.3", - "he": "1.1.1", - "mkdirp": "0.5.1", - "supports-color": "4.4.0" - } - } - } - }, - "vscode-debugadapter": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.27.0.tgz", - "integrity": "sha512-JwE3fWmKnpjYnFqhff0umqIJi4c26gh/CXZ5LNb4gLIuPd5sEAEoEbGeCcAaajuTrVxFw6FlYEep9y+IQCf+ww==", - "requires": { - "vscode-debugprotocol": "1.27.0" - } - }, - "vscode-debugadapter-testsupport": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/vscode-debugadapter-testsupport/-/vscode-debugadapter-testsupport-1.27.0.tgz", - "integrity": "sha512-rhNVFSeEtSfHZ8ZES1AKaY3vjfEgRbnikCsoDV0/Vu/jfuGnUlNMgWm+vbvtTlTIBPNjn2KKXU6ymYDjMwuW2Q==", - "dev": true, - "requires": { - "vscode-debugprotocol": "1.27.0" - } - }, - "vscode-debugprotocol": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.27.0.tgz", - "integrity": "sha512-cg3lKqVwxNpO2pLBxSwkBvE7w06+bHfbA/s14u8izSWyhJtPgRu1lQwi5tEyTRuwfEugfoPwerYL4vtY6teQDw==" - }, - "vso-node-api": { - "version": "6.1.2-preview", - "resolved": "https://registry.npmjs.org/vso-node-api/-/vso-node-api-6.1.2-preview.tgz", - "integrity": "sha1-qrNUbfJFHs2JTgcbuZtd8Zxfp48=", - "dev": true, - "requires": { - "q": "^1.0.1", - "tunnel": "0.0.4", - "typed-rest-client": "^0.9.0", - "underscore": "^1.8.3" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "yazl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", - "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3" - } - } - } -} diff --git a/package.json b/package.json old mode 100755 new mode 100644 index ae28c7e..ff61162 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "luapanda", "displayName": "LuaPanda", - "version": "2.3.0", + "version": "3.3.1", "publisher": "stuartwang", - "description": "lua debug adapters for VS Code.", + "description": "lua debug and code tools for VS Code.", "author": { "name": "stuartwang", "email": "3030078087@qq.com" @@ -12,17 +12,20 @@ "keywords": [ "lua", "debug", - "panda", "analyzer", - "luaanalyzer" + "luaanalyzer", + "luadebug", + "lua debug", + "lua panda" ], "engines": { - "vscode": "^1.18.0", - "node": "^7.9.0" + "vscode": "^1.25.0", + "node": "^16.13.2" }, - "icon": "images/luapanda_logo.png", + "icon": "res/icon/luapanda_logo.png", "categories": [ - "Debuggers" + "Debuggers", + "Programming Languages" ], "repository": { "type": "git", @@ -32,30 +35,37 @@ "url": "https://github.com/Tencent/LuaPanda.git" }, "scripts": { - "vscode:prepublish": "tsc -p ./src", + "vscode:prepublish": "npm run compile", "compile": "tsc -p ./src", + "watch": "tsc -watch -p ./src", "package": "vsce package", - "publish": "vsce package", - "postinstall": "node ./node_modules/vscode/bin/install" + "publish": "vsce package" }, "dependencies": { "await-notify": "1.0.1", + "lua-fmt": "^2.6.0", + "luaparse": "^0.2.1", + "path-reader": "^1.1.0", + "univac": "0.0.8", + "urlencode": "^1.1.0", "vscode-debugadapter": "1.27.0", "vscode-debugprotocol": "1.27.0", - "path-reader": "^1.1.0" + "vscode-languageclient": "^5.2.1", + "vscode-languageserver": "^5.2.1" }, "devDependencies": { - "@types/node": "7.0.55", "@types/mocha": "2.2.48", - "typescript": "2.6.2", - "mocha": "5.0.1", - "vscode": "1.1.10", - "vscode-debugadapter-testsupport": "1.27.0", + "@types/node": "^16.13.2", + "@types/vscode": "^1.25.0", + "mocha": "10.2.0", "tslint": "5.9.1", - "vsce": "1.37.5" + "typescript": "^5.2.2", + "vsce": "^2.15.0", + "vscode-debugadapter-testsupport": "1.27.0" }, "main": "./out/extension", "activationEvents": [ + "onLanguage:lua", "onDebug" ], "contributes": { @@ -67,106 +77,357 @@ "commands": [ { "command": "luapanda.reloadLuaDebug", - "title": "reloadLuaDebug" + "title": "Reload LuaPanda" }, { "command": "luapanda.LuaGarbageCollect", "title": "LuaGarbageCollect" + }, + { + "command": "luapanda.openSettingsPage", + "title": "openSettingsPage" + } + ], + "configuration": { + "type": "object", + "title": "lua-panda configuration", + "properties": { + "lua_analyzer.codeLinting.enable": { + "scope": "resource", + "type": "boolean", + "default": true, + "description": "Enable/Disable code lingting. This is the master switch of code linting." + }, + "lua_analyzer.codeLinting.luacheckPath": { + "scope": "resource", + "type": "string", + "default": "", + "description": "The laucheck executable file path, use plugin default config if not set." + }, + "lua_analyzer.codeLinting.luaVersion": { + "scope": "resource", + "type": "string", + "enum": [ + "5.1", + "5.3", + "5.1+5.3" + ], + "default": "5.1+5.3", + "description": "Set standard globals for luacheck." + }, + "lua_analyzer.codeLinting.checkWhileTyping": { + "scope": "resource", + "type": "boolean", + "default": true, + "description": "Controls wether to check the syntax while typing." + }, + "lua_analyzer.codeLinting.checkAfterSave": { + "scope": "resource", + "type": "boolean", + "default": true, + "description": "Controls wether to check the syntax after file saved." + }, + "lua_analyzer.codeLinting.maxNumberOfProblems": { + "scope": "resource", + "type": "number", + "default": 100, + "description": "Controls the maximum number of problems produced by the luacheck." + }, + "lua_analyzer.codeLinting.maxLineLength": { + "scope": "resource", + "type": "number", + "default": 120, + "description": "Set maximum allowed line length (default: 120)." + }, + "lua_analyzer.codeLinting.ignoreFolderRegularExpression": { + "scope": "resource", + "type": "string", + "default": ".*/res/lua/\\w+\\.lua;.*vscode/LuaPanda/IntelliSenseRes/;", + "description": "The regular expressions used to ignore files in some specific folders. Use semicolons to separate different regular expressions." + }, + "lua_analyzer.codeLinting.ignoreErrorCode": { + "scope": "resource", + "type": "string", + "default": "", + "description": "Used to ignore some warnings or errors. Use semicolons to separate different error codes. For example, 211 means \"unused local variable\", you will see \"unused variable 'xxx' lua-analyzer(211)\" in the problems window. View https://luacheck.readthedocs.io/en/stable/warnings.html for more information." + }, + "lua_analyzer.codeLinting.ignoreGlobal": { + "scope": "resource", + "type": "string", + "default": "UnityEngine;com", + "description": "Used to ignore global variable for luacheck. Use semicolons to separate different variables." + }, + "lua_analyzer.trace.server": { + "scope": "window", + "type": "string", + "enum": [ + "off", + "messages", + "verbose" + ], + "default": "off", + "description": "Traces the communication between VS Code and the language server." + } + } + }, + "snippets": [ + { + "language": "lua", + "path": "./res/snippets/snippets.json" } ], "debuggers": [ { "type": "lua", "label": "LuaPanda", - "program": "./out/debugAdapter.js", + "program": "./out/debug/debugAdapter.js", "runtime": "node", "configurationAttributes": { - "launch": { + "attach": { "properties": { - "program": { + "stopOnEntry": { + "type": "boolean", + "description": "After the debugger connect, it automatically stops at the first line of code called. \n调试器启动后自动停止在调用的第一行代码处。", + "default": true + }, + "cwd": { + "type": "string", + "description": "exactltPathMode : 拼接路径 | autoPathMode : workspace 路径。", + "default": "${workspaceFolder}" + }, + "isNeedB64EncodeStr": { + "type": "boolean", + "description": "Whether use base64 encoding for transmit strings. default true. \n是否使用base64编码传输字符串,默认请开启。", + "default": true + }, + "luaFileExtension": { "type": "string", - "description": "随调试器启动的可执行文件路径", + "description": "Lua file suffix. \nLua文件后缀。", "default": "" }, - "args": { + "pathCaseSensitivity": { + "type": "boolean", + "description": "File path is case sensitive, default true. \n文件路径大小写敏感, 默认true。", + "default": true + }, + "connectionPort": { + "type": "number", + "description": "The port number of the connection. Default is 8818. \n设置连接的端口号,默认8818。", + "default": 8818 + }, + "autoReconnect": { + "type": "boolean", + "description": "When disconnected by the lua end, VSCode automatically waits for new reconnection. \n当被lua端断开连接时,VSCode自动等待重连。", + "default": true + }, + "TempFilePath": { + "type": "string", + "description": "Temporary file storage path generated during debugging, please ensure this folder exists. \n调试产生的临时文件存放路径,请确保文件夹存在。", + "default": "${workspaceFolder}" + }, + "rootFolder": { + "type": "string", + "description": "记录vscode打开的目录,不要修改它的值,需用它找到 launch.json", + "default": "${workspaceFolder}" + }, + "logLevel": { + "type": "number", + "description": "Log level. 0: All information(for developer); 1: Only necessary information (default); 2: Only error. \n日志等级. 0:all所有信息(面向开发者); 1:info仅必要信息(默认); 2:error仅错误。", + "default": 1 + }, + "useCHook": { + "type": "boolean", + "description": "Use C Hook module to improve efficiency. The default is true. If set false debug with lua hook. \n使用C Hook模块, 提高运行效率。默认true。设置为false则使用 lua hook 调试。", + "default": true + }, + "packagePath": { "type": "array", - "description": "program 执行时需要的参数, 结构是一个字符串数组", + "description": "Path added to package.path at runtime, such as /Script/?.lua. \n运行时加入package.path的路径, 如 /Script/?.lua。", "default": [] }, - "trace": { + "luaPath": { + "type": "string", + "description": "Lua command path and file name such as C:/lua5.3/lua.exe. \nlua命令的路径和文件名. 如 C:/lua5.3/lua.exe。 ", + "default": "" + }, + "docPathReplace": { + "type": "array", + "description": "被调试文件和VSCode中加载文件路径不一致时,通过此项设置,完成路径替换。输入格式[\"被替换掉的路径\", \"新路径\"]。", + "default": [] + }, + "autoPathMode": { + "type": "boolean", + "description": "Whether to use automatic path mode. \n是否使用自动路径模式。", + "default": true + }, + "updateTips": { + "type": "boolean", + "description": "Whether open LuaPanda automatic upgrade prompt. \n是否打开 LuaPanda 自动升级提示。", + "default": true + }, + "tag": { + "type": "string", + "enum": [ + "normal", + "attach", + "independent_file" + ], + "description": "Mark run mode, don't modify it. There are three modes: normal, attach, independent_file. \n标记运行方式,请勿修改。 有三种模式 normal , attach , independent_file", + "default": "normal" + }, + "dbCheckBreakpoint": { + "type": "string", + "description": "If adapter is client , the IP to connect. Default is '127.0.0.1'。", + "default": "127.0.0.1" + }, + "distinguishSameNameFile": { + "type": "boolean", + "description": "Whether distinguish breakpoint in files with same name", + "default": false + }, + "truncatedOPath": { + "type": "string", + "description": " ", + "default": "" + }, + "DevelopmentMode": { "type": "boolean", - "description": "Enable logging of the Debug Adapter Protocol.", + "description": "Whether enter development mode", "default": false + } + } + }, + "launch": { + "properties": { + "program": { + "type": "string", + "description": "Path to executable file which launched with debugger(not a lua file), default \"\". \n随调试器启动的可执行文件路径(非lua文件), 如无可以填\"\"。", + "default": "" + }, + "args": { + "type": "array", + "description": "Program execution parameters, the structure is an array of strings. \nprogram 执行时需要的参数, 结构是一个字符串数组。", + "default": [] }, "stopOnEntry": { "type": "boolean", - "description": "调试器启动后自动停止在调用的第一行代码处", + "description": "After the debugger connect, it automatically stops at the first line of code called. \n调试器启动后自动停止在调用的第一行代码处。", "default": true }, "cwd": { "type": "string", - "description": "exactltPathMode : 拼接路径 | autoPathMode : workspace 路径", + "description": "exactltPathMode : 拼接路径 | autoPathMode : workspace 路径。", "default": "${workspaceFolder}" }, "isNeedB64EncodeStr": { "type": "boolean", - "description": "是否使用base64编码传输字符串,默认请开启。", + "description": "Whether use base64 encoding for transmit strings. default true. \n是否使用base64编码传输字符串,默认请开启。", "default": true }, "luaFileExtension": { "type": "string", - "description": "Lua文件后缀", + "description": "Lua file suffix. \nLua文件后缀。", "default": "" }, "pathCaseSensitivity": { "type": "boolean", - "description": "文件路径大小写敏感, 默认true", + "description": "File path is case sensitive, default true. \n文件路径大小写敏感, 默认true。", "default": true }, "connectionPort": { "type": "number", - "description": "设置连接的端口号,默认8818", + "description": "The port number of the connection. Default is 8818. \n设置连接的端口号,默认8818。", "default": 8818 }, "autoReconnect": { "type": "boolean", - "description": "当连接被lua端断开时,VSCode自动等待重连", + "description": "When disconnected by the lua end, VSCode automatically waits for new reconnection. \n当被lua端断开连接时,VSCode自动等待重连。", "default": true }, + "rootFolder": { + "type": "string", + "description": "记录vscode打开的目录,可以利用它找到 launch.json", + "default": "${workspaceFolder}" + }, "TempFilePath": { "type": "string", - "description": "调试产生的临时文件存放路径,请确保文件夹存在", + "description": "Temporary file storage path generated during debugging, please ensure this folder exists. \n调试产生的临时文件存放路径,请确保文件夹存在。", "default": "${workspaceFolder}" }, "logLevel": { "type": "number", - "description": "日志等级. 0:all所有信息; 1:info仅必要信息(默认); 2:error仅错误", + "description": "Log level. 0: All information(for developer); 1: Only necessary information (default); 2: Only error. \n日志等级. 0:all所有信息(面向开发者); 1:info仅必要信息(默认); 2:error仅错误。", "default": 1 }, "useCHook": { "type": "boolean", - "description": "使用C Hook模块, 提高运行效率。默认true。设置为false则使用 lua hook 调试", + "description": "Use C Hook module to improve efficiency. The default is true. If set false debug with lua hook. \n使用C Hook模块, 提高运行效率。默认true。设置为false则使用 lua hook 调试。", "default": true }, "packagePath": { "type": "array", - "description": "数组中的路径在运行时会加入package.path. 如 /Script/?.lua ", + "description": "Path added to package.path at runtime, such as /Script/?.lua. \n运行时加入package.path的路径, 如 /Script/?.lua。", "default": [] }, "luaPath": { "type": "string", - "description": "lua命令的路径和文件名. 如 C:/lua5.3/lua.exe ", + "description": "Lua command path and file name such as C:/lua5.3/lua.exe. \nlua命令的路径和文件名. 如 C:/lua5.3/lua.exe。 ", "default": "" }, "docPathReplace": { "type": "array", - "description": "被调试文件和VSCode中加载文件路径不一致时,通过此项设置,完成路径替换。输入格式[\"被替换掉的路径\", \"新路径\"]", + "description": "被调试文件和VSCode中加载文件路径不一致时,通过此项设置,完成路径替换。输入格式[\"被替换掉的路径\", \"新路径\"]。", "default": [] }, "autoPathMode": { "type": "boolean", - "description": "是否使用自动路径模式", + "description": "Whether to use automatic path mode. \n是否使用自动路径模式。", "default": true + }, + "updateTips": { + "type": "boolean", + "description": "Whether open LuaPanda automatic upgrade prompt. \n是否打开 LuaPanda 自动升级提示。", + "default": true + }, + "tag": { + "type": "string", + "description": "Mark run mode, don't modify it. There are three modes: normal, attach, independent_file. \n标记运行方式,请勿修改。 有三种模式 normal , attach , independent_file", + "default": "normal" + }, + "VSCodeAsClient": { + "type": "boolean", + "description": "Usually adapter as Server, lua process as Client. this mode is invert these. Please also set connectionIP。", + "default": false + }, + "connectionIP": { + "type": "string", + "description": "If adapter is client , the IP to connect. Default is '127.0.0.1'。", + "default": "127.0.0.1" + }, + "dbCheckBreakpoint": { + "type": "boolean", + "description": "Whether double check breakpoint hit", + "default": true + }, + "distinguishSameNameFile": { + "type": "boolean", + "description": "Whether distinguish breakpoint in files with same name", + "default": false + }, + "truncatedOPath": { + "type": "string", + "description": " ", + "default": "" + }, + "DevelopmentMode": { + "type": "boolean", + "description": "Whether enter development mode", + "default": false + }, + "description": { + "type": "string", + "description": "当前模式的描述", + "default": "" } } } @@ -175,27 +436,28 @@ { "type": "lua", "request": "launch", + "tag": "normal", "name": "LuaPanda", - "program": "", + "description": "通用模式,通常调试项目请选择此模式 | launchVer:3.2.0", "cwd": "${workspaceFolder}", "luaFileExtension": "", "connectionPort": 8818, "stopOnEntry": true, "useCHook": true, - "autoPathMode": true, - "logLevel": 1 + "autoPathMode": true }, { "type": "lua", "request": "launch", - "name": "LuaPanda-DebugFile", + "tag": "independent_file", + "name": "LuaPanda-IndependentFile", + "description": "独立文件调试模式,使用前请参考文档", "luaPath": "", "packagePath": [], "luaFileExtension": "", - "connectionPort": 8818, + "connectionPort": 8820, "stopOnEntry": true, - "useCHook": true, - "logLevel": 1 + "useCHook": true } ] } diff --git a/readme.md b/readme.md index d6dff58..ff23ac7 100644 --- a/readme.md +++ b/readme.md @@ -1,106 +1,98 @@ # LuaPanda -LuaPanda 是一个基于 VS Code 的 lua 代码调试器。设计目标是简单易用,支持多种开发框架。它由两部分组成: +LuaPanda 是一个基于 VS Code 的 lua 代码工具,设计目标是简单易用。它支持多种开发框架,主要提供以下功能: -- VS Code Extension 调试器 VSCode 插件 -- Lua Debugger 调试器的 debugger 部分 +- 代码补全(code completion) +- 代码片段(snippet completion) +- 定义跳转(definition) +- 生成注释(comment generation) +- 类型推断(limited type inference) +- 代码格式化(formatting): 依赖 [lua-fmt](https://github.com/trixnz/lua-fmt) +- 代码诊断(linting):依赖 [luacheck](https://github.com/mpeterv/luacheck) +- 调试器(debugger) -Debugger 主体使用 lua 实现,另含一个 C 扩展模块,以保证高速运行。 -LuaPanda 支持 lua5.1- 5.3,运行环境要包含 LuaSocket。 +功能详情可以参考[项目介绍](./Docs/Manual/feature-introduction.md)。LuaPanda 支持 lua5.1- 5.4,**调试器运行环境需要包含 luasocket**。 -LuaPanda 的立项源于潘多拉项目中大量的lua调试需求。`潘多拉`为游戏提供嵌入式跨引擎的运营开发能力,使游戏研发及运营各自独立闭环,在游戏内实现各种营销活动和周边系统,让游戏分工更加专业,团队更加专注,高效产出价值。 -潘多拉为游戏提供的服务包括用户生命周期的精细化运营方案、游戏内直播解决方案、游戏内内容社区解决方案、游戏内商城商业化解决方案等,已经在大量腾讯精品游戏中上线、稳定运营。 +# 文档 -# Tips - -+ 版本说明和升级建议 - - 目前调试器最新版本是2.3.0(推荐使用),2.2.x和2.1.0 也可使用,更早的版本不再支持。如调试遇到问题建议大家手动把调试器的 `LuaPanda.lua,DebugTools.lua` 两个文件更新到最新版本。`LuaPanda.lua` 的版本可以在此文件头部查看。 - - Release下载地址:https://github.com/Tencent/LuaPanda/releases - - - -+ 关于找不到`libpdebug`模块报错 +接入和使用文档 - `libpdebug.so(dll)` 是放置在VSCode插件中的调试器C扩展,会在调试器运行时自动加载,作用是加速调试。 +[项目介绍](./Docs/Manual/feature-introduction.md) | [快速试用指引](./Docs/Manual/quick-use.md) | [调试器接入指引](./Docs/Manual/access-guidelines.md) | [真机调试](./Docs/Manual/debug-on-phone.md) | [其他调试能力](./Docs/Manual/common-functions.md) | [升级说明](./Docs/Manual/update.md) | [FAQ](./Docs/Manual/FAQ.md) - xlua允许用户重写文件加载函数`CustomLoader`,sluaunreal也提供类似方法`setLoadFileDelegate`。 +更多文档 - 发生此问题的原因之一是用户重写的加载函数中没有加入对so/dll的处理,加载so/dll时会报找不到文件错误,但随后执行lua原生loader能够正确加载libpdebug。 +[全部文档](./Docs) - 查看libpdebug.so是否加载的方式是在控制台输入`LuaPanda.getInfo()`, 返回信息中有 hookLib Ver 说明libpdebug已经加载。此时可以忽略报错或在文件加载函数函数中正确处理.so/dll。 -# 近期更新 +# 特性 -+ v2.3.0 - - + 增加了自动路径识别功能 - - 2.3.0版本插件在launch.json文件中新增了`autoPathMode`设置项。当设置为`true`时,调试器会根据文件名查询完整路径。用户在接入时不必再进行繁琐的路径配置。当此配置项为false或者未配置时,使用传统的路径拼接方式。 - - 另外,用户需要确保**VSCode打开的工程目录**中不存在同名lua文件,才可以使用本功能。否则调试器可能会把断点指向错误的文件,造成执行异常。 ++ 支持常用的代码补全,代码片段,定义跳转,生成注释,符号列表等功能 - + 测试了在cocos2dx下的运行情况 ++ 支持单步调试,断点调试,条件断点,协程调试 - 之前的版本在cocos2dx中运行时会报查找 c 库错误。2.3.0 修复了此问题,测试 cocos2dx 在 win/mac 下都可以使用c库。 - - 另外调试器目前支持标准lua虚拟机,cocos2dx集成的是luajit,可能会在单步时出现跳步的情况,后续完整支持luajit会解决此问题。 - - 如希望体验新功能,请按照 [升级说明](./Docs/Manual/update.md) 手动替换工程中的 `LuaPanda.lua,DebugTools.lua` 文件。 ++ 支持lua5.1 - 5.4, win/mac 平台,支持 slua/xlua/slua-unreal 等框架 ++ 支持REPL : 在断点处可以监视和运行表达式,并返回执行结果 ++ 可以根据断点密集程度自动调整 hook 频率,有较好的效率 -+ v2.2.1 ++ 支持 attach 模式,lua 运行过程中可随时建立连接 - 小幅更新,优化了单文件调试和调试控制台的使用。 ++ 使用 lua / C 双调试引擎。lua 部分可动态下发,避免打包后无法调试。C 部分效率高,适合开发期调试。 - - 修复单文件调试 文件路径中的 \ 被当做转义符的问题。 - - 修复单文件调试 首次运行窗口报错的问题。 - - 优化调试控制台的使用,动态执行表达式不必再加p 。 ++ 支持多目标调试(multi target) ,可以同时调试多个 lua 进程。 -+ v2.2.0 +# 近期更新 - 增加了`LuaPanda.doctor()` 命令,可以检查环境中的错误(需更新LuaPanda.lua和DebugTools.lua至2.2.0)。 +​ 3.2.0 版本因依赖库太旧,无法在 VSCode 1.82 上运行,可更新 3.3.0 解决此问题,详见 #171 - 修复了VSCode请求变量信息但在lua中发生错误,导致调试器卡住的问题。 - 修复了c库在一些框架下无法正常运行,导致程序自动退出的问题。 - ++ V3.3.0 -# 特性 + + 修复了 VSCode 1.82 下插件执行错误的问题 + + lua 504 下 mac arm / win x64 已支持 chook,其他平台没有机器测试,所以未出 libpdebug 库。有需要大家可以自行打包 plibdebug 库,也可提 mr。 + + 更新了版本间的 mr + + #108 调试启动比较晚时,已经创建的协程无法调试 / 调试堆栈碰到c函数被打断 + + #114 修复调试栈中有C函数时,监听的变量获取错误的bug + + #109 无法动态attach到debug + + #139 做了 5.4.3 下 luasocket sock:receive() 默认行为不一致导致的错误 + + #152 launch.json 启动参数 program 路径带有空格则启动失败 + + 因依赖库版本太旧无法兼容新版本 VSCode,删除了导出符号用于代码提示功能 -+ 支持单步调试,条件断点,协程调试,支持调试时变量赋值。 -+ 支持lua5.1- 5.3, 支持 slua/xlua/slua-unreal 等框架 -+ 在断点处可以监视和运行表达式,返回结果 -+ 可以根据断点密集程度调整 hook 频率, 有较高的效率 -+ 支持 attach 模式,lua 运行过程中可随时建立连接 -+ 使用 lua / C 双调试引擎。lua 部分可动态下发,避免打包后无法调试。C 部分效率高,适合开发期调试。 + ++ V3.2.0 + + 代码提示支持大小写不敏感,无论输入大小写都能提示对应的符号。 + + 支持多端调试(multi target), 在一个VSCode面板中可以启动多个不同port的调试端,连接多个lua进程。 + + 支持反转client-server。目前 vscode 插件作为 server , lua 进程作为 client。支持通过设置反转,方便真机调试。 + + 支持require 路径中含有 . , 目前只支持require("A/B"), 后续支持require("A.B")的形式,无需设置 + + 在autoPath模式支持同名文件 + + 重新测试和优化真机调试,修复真机调试socket连接可能存在的问题 ++ [更多更新记录](./CHANGELOG.md) -# 接入和开发指引 -接入和使用文档 -[项目介绍](./Docs/Manual/feature-introduction.md) | [快速试用指引](./Docs/Manual/quick-use.md) | [接入指引](./Docs/Manual/access-guidelines.md) | [真机调试](./Docs/Manual/debug-on-phone.md) | [单文件调试和运行](./Docs/Manual/debug-file.md) | [升级说明](./Docs/Manual/update.md) | [FAQ](./Docs/Manual/FAQ.md) +# 依赖和适用性 -调试器开发文档 +调试器功能依赖 luasocket , 可运行于 slua,slua-unreal ,xlua 等已集成 luasocket 的开发环境,在其他环境(如 console)中运行时,需要用户自行保证 luasocket 可用 。 -[工程说明](./Docs/Development-instructions/project-description.md) | [调试器开发指引](./Docs/Development-instructions/how_to_join.md) | [特性简述](./Docs/Development-instructions/debugger-principle.md) +其他依赖项目(插件中已包含,无需用户手动安装): ++ [**luaparse**](https://github.com/oxyc/luaparse) ++ [**luacheck**](https://github.com/mpeterv/luacheck) -# 依赖和适用性 ++ [**lua-fmt**](https://github.com/trixnz/lua-fmt) -调试器依赖 LuaSocket , 可运行于 slua,slua-unreal ,xlua 等已集成 LuaSocket 的 lua 环境,也可以在 console 中调试。lua 版本支持 5.1- 5.3。 ++ [**path-reader**](https://github.com/ackerapple/path-reader) @@ -108,7 +100,7 @@ LuaPanda 的立项源于潘多拉项目中大量的lua调试需求。`潘多拉` 我们非常期待您的贡献,无论是完善文档,提出、修复 Bug 或是增加新特性。 如果您在使用过程中发现文档不够完善,欢迎记录下来并提交。 -如果发现 Bug,请通过 [issues](https://github.com/Tencent/LuaPanda/issues) 来提交并描述相关的问题,您也可以在这里查看其它的 issue,通过解决这些 issue 来贡献代码。 +如果发现 bug,请通过 [issues](https://github.com/Tencent/LuaPanda/issues) 来提交并描述相关的问题,您也可以在这里查看其它的 issue,通过解决这些 issue 来贡献代码。 请将pull request提交在 `dev` 分支上,经过测试后会在下一版本合并到 `master` 分支。更多规范请看[CONTRIBUTING](./CONTRIBUTING.md) @@ -118,7 +110,5 @@ LuaPanda 的立项源于潘多拉项目中大量的lua调试需求。`潘多拉` # 技术支持 -如有问题先参阅 [FAQ](./Docs/Manual/FAQ.md) ,如有问题建议使用 [issues](https://github.com/Tencent/LuaPanda/issues) ,我们会关注和回复。 - -QQ群:974257225 +如有问题可以先参阅 [文档](./Docs), 可以使用 [issues](https://github.com/Tencent/LuaPanda/issues) ,我会关注和回复。 diff --git a/images/luapanda_logo.png b/res/icon/luapanda_logo.png similarity index 100% rename from images/luapanda_logo.png rename to res/icon/luapanda_logo.png diff --git a/res/lua/coroutine.lua b/res/lua/coroutine.lua new file mode 100755 index 0000000..a2f4d39 --- /dev/null +++ b/res/lua/coroutine.lua @@ -0,0 +1,49 @@ +------------------------------------------------------------------------ +-- Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +-- +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------ + +-- Coroutine Manipulation http://www.lua.org/manual/5.3/manual.html#6.2 +-- This library comprises the operations to manipulate coroutines, which come inside the table coroutine. See §2.6 for a general description of coroutines. +coroutine = {} + +-- Creates a new coroutine, with body f. f must be a function. Returns this new coroutine, an object with type "thread". +function coroutine.create(f) end + +-- Returns true when the running coroutine can yield. +-- A running coroutine is yieldable if it is not the main thread and it is not inside a non-yieldable C function. +function coroutine.isyieldable() end + +-- Starts or continues the execution of coroutine co. The first time you resume a coroutine, it starts running its body. The values val1, ... are passed as the arguments to the body function. If the coroutine has yielded, resume restarts it; the values val1, ... are passed as the results from the yield. +-- If the coroutine runs without any errors, resume returns true plus any values passed to yield (when the coroutine yields) or any values returned by the body function (when the coroutine terminates). If there is any error, resume returns false plus the error message. +function coroutine.resume(co, val1, ...) end + +-- Returns the running coroutine plus a boolean, true when the running coroutine is the main one. +function coroutine.running() end + +-- Returns the status of coroutine co, as a string: "running", if the coroutine is running (that is, it called status); "suspended", if the coroutine is suspended in a call to yield, or if it has not started running yet; "normal" if the coroutine is active but not running (that is, it has resumed another coroutine); and "dead" if the coroutine has finished its body function, or if it has stopped with an error. +function coroutine.status(co) end + +-- Creates a new coroutine, with body f. f must be a function. Returns a function that resumes the coroutine each time it is called. Any arguments passed to the function behave as the extra arguments to resume. Returns the same values returned by resume, except the first boolean. In case of error, propagates the error. +function coroutine.wrap(f) end + +-- Suspends the execution of the calling coroutine. Any arguments to yield are passed as extra results to resume. +function coroutine.yield(...) end diff --git a/res/lua/debug.lua b/res/lua/debug.lua new file mode 100755 index 0000000..6d1504d --- /dev/null +++ b/res/lua/debug.lua @@ -0,0 +1,92 @@ +------------------------------------------------------------------------ +-- Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +-- +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------ + +--- The Debug Library http://www.lua.org/manual/5.3/manual.html#6.10 + +-- This library provides the functionality of the debug interface (§4.9) to Lua programs. You should exert care when using this library. Several of its functions violate basic assumptions about Lua code (e.g., that variables local to a function cannot be accessed from outside; that userdata metatables cannot be changed by Lua code; that Lua programs do not crash) and therefore can compromise otherwise secure code. Moreover, some functions in this library may be slow. +-- All functions in this library are provided inside the debug table. All functions that operate over a thread have an optional first argument which is the thread to operate over. The default is always the current thread. +debug = {} + +-- Enters an interactive mode with the user, running each string that the user enters. Using simple commands and other debug facilities, the user can inspect global and local variables, change their values, evaluate expressions, and so on. A line containing only the word cont finishes this function, so that the caller continues its execution. +-- Note that commands for debug.debug are not lexically nested within any function and so have no direct access to local variables. +function debug.debug() end + +-- Returns the current hook settings of the thread, as three values: the current hook function, the current hook mask, and the current hook count (as set by the debug.sethook function). +function debug.gethook(thread) end + +-- Returns a table with information about a function. You can give the function directly or you can give a number as the value of f, which means the function running at level f of the call stack of the given thread: level 0 is the current function (getinfo itself); level 1 is the function that called getinfo (except for tail calls, which do not count on the stack); and so on. If f is a number larger than the number of active functions, then getinfo returns nil. +-- The returned table can contain all the fields returned by lua_getinfo, with the string what describing which fields to fill in. The default for what is to get all information available, except the table of valid lines. If present, the option 'f' adds a field named func with the function itself. If present, the option 'L' adds a field named activelines with the table of valid lines. +-- For instance, the expression debug.getinfo(1,"n").name returns a name for the current function, if a reasonable name can be found, and the expression debug.getinfo(print) returns a table with all available information about the print function. +function debug.getinfo(thread, f, what) end + +-- This function returns the name and the value of the local variable with index local of the function at level f of the stack. This function accesses not only explicit local variables, but also parameters, temporaries, etc. +-- The first parameter or local variable has index 1, and so on, following the order that they are declared in the code, counting only the variables that are active in the current scope of the function. Negative indices refer to vararg arguments; -1 is the first vararg argument. The function returns nil if there is no variable with the given index, and raises an error when called with a level out of range. (You can call debug.getinfo to check whether the level is valid.) +-- Variable names starting with '(' (open parenthesis) represent variables with no known names (internal variables such as loop control variables, and variables from chunks saved without debug information). +-- The parameter f may also be a function. In that case, getlocal returns only the name of function parameters. +function debug.getlocal(thread, f, var) end + +-- Returns the metatable of the given value or nil if it does not have a metatable. +function debug.getmetatable(value) end + +-- Returns the registry table (see §4.5). +function debug.getregistry() end + +-- This function returns the name and the value of the upvalue with index up of the function f. The function returns nil if there is no upvalue with the given index. +-- Variable names starting with '(' (open parenthesis) represent variables with no known names (variables from chunks saved without debug information). +function debug.getupvalue(f, up) end + +-- Returns the Lua value associated to u. If u is not a full userdata, returns nil. +function debug.getuservalue(u, n) end + +--Sets the given function as a hook. The string mask and the number count describe when the hook will be called. The string mask may have any combination of the following characters, with the given meaning: +-- 'c': the hook is called every time Lua calls a function; +-- 'r': the hook is called every time Lua returns from a function; +-- 'l': the hook is called every time Lua enters a new line of code. +-- Moreover, with a count different from zero, the hook is called also after every count instructions. +-- When called without arguments, debug.sethook turns off the hook. +-- When the hook is called, its first argument is a string describing the event that has triggered its call: "call" (or "tail call"), "return", "line", and "count". For line events, the hook also gets the new line number as its second parameter. Inside a hook, you can call getinfo with level 2 to get more information about the running function (level 0 is the getinfo function, and level 1 is the hook function). +function debug.sethook(thread, hook, mask, count) end + +-- This function assigns the value value to the local variable with index local of the function at level level of the stack. The function returns nil if there is no local variable with the given index, and raises an error when called with a level out of range. (You can call getinfo to check whether the level is valid.) Otherwise, it returns the name of the local variable. +-- See debug.getlocal for more information about variable indices and names. +function debug.setlocal(thread, level, var, value) end + +-- Sets the metatable for the given value to the given table (which can be nil). Returns value. +function debug.setmetatable(value, table) end + +-- This function assigns the value value to the upvalue with index up of the function f. The function returns nil if there is no upvalue with the given index. Otherwise, it returns the name of the upvalue. +function debug.setupvalue(f, up, value) end + +-- Sets the given value as the Lua value associated to the given udata. udata must be a full userdata. +-- Returns udata. +function debug.setuservalue(udata, value, n) end + +-- If message is present but is neither a string nor nil, this function returns message without further processing. Otherwise, it returns a string with a traceback of the call stack. The optional message string is appended at the beginning of the traceback. An optional level number tells at which level to start the traceback (default is 1, the function calling traceback). +function debug.traceback(thread, message, level) end + +-- Returns a unique identifier (as a light userdata) for the upvalue numbered n from the given function. +-- These unique identifiers allow a program to check whether different closures share upvalues. Lua closures that share an upvalue (that is, that access a same external local variable) will return identical ids for those upvalue indices. +function debug.upvalueid(f, n) end + +-- Make the n1-th upvalue of the Lua closure f1 refer to the n2-th upvalue of the Lua closure f2. +function debug.upvaluejoin(f1, n1, f2, n2) end diff --git a/res/lua/global.lua b/res/lua/global.lua new file mode 100755 index 0000000..fbbc3a6 --- /dev/null +++ b/res/lua/global.lua @@ -0,0 +1,123 @@ +------------------------------------------------------------------------ +-- Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +-- +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------ + +-- Basic Functions http://www.lua.org/manual/5.3/manual.html#6.1 + +-- The basic library provides core functions to Lua. If you do not include this library in your application, you should check carefully whether you need to provide implementations for some of its facilities. +function assert(v, message) end + +-- Calls error if the value of its argument v is false (i.e., nil or false); otherwise, returns all its arguments. In case of error, message is the error object; when absent, it defaults to "assertion failed!" +function collectgarbage(opt, arg) end + +-- Opens the named file and executes its contents as a Lua chunk. When called without arguments, dofile executes the contents of the standard input (stdin). Returns all values returned by the chunk. In case of errors, dofile propagates the error to its caller (that is, dofile does not run in protected mode). +function dofile(filename) end + +-- Terminates the last protected function called and returns message as the error object. Function error never returns. +-- Usually, error adds some information about the error position at the beginning of the message, if the message is a string. The level argument specifies how to get the error position. With level 1 (the default), the error position is where the error function was called. Level 2 points the error to where the function that called error was called; and so on. Passing a level 0 avoids the addition of error position information to the message. +function error(message, level) end + +-- A global variable (not a function) that holds the global environment (see §2.2). Lua itself does not use this variable; changing its value does not affect any environment, nor vice versa. +_G = {} + +-- If object does not have a metatable, returns nil. Otherwise, if the object's metatable has a __metatable field, returns the associated value. Otherwise, returns the metatable of the given object. +function getmetatable(object) end + +--Returns three values (an iterator function, the table t, and 0) so that the construction +-- for i,v in ipairs(t) do body end +-- will iterate over the key–value pairs (1,t[1]), (2,t[2]), ..., up to the first nil value. +function ipairs(t) end + +-- Loads a chunk. +-- If chunk is a string, the chunk is this string. If chunk is a function, load calls it repeatedly to get the chunk pieces. Each call to chunk must return a string that concatenates with previous results. A return of an empty string, nil, or no value signals the end of the chunk. +-- If there are no syntactic errors, returns the compiled chunk as a function; otherwise, returns nil plus the error message. +-- If the resulting function has upvalues, the first upvalue is set to the value of env, if that parameter is given, or to the value of the global environment. Other upvalues are initialized with nil. (When you load a main chunk, the resulting function will always have exactly one upvalue, the _ENV variable (see §2.2). However, when you load a binary chunk created from a function (see string.dump), the resulting function can have an arbitrary number of upvalues.) All upvalues are fresh, that is, they are not shared with any other function. +-- chunkname is used as the name of the chunk for error messages and debug information (see §4.9). When absent, it defaults to chunk, if chunk is a string, or to "=(load)" otherwise. +-- The string mode controls whether the chunk can be text or binary (that is, a precompiled chunk). It may be the string "b" (only binary chunks), "t" (only text chunks), or "bt" (both binary and text). The default is "bt". +-- Lua does not check the consistency of binary chunks. Maliciously crafted binary chunks can crash the interpreter. +function load(chunk, chunkname, mode, env) end + +-- Similar to load, but gets the chunk from file filename or from the standard input, if no file name is given. +function loadfile(filename, mode, env) end + +-- Allows a program to traverse all fields of a table. Its first argument is a table and its second argument is an index in this table. next returns the next index of the table and its associated value. When called with nil as its second argument, next returns an initial index and its associated value. When called with the last index, or with nil in an empty table, next returns nil. If the second argument is absent, then it is interpreted as nil. In particular, you can use next(t) to check whether a table is empty. +-- The order in which the indices are enumerated is not specified, even for numeric indices. (To traverse a table in numerical order, use a numerical for.) +-- The behavior of next is undefined if, during the traversal, you assign any value to a non-existent field in the table. You may however modify existing fields. In particular, you may clear existing fields. +function next(table, index) end + +-- If t has a metamethod __pairs, calls it with t as argument and returns the first three results from the call. +-- Otherwise, returns three values: the next function, the table t, and nil, so that the construction +-- for k,v in pairs(t) do body end +-- will iterate over all key–value pairs of table t. +-- See function next for the caveats of modifying the table during its traversal. +function pairs(t) end + +-- Calls function f with the given arguments in protected mode. This means that any error inside f is not propagated; instead, pcall catches the error and returns a status code. Its first result is the status code (a boolean), which is true if the call succeeds without errors. In such case, pcall also returns all results from the call, after this first result. In case of any error, pcall returns false plus the error message. +function pcall(f, arg1, ...) end + +-- Receives any number of arguments and prints their values to stdout, using the tostring function to convert each argument to a string. print is not intended for formatted output, but only as a quick way to show a value, for instance for debugging. For complete control over the output, use string.format and io.write. +function print(...) end + +-- Checks whether v1 is equal to v2, without invoking the __eq metamethod. Returns a boolean. +function rawequal(v1, v2) end + +-- Gets the real value of table[index], without invoking the __index metamethod. table must be a table; index may be any value. +function rawget(table, index) end + +-- Returns the length of the object v, which must be a table or a string, without invoking the __len metamethod. Returns an integer. +function rawlen(v) end + +-- Sets the real value of table[index] to value, without invoking the __newindex metamethod. table must be a table, index any value different from nil and NaN, and value any Lua value. +-- This function returns table. +function rawset(table, index, value) end + +-- If index is a number, returns all arguments after argument number index; a negative number indexes from the end (-1 is the last argument). Otherwise, index must be the string "#", and select returns the total number of extra arguments it received. +function select(index, ...) end + +-- Sets the metatable for the given table. (To change the metatable of other types from Lua code, you must use the debug library (§6.10).) If metatable is nil, removes the metatable of the given table. If the original metatable has a __metatable field, raises an error. +-- This function returns table. +function setmetatable(table, metatable) end + +-- When called with no base, tonumber tries to convert its argument to a number. If the argument is already a number or a string convertible to a number, then tonumber returns this number; otherwise, it returns nil. +-- The conversion of strings can result in integers or floats, according to the lexical conventions of Lua (see §3.1). (The string may have leading and trailing spaces and a sign.) +-- When called with base, then e must be a string to be interpreted as an integer numeral in that base. The base may be any integer between 2 and 36, inclusive. In bases above 10, the letter 'A' (in either upper or lower case) represents 10, 'B' represents 11, and so forth, with 'Z' representing 35. If the string e is not a valid numeral in the given base, the function returns nil. +function tonumber(e, base) end + +-- Receives a value of any type and converts it to a string in a human-readable format. (For complete control of how numbers are converted, use string.format.) +-- If the metatable of v has a __tostring field, then tostring calls the corresponding value with v as argument, and uses the result of the call as its result. +function tostring(v) end + +-- Returns the type of its only argument, coded as a string. The possible results of this function are "nil" (a string, not the value nil), "number", "string", "boolean", "table", "function", "thread", and "userdata". +function type(v) end + +-- A global variable (not a function) that holds a string containing the running Lua version. The current value of this variable is "Lua 5.3". +_VERSION = "Lua 5.3" + +-- This function is similar to pcall, except that it sets a new message handler msgh. +function xpcall(f, msgh, arg1, ...) end + +-- Loads the given module. The function starts by looking into the package.loaded table to determine whether modname is already loaded. If it is, then require returns the value stored at package.loaded[modname]. Otherwise, it tries to find a loader for the module. +-- To find a loader, require is guided by the package.searchers sequence. By changing this sequence, we can change how require looks for a module. The following explanation is based on the default configuration for package.searchers. +-- First require queries package.preload[modname]. If it has a value, this value (which must be a function) is the loader. Otherwise require searches for a Lua loader using the path stored in package.path. If that also fails, it searches for a C loader using the path stored in package.cpath. If that also fails, it tries an all-in-one loader (see package.searchers). +-- Once a loader is found, require calls the loader with two arguments: modname and an extra value dependent on how it got the loader. (If the loader came from a file, this extra value is the file name.) If the loader returns any non-nil value, require assigns the returned value to package.loaded[modname]. If the loader does not return a non-nil value and has not assigned any value to package.loaded[modname], then require assigns true to this entry. In any case, require returns the final value of package.loaded[modname]. +-- If there is any error loading or running the module, or if it cannot find any loader for the module, then require raises an error. +function require(modname) end \ No newline at end of file diff --git a/res/lua/io.lua b/res/lua/io.lua new file mode 100755 index 0000000..11acd88 --- /dev/null +++ b/res/lua/io.lua @@ -0,0 +1,124 @@ +------------------------------------------------------------------------ +-- Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +-- +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------ +-- Input and Output Facilities http://www.lua.org/manual/5.3/manual.html#6.8 +-- The I/O library provides two different styles for file manipulation. The first one uses implicit file handles; that is, there are operations to set a default input file and a default output file, and all input/output operations are over these default files. The second style uses explicit file handles. +-- When using implicit file handles, all operations are supplied by table io. When using explicit file handles, the operation io.open returns a file handle and then all operations are supplied as methods of the file handle. +-- The table io also provides three predefined file handles with their usual meanings from C: io.stdin, io.stdout, and io.stderr. The I/O library never closes these files. +-- Unless otherwise stated, all I/O functions return nil on failure (plus an error message as a second result and a system-dependent error code as a third result) and some value different from nil on success. In non-POSIX systems, the computation of the error message and error code in case of errors may be not thread safe, because they rely on the global C variable errno. + +io = {} + +io.stderr = nil + +io.stdin = nil + +io.stdout = nil + +-- Equivalent to file:close(). Without a file, closes the default output file. +function io.close(file) end + +-- Equivalent to io.output():flush(). +function io.flush() end + +-- When called with a file name, it opens the named file (in text mode), and sets its handle as the default input file. When called with a file handle, it simply sets this file handle as the default input file. When called without arguments, it returns the current default input file. +-- In case of errors this function raises the error, instead of returning an error code. +function io.input(file) end + +-- Opens the given file name in read mode and returns an iterator function that works like file:lines(···) over the opened file. When the iterator function detects the end of file, it returns no values (to finish the loop) and automatically closes the file. +-- The call io.lines() (with no file name) is equivalent to io.input():lines("*l"); that is, it iterates over the lines of the default input file. In this case, the iterator does not close the file when the loop ends. +-- In case of errors this function raises the error, instead of returning an error code. +function io.lines(filename, ...) end + +-- This function opens a file, in the mode specified in the string mode. In case of success, it returns a new file handle. +-- The mode string can be any of the following: +-- "r": read mode (the default); +-- "w": write mode; +-- "a": append mode; +-- "r+": update mode, all previous data is preserved; +-- "w+": update mode, all previous data is erased; +-- "a+": append update mode, previous data is preserved, writing is only allowed at the end of file. +-- The mode string can also have a 'b' at the end, which is needed in some systems to open the file in binary mode. +function io.open(filename, mode) return file end + +-- Similar to io.input, but operates over the default output file. +function io.output(file) end + +--This function is system dependent and is not available on all platforms. +-- Starts program prog in a separated process and returns a file handle that you can use to read data from this program (if mode is "r", the default) or to write data to this program (if mode is "w"). +function io.popen(prog, mode) end + +--Equivalent to io.input():read(···). +function io.read(...) end + +-- In case of success, returns a handle for a temporary file. This file is opened in update mode and it is automatically removed when the program ends. +function io.tmpfile() end + +-- Checks whether obj is a valid file handle. Returns the string "file" if obj is an open file handle, "closed file" if obj is a closed file handle, or nil if obj is not a file handle. +function io.type(obj) end + +-- Equivalent to io.output():write(···). +function io.write(...) end + +local file = {} + +-- Closes file. Note that files are automatically closed when their handles are garbage collected, but that takes an unpredictable amount of time to happen. +-- When closing a file handle created with io.popen, file:close returns the same values returned by os.execute. +function file:close() end + +-- Saves any written data to file. +function file:flush() end + +-- Returns an iterator function that, each time it is called, reads the file according to the given formats. When no format is given, uses "l" as a default. As an example, the construction +-- for c in file:lines(1) do body end +-- will iterate over all characters of the file, starting at the current position. Unlike io.lines, this function does not close the file when the loop ends. +-- In case of errors this function raises the error, instead of returning an error code. +function file:lines(...) end + +-- Reads the file file, according to the given formats, which specify what to read. For each format, the function returns a string or a number with the characters read, or nil if it cannot read data with the specified format. (In this latter case, the function does not read subsequent formats.) When called without formats, it uses a default format that reads the next line (see below). +-- The available formats are +-- "n": reads a numeral and returns it as a float or an integer, following the lexical conventions of Lua. (The numeral may have leading spaces and a sign.) This format always reads the longest input sequence that is a valid prefix for a numeral; if that prefix does not form a valid numeral (e.g., an empty string, "0x", or "3.4e-"), it is discarded and the function returns nil. +-- "a": reads the whole file, starting at the current position. On end of file, it returns the empty string. +-- "l": reads the next line skipping the end of line, returning nil on end of file. This is the default format. +-- "L": reads the next line keeping the end-of-line character (if present), returning nil on end of file. +-- number: reads a string with up to this number of bytes, returning nil on end of file. If number is zero, it reads nothing and returns an empty string, or nil on end of file. +-- The formats "l" and "L" should be used only for text files. +function file:read(...) end + +-- Sets and gets the file position, measured from the beginning of the file, to the position given by offset plus a base specified by the string whence, as follows: +-- "set": base is position 0 (beginning of the file); +-- "cur": base is current position; +-- "end": base is end of file; +-- In case of success, seek returns the final file position, measured in bytes from the beginning of the file. If seek fails, it returns nil, plus a string describing the error. +-- The default value for whence is "cur", and for offset is 0. Therefore, the call file:seek() returns the current file position, without changing it; the call file:seek("set") sets the position to the beginning of the file (and returns 0); and the call file:seek("end") sets the position to the end of the file, and returns its size. +function file:seek(whence, offset) end + +-- Sets the buffering mode for an output file. There are three available modes: +-- "no": no buffering; the result of any output operation appears immediately. +-- "full": full buffering; output operation is performed only when the buffer is full or when you explicitly flush the file (see io.flush). +-- "line": line buffering; output is buffered until a newline is output or there is any input from some special files (such as a terminal device). +-- For the last two cases, size specifies the size of the buffer, in bytes. The default is an appropriate size. +function file:setvbuf(mode, size) end + +-- Writes the value of each of its arguments to file. The arguments must be strings or numbers. +-- In case of success, this function returns file. Otherwise it returns nil plus a string describing the error. +function file:write(...) end diff --git a/res/lua/math.lua b/res/lua/math.lua new file mode 100755 index 0000000..11ab9c6 --- /dev/null +++ b/res/lua/math.lua @@ -0,0 +1,109 @@ +------------------------------------------------------------------------ +-- Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +-- +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------ + +-- Mathematical Functions http://www.lua.org/manual/5.3/manual.html#6.7 +-- This library provides basic mathematical functions. It provides all its functions and constants inside the table math. Functions with the annotation "integer/float" give integer results for integer arguments and float results for float (or mixed) arguments. Rounding functions (math.ceil, math.floor, and math.modf) return an integer when the result fits in the range of an integer, or a float otherwise. +math = {} + +-- The value of π. +math.pi = 3.1415 + +-- An integer with the minimum value for an integer. +math.mininteger = nil + +-- Returns the absolute value of x. (integer/float) +function math.abs(x) return 0 end + +-- Returns the arc cosine of x (in radians). +function math.acos(x) return 0 end + +-- Returns the arc sine of x (in radians). +function math.asin(x) return 0 end + +-- Returns the arc tangent of y/x (in radians), but uses the signs of both arguments to find the quadrant of the result. (It also handles correctly the case of x being zero.) +-- The default value for x is 1, so that the call math.atan(y) returns the arc tangent of y. +function math.atan(y, x) return 0 end + +-- Returns the smallest integral value larger than or equal to x. +function math.ceil(x) return 0 end + +-- Returns the cosine of x (assumed to be in radians). +function math.cos(x) return 0 end + +-- Converts the angle x from radians to degrees. +function math.deg(x) return 0 end + +--- Returns the value e^x (where e is the base of natural logarithms). +function math.exp(x) end + +-- Returns the largest integral value smaller than or equal to x. +function math.floor(x) end + +-- Returns the remainder of the division of x by y that rounds the quotient towards zero. (integer/float) +function math.fmod(x, y) end + +-- The float value HUGE_VAL, a value larger than any other numeric value. +math.huge = nil + +-- Returns the logarithm of x in the given base. The default for base is e (so that the function returns the natural logarithm of x). +function math.log(x, base) end + +-- Returns the argument with the maximum value, according to the Lua operator <. (integer/float) +function math.max(x, ...) end + +-- An integer with the maximum value for an integer. +math.maxinteger = nil + +-- Returns the argument with the minimum value, according to the Lua operator <. (integer/float) +function math.min(x, ...) end + +-- Returns the integral part of x and the fractional part of x. Its second result is always a float. +function math.modf(x) end + +-- Converts the angle x from degrees to radians. +function math.rad(x) end + +-- When called without arguments, returns a pseudo-random float with uniform distribution in the range [0,1). When called with two integers m and n, math.random returns a pseudo-random integer with uniform distribution in the range [m, n]. (The value n-m cannot be negative and must fit in a Lua integer.) The call math.random(n) is equivalent to math.random(1,n). +-- This function is an interface to the underling pseudo-random generator function provided by C. +function math.random(m, n) end + +-- Sets x as the "seed" for the pseudo-random generator: equal seeds produce equal sequences of numbers. +function math.randomseed(x) end + +-- Returns the sine of x (assumed to be in radians). +function math.sin(x) return 0 end + +-- Returns the square root of x. (You can also use the expression x^0.5 to compute this value.) +function math.sqrt(x) return 0 end + +-- Returns the tangent of x (assumed to be in radians). +function math.tan(x) return 0 end + +-- If the value x is convertible to an integer, returns that integer. Otherwise, returns nil. +function math.tointeger(x) end + +-- Returns "integer" if x is an integer, "float" if it is a float, or nil if x is not a number. +function math.type(x) end + +-- Returns a boolean, true if and only if integer m is below integer n when they are compared as unsigned integers. +function math.ult(m, n) end \ No newline at end of file diff --git a/res/lua/os.lua b/res/lua/os.lua new file mode 100755 index 0000000..632f455 --- /dev/null +++ b/res/lua/os.lua @@ -0,0 +1,75 @@ +------------------------------------------------------------------------ +-- Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +-- +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------ + +-- Operating System Facilities http://www.lua.org/manual/5.3/manual.html#pdf-os.clock +-- This library is implemented through table os. +os = {} + +-- os.clock Returns an approximation of the amount in seconds of CPU time used by the program. +function os.clock() end + +-- os.date Returns a string or a table containing date and time, formatted according to the given string format. +-- If the time argument is present, this is the time to be formatted (see the os.time function for a description of this value). Otherwise, date formats the current time. +-- If format starts with '!', then the date is formatted in Coordinated Universal Time. After this optional character, if format is the string "*t", then date returns a table with the following fields: year, month (1–12), day (1–31), hour (0–23), min (0–59), sec (0–61), wday (weekday, 1–7, Sunday is 1), yday (day of the year, 1–366), and isdst (daylight saving flag, a boolean). This last field may be absent if the information is not available. +-- If format is not "*t", then date returns the date as a string, formatted according to the same rules as the ISO C function strftime. +-- When called without arguments, date returns a reasonable date and time representation that depends on the host system and on the current locale. (More specifically, os.date() is equivalent to os.date("%c").) +-- In non-POSIX systems, this function may be not thread safe because of its reliance on C function gmtime and C function localtime. +function os.date(format, time) end + +-- os.difftime Returns the difference, in seconds, from time t1 to time t2 (where the times are values returned by os.time). In POSIX, Windows, and some other systems, this value is exactly t2-t1. +function os.difftime(t2, t1) end + +-- This function is equivalent to the ISO C function system. It passes command to be executed by an operating system shell. Its first result is true if the command terminated successfully, or nil otherwise. After this first result the function returns a string plus a number, as follows: +-- "exit": the command terminated normally; the following number is the exit status of the command. +-- "signal": the command was terminated by a signal; the following number is the signal that terminated the command. +-- When called without a command, os.execute returns a boolean that is true if a shell is available. +function os.execute(command) end + +-- Calls the ISO C function exit to terminate the host program. If code is true, the returned status is EXIT_SUCCESS; if code is false, the returned status is EXIT_FAILURE; if code is a number, the returned status is this number. The default value for code is true. +-- If the optional second argument close is true, closes the Lua state before exiting. +function os.exit(code, close) end + +-- Returns the value of the process environment variable varname, or nil if the variable is not defined. +function os.getenv(varname) end + +-- Deletes the file (or empty directory, on POSIX systems) with the given name. If this function fails, it returns nil, plus a string describing the error and the error code. Otherwise, it returns true. +function os.remove(filename) end + +-- Renames the file or directory named oldname to newname. If this function fails, it returns nil, plus a string describing the error and the error code. Otherwise, it returns true. +function os.rename(oldname, newname) end + +-- Sets the current locale of the program. locale is a system-dependent string specifying a locale; category is an optional string describing which category to change: "all", "collate", "ctype", "monetary", "numeric", or "time"; the default category is "all". The function returns the name of the new locale, or nil if the request cannot be honored. +-- If locale is the empty string, the current locale is set to an implementation-defined native locale. If locale is the string "C", the current locale is set to the standard C locale. +-- When called with nil as the first argument, this function only returns the name of the current locale for the given category. +-- This function may be not thread safe because of its reliance on C function setlocale. +function os.setlocale(locale, category) end + +-- Returns the current time when called without arguments, or a time representing the local date and time specified by the given table. This table must have fields year, month, and day, and may have fields hour (default is 12), min (default is 0), sec (default is 0), and isdst (default is nil). Other fields are ignored. For a description of these fields, see the os.date function. +-- The values in these fields do not need to be inside their valid ranges. For instance, if sec is -10, it means -10 seconds from the time specified by the other fields; if hour is 1000, it means +1000 hours from the time specified by the other fields. +-- The returned value is a number, whose meaning depends on your system. In POSIX, Windows, and some other systems, this number counts the number of seconds since some given start time (the "epoch"). In other systems, the meaning is not specified, and the number returned by time can be used only as an argument to os.date and os.difftime. +function os.time(table) end + +-- Returns a string with a file name that can be used for a temporary file. The file must be explicitly opened before its use and explicitly removed when no longer needed. +-- In POSIX systems, this function also creates a file with that name, to avoid security risks. (Someone else might create the file with wrong permissions in the time between getting the name and creating the file.) You still have to open the file to use it and to remove it (even if you do not use it). +-- When possible, you may prefer to use io.tmpfile, which automatically removes the file when the program ends. +function os.tmpname() end \ No newline at end of file diff --git a/res/lua/package.lua b/res/lua/package.lua new file mode 100755 index 0000000..b61e8de --- /dev/null +++ b/res/lua/package.lua @@ -0,0 +1,77 @@ +------------------------------------------------------------------------ +-- Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +-- +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------ + +-- Modules http://www.lua.org/manual/5.3/manual.html#6.3 +-- The package library provides basic facilities for loading modules in Lua. It exports one function directly in the global environment: require. Everything else is exported in a table package. +package = {} + +-- A string describing some compile-time configurations for packages. This string is a sequence of lines: +-- The first line is the directory separator string. Default is '\' for Windows and '/' for all other systems. +-- The second line is the character that separates templates in a path. Default is ';'. +-- The third line is the string that marks the substitution points in a template. Default is '?'. +-- The fourth line is a string that, in a path in Windows, is replaced by the executable's directory. Default is '!'. +-- The fifth line is a mark to ignore all text after it when building the luaopen_ function name. Default is '-'. +package.config = "" + +-- The path used by require to search for a C loader. +-- Lua initializes the C path package.cpath in the same way it initializes the Lua path package.path, using the environment variable LUA_CPATH_5_3, or the environment variable LUA_CPATH, or a default path defined in luaconf.h. +package.cpath = "" + +-- A table used by require to control which modules are already loaded. When you require a module modname and package.loaded[modname] is not false, require simply returns the value stored there. +-- This variable is only a reference to the real table; assignments to this variable do not change the table used by require. +package.loaded = {} + +-- Dynamically links the host program with the C library libname. +-- If funcname is "*", then it only links with the library, making the symbols exported by the library available to other dynamically linked libraries. Otherwise, it looks for a function funcname inside the library and returns this function as a C function. So, funcname must follow the lua_CFunction prototype (see lua_CFunction). +-- This is a low-level function. It completely bypasses the package and module system. Unlike require, it does not perform any path searching and does not automatically adds extensions. libname must be the complete file name of the C library, including if necessary a path and an extension. funcname must be the exact name exported by the C library (which may depend on the C compiler and linker used). +-- This function is not supported by Standard C. As such, it is only available on some platforms (Windows, Linux, Mac OS X, Solaris, BSD, plus other Unix systems that support the dlfcn standard). +function package.loadlib(libname, funcname) end + +-- The path used by require to search for a Lua loader. +-- At start-up, Lua initializes this variable with the value of the environment variable LUA_PATH_5_3 or the environment variable LUA_PATH or with a default path defined in luaconf.h, if those environment variables are not defined. Any ";;" in the value of the environment variable is replaced by the default path. +package.path = "" + +-- A table to store loaders for specific modules (see require). +-- This variable is only a reference to the real table; assignments to this variable do not change the table used by require. +package.preload = {} + +-- A table used by require to control how to load modules. +-- Each entry in this table is a searcher function. When looking for a module, require calls each of these searchers in ascending order, with the module name (the argument given to require) as its sole parameter. The function can return another function (the module loader) plus an extra value that will be passed to that loader, or a string explaining why it did not find that module (or nil if it has nothing to say). +-- Lua initializes this table with four searcher functions. +-- The first searcher simply looks for a loader in the package.preload table. +-- The second searcher looks for a loader as a Lua library, using the path stored at package.path. The search is done as described in function package.searchpath. +-- The third searcher looks for a loader as a C library, using the path given by the variable package.cpath. Again, the search is done as described in function package.searchpath. For instance, if the C path is the string +-- "./?.so;./?.dll;/usr/local/?/init.so" +-- the searcher for module foo will try to open the files ./foo.so, ./foo.dll, and /usr/local/foo/init.so, in that order. Once it finds a C library, this searcher first uses a dynamic link facility to link the application with the library. Then it tries to find a C function inside the library to be used as the loader. The name of this C function is the string "luaopen_" concatenated with a copy of the module name where each dot is replaced by an underscore. Moreover, if the module name has a hyphen, its suffix after (and including) the first hyphen is removed. For instance, if the module name is a.b.c-v2.1, the function name will be luaopen_a_b_c. +-- The fourth searcher tries an all-in-one loader. It searches the C path for a library for the root name of the given module. For instance, when requiring a.b.c, it will search for a C library for a. If found, it looks into it for an open function for the submodule; in our example, that would be luaopen_a_b_c. With this facility, a package can pack several C submodules into one single library, with each submodule keeping its original open function. +-- All searchers except the first one (preload) return as the extra value the file name where the module was found, as returned by package.searchpath. The first searcher returns no extra value. +package.searchers = {} + +-- Searches for the given name in the given path. +-- A path is a string containing a sequence of templates separated by semicolons. For each template, the function replaces each interrogation mark (if any) in the template with a copy of name wherein all occurrences of sep (a dot, by default) were replaced by rep (the system's directory separator, by default), and then tries to open the resulting file name. +-- For instance, if the path is the string +-- "./?.lua;./?.lc;/usr/local/?/init.lua" +-- the search for the name foo.a will try to open the files ./foo/a.lua, ./foo/a.lc, and /usr/local/foo/a/init.lua, in that order. +-- Returns the resulting name of the first file that it can open in read mode (after closing the file), or nil plus an error message if none succeeds. (This error message lists all file names it tried to open.) +function package.searchpath(name, path, sep, rep) end + diff --git a/res/lua/string.lua b/res/lua/string.lua new file mode 100755 index 0000000..e2c9917 --- /dev/null +++ b/res/lua/string.lua @@ -0,0 +1,133 @@ +------------------------------------------------------------------------ +-- Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +-- +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------ + +-- String Manipulation http://www.lua.org/manual/5.3/manual.html#6.4 +-- This library provides generic functions for string manipulation, such as finding and extracting substrings, and pattern matching. When indexing a string in Lua, the first character is at position 1 (not at 0, as in C). Indices are allowed to be negative and are interpreted as indexing backwards, from the end of the string. Thus, the last character is at position -1, and so on. +-- The string library provides all its functions inside the table string. It also sets a metatable for strings where the __index field points to the string table. Therefore, you can use the string functions in object-oriented style. For instance, string.byte(s,i) can be written as s:byte(i). +-- The string library assumes one-byte character encodings. +string = {} + +-- Returns the internal numeric codes of the characters s[i], s[i+1], ..., s[j]. The default value for i is 1; the default value for j is i. These indices are corrected following the same rules of function string.sub. +-- Numeric codes are not necessarily portable across platforms. +function string.byte(s, i, j) end + +-- Receives zero or more integers. Returns a string with length equal to the number of arguments, in which each character has the internal numeric code equal to its corresponding argument. +-- Numeric codes are not necessarily portable across platforms. +function string.char(...) end + +-- Returns a string containing a binary representation (a binary chunk) of the given function, so that a later load on this string returns a copy of the function (but with new upvalues). If strip is a true value, the binary representation may not include all debug information about the function, to save space. +-- Functions with upvalues have only their number of upvalues saved. When (re)loaded, those upvalues receive fresh instances containing nil. (You can use the debug library to serialize and reload the upvalues of a function in a way adequate to your needs.) +function string.dump(func, strip) end + +-- Looks for the first match of pattern (see §6.4.1) in the string s. If it finds a match, then find returns the indices of s where this occurrence starts and ends; otherwise, it returns nil. A third, optional numeric argument init specifies where to start the search; its default value is 1 and can be negative. A value of true as a fourth, optional argument plain turns off the pattern matching facilities, so the function does a plain "find substring" operation, with no characters in pattern being considered magic. Note that if plain is given, then init must be given as well. +-- If the pattern has captures, then in a successful match the captured values are also returned, after the two indices. +function string.find(s, pattern, init, plain) end + +-- Returns a formatted version of its variable number of arguments following the description given in its first argument (which must be a string). The format string follows the same rules as the ISO C function sprintf. The only differences are that the options/modifiers *, h, L, l, n, and p are not supported and that there is an extra option, q. +-- The q option formats a string between double quotes, using escape sequences when necessary to ensure that it can safely be read back by the Lua interpreter. For instance, the call +-- string.format('%q', 'a string with "quotes" and \n new line') +-- may produce the string: +-- "a string with \"quotes\" and \ +-- new line" +-- Options A, a, E, e, f, G, and g all expect a number as argument. Options c, d, i, o, u, X, and x expect an integer. When Lua is compiled with a C89 compiler, options A and a (hexadecimal floats) do not support any modifier (flags, width, length). +-- Option s expects a string; if its argument is not a string, it is converted to one following the same rules of tostring. If the option has any modifier (flags, width, length), the string argument should not contain embedded zeros. +function string.format(formatstring, ...) end + +-- Returns an iterator function that, each time it is called, returns the next captures from pattern (see §6.4.1) over the string s. If pattern specifies no captures, then the whole match is produced in each call. +-- As an example, the following loop will iterate over all the words from string s, printing one per line: +-- s = "hello world from Lua" +-- for w in string.gmatch(s, "%a+") do +-- print(w) +-- end +-- The next example collects all pairs key=value from the given string into a table: +-- t = {} +-- s = "from=world, to=Lua" +-- for k, v in string.gmatch(s, "(%w+)=(%w+)") do +-- t[k] = v +-- end +-- For this function, a caret '^' at the start of a pattern does not work as an anchor, as this would prevent the iteration. +function string.gmatch(s, pattern) end + +-- Returns a copy of s in which all (or the first n, if given) occurrences of the pattern (see §6.4.1) have been replaced by a replacement string specified by repl, which can be a string, a table, or a function. gsub also returns, as its second value, the total number of matches that occurred. The name gsub comes from Global SUBstitution. +-- If repl is a string, then its value is used for replacement. The character % works as an escape character: any sequence in repl of the form %d, with d between 1 and 9, stands for the value of the d-th captured substring. The sequence %0 stands for the whole match. The sequence %% stands for a single %. +-- If repl is a table, then the table is queried for every match, using the first capture as the key. +-- If repl is a function, then this function is called every time a match occurs, with all captured substrings passed as arguments, in order. +-- In any case, if the pattern specifies no captures, then it behaves as if the whole pattern was inside a capture. +-- If the value returned by the table query or by the function call is a string or a number, then it is used as the replacement string; otherwise, if it is false or nil, then there is no replacement (that is, the original match is kept in the string). +-- Here are some examples: +-- x = string.gsub("hello world", "(%w+)", "%1 %1") +-- --> x="hello hello world world" +-- +-- x = string.gsub("hello world", "%w+", "%0 %0", 1) +-- --> x="hello hello world" +-- +-- x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1") +-- --> x="world hello Lua from" +-- +-- x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv) +-- --> x="home = /home/roberto, user = roberto" +-- +-- x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s) +-- return load(s)() +-- end) +-- --> x="4+5 = 9" +-- +-- local t = {name="lua", version="5.3"} +-- x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t) +-- --> x="lua-5.3.tar.gz" +function string.gsub(s, pattern, repl, n) end + + +-- Receives a string and returns its length. The empty string "" has length 0. Embedded zeros are counted, so "a\000bc\000" has length 5. +function string.len(s) end + +--- Receives a string and returns a copy of this string with all uppercase letters changed to lowercase. All other characters are left unchanged. The definition of what an uppercase letter is depends on the current locale. +function string.lower(s) end + + +-- Looks for the first match of pattern (see §6.4.1) in the string s. If it finds one, then match returns the captures from the pattern; otherwise it returns nil. If pattern specifies no captures, then the whole match is returned. A third, optional numeric argument init specifies where to start the search; its default value is 1 and can be negative. +function string.match(s, pattern, init) end + + +-- Returns a binary string containing the values v1, v2, etc. packed (that is, serialized in binary form) according to the format string fmt (see §6.4.2). +function string.pack(fmt, v1, v2, ...) end + +-- Returns the size of a string resulting from string.pack with the given format. The format string cannot have the variable-length options 's' or 'z' +function string.packsize(fmt) end + +-- Returns a string that is the concatenation of n copies of the string s separated by the string sep. The default value for sep is the empty string (that is, no separator). Returns the empty string if n is not positive. +-- (Note that it is very easy to exhaust the memory of your machine with a single call to this function.) +function string.rep(s, n, sep) end + +-- Returns a string that is the string s reversed. +function string.reverse(s) end + +-- Returns the substring of s that starts at i and continues until j; i and j can be negative. If j is absent, then it is assumed to be equal to -1 (which is the same as the string length). In particular, the call string.sub(s,1,j) returns a prefix of s with length j, and string.sub(s, -i) (for a positive i) returns a suffix of s with length i. +-- If, after the translation of negative indices, i is less than 1, it is corrected to 1. If j is greater than the string length, it is corrected to that length. If, after these corrections, i is greater than j, the function returns the empty string. +function string.sub(s, i, j) end + +-- Returns the values packed in string s (see string.pack) according to the format string fmt (see §6.4.2). An optional pos marks where to start reading in s (default is 1). After the read values, this function also returns the index of the first unread byte in s. +function string.unpack(fmt, s, pos) end + +-- Receives a string and returns a copy of this string with all lowercase letters changed to uppercase. All other characters are left unchanged. The definition of what a lowercase letter is depends on the current locale. +function string.upper(s) end diff --git a/res/lua/table.lua b/res/lua/table.lua new file mode 100755 index 0000000..a0aea76 --- /dev/null +++ b/res/lua/table.lua @@ -0,0 +1,55 @@ +------------------------------------------------------------------------ +-- Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +-- +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------ + +-- Table Manipulation http://www.lua.org/manual/5.3/manual.html#6.6 +-- This library provides generic functions for table manipulation. It provides all its functions inside the table table. +-- Remember that, whenever an operation needs the length of a table, all caveats about the length operator apply (see §3.4.7). All functions ignore non-numeric keys in the tables given as arguments. +table = {} + +-- Given a list where all elements are strings or numbers, returns the string list[i]..sep..list[i+1] ··· sep..list[j]. The default value for sep is the empty string, the default for i is 1, and the default for j is #list. If i is greater than j, returns the empty string. +function table.concat(list, sep, i, j) end + +-- Inserts element value at position pos in list, shifting up the elements list[pos], list[pos+1], ···, list[#list]. The default value for pos is #list+1, so that a call table.insert(t,x) inserts x at the end of list t. +function table.insert(list, pos, value) end + + +-- Moves elements from table a1 to table a2, performing the equivalent to the following multiple assignment: a2[t],··· = a1[f],···,a1[e]. The default for a2 is a1. The destination range can overlap with the source range. The number of elements to be moved must fit in a Lua integer. +-- Returns the destination table a2. +function table.move(a1, f, e, t, a2) end + +-- Returns a new table with all arguments stored into keys 1, 2, etc. and with a field "n" with the total number of arguments. Note that the resulting table may not be a sequence. +function table.pack(...) end + +-- Removes from list the element at position pos, returning the value of the removed element. When pos is an integer between 1 and #list, it shifts down the elements list[pos+1], list[pos+2], ···, list[#list] and erases element list[#list]; The index pos can also be 0 when #list is 0, or #list + 1; in those cases, the function erases the element list[pos]. +-- The default value for pos is #list, so that a call table.remove(l) removes the last element of list l. +function table.remove(list, pos) end + +-- Sorts list elements in a given order, in-place, from list[1] to list[#list]. If comp is given, then it must be a function that receives two list elements and returns true when the first element must come before the second in the final order (so that, after the sort, i < j implies not comp(list[j],list[i])). If comp is not given, then the standard Lua operator < is used instead. +-- Note that the comp function must define a strict partial order over the elements in the list; that is, it must be asymmetric and transitive. Otherwise, no valid sort may be possible. +-- The sort algorithm is not stable: elements considered equal by the given order may have their relative positions changed by the sort. +function table.sort(list, comp) end + +-- Returns the elements from the given list. This function is equivalent to +-- return list[i], list[i+1], ···, list[j] +-- By default, i is 1 and j is #list. +function table.unpack(list, i, j) end diff --git a/res/lua/utf8.lua b/res/lua/utf8.lua new file mode 100755 index 0000000..d8bbcb4 --- /dev/null +++ b/res/lua/utf8.lua @@ -0,0 +1,49 @@ +------------------------------------------------------------------------ +-- Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +-- +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------ + +-- UTF-8 Support http://www.lua.org/manual/5.3/manual.html#6.5 +-- This library provides basic support for UTF-8 encoding. It provides all its functions inside the table utf8. This library does not provide any support for Unicode other than the handling of the encoding. Any operation that needs the meaning of a character, such as character classification, is outside its scope. +-- Unless stated otherwise, all functions that expect a byte position as a parameter assume that the given position is either the start of a byte sequence or one plus the length of the subject string. As in the string library, negative indices count from the end of the string. +utf8 = {} + +-- The pattern (a string, not a function) "[\0-\x7F\xC2-\xF4][\x80-\xBF]*" (see §6.4.1), which matches exactly one UTF-8 byte sequence, assuming that the subject is a valid UTF-8 string. +utf8.charpattern = "" + +-- Receives zero or more integers, converts each one to its corresponding UTF-8 byte sequence and returns a string with the concatenation of all these sequences. +function utf8.char(...) end + +-- Returns values so that the construction +-- for p, c in utf8.codes(s) do body end +-- will iterate over all characters in string s, with p being the position (in bytes) and c the code point of each character. It raises an error if it meets any invalid byte sequence. +function utf8.codes(s) end + +-- Returns the codepoints (as integers) from all characters in s that start between byte position i and j (both included). The default for i is 1 and for j is i. It raises an error if it meets any invalid byte sequence. +function utf8.codepoint (s, i, j) end + +-- Returns the number of UTF-8 characters in string s that start between positions i and j (both inclusive). The default for i is 1 and for j is -1. If it finds any invalid byte sequence, returns a false value plus the position of the first invalid byte. +function utf8.len(s, i, j) end + +-- Returns the position (in bytes) where the encoding of the n-th character of s (counting from position i) starts. A negative n gets characters before position i. The default for i is 1 when n is non-negative and #s + 1 otherwise, so that utf8.offset(s, -n) gets the offset of the n-th character from the end of the string. If the specified character is neither in the subject nor right after its end, the function returns nil. +-- As a special case, when n is 0 the function returns the start of the encoding of the character that contains the i-th byte of s. +-- This function assumes that s is a valid UTF-8 string. +function utf8.offset (s, n, i) end diff --git a/res/luacheck/luacheck.exe b/res/luacheck/luacheck.exe new file mode 100644 index 0000000..bb6f7f5 Binary files /dev/null and b/res/luacheck/luacheck.exe differ diff --git a/res/others/launch.json b/res/others/launch.json new file mode 100644 index 0000000..58ff6c4 --- /dev/null +++ b/res/others/launch.json @@ -0,0 +1,31 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "lua", + "request": "launch", + "tag": "normal", + "name": "LuaPanda", + "description": "通用模式,通常调试项目请选择此模式 | launchVer:3.2.0", + "cwd": "${workspaceFolder}", + "luaFileExtension": "", + "connectionPort": 8818, + "stopOnEntry": true, + "useCHook": true, + "autoPathMode": true + }, + { + "type": "lua", + "request": "launch", + "tag": "independent_file", + "name": "LuaPanda-IndependentFile", + "description": "独立文件调试模式,使用前请参考文档", + "luaPath": "", + "packagePath": [], + "luaFileExtension": "", + "connectionPort": 8820, + "stopOnEntry": true, + "useCHook": true + } + ] +} \ No newline at end of file diff --git a/res/snippets/snippets.json b/res/snippets/snippets.json new file mode 100755 index 0000000..63ab644 --- /dev/null +++ b/res/snippets/snippets.json @@ -0,0 +1,99 @@ +{ + "local": { + "prefix": "local", + "body": "local ${1:var} = ${0:nil}", + "description": "local variable = ..." + }, + "if": { + "prefix": "if", + "body": "if ${1:condition} then\n\t${0:-- body}\nend", + "description": "if" + }, + "elseif": { + "prefix": "elseif", + "body": "elseif ${1:condition} then\n\t${0:-- body}", + "description": "else if" + }, + "else": { + "prefix": "else", + "body": "else\n\t${0:-- body}", + "description": "else" + }, + "for": { + "prefix": "for", + "body": "for ${1:index} = ${2:1}, ${3:10} do\n\t${0:-- body}\nend", + "description": "for index = 1, n do end" + }, + "fori": { + "prefix": "for index, value in ipairs", + "body": "for ${1:index}, ${2:value} in ipairs(${3:Table}) do\n\t${0:-- body}\nend", + "description": "for index, value in ipairs(table) do end" + }, + "forp": { + "prefix": "for key, value in pairs", + "body": "for ${1:key}, ${2:value} in pairs(${3:Table}) do\n\t${0:-- body}\nend", + "description": "for key, value in pairs(table) do end" + }, + "do": { + "prefix": "do", + "body": "do\n\t${0:-- body}\nend", + "description": "do" + }, + "while": { + "prefix": "while", + "body": "while ${1:condition} do\n\t${0:-- body}\nend", + "description": "while" + }, + "repeat": { + "prefix": "repeat", + "body": "repeat\n\t${0:-- body}\nuntil ${1:condition}", + "description": "repeat" + }, + "local function": { + "prefix": "local function", + "body": "local function ${1:func}(${2:})\n\t${0:-- body}\nend", + "description": "local function" + }, + "function": { + "prefix": "function", + "body": "function ${1:func}(${2:})\n\t${0:-- body}\nend", + "description": "function" + }, + "require": { + "prefix": "require", + "body": "require (\"${1:}\")\n${0:}", + "description": "function" + } , + "true": { + "prefix": "true", + "body": "true" + }, + "false": { + "prefix": "false", + "body": "false" + }, + "nil": { + "prefix": "nil", + "body": "nil" + } , + "and": { + "prefix": "and", + "body": "and" + }, + "or": { + "prefix": "or", + "body": "or" + }, + "not": { + "prefix": "not", + "body": "not" + }, + "end": { + "prefix": "end", + "body": "end" + }, + "then": { + "prefix": "then", + "body": "then" + } +} \ No newline at end of file diff --git a/res/snippets/snippets_backup.json b/res/snippets/snippets_backup.json new file mode 100755 index 0000000..63ab644 --- /dev/null +++ b/res/snippets/snippets_backup.json @@ -0,0 +1,99 @@ +{ + "local": { + "prefix": "local", + "body": "local ${1:var} = ${0:nil}", + "description": "local variable = ..." + }, + "if": { + "prefix": "if", + "body": "if ${1:condition} then\n\t${0:-- body}\nend", + "description": "if" + }, + "elseif": { + "prefix": "elseif", + "body": "elseif ${1:condition} then\n\t${0:-- body}", + "description": "else if" + }, + "else": { + "prefix": "else", + "body": "else\n\t${0:-- body}", + "description": "else" + }, + "for": { + "prefix": "for", + "body": "for ${1:index} = ${2:1}, ${3:10} do\n\t${0:-- body}\nend", + "description": "for index = 1, n do end" + }, + "fori": { + "prefix": "for index, value in ipairs", + "body": "for ${1:index}, ${2:value} in ipairs(${3:Table}) do\n\t${0:-- body}\nend", + "description": "for index, value in ipairs(table) do end" + }, + "forp": { + "prefix": "for key, value in pairs", + "body": "for ${1:key}, ${2:value} in pairs(${3:Table}) do\n\t${0:-- body}\nend", + "description": "for key, value in pairs(table) do end" + }, + "do": { + "prefix": "do", + "body": "do\n\t${0:-- body}\nend", + "description": "do" + }, + "while": { + "prefix": "while", + "body": "while ${1:condition} do\n\t${0:-- body}\nend", + "description": "while" + }, + "repeat": { + "prefix": "repeat", + "body": "repeat\n\t${0:-- body}\nuntil ${1:condition}", + "description": "repeat" + }, + "local function": { + "prefix": "local function", + "body": "local function ${1:func}(${2:})\n\t${0:-- body}\nend", + "description": "local function" + }, + "function": { + "prefix": "function", + "body": "function ${1:func}(${2:})\n\t${0:-- body}\nend", + "description": "function" + }, + "require": { + "prefix": "require", + "body": "require (\"${1:}\")\n${0:}", + "description": "function" + } , + "true": { + "prefix": "true", + "body": "true" + }, + "false": { + "prefix": "false", + "body": "false" + }, + "nil": { + "prefix": "nil", + "body": "nil" + } , + "and": { + "prefix": "and", + "body": "and" + }, + "or": { + "prefix": "or", + "body": "or" + }, + "not": { + "prefix": "not", + "body": "not" + }, + "end": { + "prefix": "end", + "body": "end" + }, + "then": { + "prefix": "then", + "body": "then" + } +} \ No newline at end of file diff --git a/res/web/settings.html b/res/web/settings.html new file mode 100644 index 0000000..2541515 --- /dev/null +++ b/res/web/settings.html @@ -0,0 +1,473 @@ + + + + + + + LuaPanda Setting + + + + +

LuaPanda Settings

+ + +

+ +

+ + +
+    
+    
+    
+ + +
+ + +

+ + +
+ + +

+ + +
+ + +

+ + + + +
+ + +

+ + + +
+ +
+ + +
+ + +
+ + +
+ +
+ + +
+ + +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/Tools.ts b/src/Tools.ts deleted file mode 100644 index f22a3a2..0000000 --- a/src/Tools.ts +++ /dev/null @@ -1,200 +0,0 @@ -import * as vscode from 'vscode'; -import { isArray } from 'util'; -let path = require("path"); -let pathReader = require('path-reader'); -import { DebugLogger } from './LogManager'; - -export class Tools { - public static extMap = new Object(); // 可处理的文件后缀列表 - public static fileNameToPathMap; // 文件名-路径Map - public static useAutoPathMode = false; - - // 把传入的路径标准路径 - public static genUnifiedPath(beProcessPath) : string{ - //全部使用 / - beProcessPath = beProcessPath.replace(/\\/g, '/'); - while(beProcessPath.match(/\/\//)){ - beProcessPath = beProcessPath.replace(/\/\//g, '/'); - } - //win盘符转为小写 - beProcessPath = beProcessPath.replace(/^\w:/, function($1){return $1.toLocaleLowerCase()}); - return beProcessPath; - } - - // 获取当前VScode活跃窗口的文件路径 - public static getVSCodeAvtiveFilePath(): Object{ - let retObject = {retCode : 0, retMsg : "", filePath: "" }; - - let activeWindow = vscode.window.activeTextEditor; - if (activeWindow){ - let activeFileUri = ''; - // 先判断当前活动窗口的 uri 是否有效 - let activeScheme = activeWindow.document.uri.scheme; - if( activeScheme !== "file" ){ - // 当前活动窗口不是file类型,遍历 visibleTextEditors,取出file类型的窗口 - let visableTextEditorArray = vscode.window.visibleTextEditors; - for (const key in visableTextEditorArray) { - const editor = visableTextEditorArray[key]; - let editScheme = editor.document.uri.scheme; - if(editScheme === "file"){ - activeFileUri = editor.document.uri.fsPath; - break; - } - } - }else{ - // 使用 activeWindow - activeFileUri = activeWindow.document.uri.fsPath - } - if(activeFileUri === ''){ - retObject.retMsg = "[Error]: adapter start file debug, but file Uri is empty string"; - retObject.retCode = -1; - return retObject; - } - - let pathArray = activeFileUri.split(path.sep); - let filePath = pathArray.join('/'); - filePath = '"' + filePath + '"'; //给路径加上"" - - retObject.filePath = filePath; - return retObject; - - }else{ - retObject.retMsg = "[Error]: can not get vscode activeWindow"; - retObject.retCode = -1; - return retObject; - } - } - - // 构建可接受的后缀列表 - public static rebuildAcceptExtMap(userSetExt? : string){ - this.extMap = new Object(); - this.extMap['lua'] = true; - this.extMap['lua.txt'] = true; - if(typeof userSetExt == 'string' && userSetExt != ''){ - this.extMap[userSetExt] = true; - } - } - - //建立/刷新 工程下 文件名-路径Map - // 评估执行效率,这个函数可以考虑应该区分同步,以优化体验 - public static rebuildWorkspaceNamePathMap(rootPath : string){ - let beginMS = this.getCurrentMS();//启动时毫秒数 - let _fileNameToPathMap = new Array(); // 文件名-路径 cache - let workspaceFiles = pathReader.files(rootPath, {sync:true}); //同步读取工程中所有文件名 - let workspaceFileCount = workspaceFiles.length; - let processFilNum = 0; //记录最终处理了多少个文件 - for(let processingFileIdx = 0; processingFileIdx < workspaceFileCount ; processingFileIdx++){ - let nameExtObject = this.getPathNameAndExt(workspaceFiles[processingFileIdx]); - if( !this.extMap[nameExtObject['ext']] ){ - // 文件类型不在可处理列表中 - continue; - } - processFilNum = processFilNum + 1; - let fileNameKey = nameExtObject['name']; // key是文件名,不包含路径和文件后缀 - if(_fileNameToPathMap[fileNameKey]){ - //存在同名文件 - if(isArray(_fileNameToPathMap[fileNameKey])){ - _fileNameToPathMap[fileNameKey].push(workspaceFiles[processingFileIdx]); - }else if(typeof _fileNameToPathMap[fileNameKey] === "string"){ - //冲突, 对应的key已有值(存在同名文件), 使用数组保存数据 - let tempSaveValue = _fileNameToPathMap[fileNameKey]; - let tempArray = new Array(); - tempArray.push(tempSaveValue); - tempArray.push(workspaceFiles[processingFileIdx]); - _fileNameToPathMap[fileNameKey] = tempArray; - } - }else{ - _fileNameToPathMap[fileNameKey] = workspaceFiles[processingFileIdx]; - } - // 显示进度 - let processingRate = Math.floor( processingFileIdx / workspaceFileCount * 100 ); - let completePath = ''; - if(isArray(_fileNameToPathMap[fileNameKey])){ - completePath = _fileNameToPathMap[fileNameKey][_fileNameToPathMap[fileNameKey].length-1]; - }else if(typeof _fileNameToPathMap[fileNameKey] === "string"){ - completePath = _fileNameToPathMap[fileNameKey]; - } - DebugLogger.AdapterInfo(processingRate + "% | " + fileNameKey + " " + completePath); - } - let endMS = this.getCurrentMS();//文件分析结束时毫秒数 - DebugLogger.AdapterInfo("文件Map刷新完毕,使用了" + (endMS - beginMS) + "毫秒。检索了"+ workspaceFileCount +"个文件, 其中" + processFilNum + "个lua类型文件"); - if(processFilNum <= 0){ - DebugLogger.showTips("没有在工程中检索到lua文件。请检查launch.json文件中lua后缀是否配置正确, 以及VSCode打开的工程是否正确",2) - let noLuaFileTip = "[!] 没有在VSCode打开的工程中检索到lua文件,请进行如下检查\n 1. VSCode打开的文件夹是否正确 \n 2. launch.json 文件中 luaFileExtension 选项配置是否正确" - DebugLogger.DebuggerInfo(noLuaFileTip); - DebugLogger.AdapterInfo(noLuaFileTip); - } - this.fileNameToPathMap = _fileNameToPathMap; - } - - //获取当前毫秒数 - public static getCurrentMS(){ - var currentMS = new Date();//获取当前时间 - return currentMS.getTime(); - } - - // 检查同名文件 - public static checkSameNameFile(){ - let sameNameFileStr; - for (const nameKey in this.fileNameToPathMap) { - let completePath = this.fileNameToPathMap[nameKey] - if(isArray(completePath)){ - //初始化语句 - if(sameNameFileStr === undefined){ - sameNameFileStr = "\n请注意VSCode打开工程中存在以下同名lua文件: \n"; - } - sameNameFileStr = sameNameFileStr + " + " + completePath.join("\n + ") + "\n\n" - } - } - - if(sameNameFileStr){ - DebugLogger.showTips("\nVSCode打开工程中存在同名lua文件, 可能会影响调试器执行, 详细信息请查看VSCode控制台 OUTPUT - Debugger/log 日志",1) - sameNameFileStr = sameNameFileStr + "调试器在自动路径模式下,可能无法识别同名lua文件中的断点,导致打开错误的文件。请修改VSCode打开的文件夹,确保其中没有同名文件。或者关闭launch.json中的autoPathMode, 改为手动配置路径。\n详细参考: https://github.com/Tencent/LuaPanda/blob/master/Docs/Manual/access-guidelines.md#第二步-路径规范 \n" - DebugLogger.DebuggerInfo(sameNameFileStr); - DebugLogger.AdapterInfo(sameNameFileStr); - } - } - - // 从URI分析出文件名和后缀 - public static getPathNameAndExt(UriOrPath): Object{ - let name_and_ext = path.basename(UriOrPath).split('.'); - let name = name_and_ext[0]; //文件名 - let ext = name_and_ext[1] || ''; //文件后缀 - for (let index = 2; index < name_and_ext.length; index++) { - ext = ext + '.' + name_and_ext[index]; - } - return { name, ext }; - } - - // 传入局部路径,返回完整路径 - public static checkFullPath( shortPath: string ): string{ - if(this.useAutoPathMode === false){ - return shortPath; - } - - //如果首字符是@,去除@ - if('@' === shortPath.substr(0,1)){ - shortPath = shortPath.substr(1); - } - - let nameExtObject = this.getPathNameAndExt(shortPath); - let fileName = nameExtObject['name']; - let fullPath = this.fileNameToPathMap[fileName]; - if(fullPath){ - if(isArray(fullPath)){ - // 存在同名文件 - for (const key in fullPath) { - const element = fullPath[key]; - if(element.indexOf(shortPath)){ - return element; // 这里固定返回第一个元素 - } - } - }else if(typeof fullPath === "string"){ - return fullPath; - } - } - //最终没有找到,返回输入的地址 - DebugLogger.showTips("调试器没有找到文件 " + shortPath + " 。 请检查launch.json文件中lua后缀是否配置正确, 以及VSCode打开的工程是否正确", 2); - return shortPath; - } -} diff --git a/src/code/server/codeCompletion.ts b/src/code/server/codeCompletion.ts new file mode 100644 index 0000000..ae9eae5 --- /dev/null +++ b/src/code/server/codeCompletion.ts @@ -0,0 +1,218 @@ +// Tencent is pleased to support the open source community by making LuaPanda available. +// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +// https://opensource.org/licenses/BSD-3-Clause +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +import { CodeEditor } from './codeEditor'; +import * as Tools from "./codeTools"; +import { + CompletionItem, + CompletionItemKind, + InsertTextFormat, + Position +} from 'vscode-languageserver'; +// import { Logger } from './codeLogManager'; +import { CodeSymbol } from "./codeSymbol"; +import { CodeDefinition } from './codeDefinition'; +import { TypeInfer } from './typeInfer'; +import { isArray } from 'util'; + +export class CodeCompletion { + // 代码补全入口函数 + public static completionEntry(uri : string, pos: Position): CompletionItem[]{ + // 获取用户输入的前缀 + let luaText = CodeEditor.getCode(uri); + let userInputString = Tools.getTextByPosition(luaText , pos); + // 处理注释 + if (userInputString == "---") { + let completingArray = this.completionComment(uri, pos, luaText); + return completingArray; + } + + userInputString = userInputString.replace(/:/g,"."); //因为chunk,符号列表中的: 都被转换为 . 这里也要转换,否则查不到 + // 先对[用户的完整输入]做一次[直接搜索] + let searchResArray = this.commonCompletionSearch(uri, userInputString) || []; // 这里搜索的范围应该是用户代码, 所有预制文件 + // 保存Completion信息的数组 + let retCompletionArray; + // 如果用户输入字段中含有分隔符[.:], 准备分段处理, 检索tag + let userInputSplitArr = this.splitStringwithTrigger(userInputString); //userInputSplitArr 用户输入的字符串 + if(userInputSplitArr && userInputSplitArr.length > 1){ + // 用户输入中含有分隔符[.:] + let lastPrefixSearchRet = TypeInfer.SymbolTagForCompletionEntry(uri, userInputString) || []; + searchResArray = searchResArray.concat(lastPrefixSearchRet); + // 把带有分隔符,的直搜结果去除前缀,仅保留要补全的后缀 + retCompletionArray = this.symbolToCompletionArray(searchResArray, true); + }else{ + // 处理直接搜索到的, 不含分隔符[.:] + retCompletionArray = this.symbolToCompletionArray(searchResArray); + } + + let retCompletionItem = this.completeItemDuplicateRemoval(retCompletionArray); + return retCompletionItem; + } + + private static fmtParamToSnippet(paramArray: string[]): string { + let snippet = '(' + paramArray.map((param, i) => `\${${i + 1}:${param}}`).join(', ') + ')'; + return snippet; + } + + private static getDocCommentInsertText(functionName: string, paramArray: string[]): string { + // 判断param ,自动生成param类型注释 + let docCommentSnippet = functionName + " ${1:Description of the function}"; + let maxParamLength = 0; + paramArray.forEach((param) => { + maxParamLength = Math.max(maxParamLength, param.length); + }); + // add param + let i = 2; + paramArray.forEach((param) => { + param += Tools.getNSpace(maxParamLength - param.length); + docCommentSnippet += `\n---@param ${param} \${${i++}:Type} \${${i++}:Description}`; + }); + // add return + docCommentSnippet += `\n\${${i++}:---@return } \${${i++}:Type} \${${i++}:Description}`; + return docCommentSnippet; + } + + private static getReturnComment():CompletionItem{ + let completeItem = { + label: "mark return", + kind: CompletionItemKind.Snippet, + insertText: "@return ", + detail: "Mark return type for this function", + insertTextFormat: InsertTextFormat.Snippet + }; + return completeItem; + } + + private static getDocCommentCompletingItem(uri: string, line: number): CompletionItem { + let functionInfo = CodeDefinition.getFunctionInfoByLine(uri, line); + if (functionInfo.functionName == "") { + return null; + } + + let completeItem = { + label: functionInfo.functionName + " comment", + kind: CompletionItemKind.Snippet, + insertText: this.getDocCommentInsertText(functionInfo.functionName, functionInfo.functionParam), + detail: "Write comments or mark return type for this function.", + insertTextFormat: InsertTextFormat.Snippet + }; + return completeItem; + } + + private static commentVarTypeTips(uri: string, line: number): CompletionItem { + //TODO 这里要加入判断一下本行有没有数据 + let completeItem = { + label: "@type", + kind: CompletionItemKind.Snippet, + insertText: "@type ${1:Type} ${2:Description}", + detail: "comment var type", + insertTextFormat: InsertTextFormat.Snippet + }; + return completeItem; + } + + //把字符串按. 或者 : 分割成数组。若不含: . 则数组中只有一个元素 + private static splitStringwithTrigger(str){ + let userInputTxt_DotToBlank = str.replace(/[\.:]/g, ' '); + let userInputArr = userInputTxt_DotToBlank.split(' '); + return userInputArr; + } + + // 把符号数组转换为VSCode能够识别的补全数组 + // @onlyKeepPostfix 仅保留后缀 + private static symbolToCompletionArray(retSymb, onlyKeepPostfix = false){ + if (!isArray(retSymb)) { + return []; + } + + let completingArray = []; + for (let idx = 0; idx < retSymb.length; idx++) { + + let finalInsertText = retSymb[idx]['searchName']; + if(onlyKeepPostfix){ + let userInputSplitArr = this.splitStringwithTrigger(finalInsertText); + finalInsertText = userInputSplitArr.pop(); + } + let completeKind : CompletionItemKind + let labelTxt = finalInsertText; + switch(retSymb[idx]['kind']){ + case 12: + completeKind = CompletionItemKind.Function; + finalInsertText = finalInsertText + this.fmtParamToSnippet(retSymb[idx]['funcParamArray']); + break; + default: + completeKind = CompletionItemKind.Text; + } + + let completeItem = { + label: labelTxt, + kind: completeKind, + insertText: finalInsertText, + detail : retSymb[idx]['name'], + insertTextFormat: InsertTextFormat.Snippet + } + if(completeItem.label == undefined){ + completeItem.label = "error undefined!"; + }else{ + completingArray.push(completeItem); + } + } + return completingArray; + } + + + // 普通搜索。这里返回的必须是一个数组,哪怕是一个空数组 + private static commonCompletionSearch(uri, searchPrefix){ + let retSymb = CodeSymbol.searchSymbolforCompletion(uri, searchPrefix, Tools.SearchMode.PrefixMatch); + if(!isArray(retSymb)){ + return []; + } + return retSymb; + } + + // 用户输入了--- , 生成注释 + private static completionComment(uri, pos, luaText){ + let completingArray = new Array(); + if (Tools.isNextLineHasFunction(luaText, pos) == true) { + completingArray.push(this.getDocCommentCompletingItem(uri, pos.line + 1)); + completingArray.push(this.getReturnComment()); + }else{ + completingArray.push(this.commentVarTypeTips(uri, pos.line)); + } + return completingArray; + } + + // 删除重复的符号 + private static completeItemDuplicateRemoval(completingArray){ + let retCompItemList = new Array(); + for (let index = 0; index < completingArray.length; index++) { + let DuplicateFlag = false; + const completeItem = completingArray[index]; + for (let retIdx = 0, len = retCompItemList.length; retIdx < len; retIdx++) { + if( this.ItemIsEq( completeItem, retCompItemList[retIdx] ) ){ + DuplicateFlag = true; + break; + } + } + if(! DuplicateFlag ){ + retCompItemList.push(completeItem); + } + } + return retCompItemList; + } + + //判断CompleteItem是否相等 + private static ItemIsEq(item1, item2):boolean{ + if(item1.label === item2.label && + item1.kind === item2.kind && + item1.insertText === item2.insertText && + item1.insertTextFormat === item2.insertTextFormat ){ + return true; + } + return false; + } + +} diff --git a/src/code/server/codeDefinition.ts b/src/code/server/codeDefinition.ts new file mode 100644 index 0000000..e0178a5 --- /dev/null +++ b/src/code/server/codeDefinition.ts @@ -0,0 +1,206 @@ +// Tencent is pleased to support the open source community by making LuaPanda available. +// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +// https://opensource.org/licenses/BSD-3-Clause +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +import { + Location, + TextDocumentPositionParams, + SymbolKind +} from 'vscode-languageserver'; +import { Logger } from './codeLogManager'; +import { CodeSymbol } from "./codeSymbol"; +import { TypeInfer } from "./typeInfer" +import * as Tools from "./codeTools"; +import { isArray } from 'util'; + +//查找定义的主函数 +export class CodeDefinition { + public static getSymbalDefine(info: TextDocumentPositionParams, isRetSymbol?) { + isRetSymbol = isRetSymbol || false; + Tools.transPosStartLineTo1(info.position); + //获取指定文件的doc信息容器 + let uri = info.textDocument.uri; + let astContainer = CodeSymbol.docSymbolMap.get(uri); + if(!astContainer){ + Logger.InfoLog("[Error] getSymbalDefine can’t find AST"); + return null; + } + // 根据VSCode提供的符号位置查询查号名 + let symbRet = astContainer.searchDocSymbolfromPosition(info.position); + // 全局查找定义 + if (symbRet != undefined && symbRet['sybinfo'] != undefined) { + let symbolInfo = symbRet['sybinfo']; + let containerList = symbRet['container']; + //先做一次普通搜索,如果有结果,就以普通搜索结果优先 + if(symbolInfo.name.match(':')){ + symbolInfo.name = symbolInfo.name.replace(/:/g,"."); + } + let symbInstance = this.directSearch(uri, symbolInfo.name, Tools.SearchMode.ExactlyEqual); + if(isArray(symbInstance) && symbInstance.length > 0 ){ + // 已经搜到了结果 + }else{ + symbInstance = TypeInfer.SymbolTagForDefinitionEntry(symbolInfo, uri); // 查找定义 + } + + if(!symbInstance || symbInstance.length == 0) return; // 未能查到定义 + Tools.transPosStartLineTo0(info.position); + let finalRetSymbols; + if(symbolInfo.isLocal){ + // 同级优先,上方最近 + finalRetSymbols = this.judgeLocalDefinition(symbInstance, containerList, info); + if(!finalRetSymbols && symbInstance && symbInstance.length > 0){ + // 因为可以使用@type标记,所以搜出的结果并不在同一个文件中,导致judgeLocalDefinition误判为没有定义 + finalRetSymbols = symbInstance[0]; + } + }else{ + // 最远原则 + finalRetSymbols = symbInstance[0]; + } + + // 此处应该保证 symbInstance是一个实例(不是数组) + if( !finalRetSymbols ) return; //没找到,或过滤后没有适合的符号 + if(isRetSymbol) return finalRetSymbols; //回传符号,而不是位置信息 + let retLoc = Location.create(finalRetSymbols['containerURI'], finalRetSymbols['location'].range); + return retLoc; + } else { + // 没找到符号,判断require文件的情况 + let reqFileName = astContainer.searchDocRequireFileNameFromPosition(info.position); + let uri = Tools.transFileNameToUri(reqFileName); + if(uri.length > 0){ + return Tools.createEmptyLocation(uri); + } + return; + } + } + + // 直接搜索,其逻辑应该是搜索本文件的所有变量 -> 引用文件的全局变量 -> 所有文件的全局变量 + private static directSearch(uri, symbolStr, method){ + let ret = CodeSymbol.searchSymbolinDoc(uri, symbolStr, method) || []; + if(ret.length === 0){ + ret = CodeSymbol.searchSymbolforGlobalDefinition(uri, symbolStr, method, Tools.SearchRange.GlobalSymbols) || []; + } + return ret; + } + +//----------------------------------------------------------------------------- +//-- 局部变量的处理 +//----------------------------------------------------------------------------- + // 得到了多个 symbInstance,要判断那个是目标。 这个函数只处理局部变量,全局变量按照引用树只搜索出一个结果,无需判断 + // @symbArray match的symb数组 + // @findoutSymbols 找到的定义信息 + // @containerList 被查找信息的深度 + // @docPosition vscode获取的位置 + //函数要求,可能传入1到多个符号,返回最符合的1个符号. 先比较container , 查出所有本层/上层定义,之后找同层最近的,没有则向上 + private static judgeLocalDefinition(findoutSymbols, containerList, docPosition) { + // let userClickLine = docPosition.position.line; + if( !findoutSymbols || findoutSymbols.length <= 0 || !docPosition || !containerList || containerList.length <= 0) return; + + + if(findoutSymbols.length == 1) return findoutSymbols[0]; + //查出所有findoutSymbols的共同深度,数字越大共同深度越多,不在一个chunk中是-1 + let commonDepth = this.findCommonDepth(containerList, findoutSymbols); + //找出共同深度最大值 + let maxComDep = 0; //最大相同深度 + for (let index = 0; index < commonDepth.length; index++) { + if (maxComDep < commonDepth[index]){ + maxComDep = commonDepth[index]; + } + } + + let maxArray = new Array(); + for (let index = 0; index < commonDepth.length; index++) { + if (maxComDep == commonDepth[index]){ + maxArray.push(findoutSymbols[index]); + } + } + //此时maxArray中记录了共同深度最大的符号 + if(maxArray.length == 1){ + return maxArray[0]; + } + //findUpNearestSymbol 的作用是寻找line在上方并最近的符号 + return this.findUpNearestSymbol(docPosition.position, maxArray); + } + + //查找上方最近的符号 + public static findUpNearestSymbol(docPosition, maxArray){ + let distanceLineNumber = new Array(); + let standardLine = docPosition.line; + // 使用standardLine - upLine得到符号差值,这个值如果为负,忽略。 + // 差值越小离得越近 + for (const key in maxArray) { + const element = maxArray[key]; + let upLine = element.location.range.start.line; + distanceLineNumber[key] = standardLine - upLine; //数值越小越好 + } + + //寻找最小差值 + let minComDep = 99999; + for (let index = 0; index < distanceLineNumber.length; index++) { + if (distanceLineNumber[index] < minComDep && distanceLineNumber[index] >= 0){ + minComDep = distanceLineNumber[index]; + } + } + + let minSymbolIdx = 0; + for (let index = 0; index < distanceLineNumber.length; index++) { + if (minComDep == distanceLineNumber[index]){ + minSymbolIdx = index; + break; + } + } + + return maxArray[minSymbolIdx]; + } + + //查找符号的共同深度 , 当不在同一个chunk时返回-1 , 数字表示相同的chunk数 + //standradDepth被查找的符号深度 + //beFindSymbolList找到的定义深度 + public static findCommonDepth( standradDepth , beFindSymbolList){ + let retArray = new Array(); + + //评估各个符号和标准深度的相同深度 + for (const key in beFindSymbolList) { + const element = beFindSymbolList[key]; + + //定义深度 > 被查找元素深度 + if (standradDepth.length < element.containerList.length) { + retArray[key] = -1; + continue; + } + //遍历一个具体的待查找深度 + for (let index = 0; index < standradDepth.length; index++) { + let standardChunk = standradDepth[index]; + let beAnalyzeDepth = element.containerList[index]; + if (standardChunk && beAnalyzeDepth &&standardChunk.chunkName == beAnalyzeDepth.chunkName && standardChunk.loc.start.line == beAnalyzeDepth.loc.start.line && standardChunk.loc.end.line == beAnalyzeDepth.loc.end.line){ + retArray[key] = index + 1; + }else{ + if(standardChunk && !beAnalyzeDepth){ + + }else{ + retArray[key] = -1; + } + } + } + } + //如果有多个最大值,对比 + return retArray; + } + + + // 按行号查询function + public static getFunctionInfoByLine(uri: string, line: number): { functionName: string, functionParam: string[] } { + let displaySymbolArray = CodeSymbol.getOneDocSymbolsArray(uri, null, Tools.SearchRange.AllSymbols); + let result = { functionName: "", functionParam: [] }; + for (const key in displaySymbolArray) { + const docDisplaySymbol = displaySymbolArray[key]; + if (docDisplaySymbol.kind == SymbolKind.Function && docDisplaySymbol.location.range.start.line == line) { + result.functionName = docDisplaySymbol.searchName; + result.functionParam = docDisplaySymbol.funcParamArray; + return result; + } + } + return result; + } +} \ No newline at end of file diff --git a/src/code/server/codeEditor.ts b/src/code/server/codeEditor.ts new file mode 100644 index 0000000..5e29a75 --- /dev/null +++ b/src/code/server/codeEditor.ts @@ -0,0 +1,30 @@ +// Tencent is pleased to support the open source community by making LuaPanda available. +// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +// https://opensource.org/licenses/BSD-3-Clause +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +import * as Tools from './codeTools'; +import { Logger } from './codeLogManager'; + +// 保存Editor中的代码 +export class CodeEditor { + public static codeInEditor = new Map(); + + public static saveCode( uri : string , text : string) { + this.codeInEditor[uri] = text; + } + + public static getCode( uri : string ): string { + if(this.codeInEditor[uri]){ + return this.codeInEditor[uri]; + }else{ + let luatxt = Tools.getFileContent(Tools.uriToPath(uri)); + if(!luatxt){ + Logger.InfoLog("Can’t get file content. uri:" + uri); + return; + } + return luatxt; + } + } +} \ No newline at end of file diff --git a/src/code/server/codeExport/cppCodeProcessor.ts b/src/code/server/codeExport/cppCodeProcessor.ts new file mode 100644 index 0000000..cc4a657 --- /dev/null +++ b/src/code/server/codeExport/cppCodeProcessor.ts @@ -0,0 +1,1026 @@ +// Tencent is pleased to support the open source community by making LuaPanda available. +// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +// https://opensource.org/licenses/BSD-3-Clause +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +// import path = require('path'); +// import dir = require('path-reader'); +// import fs = require('fs'); +// import { parseAst, Language, Node } from 'univac'; +// import { Logger } from '../codeLogManager'; +// import { CodeSymbol } from '../codeSymbol'; +// import * as Tools from '../codeTools'; + +// export class CppCodeProcessor { +// // workspace 根目录,server初始化时设置。 +// private static _cppInterfaceIntelliSenseResPath: string | null; +// // sluaUE的分析路径 +// public static get cppInterfaceIntelliSenseResPath() { +// if(!this._cppInterfaceIntelliSenseResPath){ +// // joezhuoli TODO +// if(Tools.getVSCodeOpenedFolders() && Tools.getVSCodeOpenedFolders().length > 0){ +// this._cppInterfaceIntelliSenseResPath = Tools.getVSCodeOpenedFolders()[0] + "/.vscode/LuaPanda/IntelliSenseRes/UECppInterface/"; +// } +// } +// return this._cppInterfaceIntelliSenseResPath; +// } + +// public static loadIntelliSenseRes() { +// if (fs.existsSync(this.cppInterfaceIntelliSenseResPath)) { +// CodeSymbol.refreshUserPreloadSymbals(this.cppInterfaceIntelliSenseResPath); +// } +// } + +// /** +// * 将静态导出的C++代码处理成Lua table用于代码提示。 +// * @param cppDir C++代码根目录。 +// */ +// public static async processCppDir(cppDir: string): Promise { +// if (this.cppInterfaceIntelliSenseResPath === null) { +// Logger.ErrorLog('未打开文件夹,无法使用此功能!'); +// Tools.showTips('未打开文件夹,无法使用此功能!'); +// return; +// } +// Tools.showTips('正在分析处理中,请稍后。分析完成后会有提示,请不要重复点击。'); + +// // 生成一个子目录对应用户代码路径 +// let subDir = cppDir; +// subDir = subDir.replace(/\//g, ' '); +// subDir = subDir.replace(/\\/g, ' '); +// subDir = subDir.replace(/:/g, ''); +// subDir = subDir.trim(); +// subDir = subDir.replace(/ /g, '-'); + +// this.removeCppInterfaceIntelliSenseRes(path.join(this.cppInterfaceIntelliSenseResPath, subDir)); +// let cppHeaderFiles = this.getCppHeaderFiles(cppDir); +// let cppSourceFiles = this.getCppSourceFiles(cppDir); + +// let totalProcessNum = await this.processParse(cppHeaderFiles, cppSourceFiles, subDir); +// return totalProcessNum; +// } + +// private static async processParse(cppHeaderFiles: string[], cppSourceFiles: string[], subDir: string): Promise { +// await this.parseCppFiles(cppHeaderFiles, CppFileType.CppHeaderFile, subDir); +// await this.parseCppFiles(cppSourceFiles, CppFileType.CppSourceFile, subDir); + +// let totalProcessNum = cppHeaderFiles.length + cppSourceFiles.length; +// return Promise.resolve(totalProcessNum); +// // Tools.showTips('CPP 导出文件处理完成!共解析 ' + totalProcessNum + ' 个文件。'); +// } + +// private static async parseCppFiles(filePaths: string[], cppFileType: CppFileType, subDir: string) { +// for (let i = 0; i < filePaths.length; i++) { +// let cppText = this.getCppCode(filePaths[i], cppFileType); +// if (cppText === '') { +// continue; +// } + +// let astNode: Node; +// try { +// astNode = await parseAst({ +// input: cppText, +// language: Language.cpp, +// omitPosition: true, +// text: true, +// basePath: this.getWasmDir() +// }); +// if (cppFileType === CppFileType.CppHeaderFile) { +// this.parseCppHeaderAST2LuaCode(astNode, subDir); +// } else if (cppFileType === CppFileType.CppSourceFile) { +// let classFunctionInfo = this.getClassFunctionInfo(astNode); +// this.parseCppSourceAST2LuaCode(astNode, classFunctionInfo, subDir); +// } +// } catch(e) { +// Logger.ErrorLog("Parse cpp file failed, filePath: " + filePaths[i] +" error: "); +// Logger.ErrorLog(e); +// } +// } +// } + +// /* +// private static parseCppFile(filePath: string) { +// let cppText = this.getCppCode(filePath); + +// let parseProcess: Promise = parseAst({ +// input: cppText, +// language: Language.cpp, +// omitPosition: true, +// text: true, +// basePath: this.getWasmDir() +// }); + +// parseProcess.then( +// (astNode) => { +// // let str = JSON.stringify(astNode, null, 2); +// // Logger.DebugLog(str); +// this.parseAST2LuaCode(astNode); +// } +// ) +// .catch((e) => { +// Logger.ErrorLog("Parse cpp file failed, filePath: " + filePath +" error: "); +// Logger.ErrorLog(e); +// }); +// } +// */ + +// /** +// * 获取文件内容,并对内容进行预处理。 +// * @param filePath 文件路径。 +// * @param cppFileType 文件类型 +// */ +// private static getCppCode(filePath: string, cppFileType: CppFileType): string { +// let content = Tools.getFileContent(filePath); + +// if (this.isFileNeedParse(cppFileType, content) === false) { +// return ''; +// } + +// content = this.pretreatCppCode(content); + +// return content; +// } + +// private static isFileNeedParse(cppFileType: CppFileType, content: string): boolean { +// let regex: RegExp; +// let result: RegExpExecArray | null; +// switch (cppFileType) { +// case CppFileType.CppHeaderFile: +// regex = URegex.UCLASS +// if ((result = regex.exec(content)) !== null) { +// return true; +// } +// regex = URegex.USTRUCT +// if ((result = regex.exec(content)) !== null) { +// return true; +// } +// regex = URegex.UENUM +// if ((result = regex.exec(content)) !== null) { +// return true; +// } +// break; + +// case CppFileType.CppSourceFile: +// regex = URegex.DefLuaClass; +// if ((result = regex.exec(content)) !== null) { +// return true; +// } +// break; +// } +// return false; +// } + +// /** +// * 将 class XXX ClassName 替换为 class className +// * 去除宏 GENERATED_BODY +// * 去除宏 GENERATED_UCLASS_BODY +// * 去除宏 GENERATED_USTRUCT_BODY +// * 去除宏 DEPRECATED +// * 去除宏 UE_DEPRECATED +// * 去除宏 DECLARE_XXX +// * 去除宏 PRAGMA_XXX +// */ +// private static pretreatCppCode(content: string): string { +// let regex: RegExp; +// let result: RegExpExecArray | null; + +// // 将 class XXX ClassName 替换为 class className +// regex = /\s*(class\s+[A-Z0-9_]+)\s+\w+.+/; +// while ((result = regex.exec(content)) !== null) { +// content = content.replace(result[1], 'class'); +// } + +// // 将 struct XXX StructName 替换为 struct StructName +// regex = /\s*(struct\s+[A-Z0-9_]+)\s+\w+.+/; +// while ((result = regex.exec(content)) !== null) { +// content = content.replace(result[1], 'struct'); +// } + +// let regex2CommentArray: RegExp[] = new Array(); +// // 去除宏 GENERATED_BODY +// regex2CommentArray.push(URegex.GENERATED_BODY); +// // 去除宏 GENERATED_UCLASS_BODY +// regex2CommentArray.push(URegex.GENERATED_UCLASS_BODY); +// // 去除宏 GENERATED_USTRUCT_BODY +// regex2CommentArray.push(URegex.GENERATED_USTRUCT_BODY); +// // 去除宏 UE_DEPRECATED +// regex2CommentArray.push(URegex.UE_DEPRECATED); +// // 去除宏 DEPRECATED +// regex2CommentArray.push(URegex.DEPRECATED); +// // 去除宏 DECLARE_XXX +// regex2CommentArray.push(URegex.DECLARE); +// // 去除宏 PRAGMA_XXX +// regex2CommentArray.push(URegex.PRAGMA); + +// let regex2BlankArray: RegExp[] = new Array(); +// // 去除 UMETA(xxx) +// regex2BlankArray.push(URegex.UMETA); +// // 去除 ENGINE_API +// regex2BlankArray.push(URegex.ENGINE_API); + +// content = this.removeByRegex(content, regex2CommentArray, regex2BlankArray); + +// return content; +// } + +// private static removeByRegex(content: string, regex2CommentArray: RegExp[], regex2BlankArray: RegExp[]): string { +// let result: RegExpExecArray | null; + +// regex2CommentArray.forEach((regex: RegExp) => { +// while ((result = regex.exec(content)) !== null) { +// content = content.replace(result[1], '//'); +// } +// }); + +// regex2BlankArray.forEach((regex: RegExp) => { +// while ((result = regex.exec(content)) !== null) { +// content = content.replace(result[1], ''); +// } +// }); + +// return content; +// } + +// private static parseCppHeaderAST2LuaCode(astNode: Node, subDir: string) { +// let foundUCLASS: boolean = false; +// let foundUSTRUCT: boolean = false; +// let foundUENUM: boolean = false; + +// astNode.children.forEach((child: Node) => { +// if (child.type === 'comment') { +// return; +// } + +// if (child.type === 'expression_statement' && child.text.match(URegex.UCLASS)) { +// // 标记找到UCLASS,即下一个Node。 +// foundUCLASS = true; +// } else if (child.type === 'expression_statement' && child.text.match(URegex.USTRUCT)) { +// foundUSTRUCT = true; +// } else if (child.type === 'expression_statement' && child.text.match(URegex.UENUM)) { +// foundUENUM = true; +// } else if (foundUCLASS === true) { +// let result = this.handleUCLASS(child); +// foundUCLASS = false; +// if (result.className !== '') { +// let filePath = path.join(this.cppInterfaceIntelliSenseResPath, subDir, result.className + '.lua'); +// this.appendText2File(result.luaText, filePath); +// CodeSymbol.refreshOneUserPreloadDocSymbols(filePath); +// } +// } else if (foundUSTRUCT === true) { +// let result = this.handleUSTRUCT(child); +// foundUSTRUCT = false; +// if (result.structName !== '') { +// let filePath = path.join(this.cppInterfaceIntelliSenseResPath, subDir, result.structName + '.lua'); +// this.appendText2File(result.luaText, filePath); +// CodeSymbol.refreshOneUserPreloadDocSymbols(filePath); +// } +// } else if (foundUENUM === true) { +// let result = this.handleUENUM(child); +// foundUENUM = false; +// if (result.enumType !== '') { +// let filePath = path.join(this.cppInterfaceIntelliSenseResPath, subDir, result.enumType + '.lua'); +// this.appendText2File(result.luaText, filePath); +// CodeSymbol.refreshOneUserPreloadDocSymbols(filePath); +// } +// // 外层有namespace的情况,要放到UENUM后面,UENUM后面的节点有可能是namespace +// child.children.forEach((child: Node) => { +// if (child.type === 'declaration_list') { +// this.parseCppHeaderAST2LuaCode(child, subDir); +// } +// }); +// } +// }); +// } + +// private static handleUCLASS(astNode: Node): {luaText: string, className: string} { +// let luaText = ''; +// let className = ''; +// let baseClass = []; +// let declarationList: {uPropertys: string, uFunctions: string} = {uPropertys: '', uFunctions: ''}; + +// // class ClassName: public BaseClass +// astNode.children.forEach((child: Node) => { +// switch (child.type) { +// case 'type_identifier': +// className = child.text; +// break; + +// case 'base_class_clause': +// baseClass = baseClass.concat(this.handleBaseClassClause(child, className)); +// break; + +// case 'field_declaration_list': +// declarationList = this.handleDeclarationList(child, className); +// break; +// } +// }); +// luaText = declarationList.uPropertys + declarationList.uFunctions; + +// let classDeclaration: string; +// if (baseClass.length > 0) { +// // 默认选取继承类中的第一个 +// classDeclaration = className + ' = {} ---@type ' + baseClass[0] + '\n'; +// } else { +// classDeclaration = className + ' = {}\n'; +// } +// return {luaText: classDeclaration + luaText, className: className}; +// } + +// private static handleUSTRUCT(astNode: Node): {luaText: string, structName: string} { +// let luaText = ''; +// let structName = ''; +// let declarationList: {uPropertys: string, uFunctions: string} = {uPropertys: '', uFunctions: ''}; + +// if (astNode.type === 'struct_specifier') { +// astNode.children.forEach((child: Node) => { +// switch (child.type) { +// case 'type_identifier': +// structName = child.text; +// break; + +// case 'field_declaration_list': +// declarationList = this.handleDeclarationList(child, structName); +// break; +// } +// }); +// luaText = declarationList.uPropertys + declarationList.uFunctions; + +// let structDeclaration: string; +// structDeclaration = structName + ' = {}\n'; +// luaText = structDeclaration + luaText; +// } else if (astNode.type === 'declaration') { +// astNode.children.forEach((child: Node) => { +// if (child.type === 'struct_specifier') { +// let result = this.handleUSTRUCT(child); +// luaText = result.luaText; +// structName = result.structName; +// } +// }); +// } + +// return {luaText: luaText, structName: structName}; +// } + +// private static handleUENUM(astNode: Node): {enumType: string, luaText: string} { +// let luaText = ''; +// let enumType = ''; + +// if (astNode.type === 'namespace_definition') { +// astNode.children.forEach((child: Node) => { +// switch (child.type) { +// case 'identifier': +// enumType = child.text; +// break; + +// case 'declaration_list': +// child.children.forEach((child: Node) => { +// if (child.type === 'enum_specifier') { +// let result = this.handleEnumSpecifier(child); +// luaText += enumType + ' = {}\n'; +// result.enumeratorList.forEach((enumerator) => { +// luaText += enumType + '.' + enumerator + ' = nil\n'; +// }); +// } +// }); +// break; +// } +// }); +// } else if (astNode.type === 'enum_specifier') { +// let result = this.handleEnumSpecifier(astNode); +// enumType = result.enumType; +// luaText += enumType + ' = {}\n'; +// result.enumeratorList.forEach((enumerator) => { +// luaText += enumType + '.' + enumerator + ' = nil\n'; +// }); +// } else if (astNode.type === 'declaration') { +// // enum class +// astNode.children.forEach((child: Node) => { +// if (child.type === 'init_declarator') { +// let result = this.handleInitDeclarator(child); +// enumType = result.enumType; +// luaText += enumType + ' = {}\n'; +// result.enumeratorList.forEach((enumerator) => { +// luaText += enumType + '.' + enumerator + ' = nil\n'; +// }); +// } +// }); +// } + +// return {enumType: enumType, luaText: luaText}; +// } + +// private static handleInitDeclarator(astNode: Node): {enumType: string, enumeratorList: string[]} { +// let enumType = ''; +// let enumeratorList: string[] = []; + +// astNode.children.forEach((child: Node) => { +// switch (child.type) { +// case 'identifier': +// enumType = child.text; +// break; +// case 'initializer_list': +// enumeratorList = this.handleEnumeratorList(child); +// break; +// } +// }); + +// return {enumType: enumType, enumeratorList: enumeratorList}; +// } +// private static handleEnumSpecifier(astNode: Node): {enumType: string, enumeratorList: string[]} { +// let enumType = ''; +// let enumeratorList: string[] = []; + +// astNode.children.forEach((child: Node) => { +// switch (child.type) { +// case 'type_identifier': +// enumType = child.text; +// break; + +// case 'enumerator_list': +// enumeratorList = this.handleEnumeratorList(child); +// break; +// } +// }); + +// return {enumType: enumType, enumeratorList: enumeratorList}; +// } + +// private static handleEnumeratorList(astNode: Node): string[] { +// let enumeratorList: string[] = []; + +// astNode.children.forEach((child: Node) => { +// if (child.type === 'identifier') { +// enumeratorList.push(child.text); +// } else if (child.type === 'enumerator') { +// child.children.forEach((child: Node) => { +// if (child.type === 'identifier') { +// enumeratorList.push(child.text); +// } +// }); +// } +// }); + +// return enumeratorList; +// } + +// private static handleBaseClassClause(astNode: Node, className: string): string[] { +// let baseClass: string[] = []; +// astNode.children.forEach((child: Node) => { +// if (child.type === 'type_identifier') { +// baseClass.push(child.text); +// } +// }); +// return baseClass; +// } + +// private static handleDeclarationList(astNode: Node, className: string): {uPropertys: string, uFunctions: string} { +// let uPropertys = ''; +// let uFunctions = ''; +// let foundUFUNCTION = false; +// let foundUPROPERTY = false; +// astNode.children.forEach((child: Node) => { +// if (child.type === 'comment') { +// return; +// } + +// if (foundUFUNCTION === true) { +// uFunctions += this.handleUFUNCTION(child, className); +// foundUFUNCTION = false; +// } else if (foundUPROPERTY === true) { +// uPropertys += this.handleUPROPERTY(child, className); +// foundUPROPERTY = false; +// } else if ((child.type === 'field_declaration' || child.type === 'declaration') && child.text.match(URegex.UFUNCTION)) { +// foundUFUNCTION = true; +// } else if ((child.type === 'field_declaration' || child.type === 'declaration') && child.text.match(URegex.UPROPERTY)) { +// foundUPROPERTY = true; +// } else if (child.type === 'preproc_if' || child.type === 'preproc_ifdef') { +// let declarationList = this.handleDeclarationList(child, className); +// uPropertys += declarationList.uPropertys; +// uFunctions += declarationList.uFunctions; +// } +// }); +// return {uPropertys: uPropertys, uFunctions: uFunctions}; +// } + +// private static handleUFUNCTION(astNode: Node, className: string): string { +// let luaText = 'function '; +// let returnType = ''; + +// astNode.children.forEach((child: Node) => { +// switch (child.type) { +// case 'type_identifier': +// case 'primitive_type': +// // 记录返回值类型(非引用和指针) +// returnType = child.text; +// break; + +// // 模板类型 TArray +// case 'template_type': +// returnType = this.getTemplateType(child); +// break; + +// // 类类型 +// case 'class_specifier': +// returnType = this.getClassInfo(child).className; +// break; + +// // 结构体类型 +// case 'struct_specifier': +// returnType = this.getStructType(child); +// break; + +// // 函数定义 +// case 'function_declarator': +// luaText += this.handleFunctionDeclarator(child, className); +// break; + +// // 函数返回类型为指针或引用,需要向内解一层 +// case 'pointer_declarator': +// case 'reference_declarator': +// child.children.forEach((child: Node) => { +// if (child.type === 'function_declarator') { +// luaText += this.handleFunctionDeclarator(child, className); +// } +// }); +// break; +// } +// }); +// luaText += ' end\n'; + +// if (this.returnTypeMap.has(returnType)) { +// returnType = this.returnTypeMap.get(returnType); +// } +// if (returnType !== '') { +// luaText = '---@return ' + returnType + '\n' + luaText; +// } + +// return luaText; +// } + +// private static handleFunctionDeclarator(astNode: Node, className: string): string { +// let luaText = ''; + +// astNode.children.forEach((child: Node) => { +// switch (child.type) { +// case 'identifier': +// case 'field_identifier': +// luaText += className + '.' + child.text; +// break; + +// case 'parameter_list': +// luaText += this.handleParameterList(child, className); +// break; +// } +// }); +// luaText += ')'; +// return luaText; +// } + +// private static handleParameterList(astNode: Node, className: string): string { +// let luaText = '('; +// let params: string[] = []; + +// astNode.children.forEach((child: Node) => { +// if (child.type === 'parameter_declaration') { +// params = params.concat(this.handleParameterDeclaration(child)); +// } +// }); +// for (let i = 0; i < params.length; i++) { +// if (i === 0) { +// luaText += params[i]; +// } else { +// luaText += ", " + params[i]; +// } +// } +// return luaText; +// } + +// private static handleParameterDeclaration(astNode: Node): string[] { +// let params: string[] = []; +// astNode.children.forEach((child: Node) => { +// switch (child.type) { +// case 'reference_declarator': +// params.push(this.handleReferenceDeclarator(child)); +// break; + +// case 'pointer_declarator': +// params.push(this.handlePointerDeclarator(child)); +// break; + +// case 'identifier': +// params.push(child.text); +// break; +// } +// }); + +// return params; +// } + +// private static handleReferenceDeclarator(astNode: Node): string { +// let param = ''; +// astNode.children.forEach((child: Node) => { +// if (child.type === 'identifier') { +// param = child.text; +// } +// }); +// return param; +// } + +// private static handlePointerDeclarator(astNode: Node): string { +// let param = ''; +// astNode.children.forEach((child: Node) => { +// if (child.type === 'identifier') { +// param = child.text; +// } +// }); +// return param; +// } + +// private static getTemplateType(astNode: Node): string { +// let templateType = ''; +// astNode.children.forEach((child: Node) => { +// if (child.type === 'type_identifier') { +// templateType = child.text; +// } +// }); + +// return templateType; +// } + +// private static getStructType(astNode: Node): string { +// let structType = ''; +// astNode.children.forEach((child: Node) => { +// if (child.type === 'type_identifier') { +// structType = child.text; +// } +// }); + +// return structType; +// } + + +// private static handleUPROPERTY(astNode: Node, className: string): string { +// let luaText = ''; + +// astNode.children.forEach((child: Node) => { +// switch (child.type) { +// case 'identifier': +// case 'field_identifier': +// luaText += className + '.' + child.text + " = nil\n"; +// break; + +// case 'init_declarator': +// child.children.forEach((child: Node) => { +// if (child.type === 'identifier') { +// luaText += className + '.' + child.text + " = nil\n"; +// } +// }); +// break; + +// case 'pointer_declarator': +// case 'reference_declarator': +// child.children.forEach((child: Node) => { +// if (child.type === 'field_identifier') { +// luaText += className + '.' + child.text + " = nil\n"; +// } +// }); +// break; +// } +// }); +// return luaText; +// } + +// /** +// * 获取当前文件中定义的函数列表,用于填充LuaMethod的参数。 +// * @param astNode cpp源码解析后的语法树根节点。 +// * @return 存储函数列表信息的map,结构:>,暂时忽略namespace。 +// */ +// private static getClassFunctionInfo(astNode: Node): Map> { +// let classFunctionInfo = new Map>(); + +// astNode.children.forEach((child: Node) => { +// if (child.type === 'namespace_definition') { +// child.children.forEach((child: Node) => { +// if (child.type === 'declaration_list') { +// child.children.forEach((child: Node) => { +// if (child.type === 'class_specifier') { +// let classInfo = this.getClassInfo(child); +// if (classInfo.className !== '' && classInfo.functionListMap !== undefined) { +// // functionMap[classInfo.className] = classInfo.functionListMap; +// classFunctionInfo.set(classInfo.className, classInfo.functionListMap); +// } +// } +// }); +// } +// }); +// } else if (child.type === 'class_specifier') { +// let classInfo = this.getClassInfo(child); +// classFunctionInfo.set(classInfo.className, classInfo.functionListMap); +// } +// }); + +// return classFunctionInfo; +// } + +// private static getClassInfo(astNode: Node): {className: string, functionListMap: Map} { +// let className = ''; +// let functionListMap = new Map(); + +// astNode.children.forEach((child: Node) => { +// if (child.type === 'type_identifier') { +// className = child.text; +// } else if (child.type === 'field_declaration_list') { +// child.children.forEach((child: Node) => { +// if (child.type === 'function_definition') { +// let functionInfo = this.getFunctionInfo(child); +// if (functionInfo.functionName !== '') { +// functionListMap.set(functionInfo.functionName, functionInfo.paramList); +// } +// } +// }); +// } +// }); +// return {className: className, functionListMap: functionListMap} +// } + +// private static getFunctionInfo(astNode: Node): {functionName: string, paramList: string[]} { +// let functionName = ''; +// let paramList = []; +// astNode.children.forEach((child: Node) => { +// if (child.type === 'function_declarator') { +// child.children.forEach((child: Node) => { +// if (child.type === 'identifier' || child.type === 'field_identifier') { +// functionName = child.text; +// } else if (child.type === 'parameter_list') { +// paramList = this.getParamList(child); +// } +// }); +// } +// }); + +// return {functionName: functionName, paramList: paramList}; +// } + +// private static getParamList(astNode: Node): string[] { +// let paramList = []; +// astNode.children.forEach((child: Node) => { +// if (child.type === 'parameter_declaration') { +// paramList = paramList.concat(this.handleParameterDeclaration(child)); +// } +// }); + +// return paramList; +// } + +// private static parseCppSourceAST2LuaCode(astNode: Node, classFunctionInfo: Map>, subDir: string) { +// let className: string = ""; +// let baseClass: string[] = []; +// let methodList: string[] = []; + +// astNode.children.forEach((child: Node) => { +// if (child.type === 'comment') { +// return; +// } + +// if (child.type === 'expression_statement' && child.text.match(URegex.DefLuaClass)) { +// let result = this.handleDefLuaClass(child); +// className = result.className; +// baseClass = result.baseClass; +// } else if (child.type === 'expression_statement' && child.text.match(URegex.DefLuaMethod)) { +// let functionInfo = classFunctionInfo.get(className); +// methodList.push(this.handleDefLuaMethod(child, className, functionInfo)); +// } else if (child.type === 'expression_statement' && child.text.match(URegex.EndDef)) { +// if (className !== '') { +// let filePath = path.join(this.cppInterfaceIntelliSenseResPath, subDir, className + '.lua'); +// let luaText = this.assembleLuaClassText(className, baseClass, methodList); +// this.appendText2File(luaText, filePath); +// CodeSymbol.refreshOneUserPreloadDocSymbols(filePath); +// className = ''; +// baseClass.length = 0; +// methodList.length = 0; +// } +// } + +// else if (child.type === 'namespace_definition') { +// child.children.forEach((child: Node) => { +// if (child.type === 'declaration_list') { +// this.parseCppSourceAST2LuaCode(child, classFunctionInfo, subDir); +// } +// }); +// } +// }); +// } + +// private static handleDefLuaClass(astNode: Node): {className: string, baseClass: string[]} { +// let argumentList: string[] = []; + +// let argumentListNode: Node; +// astNode.children.forEach((child: Node) => { +// if (child.type === 'call_expression') { +// child.children.forEach((child: Node) => { +// if (child.type === 'argument_list') { +// argumentListNode = child; +// } +// }); +// } +// }); + +// argumentListNode.children.forEach((child: Node) => { +// if (child.type === 'identifier') { +// argumentList.push(child.text); +// } +// }); + +// return {className: argumentList[0], baseClass: argumentList.slice(1)}; +// } + +// private static handleDefLuaMethod(astNode: Node, className: string, functionInfo: Map): string { +// let luaText: string = 'function '; + +// astNode.children.forEach((child: Node) => { +// if (child.type === 'call_expression') { +// child.children.forEach((child: Node) => { +// if (child.type === 'argument_list') { +// child.children.forEach((child: Node) => { +// if (child.type === 'identifier') { +// luaText += className + '.' + child.text + '('; +// // 处理函数参数列表 +// if (functionInfo.has(child.text)) { +// let paramList = functionInfo.get(child.text); +// for (let i = 0; i < paramList.length; i++) { +// if (i === 0) { +// luaText += paramList[i]; +// } else { +// luaText += ", " + paramList[i]; +// } +// } +// } +// luaText += ')'; +// } +// }); +// } +// }); +// } + +// }); +// luaText += ' end\n' + +// return luaText; +// } + +// private static assembleLuaClassText(className: string, baseClass: string[], methodList: string[]): string { +// let luaText: string = className + ' = {}' + +// if (baseClass.length > 0) { +// luaText += ' ---@type ' + baseClass[0] + '\n'; +// } else { +// luaText += '\n'; +// } +// methodList.forEach((method: string) => { +// luaText += method; +// }); + +// return luaText; +// } + +// // UFUNCTION返回值类型映射 +// private static _returnTypeMap: Map; +// private static get returnTypeMap() { +// if (!this._returnTypeMap) { +// this._returnTypeMap = new Map(); +// this._returnTypeMap.set('void', ''); +// this._returnTypeMap.set('int', 'number'); +// this._returnTypeMap.set('int8', 'number'); +// this._returnTypeMap.set('int16', 'number'); +// this._returnTypeMap.set('int32', 'number'); +// this._returnTypeMap.set('int64', 'number'); +// this._returnTypeMap.set('uint8', 'number'); +// this._returnTypeMap.set('uint16', 'number'); +// this._returnTypeMap.set('uint32', 'number'); +// this._returnTypeMap.set('uint64', 'number'); +// this._returnTypeMap.set('float', 'number'); +// this._returnTypeMap.set('double', 'number'); +// this._returnTypeMap.set('bool', 'boolean'); +// this._returnTypeMap.set('FName', 'string'); +// this._returnTypeMap.set('FString', 'string'); +// this._returnTypeMap.set('FText', 'string'); +// } + +// return this._returnTypeMap; +// } + +// /** +// * 获取tree-sitter wasm文件目录 +// */ +// private static getWasmDir(): string { +// return path.join(Tools.getVScodeExtensionPath(), "node_modules/univac/dist/static/"); +// } + +// private static getCppHeaderFiles(dirPath: string) { +// let options = { +// sync: true, +// recursive: true, +// valuetizer:function(stat:fs.Stats, fileShortName: string, fileFullPath: string) { +// if (stat.isDirectory()) { +// return fileFullPath; +// } +// return fileShortName.match(/\.h$/)? fileFullPath : null; +// } +// }; + +// return dir.files(dirPath, 'file', null, options); +// } + +// private static getCppSourceFiles(dirPath: string): string[] { +// let options = { +// sync: true, +// recursive: true, +// valuetizer:function(stat:fs.Stats, fileShortName: string, fileFullPath: string) { +// if (stat.isDirectory()) { +// return fileFullPath; +// } +// return fileShortName.match(/\.cpp$/)? fileFullPath : null; +// } +// }; + +// return dir.files(dirPath, 'file', null, options); +// } + +// /** +// * 将文本写入指定文件。 +// * @param text 要写入的文本。 +// * @param filePath 文件路径,若不存在则创建,已存在则追加到文件末尾。 +// */ +// private static appendText2File(text: string, filePath: string) { +// let dirPath = path.dirname(filePath); +// this.makeDirSync(dirPath); +// let options = { +// flag: 'a' +// }; +// try { +// fs.writeFileSync(filePath, text, options); +// } catch (e) { +// Logger.ErrorLog('写入文件出错,filePath: ' + filePath + 'error: '); +// Logger.ErrorLog(e); +// } +// } + +// private static makeDirSync(dirPath: string) { +// if (fs.existsSync(dirPath)) { +// return; +// } +// let baseDir = path.dirname(dirPath); +// this.makeDirSync(baseDir); +// fs.mkdirSync(dirPath); +// } + +// /** +// * 删除目录,递归删除子目录。 +// * @param dirPath 要删除的目录。 +// */ +// private static removeCppInterfaceIntelliSenseRes(dirPath: string) { +// if (fs.existsSync(dirPath)) { +// let files = fs.readdirSync(dirPath); +// files.forEach((file) => { +// let currentPath = path.join(dirPath, file); +// if (fs.statSync(currentPath).isDirectory()) { +// this.removeCppInterfaceIntelliSenseRes(currentPath); +// } else { +// // 删除preload symbol +// // 先清空文件内容,然后刷新symbol,再删除文件。 +// fs.writeFileSync(currentPath, ''); +// CodeSymbol.refreshOneUserPreloadDocSymbols(currentPath); +// fs.unlinkSync(currentPath); +// } +// }); +// fs.rmdirSync(dirPath); +// } +// } +// } + +// class URegex { +// public static UCLASS = new RegExp(/\s*(UCLASS\s*\(.*\))/); +// public static USTRUCT = new RegExp(/\s*(USTRUCT\s*\(.*\))/); +// public static UENUM = new RegExp(/\s*(UENUM\s*\(.*\))/); +// public static UFUNCTION = new RegExp(/\s*(UFUNCTION\s*\(.*\))/); +// public static UPROPERTY = new RegExp(/\s*(UPROPERTY\s*\(.*\))/); + +// public static GENERATED_BODY = new RegExp(/\s*(GENERATED_BODY\s*\(.*\))/); +// public static GENERATED_UCLASS_BODY = new RegExp(/\s*(GENERATED_UCLASS_BODY\s*\(.*\))/); +// public static GENERATED_USTRUCT_BODY = new RegExp(/\s*(GENERATED_USTRUCT_BODY\s*\(.*\))/); +// public static DEPRECATED = new RegExp(/\s*(DEPRECATED\s*\(.*\))/); +// public static UE_DEPRECATED = new RegExp(/\s*(UE_DEPRECATED\s*\(.*\))/); +// public static PRAGMA = new RegExp(/\s*(PRAGMA_\w+WARNINGS)/); +// public static DECLARE = new RegExp(/\s*(DECLARE_\w+\s*\(.*\))/); +// public static UMETA = new RegExp(/\s*(UMETA\s*\(.*\))/); +// public static ENGINE_API = new RegExp(/(ENGINE_API\s*)/); + +// public static DefLuaClass = new RegExp(/\s*(DefLuaClass\s*\(.*\))/); +// public static DefLuaMethod = new RegExp(/\s*(DefLuaMethod\s*\(.*\))/); +// public static EndDef = new RegExp(/\s*(EndDef\s*\(.*\))/); +// } + +// enum CppFileType { +// CppHeaderFile = 0, +// CppSourceFile = 1, +// } diff --git a/src/code/server/codeExport/nativeCodeExportBase.ts b/src/code/server/codeExport/nativeCodeExportBase.ts new file mode 100644 index 0000000..60b2d3e --- /dev/null +++ b/src/code/server/codeExport/nativeCodeExportBase.ts @@ -0,0 +1,53 @@ +import * as Tools from '../codeTools'; +import { CodeSymbol } from '../codeSymbol'; +// import { CppCodeProcessor } from './cppCodeProcessor'; +import { SluaCSharpProcessor } from './sluaCSharpProcessor'; +import { Logger } from '../codeLogManager'; +import fs = require('fs'); + +//基类的作用是完成一些公共方法,对外暴露接口,屏蔽下层 +export class NativeCodeExportBase { + // 导出文件存放根路径 + private static _LuaPandaInterfaceIntelliSenseResPath; + public static get LuaPandaInterfaceIntelliSenseResPath() { + if(!this._LuaPandaInterfaceIntelliSenseResPath){ + // stuartwang TODO + if(Tools.getVSCodeOpenedFolders() && Tools.getVSCodeOpenedFolders().length > 0){ + this._LuaPandaInterfaceIntelliSenseResPath = Tools.getVSCodeOpenedFolders()[0] + "/.vscode/LuaPanda/IntelliSenseRes/"; + } + } + return this._LuaPandaInterfaceIntelliSenseResPath; + } + + // 加载原生接口导出的分析结果 + public static loadIntelliSenseRes() { + // 如果文件存在,刷新 + let dirPath = this.LuaPandaInterfaceIntelliSenseResPath; + if (fs.existsSync(dirPath)) { + CodeSymbol.refreshUserPreloadSymbals(dirPath); + } + } + + // 收到需要预处理的文件 + public static async processNativeCodeDir(anaPath){ + // // 判断预处理的路径是否存在 + // if (!fs.existsSync(anaPath)) { + // Logger.ErrorLog("输入了不存在的路径!"); + // return; + // } + + // anaPath = anaPath.trim(); + // let cppfileCount = await CppCodeProcessor.processCppDir(anaPath); + // let csfileCount = SluaCSharpProcessor.processluaCSDir(anaPath); + // let tipString = '处理完成,解析了 '; + // if(cppfileCount > 0){ + // tipString += cppfileCount + ' 个cpp文件,'; + // } + // if(csfileCount > 0){ + // tipString += csfileCount + ' 个c#文件。'; + // } + + // tipString += '请重启 VSCode 以加载解析出的 lua 符号文件!' + // Tools.showTips(tipString); + } +} \ No newline at end of file diff --git a/src/code/server/codeExport/sluaCSharpProcessor.ts b/src/code/server/codeExport/sluaCSharpProcessor.ts new file mode 100644 index 0000000..e489409 --- /dev/null +++ b/src/code/server/codeExport/sluaCSharpProcessor.ts @@ -0,0 +1,168 @@ +import * as Tools from '../codeTools'; +import { Logger } from '../codeLogManager'; +import fs = require('fs'); +import dir = require('path-reader'); +import path = require('path'); +import { CodeSymbol } from '../codeSymbol'; + +export class SluaCSharpProcessor { + private static _sluaCSharpInterfaceIntelliSenseResPath; + + // 加载原生接口导出的分析结果 + public static loadIntelliSenseRes() { + CodeSymbol.refreshUserPreloadSymbals(this.sluaCSharpInterfaceIntelliSenseResPath); + } + + // sluaUE的分析路径 + public static get sluaCSharpInterfaceIntelliSenseResPath() { + if(!this._sluaCSharpInterfaceIntelliSenseResPath){ + // TODO support multi folder + if(Tools.getVSCodeOpenedFolders() && Tools.getVSCodeOpenedFolders().length > 0){ + this._sluaCSharpInterfaceIntelliSenseResPath = Tools.getVSCodeOpenedFolders()[0] + "/.vscode/LuaPanda/IntelliSenseRes/SluaCSharpInterface/"; + } + } + return this._sluaCSharpInterfaceIntelliSenseResPath; + } + + // 解析文件夹 + public static processluaCSDir(cppDir: string) { + let intelLuaPath = this.sluaCSharpInterfaceIntelliSenseResPath; + if(!intelLuaPath){ + Logger.ErrorLog('未打开文件夹,无法使用此功能!'); + Tools.showTips('未打开文件夹,无法使用此功能!'); + } + + // 生成一个子目录对应用户代码路径 + let subDir = cppDir; + subDir = subDir.replace(/\//g, ' '); + subDir = subDir.replace(/\\/g, ' '); + subDir = subDir.replace(/:/g, ''); + subDir = subDir.trim(); + subDir = subDir.replace(/ /g, '-'); + + // 从cppDir中读出files列表 + let files = this.getCSharpFiles(cppDir); + let fileCount = this.readSluaCSSymbols(files, subDir); + CodeSymbol.refreshUserPreloadSymbals(intelLuaPath); + // Tools.showTips('CS导出符号处理完成!共解析 ' + fileCount + ' 个文件'); + return fileCount; + } + + private static getCSharpFiles(dirPath: string) { + let options = { + sync: true, + recursive: true, + valuetizer:function(stat:fs.Stats, fileShortName: string, fileFullPath: string) { + if (stat.isDirectory()) { + return fileFullPath; + } + return fileShortName.match(/\.cs$/)? fileFullPath : null; + } + }; + + return dir.files(dirPath, 'file', null, options); + } + + public static readSluaCSSymbols(filepath, writepath){ + let sluaRootPath = this.sluaCSharpInterfaceIntelliSenseResPath + writepath; + this.makeDirSync(sluaRootPath); + let fileCount = 0; + // 读取文件内容 + for (const file of filepath) { + let codeTxt = Tools.getFileContent(file); + if(codeTxt){ + let luaTxt = this.parseSluaCSSymbols(codeTxt); + if(luaTxt && luaTxt != ""){ + fileCount ++; + let csFilePath = sluaRootPath + '/' + path.basename(file, "cs") + "lua"; + fs.writeFileSync(csFilePath, luaTxt); + } + } + } + + if(fileCount > 0){ + // 建立一个UnityEngine符号 + let engineFileName = "Lua_UnityEngine.lua"; + let engineFileContent = "UnityEngine = {}"; + fs.writeFileSync(sluaRootPath + '/' + engineFileName, engineFileContent); + } + return fileCount; + } + + private static makeDirSync(dirPath: string) { + if (fs.existsSync(dirPath)) { + return; + } + let baseDir = path.dirname(dirPath); + this.makeDirSync(baseDir); + fs.mkdirSync(dirPath); + } + + public static parseSluaCSSymbols(codeTxt){ + let currentClass; //当前文件中的类 + let parentClass; //父类 + let members = []; //类中的成员 + //用正则分析出主成员和继承关系 + let createTypeMetatableREG = /createTypeMetatable\((.*)\)/; + let dver = codeTxt.match(createTypeMetatableREG); + if(!dver) return; + + if(dver && dver.length === 2){ + let paramsArray = dver[1].split(','); + if(paramsArray.length === 4 && paramsArray[3].trim().search('typeof') != 0){ + // "typeof(System.Collections.Generic.Dictionary)" 也被逗号打断了,拼合回去 + paramsArray[2] = paramsArray[2] + paramsArray.pop(); + } + + if(paramsArray.length === 3){ + // 无继承关系 + currentClass = paramsArray[2].trim().match(/typeof\((.*)\)/)[1]; + }else if(paramsArray.length === 4){ + // 有继承关系 + currentClass = paramsArray[2].trim().match(/typeof\((.*)\)/)[1]; + parentClass = paramsArray[3].trim().match(/typeof\((.*)\)/)[1].replace('_','.'); + } + } + + //获取所有成员 + let memberREG = /addMember\((.*?)\)/g; + let dver2 = codeTxt.match(memberREG); + if(dver2) { + for (const mems of dver2) { + let paras = mems.match(/addMember\(l,("(.*?)"|(.*?))(,|\))/); + if(paras[2]){ + //成员 addMember(l,"name",get_name,set_name,true);\n\ + let functionObj = new Object(); + functionObj['var'] = paras[2] ; + functionObj['type'] = "variable"; + members.push(functionObj); + }else if(paras[3]){ + //函数 addMember(l,getItem);\n\ + let varObj = new Object(); + let functionNameStr = paras[3]; + functionNameStr = functionNameStr.replace(/_s$/, ''); + varObj['var'] = functionNameStr + '()'; + varObj['type'] = "function"; + members.push(varObj); + } + } + } + + // 构建lua文本 + let luaCode = currentClass + " = {}"; + if(parentClass){ + luaCode += " ---@type " + parentClass; + } + luaCode += '\n' + for (const oneMember of members) { + if (oneMember.type === "variable") { + luaCode += currentClass + '.' + oneMember.var + ' = nil\n'; + }else if(oneMember.type === "function"){ + luaCode += "function " + currentClass + '.' + oneMember.var + ' end\n'; + } + + } + // luaCode 写入文件 + return luaCode; + } +} \ No newline at end of file diff --git a/src/code/server/codeFormat.ts b/src/code/server/codeFormat.ts new file mode 100644 index 0000000..df59882 --- /dev/null +++ b/src/code/server/codeFormat.ts @@ -0,0 +1,104 @@ +/*--------------------------------------------------------- + author:trixnz + https://github.com/trixnz/vscode-lua + *--------------------------------------------------------*/ + +import { CodeEditor } from './codeEditor'; +import { formatText } from 'lua-fmt'; +import { TextEdit, Range, Position } from 'vscode-languageserver'; +import { producePatch } from 'lua-fmt'; +import { parsePatch } from 'diff'; + +enum EditAction { + Replace, + Insert, + Delete +} + +class Edit { + public action: EditAction; + public start: Position; + public end: Position; + public text: string = ''; + + public constructor(action: EditAction, start: Position) { + this.action = action; + this.start = start; + this.end = Position.create(0, 0); + } +} + +export class CodeFormat { + public static format(uri: string) { + let text = CodeEditor.getCode(uri); + let formattedText = formatText(text); + if (process.platform === 'win32') { + text = text.split('\r\n').join('\n'); + formattedText = formattedText.split('\r\n').join('\n'); + } + + return this.getEditsFromFormattedText(uri, text, formattedText); + } + + public static getEditsFromFormattedText(documentUri: string, originalText: string, formattedText: string, + startOffset: number = 0): TextEdit[] { + const diff = producePatch(documentUri, originalText, formattedText); + const unifiedDiffs = parsePatch(diff); + + const edits: Edit[] = []; + let currentEdit: Edit | null = null; + + for (const uniDiff of unifiedDiffs) { + for (const hunk of uniDiff.hunks) { + let startLine = hunk.oldStart + startOffset; + + for (const line of hunk.lines) { + switch (line[0]) { + case '-': + if (currentEdit === null) { + currentEdit = new Edit(EditAction.Delete, Position.create(startLine - 1, 0)); + } + currentEdit.end = Position.create(startLine, 0); + startLine++; + break; + + case '+': + if (currentEdit === null) { + currentEdit = new Edit(EditAction.Insert, Position.create(startLine - 1, 0)); + } else if (currentEdit.action === EditAction.Delete) { + currentEdit.action = EditAction.Replace; + } + + currentEdit.text += line.substr(1) + '\n'; + + break; + + case ' ': + startLine++; + if (currentEdit != null) { + edits.push(currentEdit); + } + currentEdit = null; + break; + } + } + } + + if (currentEdit != null) { + edits.push(currentEdit); + } + } + + return edits.map(edit => { + switch (edit.action) { + case EditAction.Replace: + return TextEdit.replace(Range.create(edit.start, edit.end), edit.text); + case EditAction.Insert: + return TextEdit.insert(edit.start, edit.text); + case EditAction.Delete: + return TextEdit.del(Range.create(edit.start, edit.end)); + } + }); + } + +} \ No newline at end of file diff --git a/src/code/server/codeHighlight.ts b/src/code/server/codeHighlight.ts new file mode 100644 index 0000000..77d07bd --- /dev/null +++ b/src/code/server/codeHighlight.ts @@ -0,0 +1 @@ +//代码高亮 \ No newline at end of file diff --git a/src/code/server/codeLinting.ts b/src/code/server/codeLinting.ts new file mode 100644 index 0000000..34e789a --- /dev/null +++ b/src/code/server/codeLinting.ts @@ -0,0 +1,157 @@ +// Tencent is pleased to support the open source community by making LuaPanda available. +// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +// https://opensource.org/licenses/BSD-3-Clause +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +// 语法检查 +import { + TextDocument, + // Connection, + DiagnosticSeverity, + Diagnostic, + Range +} from 'vscode-languageserver'; +import * as Tools from "./codeTools"; +import { LuaAnalyzerSettings } from "./server"; +import { spawnSync } from 'child_process'; + +let os = require('os'); + +export class CodeLinting { + static luacheckResultRegExp = /^(.+):(\d+):(\d+)-(\d+): \(([EW])(\d+)\) (.+)$/; + public static processLinting(textDocument: TextDocument, settings: LuaAnalyzerSettings, globalVariables: string[]): Promise<{}> { + let fileName = Tools.uriToPath(Tools.urlDecode(textDocument.uri)); + let luacheck: string = this.getLuacheck(settings); + let luacheckArgs: string[] = this.getLuacheckArgs(settings, fileName, globalVariables); + let fileContent = textDocument.getText(); + let luacheckProcess = new Promise((resolve, reject) => { + let checkResult = spawnSync(luacheck, luacheckArgs, {input: fileContent}); + // luacheck 错误码1或2表示检查到error或warning + if (checkResult.status == 1 || checkResult.status == 2) { + reject(checkResult.output.join('\n')); + } + else if (checkResult.status == 0) { + //@ts-ignore + resolve(); + } + else { + //@ts-ignore + resolve(); + } + }); + return luacheckProcess; + } + + private static getLuacheck(settings: LuaAnalyzerSettings): string { + let luacheck: string = settings.codeLinting.luacheckPath; + if ( luacheck != "") { + return luacheck; + } + + // 如果用户未配置则使用默认配置 + if (os.type() == "Windows_NT") { + luacheck = Tools.getVScodeExtensionPath() + "/res/luacheck/luacheck.exe"; + } + else { + luacheck = '/usr/local/bin/luacheck'; + } + return luacheck; + } + + private static mergeIgnoreGlobals(globalsInSetting: string[], globalVariables: string[]): string[] { + let globalsMap = new Map(); + for (let g of globalsInSetting) { + globalsMap[g] = true; + } + for (let g of globalVariables) { + if (globalsMap[g]) continue; + + let arr:string[] = g.split('.'); + globalsMap[arr[0]] = true; + } + + let ret: string[] = []; + for (let key in globalsMap) { + ret.push(key) + } + return ret; + } + + private static getLuacheckArgs(settings: LuaAnalyzerSettings, fileName: string, globalVariables: string[]): string[] { + let luacheckArgs: string[] = []; + + let luaVersion = settings.codeLinting.luaVersion; + switch (luaVersion) { + case "5.1": + luacheckArgs.push("--std", "lua51"); + break; + case "5.3": + luacheckArgs.push("--std", "lua53"); + break; + case "5.1+5.3": + luacheckArgs.push("--std", "lua51+lua53"); + break; + default: + } + let userIgnoreGlobals: string[] = settings.codeLinting.ignoreGlobal.split(";"); + let ignoreGlobals: string[] = this.mergeIgnoreGlobals(userIgnoreGlobals, globalVariables); + if (ignoreGlobals.length > 0) { + luacheckArgs.push("--globals", ...ignoreGlobals); + } + let maxLineLength = settings.codeLinting.maxLineLength; + luacheckArgs.push("--max-line-length", maxLineLength.toString()); + luacheckArgs.push("--allow-defined"); + luacheckArgs.push("--ranges"); + luacheckArgs.push("--codes"); + luacheckArgs.push("--formatter", "plain"); + luacheckArgs.push("--filename", fileName); + luacheckArgs.push("-"); + return luacheckArgs; + } + + public static parseLuacheckResult(luaErrorOrWarning, settings: LuaAnalyzerSettings) { + let diagnosticArray: Diagnostic[] = []; + + // if (luaErrorOrWarning.stdout === undefined) { + // return diagnosticArray; + // } + + let maxNumberOfProblems = settings.codeLinting.maxNumberOfProblems; + let ignoreErrorCode: string[] = settings.codeLinting.ignoreErrorCode.split(";"); + const luaErrorOrWarningArray: string[] = luaErrorOrWarning.split(/\r\n|\r|\n/); + for (let i = 0, problems = 0; i < luaErrorOrWarningArray.length && problems < maxNumberOfProblems; i++) { + let regResult = this.luacheckResultRegExp.exec(luaErrorOrWarningArray[i]); + if (!regResult) { + continue; + } + + let line = parseInt(regResult[2]); + let startCharacter = parseInt(regResult[3]); + let endCharacter = parseInt(regResult[4]); + let errorType = regResult[5]; + let severity = errorType == "E"? DiagnosticSeverity.Error : DiagnosticSeverity.Warning; + let errorCode = parseInt(regResult[6]); + let message = regResult[7]; + let range = Range.create(line - 1, startCharacter - 1, line - 1, endCharacter); + + // 根据错误码忽略错误提示。luacheck ignore参数不能忽略error + if (ignoreErrorCode.includes(errorCode.toString())) { + continue; + } + + let diagnosic: Diagnostic = { + range : range, + severity: severity, + code : errorCode, + message : message, + source : "lua-analyzer" + }; + + problems++; + diagnosticArray.push(diagnosic); + } + + return diagnosticArray; + } +} diff --git a/src/code/server/codeLogManager.ts b/src/code/server/codeLogManager.ts new file mode 100644 index 0000000..bc362f8 --- /dev/null +++ b/src/code/server/codeLogManager.ts @@ -0,0 +1,43 @@ +// Tencent is pleased to support the open source community by making LuaPanda available. +// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +// https://opensource.org/licenses/BSD-3-Clause +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +import { CodeSettings, LogLevel } from './codeSettings'; + +export class Logger { + public static connection; + public static init() { + } + + public static log(str: string, level?) { + if(! level){ + level = LogLevel.DEBUG; + } + + if (str != "" && str != null) { + if(level == LogLevel.ERROR) this.ErrorLog(str); + if(level == LogLevel.INFO) this.InfoLog(str); + if(level == LogLevel.DEBUG) this.DebugLog(str); + } + } + + public static DebugLog(str: string){ + if( CodeSettings.logLevel <= LogLevel.DEBUG){ + this.connection.console.log(str); + } + } + + public static InfoLog(str: string){ + if( CodeSettings.logLevel <= LogLevel.INFO){ + this.connection.console.log(str); + } + } + + public static ErrorLog(str: string){ + if( CodeSettings.logLevel <= LogLevel.ERROR){ + this.connection.console.log(str); + } + } + +} diff --git a/src/code/server/codeReference.ts b/src/code/server/codeReference.ts new file mode 100644 index 0000000..5a5430b --- /dev/null +++ b/src/code/server/codeReference.ts @@ -0,0 +1,27 @@ +//查找引用 +// import * as Tools from "./codeTools"; +import { CodeSymbol } from "./codeSymbol"; +// import { Logger } from './codeLogManager'; +import { CodeDefinition } from './codeDefinition'; +// import { +// Location +// } from 'vscode-languageserver-protocol'; + +export class CodeReference { + public static getSymbalReferences(info){ + let refRet = new Array() + // 此处getDefine应该直接搜索 + let def = CodeDefinition.getSymbalDefine(info, true); + + let findDocRes = CodeSymbol.searchSymbolReferenceinDoc(def); + refRet.concat(findDocRes); + //形式转换 + for (let index = 0; index < findDocRes.length; index++) { + findDocRes[index].range.start.line = findDocRes[index].range.start.line -1; + findDocRes[index].range.start.character = findDocRes[index].range.start.column; + findDocRes[index].range.end.line = findDocRes[index].range.end.line -1; + findDocRes[index].range.end.character = findDocRes[index].range.end.column; + } + return findDocRes; + } +} \ No newline at end of file diff --git a/src/code/server/codeSettings.ts b/src/code/server/codeSettings.ts new file mode 100644 index 0000000..0ee8ffb --- /dev/null +++ b/src/code/server/codeSettings.ts @@ -0,0 +1,19 @@ +// 在这个文件中进行调试器的相关配置 + +export enum LogLevel{ + DEBUG = 0, + INFO = 1 , + ERROR = 2, +} + +export class CodeSettings{ + //DEV SETTINGS + // public static logLevel = LogLevel.DEBUG; + // public static isOpenDebugCode = true; //是否打开debug代码段 + // public static isAllowDefJumpPreload = true; //是否允许定义跳转到预置文件 + + //RELEASE SETTINGS + public static logLevel = LogLevel.INFO; + public static isOpenDebugCode = false; //是否打开debug代码段 + public static isAllowDefJumpPreload = true; //是否允许定义跳转到预置文件 +} \ No newline at end of file diff --git a/src/code/server/codeSymbol.ts b/src/code/server/codeSymbol.ts new file mode 100644 index 0000000..cab1056 --- /dev/null +++ b/src/code/server/codeSymbol.ts @@ -0,0 +1,504 @@ +// Tencent is pleased to support the open source community by making LuaPanda available. +// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +// https://opensource.org/licenses/BSD-3-Clause +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +// CodeSymbol 管理AST中的符号, 对上提供各种接口。 +// 获取/刷新文件符号 +// + +import * as Tools from './codeTools'; +import { CodeEditor } from './codeEditor'; +import { DocSymbolProcessor } from './docSymbolProcessor'; +import { Logger } from './codeLogManager'; +import { CodeSettings } from './codeSettings'; +let dir = require('path-reader'); + +export class CodeSymbol { + // 用 kv 结构保存所有用户文件以及对应符号结构(包含定义符号和AST,以及方法) + public static docSymbolMap = new Map(); // 用户代码中的lua文件 + public static luaPreloadSymbolMap = new Map(); // lua预制文件(LuaPanda集成) + public static userPreloadSymbolMap = new Map(); // 用户的lua导出接口 + + // 已处理文件列表,这里是防止循环引用 + private static alreadySearchList; //TODO 要标记一下这个变量被哪些函数使用了 + + // 获取指定文件中的chunk列表 + public static getCretainDocChunkDic(uri){ + let processor = this.getFileSymbolsFromCache(uri); + if(processor){ + return processor.getChunksDic(); + } + } +//----------------------------------------------------------------------------- +//-- 创建单文件、工作区、预加载区、特定文件符号 +//----------------------------------------------------------------------------- + // 单文件内符号处理 + // 指定文件的符号 [单文件创建] | 无返回 . 如文档的符号已建立则直接返回 + public static createOneDocSymbols(uri: string, luaText?: string) { + if ( ! this.docSymbolMap.has(uri)) { + this.refreshOneDocSymbols(uri, luaText); + } + } + + // 指定文件的符号 [单文件刷新] | 无返回, 强制刷新 + public static refreshOneDocSymbols(uri: string, luaText?: string) { + if(luaText == undefined){ + luaText = CodeEditor.getCode(uri); + } + this.createDocSymbol(uri, luaText); + } + + // 创建指定后缀的lua文件的符号 + public static createSymbolswithExt(luaExtname: string, rootpath: string) { + //记录此后缀代表lua + Tools.setLoadedExt(luaExtname); + //解析workSpace中同后缀文件 + let exp = new RegExp(luaExtname + '$', "i"); + dir.readFiles(rootpath, { match: exp }, function (err, content, filePath, next) { + if (!err) { + let uri = Tools.pathToUri(filePath); + if(!Tools.isinPreloadFolder(uri)){ + CodeSymbol.createOneDocSymbols(uri, content); + }else{ + CodeSymbol.refreshOneUserPreloadDocSymbols( Tools.uriToPath(uri)); + } + } + next(); + }, (err) => { + if (err) { + return; + } + }); + } + + // 获取指定文件的所有符号 , 返回Array形式 + public static getOneDocSymbolsArray(uri: string, luaText?: string, range?:Tools.SearchRange): Tools.SymbolInformation[] { + let docSymbals: Tools.SymbolInformation[] = []; + this.createOneDocSymbols(uri, luaText); + switch(range){ + case Tools.SearchRange.GlobalSymbols: + docSymbals = this.getFileSymbolsFromCache(uri).getGlobalSymbolsArray(); break; + case Tools.SearchRange.LocalSymbols: + docSymbals = this.getFileSymbolsFromCache(uri).getLocalSymbolsArray(); break; + case Tools.SearchRange.AllSymbols: + docSymbals = this.getFileSymbolsFromCache(uri).getAllSymbolsArray(); break; + } + return docSymbals; + } + + // 获取指定文件的所有符号 , 返回Dictionary形式 + public static getOneDocSymbolsDic(uri: string, luaText?: string, range?:Tools.SearchRange): Tools.SymbolInformation[] { + let docSymbals: Tools.SymbolInformation[] = []; + this.createOneDocSymbols(uri, luaText); + switch(range){ + case Tools.SearchRange.GlobalSymbols: + docSymbals = this.getFileSymbolsFromCache(uri).getGlobalSymbolsDic(); break; + case Tools.SearchRange.LocalSymbols: + docSymbals = this.getFileSymbolsFromCache(uri).getLocalSymbolsDic(); break; + case Tools.SearchRange.AllSymbols: + docSymbals = this.getFileSymbolsFromCache(uri).getAllSymbolsDic(); break; + } + return docSymbals; + } + + // 获取指定文件的返回值,如无返回null + public static getOneDocReturnSymbol(uri):string{ + this.createOneDocSymbols(uri); + let docSymbals = this.docSymbolMap.get(uri); + if(docSymbals){ + return docSymbals.getFileReturnArray(); + } + else{ + return null; + } + } + + // 指定文件夹中的符号处理 + // 创建指定文件夹中所有文件的符号 [批量创建] + public static createFolderSymbols(path: string){ + if(path === undefined || path === ''){ + return; + } + let filesArray = Tools.getDirFiles( path ); + filesArray.forEach(pathArray => { + let uri = Tools.pathToUri(pathArray); + if(!this.docSymbolMap.has(uri)){ + this.createDocSymbol( uri , pathArray ); + } + }); + } + + // 刷新 指定文件夹中所有文件的符号 [批量刷新] + public static refreshFolderSymbols(path: string){ + if(path === undefined || path === ''){ + return; + } + let filesArray = Tools.getDirFiles( path ); + filesArray.forEach(element => { + this.createDocSymbol( element ); + }); + } + + // 创建 lua预制的符号表 参数是文件夹路径 + public static createLuaPreloadSymbols(path: string){ + if(path === undefined || path === ''){ + return; + } + let filesArray = Tools.getDirFiles( path ); + filesArray.forEach(pathElement => { + this.createPreLoadSymbals( Tools.pathToUri(pathElement), 0 ); + }); + } + + // 刷新 用户PreLoad 所有文件的符号 [PreLoad批量刷新] 参数是文件夹路径 + public static refreshUserPreloadSymbals(path: string){ + if(path === undefined || path === ''){ + return; + } + let filesArray = Tools.getDirFiles( path ); + filesArray.forEach(pathElement => { + this.createPreLoadSymbals( Tools.pathToUri(pathElement), 1 ); + }); + } + + // 刷新 PreLoad 单个文件的符号 [PreLoad刷新] 通常lua预制符号只需要创建,无需刷新。 + public static refreshOneUserPreloadDocSymbols(filePath: string){ + if(filePath === undefined || filePath === ''){ + return; + } + this.createPreLoadSymbals(Tools.pathToUri(filePath), 1); + } + + // 获取 workspace 中的全局符号, 以dictionary的形式返回 + public static getWorkspaceSymbols(range? : Tools.SearchRange){ + range = range || Tools.SearchRange.AllSymbols; + let filesMap = Tools.get_FileName_Uri_Cache(); + let g_symb = {}; + + for (const fileUri in filesMap) { + if(!Tools.isinPreloadFolder(filesMap[fileUri])){ + let g_s = this.getOneDocSymbolsDic( filesMap[fileUri], null, range); + for (const key in g_s) { + const element = g_s[key]; + g_symb[key] = element; + } + } + } + + return g_symb; + } + + //reference处理 + public static searchSymbolReferenceinDoc(searchSymbol) { + let uri = searchSymbol.containerURI; + let docSymbals = this.getFileSymbolsFromCache(uri); + return docSymbals.searchDocSymbolReference(searchSymbol); + } + +//----------------------------------------------------------------------------- +//-- 搜索符号 +//----------------------------------------------------------------------------- + // 在[指定文件]查找符号(模糊匹配,用作搜索符号) + // @return 返回值得到的排序: + // 如果是Equal搜索,从dic中检索,按照AST深度遍历顺序返回 + public static searchSymbolinDoc(uri:string, symbolStr: string, searchMethod: Tools.SearchMode, range:Tools.SearchRange = Tools.SearchRange.AllSymbols): Tools.SymbolInformation[] { + if (symbolStr === '' || uri === '' ) { + return null; + } + let docSymbals = this.getFileSymbolsFromCache(uri);; + let retSymbols = docSymbals.searchMatchSymbal(symbolStr, searchMethod, range); + return retSymbols; + } + + public static getFileSymbolsFromCache(uri){ + let docSymbals = this.docSymbolMap.get(uri); + if(!docSymbals){ + docSymbals = this.userPreloadSymbolMap.get(uri); + } + if(!docSymbals){ + docSymbals = this.luaPreloadSymbolMap.get(uri); + } + return docSymbals; + } + + // 在[工作空间]查找符号, 主要用于模糊搜索。搜索文件顺序完全随机( isSearchPreload = false 全局符号模糊查找默认不展示预制变量) + // useAlreadySearchList 是否使用已经搜索列表。需要使用一搜索列表的场景是 先进行了引用树搜素,之后进行全局搜索,为了避免重读降低效率,此项设置为true + public static searchSymbolinWorkSpace(symbolStr: string, searchMethod: Tools.SearchMode = Tools.SearchMode.FuzzyMatching, searchRange: Tools.SearchRange = Tools.SearchRange.AllSymbols, isSearchPreload = false , useAlreadySearchList = false): Tools.SymbolInformation[] { + if (symbolStr === '') { + return []; + } + + let retSymbols: Tools.SymbolInformation[] = []; + for (let [ key , value] of this.docSymbolMap) { + if(useAlreadySearchList){ + if(this.alreadySearchList[key]){ + continue; + } + } + + let docSymbals = value.searchMatchSymbal(symbolStr, searchMethod, searchRange); + retSymbols = retSymbols.concat(docSymbals); + } + + if(isSearchPreload){ + let preS = this.searchUserPreLoadSymbols(symbolStr, searchMethod); + retSymbols = retSymbols.concat(preS); + preS = this.searchLuaPreLoadSymbols(symbolStr, searchMethod); + retSymbols = retSymbols.concat(preS); + } + + return retSymbols; + } + + // 搜索全局变量的定义,查找顺序是本文件,引用树,全局 + // 不优先搜全局,不搜预制 + public static searchSymbolforGlobalDefinition (uri:string, symbolStr: string, searchMethod: Tools.SearchMode = Tools.SearchMode.ExactlyEqual, searchRange: Tools.SearchRange = Tools.SearchRange.GlobalSymbols): Tools.SymbolInformation[] { + if (symbolStr === '' || uri === '' ) { + return []; + } + + let retSymbols: Tools.SymbolInformation[] = []; + //搜索顺序 用户 > 系统 + CodeSymbol.alreadySearchList = new Object(); // 记录已经搜索过的文件。避免重复搜索耗时 + let preS = this.recursiveSearchRequireTree(uri, symbolStr, searchMethod, searchRange); + if(preS){ + retSymbols = retSymbols.concat(preS); + } + + // 这里建议搜到了,就不查全局文件了,因为全局查找是无序的。 这里最好有一个记录措施,避免同一个文件被多次查找,降低效率。 + if(retSymbols.length === 0){ + // 全局查找, 不含预制文件 + let preS0 = this.searchSymbolinWorkSpace(symbolStr, searchMethod, Tools.SearchRange.GlobalSymbols, CodeSettings.isAllowDefJumpPreload , true); + if(preS0){ + retSymbols = retSymbols.concat(preS0); + } + } + return retSymbols; + } + + // 在[本文件引用的其他文件]上搜索所有符合的符号,用于 [代码提示 auto completion] + // 一定会搜全局,搜预制. 比较通用的一种方式,但是比较慢。(因为加入了预制搜索) + public static searchSymbolforCompletion (uri:string, symbolStr: string, searchMethod: Tools.SearchMode = Tools.SearchMode.PrefixMatch, searchRange: Tools.SearchRange = Tools.SearchRange.AllSymbols): Tools.SymbolInformation[] { + if (symbolStr === '' || uri === '' ) { + return []; + } + + let retSymbols: Tools.SymbolInformation[] = []; + //搜索顺序 用户 > 系统 + CodeSymbol.alreadySearchList = new Object(); + let preS = this.recursiveSearchRequireTree(uri, symbolStr, searchMethod, searchRange); + if(preS){ + retSymbols = retSymbols.concat(preS); + } + + // 全局, 含有预制文件 + let preS0 = this.searchSymbolinWorkSpace(symbolStr, searchMethod, Tools.SearchRange.GlobalSymbols, true, true); + if(preS0){ + retSymbols = retSymbols.concat(preS0); + } + + return retSymbols; + } + +//----------------------------------------------------------------------------- +//-- 私有方法 +//----------------------------------------------------------------------------- + + // 搜索预制lua符号 + private static searchLuaPreLoadSymbols(symbolStr, searchMethod){ + if(!symbolStr || symbolStr === ''){ + return []; + } + let retSymbols = new Array(); + this.luaPreloadSymbolMap.forEach(element => { + let res = element.searchMatchSymbal(symbolStr, searchMethod, Tools.SearchRange.GlobalSymbols); + if(res.length > 0){ + retSymbols = retSymbols.concat(res); + } + }); + return retSymbols; + } + + // 搜索用户预制符号 + private static searchUserPreLoadSymbols(symbolStr, searchMethod){ + if(!symbolStr || symbolStr === ''){ + return []; + } + let retSymbols = new Array(); + this.userPreloadSymbolMap.forEach(element => { + let res = element.searchMatchSymbal(symbolStr, searchMethod, Tools.SearchRange.GlobalSymbols); + if(res.length > 0){ + retSymbols = retSymbols.concat(res); + } + }); + return retSymbols; + } + + /** + * 重新分析文件后,根据require的文件的变动,更新本文件require的文件的reference,保留本文件的reference + * @param oldDocSymbol 上一次的docSymbol + * @param newDocSymbol 本次更新之后的docSymbol + */ + private static updateReference(oldDocSymbol: DocSymbolProcessor, newDocSymbol: DocSymbolProcessor) { + if (!oldDocSymbol) { + // 初次处理无需更新 + return; + } + // 保留本文件的reference(create的时候会被清空) + newDocSymbol.setReferences(oldDocSymbol.getReferencesArray()); + + let lastRequireFileArray = oldDocSymbol.getRequiresArray(); + let currentRequireFiles = newDocSymbol.getRequiresArray(); + + // 以下requireFile的含义均为本文件require的其他文件 + lastRequireFileArray.forEach((lastRequireFile) => { + // 本次代码改动删除之前的require语句,需要删除对应的reference关系 + let needDeleteReference = true; + currentRequireFiles.forEach((currentRequireFile) => { + if (currentRequireFile.reqName == lastRequireFile.reqName) { + needDeleteReference = false; + return; + } + }); + if (needDeleteReference) { + let lastRequireFileUri = Tools.transFileNameToUri(lastRequireFile.reqName); + if(lastRequireFileUri.length === 0) return; + let lastRequireFileDocSymbol = this.docSymbolMap.get(lastRequireFileUri); + let lastRequireFileReference = lastRequireFileDocSymbol.getReferencesArray(); + let index = lastRequireFileReference.indexOf(newDocSymbol.getUri()); + // 删除本文件require的文件对本文件的reference + lastRequireFileReference.splice(index, 1); + } + }); + } + + // 创建某个lua文件的符号 + // @uri 文件uri + // @text 文件内容 + private static createDocSymbol(uri: string, luaText?: string){ + if(uri == null) return; + if (luaText == undefined) { + luaText = Tools.getFileContent(Tools.uriToPath(uri)); + } + + let oldDocSymbol = this.getFileSymbolsFromCache(uri); + let newDocSymbol: DocSymbolProcessor = DocSymbolProcessor.create(luaText, uri); + if(newDocSymbol){ + Tools.AddTo_FileName_Uri_Cache(Tools.getPathNameAndExt(uri)['name'] , uri) + if( newDocSymbol.docInfo.parseSucc ){ + //解析无误,覆盖旧的 + this.docSymbolMap.set(uri, newDocSymbol); + this.updateReference(oldDocSymbol, newDocSymbol); + }else{ + //解析过程有误 + if ( !this.getFileSymbolsFromCache(uri) ){ + //map中还未解析过这个table,放入本次解析结果 + this.docSymbolMap.set(uri, newDocSymbol); + }else{ + //map中已有, 且之前保存的同样是解析失败,覆盖 + if (!this.getFileSymbolsFromCache(uri).docInfo.parseSucc){ + this.docSymbolMap.set(uri, newDocSymbol); + this.updateReference(oldDocSymbol, newDocSymbol); + } + } + } + }else{ + return; + } + } + + // 创建前置搜索文件的所有符号 + // @uri 文件uri + // @type 0lua预制 1用户导出 + private static createPreLoadSymbals(uri: string, type:number){ + let path = Tools.uriToPath(uri); + let luaText = Tools.getFileContent(path); + let docSymbol: DocSymbolProcessor = DocSymbolProcessor.create(luaText, uri); + if(type === 0){ + this.luaPreloadSymbolMap.set(uri, docSymbol); + }else{ + this.userPreloadSymbolMap.set(uri, docSymbol); + } + } + + private static deepCounter = 0; + // 递归搜索 引用树,查找符号 + // @fileName 文件名 + // @symbolStr 符号名 + // @uri + private static recursiveSearchRequireTree(uri: string, symbolStr, searchMethod :Tools.SearchMode, searchRange:Tools.SearchRange = Tools.SearchRange.AllSymbols, isFirstEntry:boolean = true){ + if(!uri || uri === ''){ + return []; + } + + if(!symbolStr || symbolStr === ''){ + return []; + } + + let retSymbArray = new Array(); + + if(isFirstEntry){ + // 首次进入 + this.deepCounter = 0; + }else{ + //递归中 + this.deepCounter++; + if(this.deepCounter >= 50){ + return retSymbArray; + } + } + + //如果 uri 的符号列表不存在,创建 + if (!this.docSymbolMap.has(uri)) { + Logger.log("createDocSymbals : "+ uri); + let luaText = CodeEditor.getCode(uri); + this.createDocSymbol(uri, luaText); + } + //开始递归 + //如果uri所在文件存在错误,则无法创建成功。这里docProcesser == null + let docProcessor = this.docSymbolMap.get(uri); + if(docProcessor == null || docProcessor.getRequiresArray == null){ + Logger.log("get docProcessor or getRequireFiles error!"); + return []; + } + + //当前文件已经在递归处理中了 + if(this.alreadySearchList[uri] == 1){ + return []; + }else{ + this.alreadySearchList[uri] = 1; + } + + // Logger.log("recursiveSearchRequireTree process :" + uri); + // 在引用树上搜索符号,搜索的原则为优先搜索最近的定义,即先搜本文件,然后逆序搜索require的文件,再逆序搜索reference + // 分析自身文件的符号. 本文件,要查找所有符号,引用文件,仅查找global符号。这里要求符号分析分清楚局部和全局符号 + let docS = this.docSymbolMap.get(uri); + let retSymbols = docS.searchMatchSymbal(symbolStr, searchMethod, searchRange); + if(retSymbols.length > 0){ + //找到了,查找全部符号,压入数组 + retSymbArray = retSymbArray.concat(retSymbols); + } + // 逆序搜索require + let reqFiles = docProcessor.getRequiresArray(); + for(let idx = reqFiles.length -1; idx >= 0; idx--){ + let newuri = Tools.transFileNameToUri(reqFiles[idx]['reqName']); + if(newuri.length === 0) return retSymbArray; + let retSymbols = this.recursiveSearchRequireTree(newuri, symbolStr, searchMethod, searchRange, false); + if(retSymbols != null && retSymbols.length > 0){ + retSymbArray = retSymbArray.concat(retSymbols); + } + } + // 逆序搜索reference + let refFiles = docProcessor.getReferencesArray(); + for(let idx = refFiles.length -1; idx >= 0; idx--){ + let newuri = refFiles[idx]; + let retSymbols = this.recursiveSearchRequireTree(newuri, symbolStr, searchMethod, searchRange, false); + if (retSymbols != null && retSymbols.length > 0) { + retSymbArray = retSymbArray.concat(retSymbols); + } + } + return retSymbArray; + } +} diff --git a/src/code/server/codeTools.ts b/src/code/server/codeTools.ts new file mode 100644 index 0000000..287f077 --- /dev/null +++ b/src/code/server/codeTools.ts @@ -0,0 +1,605 @@ +// Tencent is pleased to support the open source community by making LuaPanda available. +// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +// https://opensource.org/licenses/BSD-3-Clause +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +import { Logger } from './codeLogManager'; +import URI from 'vscode-uri'; +let path = require('path'); +let dir = require('path-reader'); +let os = require('os'); +let urlencode = require('urlencode'); + +import { + Location, + Position, + SymbolKind, + Range, + Connection, + DocumentSymbol +} from 'vscode-languageserver'; +import * as fs from "fs"; +// import { isArray } from 'util'; +//----------------------------------------------------------------------------- +//-- 暂存的数据 +//----------------------------------------------------------------------------- +let initParameter; //初始化参数 +export function setInitPara(para){ + initParameter = para; +} + +// 插件安装位置 +let VScodeExtensionPath; +export function getVScodeExtensionPath(){ + return VScodeExtensionPath; +} + +// VSCode 打开的所有文件夹 +let VSCodeOpenedFolders = [] +export function getVSCodeOpenedFolders(){ + if(VSCodeOpenedFolders.length === 0 && initParameter && initParameter.workspaceFolders){ + for (const rootFold of initParameter.workspaceFolders ) { + VSCodeOpenedFolders.push(uriToPath(rootFold.uri)); + } + } + return VSCodeOpenedFolders; +} + +export function addOpenedFolder(newFolders){ + let rootFolders = getVSCodeOpenedFolders(); + for (const folder of newFolders) { + // 测试不会出现重复添加的情况 + rootFolders.push(uriToPath(folder.uri)); + } +} + +export function removeOpenedFolder(beDelFolders){ + let rootFolders = getVSCodeOpenedFolders(); + for (const folder of beDelFolders) { + for(let idx =0; idx < rootFolders.length; idx++ ){ + if( uriToPath(folder.uri) === rootFolders[idx] ){ + rootFolders.splice(idx , 1); + break; + } + } + } +} + +export function setVScodeExtensionPath(_VScodeExtensionPath:string){ + VScodeExtensionPath = _VScodeExtensionPath; +} + +let loadedExt; // 记录已被处理的文件后缀 +export function initLoadedExt(){ + loadedExt = new Object(); +} + +export function getLoadedExt(){ + return loadedExt; +} + +export function setLoadedExt(key){ + loadedExt[key] = true; +} + +let connection: Connection; //保存一份connection +export function setToolsConnection(conn: Connection) { + connection = conn; +} + +let fileName_Uri_Cache; //文件名 - path cache +let uriToPathCache = new Object(); //uri - path cache +let pathToUriCache = new Object(); +//----------------------------------------------------------------------------- +//-- 枚举 +//----------------------------------------------------------------------------- +//搜索类型 +export enum SearchMode{ + ExactlyEqual, //精确匹配 + FuzzyMatching, //模糊匹配 + PrefixMatch, //前序匹配 +} + +//搜索范围 +export enum SearchRange{ + AllSymbols, //全体符号 + GlobalSymbols, + LocalSymbols +} + +// 记录tag的原因 +export enum TagReason{ + UserTag, + Equal, + MetaTable +} + +//----------------------------------------------------------------------------- +//-- 常用结构体 +//----------------------------------------------------------------------------- + +// 生成的符号信息 +export interface SymbolInformation { + name: string; //展示名 local a.b.c | function mt:fcun(para1) + searchName: string; //搜索名 a.b.c | mt:func (searchName在保存的时候,:全都用 . ) + originalName: string //符号原本名 c | func + kind: SymbolKind; //类型 + location: Location; //位置 + isLocal: boolean; // local / global + containerURI: string; // 所属的URI (file://) + containerPath: string; // 所属的文件路径 + containerName?: string; // 所属的函数名(展示用) + containerList?: Array; // 容器列表array + funcParamArray?: Array; // 函数参数数组,生成注释用 + tagReason?: TagReason; // 标记原因,有标记必须写原因 + tagType?: string; // 用户标记此符号的类型,用于处理 local a = require("xxx") 等接收返回值的形式 + requireFile?:string; // 本符号是require文件的返回 + funcRets?; // 如果此符号是function的返回值,记录对应的function . 值是{ name; local }结构 + chunk?:chunkClass; // 如果此符号是一个function, 对应的chunk结构 +} + +// 搜索符号返回结构 +// 这个结构类似一个联合体,其中可能有搜索到的符号retSymbol, 也可以记录baseinfo信息。使用isFindout来指示是否搜索到符号 +export class searchRet { + isFindout: boolean; //是否找到 + container?: string[]; //深度层级列表 + retSymbol?: searchSymbolRet; //符号自身的信息 + baseinfo?: baseInfo; //基础层级的信息 + constructor(){ + this.isFindout = false; + } +} + +// 搜索结果(searchRet的子结构) +export interface searchSymbolRet { + name: string; //展示名 + isLocal: boolean; //是否local符号. 下面三个属性是找到才需要的 + location?: Location; + containerURI: string | null; //所属的文件名 +} + +// base 基类(table)信息 (searchRet的子结构) +export interface baseInfo { + name: string; //展示名 + isLocal: boolean; //是否local符号. 下面三个属性是找到才需要的 + identiferStr?:string; +} + +// 注释的类型信息 +export interface commentTypeInfo { + reason: TagReason; //注释的原因 + newType: string; //新类型 + oldType?: string; //旧类型 setmetatable(旧,新) + location?: Location; + name?:string; //被注释的变量名 +} + +// 注释的类型信息 +export interface functionRetInfo { + functionName:string; // 如果是函数返回信息,要填充这个参数 + loc: Location; +} + +// 引用文件的信息, require保存文件名是为了实现点击文件名跳转 +export interface requireFileInfo{ + reqName:string; + loc:Location; +} + +//chunks 结构体 +export class chunkClass { + chunkName:string; + loc:Location; + returnSymbol; //返回的变量值 + constructor(name, loc){ + this.chunkName = name; + this.loc = loc; + } +} + +// 一个lua文件中包含的所有信息 +export class docInformation { + // lua文本基础内容 + parseSucc; // 记录解析是否成功,默认true + docAST; //文本解析出的AST树 + docUri:string; //文件URL + docPath :string; //文件路径 + // 符号表 + defineSymbols; //定义符号表 + // 文件的引用和被引用情况 + requires:requireFileInfo[]; //本文件引用的文件列表(require 是有序的,类型是array) + references: string[]; // require本文件的其他文件的uri(array) + + constructor(docAST , docUri , docPath){ + this.parseSucc = true; + this.docAST = docAST; + this.docUri = docUri; + this.docPath = docPath; + this.defineSymbols = new Object(); + this.defineSymbols["allSymbols"] = new Array();//字典,以searchName为key + this.defineSymbols["allSymbolsArray"] = new Array(); + this.defineSymbols["allSymbolsTrie"]; + this.defineSymbols["globalSymbols"] = new Array(); + this.defineSymbols["globalSymbolsArray"] = new Array(); + this.defineSymbols["globalSymbolsTrie"]; + this.defineSymbols["localSymbols"] = new Array(); + this.defineSymbols["localSymbolsArray"] = new Array(); + this.defineSymbols["localSymbolsTrie"]; + this.defineSymbols["chunks"] = new Array(); //记录每个chunk中的名字,位置,global/local,(文件/函数)返回信息 + this.defineSymbols["chunksArray"] = new Array(); //记录每个chunk中的名字,位置,global/local,返回信息 + this.requires = new Array(); + this.references = new Array(); + } +} + +//----------------------------------------------------------------------------- +//-- 工具方法 +//----------------------------------------------------------------------------- + +// uri 中html编码转换为原字符 +export function urlDecode(url):string{ + return urlencode.decode(url); +} + +// 从URI分析出文件名和后缀 +export function getPathNameAndExt(UriOrPath): Object{ + let name_and_ext = path.basename(UriOrPath).split('.'); + let name = name_and_ext[0]; //文件名 + let ext = name_and_ext[1]; //文件后缀 + for (let index = 2; index < name_and_ext.length; index++) { + ext = ext + '.' + name_and_ext[index]; + } + return { name, ext }; +} + +export function get_FileName_Uri_Cache(){ + return fileName_Uri_Cache; +} + +// 向cache中添加内容 +export function AddTo_FileName_Uri_Cache(name , uri){ + fileName_Uri_Cache[name] = urlDecode(uri); +} + +export function isinPreloadFolder(uri):boolean{ + if(!uri) return false; + let matchRes = uri.match('.vscode/LuaPanda/IntelliSenseRes'); + if(matchRes){ + return true; + } + return false; +} + + +// 刷新Cache +export function refresh_FileName_Uri_Cache(){ + //Cache 中没有找到,遍历RootPath + // Logger.InfoLog("start refresh_FileName_Uri_Cache: "); + let totalFileNum = 0; // 已处理的文件总数 + fileName_Uri_Cache = new Array(); + let processFilNum = 0; + // if(initParameter && initParameter.rootPath){ + for (const rootFolder of getVSCodeOpenedFolders()) { + //rootFiles为空,构建rootFilesMap,这个步骤应该放在init时,或者打开首个文件时 + //构建操作,只执行一次 + let rootFiles = dir.files(rootFolder, {sync:true}); + totalFileNum += rootFiles.length + for(let idx = 0, len = rootFiles.length; idx < len ; idx++){ + // let currentFileIdx = idx + 1; + let name_and_ext = getPathNameAndExt(rootFiles[idx]); + let trname = name_and_ext['name']; + let ext = name_and_ext['ext']; + let validExt = getLoadedExt(); //可用的文件后缀 + if(validExt[ext]){ + let trUri = pathToUri(rootFiles[idx]); //uri + fileName_Uri_Cache[trname] = urlDecode(trUri); + // 文件信息 + Logger.DebugLog(trUri); + processFilNum = processFilNum + 1; + // 显示进度 + // let rate = Math.floor(currentFileIdx / totalFileNum * 100); + // showProgressMessage(rate, trUri); + } + } + } + Logger.InfoLog("文件Cache刷新完毕,共计" + totalFileNum + "个文件, 其中" + processFilNum + "个lua类型文件"); + showProgressMessage(100, "done!"); +} + +// 把文件名转换为 uri 凡是调用本函数,要判断返回值 +// @fileName 文件名 +// @return uri string +export function transFileNameToUri(requireName : string): string{ + if(requireName == null){ + return ''; + } + //从路径中提取文件名 + let parseName = path.parse(requireName); + //从fileMap中查找文件全路径 + let cacheUri = fileName_Uri_Cache[parseName.name]; + if(cacheUri){ + return cacheUri; + } + return ''; +} + +//把win下盘符转换成大写 +export function transWinDiskToUpper(uri: string):string{ + if (os.type() == "Windows_NT") { + let reg = /^file:\/\/\/(\w)/; + uri = uri.replace(reg,function(m){ + let diskSymbol = m.charAt(8); + diskSymbol = 'file:///' + diskSymbol.toUpperCase() + return diskSymbol}); + return uri; + } +} + +// path -> uri string +export function pathToUri(pathStr : string): string{ + if(pathToUriCache[pathStr]){ + return pathToUriCache[pathStr]; + } + + let retUri; + if (os.type() == "Windows_NT") { + let pathArr = pathStr.split( path.sep ); + let stdPath = pathArr.join('/'); + retUri = 'file:///' + stdPath; + } + else{ + //Darwin + retUri = 'file://' + pathStr; + } + + pathToUriCache[pathStr] = retUri; + return retUri; +} + +// uri string -> path +export function uriToPath(uri: string): string { + if(uriToPathCache[uri]){ + return uriToPathCache[uri]; + } + let pathStr = URI.parse(uri).fsPath; + uriToPathCache[uri] = pathStr + return pathStr; +} + +// 返回整个目录下的文件列表 +// @path 文件夹路径 +// @return string[] | 返回的文件列表 +export function getDirFiles(path : string){ + if(path){ + return dir.files(path, {sync:true}); + } +} + +// 读文本文件内容 +// @path 文件路径 +// @return 文件内容 +export function getFileContent(path: string): string { + if(path == '' || path == undefined){ + return ''; + } + let data = fs.readFileSync(path); + let dataStr = data.toString(); + return dataStr; +} + +// 把position中起始行号转换为1 (用户选择- > vacode) +export function transPosStartLineTo1(position){ + position.line = position.line + 1; +} + +export function transPosStartLineTo0(position){ + position.line = position.line - 1; +} + + +// 从给定文本中,读出pos位置处的信息 +// @luaText 文本 +// @pos 位置信息 +// @return 指定位置的lua字符串 +export function getTextByPosition(luaText : string, pos : Position): string{ + if(luaText == null){ + return ''; + } + // 拆分luaText + let stringArr = luaText.split(/\r\n|\r|\n/); + let startStr = stringArr[pos.line].substring(0, pos.character); + //使用正则搜索最后一个出现的 符号或者空格 TODO 待完善 + // let reg= /[~!#%&\*\(\)\|,<>\?"';\+\-\=\[\]\{\}]/g; + let reg= /[~!#%&\t\*\(\)\|,<>\?"';\+\=\[\]\{\}]/g; // 保留"-",用于触发文档注释 + let blankStr = startStr.replace(reg, ' '); + let finalArr = blankStr.split(' '); + let retStr = finalArr.pop(); + return retStr; +} + +/** + * isNextLineHasFunction 使用正则判断下一行是否有function关键字,如果有返回true + * @param luaText 文件内容 + * @param position 位置 + */ +export function isNextLineHasFunction(luaText: string, position: Position): boolean { + let luaTextArray = luaText.split(/\r\n|\r|\n/); + + // 溢出 + if (luaTextArray.length <= position.line + 1) { + return false; + } + + let nextLineText = luaTextArray[position.line + 1]; + let regExp = /\bfunction\b/; + if (regExp.exec(nextLineText)) { + return true; + } + return false; +} + +export function createEmptyLocation(uri) { + let pos = Position.create(0,0); + let rg = Range.create(pos, pos) + let retLoc = Location.create(uri, rg); + return retLoc; +} + +// 根据uri判断文件是否在预设的忽略列表里 +// @param ignoreRegExp 要忽略的文件夹的正则表达式数组 +export function isMatchedIgnoreRegExp(uri: string, ignoreRegExp: string[]): boolean { + for (let i = 0; i < ignoreRegExp.length; i++) { + if (ignoreRegExp[i] === "") { + continue; + } + let regExp = new RegExp(ignoreRegExp[i]); + if (regExp.exec(uri)) { + return true; + } + } + return false; + +} + +export function getNSpace(n: number) { + let str = ""; + for (let i = 0; i < n; i++) { + str += " "; + } + return str; +} + +export function showProgressMessage(progress: number, message: string) { + connection.sendNotification("showProgress", progress + "% " + message); + if (progress == 100) { + connection.sendNotification("showProgress", "LuaPanda 👍"); + } +} + +export function showTips(str: string, level?: number) { + if(level === 2 ){ + connection.sendNotification("showErrorMessage", str); + }else if(level === 1 ){ + connection.sendNotification("showWarningMessage", str); + }else{ + connection.sendNotification("showInformationMessage", str); + } +} + +// 新加入的方法,把dic转换为array +export function changeDicSymboltoArray(dic){ + let array = new Array(); + for (const key in dic) { + const element = dic[key]; + if(Array.isArray(element)){ + for (const k in element) { + const ele = element[k]; + array.push(ele); + } + }else{ + array.push(element); + } + } + return array; +} + +// 将原有的containerList和searchName用点和冒号切割,拼成新的containerList,用来处理层级 +function getVerboseSymbolContainer(verboseSymbolInfo: SymbolInformation): chunkClass[] { + let searchName = verboseSymbolInfo.searchName; + let searchNameArray = Array(); + if (searchName != "...") { + searchName = searchName.replace(/\[/g, '.'); + searchName = searchName.replace(/]/g, ''); + searchNameArray = splitToArrayByDot(searchName); + } + let searchNameContainer: chunkClass[] = Array(); + for (let i = 0; i < searchNameArray.length - 1; i++) { + searchNameContainer.push(new chunkClass(searchNameArray[i], undefined)); + } + + let containerList: chunkClass[] = Array(); + containerList.push(verboseSymbolInfo.containerList[0]); + for (let i = 1; i < verboseSymbolInfo.containerList.length; i++) { + let chunkNameArray = splitToArrayByDot(verboseSymbolInfo.containerList[i].chunkName); + if (chunkNameArray.length > 1) { + for (let j = 0; j < chunkNameArray.length; j++) { + containerList.push(new chunkClass(chunkNameArray[j], undefined)); + } + } else { + containerList.push(verboseSymbolInfo.containerList[i]); + } + } + + let verboseSymbolContainer = containerList.concat(searchNameContainer); + return verboseSymbolContainer; +} + +function handleDocumentSymbolChildren(symbolContainer: chunkClass[], documentSymbol: DocumentSymbol, outlineSymbolArray: DocumentSymbol[], chunkMap: Map) { + let index = chunkMap.get(symbolContainer[1].chunkName); + if (index === undefined) { + return; + } + let parent: DocumentSymbol = outlineSymbolArray[index]; + for (let i = 2; i < symbolContainer.length; i++) { + for (let j = 0; j < parent.children.length; j++) { + if (symbolContainer[i].chunkName == parent.children[j]["originalName"]) { + parent = parent.children[j]; + break; + } + } + } + if(!parent.children){ + parent.children = new Array(); + } + + parent.children.push(documentSymbol); +} + +/** + * 列出本文件中的符号,用于在outline窗口中分层显示符号列表 + * @param symbolInfoArray CodeSymbol.getCertainDocSymbolsArray返回的符号信息数组 + * @return 本文件所有符号列表,DocumentSymbol数组,带有层次结构 + */ +export function getOutlineSymbol(symbolInfoArray: SymbolInformation[]): DocumentSymbol[] { + let outlineSymbolArray = Array(); + + // 存储最外层SymbolInformation.name - outlineSymbolArray索引 的map + let chunkMap = new Map(); + + for (let i = 0; i < symbolInfoArray.length; i++) { + let symbolInfo: SymbolInformation = symbolInfoArray[i]; + let documentSymbol: DocumentSymbol = { + name: symbolInfo.originalName, + kind: symbolInfo.kind, + range: symbolInfo.location.range, + selectionRange: symbolInfo.location.range, + children: Array() + }; + documentSymbol["originalName"] = symbolInfo.originalName; + // 变量展示originalName,函数展示name + if (symbolInfo.kind == SymbolKind.Function) { + documentSymbol.name = symbolInfo.name; + } + + let verboseSymbolContainer = getVerboseSymbolContainer(symbolInfoArray[i]); + + if (verboseSymbolContainer.length > 1) { + handleDocumentSymbolChildren(verboseSymbolContainer, documentSymbol, outlineSymbolArray, chunkMap); + continue; + } + + outlineSymbolArray.push(documentSymbol); + + chunkMap.set(symbolInfo.searchName, outlineSymbolArray.length - 1); + } + + return outlineSymbolArray; +} + +// 使用: . 分割符号,并返回数组 +export function splitToArrayByDot(input) { + let userInputTxt_DotToBlank = input.replace(/[\.:]/g, ' '); //把.和:转为空格 + let L = userInputTxt_DotToBlank.split(' '); + return L; +} diff --git a/src/code/server/docSymbolProcessor.ts b/src/code/server/docSymbolProcessor.ts new file mode 100644 index 0000000..f5b4c28 --- /dev/null +++ b/src/code/server/docSymbolProcessor.ts @@ -0,0 +1,2003 @@ +// Tencent is pleased to support the open source community by making LuaPanda available. +// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +// https://opensource.org/licenses/BSD-3-Clause +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +/* 本文件的作用是遍历一个文件url对应的AST树,并在遍历过程中记录一些信息 + 通常在三种情况下会遍历AST。 + 1 由AST构建定义符号表 + 2 已经有一个符号位置,由AST中查找其对应的符号 + 3 给定一个符号信息,由这个信息查询所有引用这个符号的位置 + + 本文件还提供在构建的定义表只能中搜索符号的能力searchMatchSymbal,支持各种搜索方式 + + 本文件的公有方法分为几部分: + + 静态创建方法 + + 获取本url及对应AST信息 + + 功能实现接口 + + 符号查找(多种方式) + + 私有方法 + + 工具方法 + + AST分析后的尾处理 + + 遍历AST + + 时序处理:分析不完整的AST +*/ + +import * as luaparse from 'luaparse'; +import * as Tools from './codeTools'; +import { Logger } from './codeLogManager'; +import { CodeSymbol } from './codeSymbol'; +import { Location, Range, Position, SymbolKind } from 'vscode-languageserver'; +import { trieTree } from './trieTree'; +import { isArray } from 'util'; +// import { isArray } from 'util'; + +// 遍历AST模式 +enum travelMode { + BUILD = 0, //构建定义符号 + GET_DEFINE = 1, //查找定义符号 + FIND_REFS = 2 //查找引用符号 +} + +export class DocSymbolProcessor { + + public docInfo: Tools.docInformation; // 记录.lua文本中的所有信息 + // 下面是临时记录信息的变量 + // refs 引用相关 + private searchInfo; //记录被查找的符号信息 + private refsLink; //引用符号队列 + // 按位置查找符号 + private searchPosition: Position; // 记录搜索符号所在的Position + private posSearchRet: Tools.searchRet; // 记录按配置查找的结果 + //解析相关 + private static tempSaveInstance; // 临时记录实例 + //build 相关 + private docCommentType; //注释(等号)类型表 | 符号标记类型 记录标记的类型信息。标记来源于 1.用户注释 2.元表 3. 等号 + private funcReturnRecoder; // 用来记录每个函数符号的返回值,并放入符号列表的 chunk 结构中 + private callFunctionRecoder; //用来记录函数调用 + + // 静态创建方法,创建文件的 定义符号列表(定义符号tools.docInformation 数组) + public static create(luaText: string, uri: string) { + let instance: DocSymbolProcessor = new DocSymbolProcessor(); + let path = Tools.uriToPath(uri); + try { + let AST = luaparse.parse(luaText, { locations: true, scope: true, comments: true}); + instance.docInfo = new Tools.docInformation(AST, uri, path); + instance.buildDocDefineSymbols(); + instance.docInfo.parseSucc = true; + return instance; + } catch (error) { + // Logger.ErrorLog("[Error] 解析文件 " + uri + " AST的过程中出错:"); + // Logger.ErrorLog("[error stack]:" + error.stack ); + + //建立空文件(没有AST) + instance.docInfo = new Tools.docInformation(new Object, uri, path); + DocSymbolProcessor.tempSaveInstance = instance; + //解析 + try { + luaparse.parse(luaText, { locations: true, scope: true, onCreateNode: instance.onCreateNode}); + }catch{} + instance.docInfo.parseSucc = false; + return instance; + } + } + +//----------------------------------------------------------------------------- +//-- get/set 获取本文件的基础信息 +//----------------------------------------------------------------------------- + + // 获取本文件uri + public getUri() { + return this.docInfo.docUri; + } + + // 获取本文件 [所有符号列表] (kv) + public getAllSymbolsDic() { + return this.docInfo.defineSymbols.allSymbols; + } + + // 获取本文件 [所有符号列表] (Trie) + public getAllSymbolsTrie() { + return this.docInfo.defineSymbols.allSymbolsTrie; + } + + // 获取本文件 [全局符号列表] (kv) + public getGlobalSymbolsDic() { + return this.docInfo.defineSymbols.globalSymbols; + } + + // 获取本文件 [局部符号列表] (kv) + public getLocalSymbolsDic() { + return this.docInfo.defineSymbols.localSymbols; + } + + // 获取本文件 [chunk信息列表] (kv) + public getChunksDic(){ + return this.docInfo.defineSymbols.chunks; + } + + // 因为符号kv dic中存在冲突,所以新增了返回array接口 + public getAllSymbolsArray() { + return this.docInfo.defineSymbols.allSymbolsArray; + } + + // 获取本文件 [全局符号列表] (array) + public getGlobalSymbolsArray() { + return this.docInfo.defineSymbols.globalSymbolsArray; + } + + // 获取本文件 [Global符号列表] (Trie) + public getGlobalSymbolsTrie() { + return this.docInfo.defineSymbols.globalSymbolsTrie; + } + + // 获取本文件 [局部符号列表] (array) + public getLocalSymbolsArray() { + return this.docInfo.defineSymbols.localSymbolsArray; + } + + // 获取本文件 [局部符号列表] (Trie) + public getLocalSymbolsTrie() { + return this.docInfo.defineSymbols.localSymbolsTrie; + } + + // 获取本文件 [chunk信息列表] (array) + public getChunksArray(){ + return this.docInfo.defineSymbols.chunksArray; + } + + // 获取本文件 [返回值列表] (array) + public getFileReturnArray(){ + let chunks = this.docInfo.defineSymbols.chunks; + return chunks[this.docInfo.docPath].returnSymbol; + } + + // 获取本文件 [require列表] (array) + public getRequiresArray(){ + return this.docInfo.requires; + } + + // 获取本文件的[被引用信息] (array) + public getReferencesArray() { + return this.docInfo.references; + } + // 设置本文件的被引用信息 + public setReferences(references: string[]) { + return this.docInfo.references = references; + } + + //构建字典树(用于前缀搜索) + private buildSymbolTrie(){ + let all = this.getAllSymbolsArray(); + this.docInfo.defineSymbols.allSymbolsTrie = trieTree.createSymbolTree(all); + let global = this.getGlobalSymbolsArray(); + this.docInfo.defineSymbols.globalSymbolsTrie = trieTree.createSymbolTree(global); + let local = this.getLocalSymbolsArray(); + this.docInfo.defineSymbols.localSymbolsTrie = trieTree.createSymbolTree(local); + } + + +//----------------------------------------------------------------------------- +//-- 主要对外接口 +//----------------------------------------------------------------------------- + // 构建文件的 [定义符号表] + public buildDocDefineSymbols() { + let deepLayer: Array = new Array(); + this.docCommentType = new Array(); + this.callFunctionRecoder = new Array(); + // 由AST建立符号表 + this.traversalAST(this.docInfo["docAST"], travelMode.BUILD, deepLayer); + // 符号表后处理,记录comment和文件/函数返回值 + this.processRequireArrayPath() + this.buildSymbolTag(); + this.buildSymbolReturns(); // 构建 B = require("A") , 记录B的类型 + this.buildSymbolTrie(); //构建字典树 + + // Debug info 查看序列化的AST + // let tempStr = JSON.stringify(this.docInfo["docAST"]); + // DebugInfo + // console.log(tempStr) + } + + // 根据 VSCode 传来的 Position 信息从文件 AST 中查找对应符号 + // 这里可以考虑从AST中或者从luaText中查找。使用AST的优点是可以查询到更多的信息,包括isLocal,container。使用 text查找比较快,但只有文本信息。 + // 使用luaText查找的好处是可以搜索未完成的代码 + // 目前 定义查找使用 AST, 代码补全使用 luaText + public searchDocSymbolfromPosition(pos) { + this.searchPosition = pos; + let container = new Array(); + this.posSearchRet = new Tools.searchRet(); + this.traversalAST(this.docInfo["docAST"], travelMode.GET_DEFINE, container); + return { sybinfo: this.posSearchRet.retSymbol, container: container }; + } + + // 查找一个符号的引用 + public searchDocSymbolReference(info) { + //先清空数组 + this.searchInfo = info; + this.refsLink = new Array(); // 引用符号队列 + this.traversalAST(this.docInfo["docAST"], travelMode.FIND_REFS, new Array()); + return this.refsLink; + } + + // 按 position 搜索符号是否是文件名(用于点击require文件名跳转) + public searchDocRequireFileNameFromPosition(pos):string { + let reqFiles = this.getRequiresArray(); + for (let index = 0; index < reqFiles.length; index++) { + const element = reqFiles[index]; + let res = this.isInASTLoc(element.loc, pos); + if(res){ + return element.reqName; + } + } + } + + // 在当前 文件的定义符号表中查找符号(提供多种查找方式,查找范围) + // @symbalName 符号名 + // @matchMode 搜索方式 + // @searchRange 0:all 1 global 2 display + public searchMatchSymbal(symbalName: string, matchMode: Tools.SearchMode, searchRange?: Tools.SearchRange ): Tools.SymbolInformation[] { + searchRange = searchRange || Tools.SearchRange.AllSymbols ; + let retSymbols = []; + let SymbolArrayForSearch; + // let reg = /[A-Z]/; + //精确查找。直接使用字典匹配 + if (matchMode === Tools.SearchMode.ExactlyEqual) { + if( searchRange == Tools.SearchRange.AllSymbols ){ + SymbolArrayForSearch = this.getAllSymbolsDic(); + }else if( searchRange == Tools.SearchRange.GlobalSymbols){ + SymbolArrayForSearch = this.getGlobalSymbolsDic() ; + }else if( searchRange == Tools.SearchRange.LocalSymbols){ + SymbolArrayForSearch = this.getLocalSymbolsDic(); + } + + //精确匹配 searchName。保证SymbolArrayForSearch其中的key中只有.而没有: + let tgtSymbol = SymbolArrayForSearch[symbalName]; + if(tgtSymbol){ + //搜索到了,根据值类型不同(字典中存在冲突,可能是符号或是数组),放入返回符号表 + if(Array.isArray(tgtSymbol)){ + retSymbols = tgtSymbol; + }else{ + retSymbols.push(tgtSymbol); + } + } + }else if(matchMode === Tools.SearchMode.PrefixMatch){ + // 前缀搜索 + let root; + if( searchRange == Tools.SearchRange.AllSymbols ){ + root = this.getAllSymbolsTrie(); + }else if( searchRange == Tools.SearchRange.GlobalSymbols){ + root = this.getGlobalSymbolsTrie(); + }else if( searchRange == Tools.SearchRange.LocalSymbols){ + root = this.getLocalSymbolsTrie(); + } + let trieRes = trieTree.searchOnTrieTreeWithoutTableChildren(root, symbalName); + if(isArray(trieRes)){ + retSymbols = trieRes; + } + }else if(matchMode === Tools.SearchMode.FuzzyMatching){ + // 模糊搜索,如果用户输入的字符串中有大写字母,则大小写敏感。否则大小写不敏感。目前模糊搜索使用的是便利的方式,效率较低 + if( searchRange == Tools.SearchRange.AllSymbols ){ + SymbolArrayForSearch = this.getAllSymbolsArray(); + }else if( searchRange == Tools.SearchRange.GlobalSymbols){ + SymbolArrayForSearch = this.getGlobalSymbolsArray(); + }else if( searchRange == Tools.SearchRange.LocalSymbols){ + SymbolArrayForSearch = this.getLocalSymbolsArray(); + } + + for (let idx in SymbolArrayForSearch){ + let sym = SymbolArrayForSearch[idx]; + let searchName = sym.name; + if(searchName){ + let reg = new RegExp(symbalName ,'i'); + let hit = searchName.match(reg); + if(hit){ + retSymbols.push(sym); + } + } + } + } + return retSymbols; + } + +//----------------------------------------------------------------------------- +//-- 以下是遍历AST的私有方法 +//-- 工具方法 +//----------------------------------------------------------------------------- + + // loc2是否在loc1之中 + private isInLocation(loc1, loc2: Position): boolean { + if (loc1.range.start.line <= loc2.line && loc1.range.end.line >= loc2.line) { + if (loc1.range.start.line === loc2.line) { + let character = loc1.range.start.character || loc1.range.start.column; + //start > pos + if (character > loc2.character) return false; + } + + if (loc1.range.end.line === loc2.line) { + let character = loc1.range.end.character || loc1.range.end.column; + if (character < loc2.character) return false; + } + return true; + } + return false; + } + + //list2是否是list1的子集 + // private listContainer(list1, list2): boolean { + // if(list2.length > list1.length) return false; + // for(let idx = 0, len = list2.length ; idx < len; idx++){ + // if(list1[idx] != list2[idx]){ + // return false; + // } + // } + // return true; + // } + + // 判断 loc2 是否被包含在 loc1 之中 + private isInASTLoc(loc1, loc2: Position): boolean { + if (loc1["start"].line <= loc2.line && loc1["end"].line >= loc2.line) { + if (loc1.start.line === loc2.line) { + let character = loc1.start.character || loc1.start.column; + //start > pos + if (character > loc2.character) return false; + } + + if (loc1.end.line === loc2.line) { + let character = loc1.end.character || loc1.end.column; + if (character < loc2.character) return false; + } + return true; + } + return false; + } + + // 创建一个符号的信息 + private createSymbolInfo(name: string, searchName:string, originalName:string, + kind:SymbolKind, location:Location, isLocal:boolean, + containerName?:string, containerList?:Array, funcParamArray?:Array, tagType? :string, reason?: Tools.TagReason): Tools.SymbolInformation{ + //searchName中的全部:被替换为 . , 目的是提高查找效率 + if(searchName.match(':')){ + searchName = searchName.replace(/:/g,"."); + } + + return{ + name: name, + searchName: searchName, + originalName: originalName, + kind: kind, + location: location, + isLocal: isLocal, + containerURI: this.docInfo["docUri"], + containerPath: this.docInfo["docPath"], + containerName: containerName, + containerList: containerList, + funcParamArray:funcParamArray, + // alreadyAddDisplay: false, + tagType:tagType, + tagReason:reason + }; + } + + // 检查符号是否已经在符号表中存在 + // @name 符号名 + private checkIsSymbolExist(name) { + if (this.getAllSymbolsDic()[name] != undefined){ + return true; + } + return false; + } + + //-------查到符号后的填充 + private pushToAllList(symbol:Tools.SymbolInformation){ + if(this.docInfo.defineSymbols.allSymbols[symbol.searchName]){ + let travlSymbol = this.docInfo.defineSymbols.allSymbols[symbol.searchName]; + //判断是否数组 + if ( Array.isArray(travlSymbol) ){ + travlSymbol.push(symbol); + }else{ + //只有一个元素,还不是数组 + let newArray = new Array(); + newArray.push(travlSymbol); + newArray.push(symbol); + this.docInfo.defineSymbols.allSymbols[symbol.searchName] = newArray; + } + }else{ + this.docInfo.defineSymbols.allSymbols[symbol.searchName] = symbol; + } + //放入array队列 + this.docInfo.defineSymbols.allSymbolsArray.push(symbol); + } + + private pushToLocalList(symbol:Tools.SymbolInformation){ + if(this.docInfo.defineSymbols.localSymbols[symbol.searchName]){ + let travlSymbol = this.docInfo.defineSymbols.localSymbols[symbol.searchName]; + if ( Array.isArray(travlSymbol) ){ + travlSymbol.push(symbol); + }else{ + let newArray = new Array(); + newArray.push(travlSymbol); + newArray.push(symbol); + this.docInfo.defineSymbols.localSymbols[symbol.searchName] = newArray; + } + }else{ + this.docInfo.defineSymbols.localSymbols[symbol.searchName] = symbol; + } + //放入array队列 + this.docInfo.defineSymbols.localSymbolsArray.push(symbol); + } + + // 把symbol信息放入 docGlobalSymbols | 注意这里是 kv 形式 + private pushToGlobalList(symbol:Tools.SymbolInformation){ + if(this.docInfo.defineSymbols.globalSymbols[symbol.searchName]){ + let travlSymbol = this.docInfo.defineSymbols.globalSymbols[symbol.searchName]; + if ( Array.isArray(travlSymbol) ){ + travlSymbol.push(symbol); + }else{ + let newArray = new Array(); + newArray.push(travlSymbol); + newArray.push(symbol); + this.docInfo.defineSymbols.globalSymbols[symbol.searchName] = newArray; + } + }else{ + this.docInfo.defineSymbols.globalSymbols[symbol.searchName] = symbol; + } + //放入array队列 + this.docInfo.defineSymbols.globalSymbolsArray.push(symbol); + } + + // 根据符号自动识别应该放入哪个列表 + private pushToAutoList(symbol:Tools.SymbolInformation){ + if(symbol.isLocal){ + this.pushToLocalList(symbol); + }else{ + this.pushToGlobalList(symbol); + } + this.pushToAllList(symbol); + } + + private pushToChunkList(name, chunk){ + + if(name.match(':')){ + //chunkname 除了是函数名外,还有可能是一个文件的路径。当name是路径的时候。不要把:转换为 . + if(!name.match(new RegExp(/^\w:[\\\/]/))){ + name = name.replace(/:/g,"."); + } + } + + if(this.docInfo.defineSymbols["chunks"][name]){ + let travlSymbol = this.docInfo.defineSymbols["chunks"][name]; + if ( Array.isArray(travlSymbol) ){ + travlSymbol.push(chunk); + }else{ + let newArray = new Array(); + newArray.push(travlSymbol); + newArray.push(chunk); + this.docInfo.defineSymbols["chunks"][name] = newArray; + } + }else{ + this.docInfo.defineSymbols["chunks"][name] = chunk; + } + //放入array队列 + this.docInfo.defineSymbols.chunksArray.push(chunk); + } + + private pushToCommentList(cmt:Tools.commentTypeInfo){ + this.docCommentType.push(cmt); + } + + // 记录函数调用 + private recordFuncCall(cmt:Tools.functionRetInfo){ + this.callFunctionRecoder.push(cmt); + } + //------------ + +//----------------------------------------------------------------------------- +//-- 遍历AST +//----------------------------------------------------------------------------- + // 遍历AST + // @node AST 当前节点 + // @type : travelMode.BUILD / travelMode.FIND_DEFINE / travel.FIND_REFS + // @deepLayer 深度队列。用来指示当前symbol所在的chunk + // @prefix 前缀。用来指示当前symbol所在chunk (此信息直接展示给用户) + // @isBody ? + private traversalAST(node, type : travelMode, deepLayer: Array , prefix?: string, isBody?:boolean) { + //传入的node是一个数组的时候,traversalAST递归其中每一个元素 + if (Array.isArray(node) === true) { + let ASTArray = Array.prototype.slice.call(node); + for (let idx = 0, len = ASTArray.length; idx < len; idx++) { + this.traversalAST(ASTArray[idx], type, deepLayer, prefix, isBody); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + } else { + let nodeType = node["type"]; + switch (nodeType) { + //container + case 'Chunk': this.processChunk(node, type, deepLayer, prefix); break; + case 'LocalStatement': this.LocalStatement(node, type, deepLayer, prefix); break; + case 'FunctionDeclaration': this.processFunction(node, type, deepLayer, prefix); break; + case 'AssignmentStatement': this.processAssignment(node, type, deepLayer, prefix); break; + case 'CallExpression': this.processCallExpression(node, type, deepLayer, prefix); break; + case 'StringCallExpression': this.processStringCallExpression(node, type, deepLayer, prefix); break; + case 'CallStatement': this.processCallStatement(node, type, deepLayer, prefix); break; + //循环结构,其中可能有定义,也可能有查找的符号 + case 'WhileStatement': this.processWhileStatement(node, type, deepLayer, prefix); break; + case 'RepeatStatement': this.processRepeatStatement(node, type, deepLayer, prefix); break; + case 'IfStatement': this.processIfStatement(node, type, deepLayer, prefix); break; + case 'ReturnStatement': this.processReturnStatement(node, type, deepLayer, prefix, isBody); break; + case 'ForNumericStatement': this.processForNumericStatement(node, type, deepLayer, prefix); break; + case 'ForGenericStatement': this.processForGenericStatement(node, type, deepLayer, prefix); break; + //二进制表达式 a == b + case 'BinaryExpression': this.processBinaryExpression(node, type, deepLayer, prefix); break; + case 'UnaryExpression': this.processUnaryExpression(node, type, deepLayer, prefix); break; + // case 'TableConstructorExpression': retsyb = this.processTableConstructorExpression(ASTNode, type, deepLayer, prefix); break; + case 'MemberExpression': this.processMemberExpression(node, type, deepLayer, prefix); break; + case 'IndexExpression': this.processIndexExpression(node, type, deepLayer, prefix); break; + // Terminal + case 'Identifier': this.processIdentifier(node, type, deepLayer, prefix); break; + } + } + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + // ------异常处理 + //分析不完全AST时的回调,这里只处理有限的情况 + // 把symbol信息放入docSymbols + // luaparse.parse 方法创建AST Node时的回调。这里只处理有限的接口 + private onCreateNode(node){ + //require + let deepLayer: Array = new Array(); + if(node['type'] == 'CallExpression' || node['type'] == 'StringCallExpression'){ + DocSymbolProcessor.tempSaveInstance.traversalAST(node, travelMode.BUILD, deepLayer); + } + //定义 | 可以处理,但是失去了深度信息 + if(node['type'] == "LocalStatement"){ + DocSymbolProcessor.tempSaveInstance.traversalAST(node, travelMode.BUILD, deepLayer); + } + //function 定义 + if(node['type'] == "FunctionDeclaration"){ + DocSymbolProcessor.tempSaveInstance.traversalAST(node, travelMode.BUILD, deepLayer); + } + } + + //---------前处理和后处理 + // 处理注释. 基本思路是在注释中查询 -@type + // 用一个结构体记录位置和类型,并放入CommentList中 + private processComments(commentArray){ + for (let idx = 0, len = commentArray.length; idx < len; idx++) { + let comValue = commentArray[idx].value; + let strArr = comValue.split(' ') + for (let j = 0; j < strArr.length; j++) { + const element = strArr[j]; + if(element.match('-@type') || element.match('-@return') || element.match('-@param')) { + let commentTypeIdx = j+1; + for (let k = commentTypeIdx; k < strArr.length; k++) { + if(strArr[k] != ''){ + commentTypeIdx = k; + break; + } + } + + if(element.match('-@param')){ + // param 类型的注释, 要标记当前变量和其对应的类型,不能仅凭行号区分 + let functionParameter = strArr[commentTypeIdx]; + let newType = strArr[commentTypeIdx + 1]; + let info = { + reason: Tools.TagReason.UserTag, + functionParameter: functionParameter, + newType: newType, + location : commentArray[idx].loc + } + this.pushToCommentList(info); + }else{ + let multiTypeArray = strArr[commentTypeIdx].split(','); + //返回值和赋值存在 ---@type a,b 多注释的情况 + for (const multiElement of multiTypeArray) { + let info = { + reason: Tools.TagReason.UserTag, + newType: multiElement, + location : commentArray[idx].loc + } + + this.pushToCommentList(info); + } + } + // 结束本行 + break; + } + } + } + } + + /** + * 记录反向引用关系 + * @param requireName require语句中的文件名 + * @param fileUri 调用require语句的文件的uri + */ + private recordReference(fileUri: string, requireName: string) { + let requireFileUri = Tools.transFileNameToUri(requireName); + if (requireFileUri == "") { + // 未找到require的文件 + return; + } + + // 被require的文件还没有处理,先创建符号 + if (CodeSymbol.docSymbolMap.has(requireFileUri) == false) { + CodeSymbol.createOneDocSymbols(requireFileUri); + } + let references = CodeSymbol.docSymbolMap.get(requireFileUri).getReferencesArray(); + if (references.includes(fileUri)) { + return; + } + references.push(fileUri); + } + + + private createRetBase(baseName, baseLocal, identifer?): Tools.searchRet { + let retBase: Tools.baseInfo = { + name: baseName, + isLocal: baseLocal, + identiferStr:identifer + }; + let ret: Tools.searchRet = { isFindout: false, baseinfo: retBase }; + return ret; + } + + private createRetSymbol(sybName, sybisLocal, sybLocation?, sybPath?){ + sybPath = sybPath || this.docInfo["docPath"]; + let retSymbol: Tools.searchSymbolRet = { + name: sybName, + isLocal: sybisLocal, + location: sybLocation, + containerURI: sybPath + }; + let ret: Tools.searchRet = { isFindout: true, retSymbol: retSymbol }; + return ret; + } + + // 记录一个符号的标记和标记原因(准备删除) + private setTagTypeToSymbolInfo(symbol: Tools.SymbolInformation, tagType, tagReason){ + if(symbol.tagReason != undefined && symbol.tagReason == Tools.TagReason.UserTag){ + // 用户标记的类型权重 > 赋值类型权重 + return false + } + symbol.tagType = tagType; + symbol.tagReason = tagReason; + return true; + } + + //---文件尾处理(生成符号表后,对一些有注释类型的符号,进行添加tag信息) + // 根据docCommentType记录的信息,在符号中标记tag + private buildSymbolTag(){ + let tagArray = this.docCommentType; + for(var key in tagArray) { + let tagInfo = tagArray[key]; + let loc = tagInfo.location; + let reason = tagInfo.reason; + let functionParam = tagInfo.functionParameter; + let paramRealLine = 0; + //从allSymbols中遍历location和tag相符合的符号 + for (let index = 0; index < this.getAllSymbolsArray().length; index++) { + const elm = this.getAllSymbolsArray()[index]; + if (functionParam){ + // 用户注释的 function 参数类型 + if(tagInfo.newType === "Type"){ + // Type是预设值 + break; + } + + if(reason == Tools.TagReason.UserTag && elm.location.range.start.line + 1 > loc['end'].line ){ + // functionParam 表示对应的文件,在location之后向下找,找到对应的符号名 + if(paramRealLine === 0){ + // 记录 param 类型注释后,收个符号的行号 + paramRealLine = elm.location.range.start.line; + } + else if(elm.location.range.start.line > paramRealLine){ + // 在 param 所在的真实行号没有找到被注释类型的参数 + break; + } + + if(functionParam === elm.searchName){ + this.setTagTypeToSymbolInfo(elm, tagInfo.newType, tagInfo.reason); + break; + } + } + }else{ + // 用户本行标记 + if(reason == Tools.TagReason.UserTag && elm.location.range.start.line + 1 === loc['end'].line) + { + let mulitTypeIdx = 0; + // 支持同一行多个类型类型注释,如 local a,b,c ---@type d,e,f 但注意 d,e,f之间不要有空格 + while (this.getAllSymbolsArray()[mulitTypeIdx + index].location.range.start.line + 1 === loc['end'].line){ + const elm = this.getAllSymbolsArray()[mulitTypeIdx + index]; + let res = this.setTagTypeToSymbolInfo(elm, tagInfo.newType, tagInfo.reason); + if(res){ + break; + }else{ + mulitTypeIdx++; + } + } + break; + } + // 在函数定义上一行的标记, 查找 return 注释, 以及支持在上一行注释 @type 的情况 + if(reason == Tools.TagReason.UserTag && elm.location.range.start.line === loc['end'].line) + { + this.setTagTypeToSymbolInfo(elm, tagInfo.newType, tagInfo.reason); + break; + } + // 相等符号 + if(reason == Tools.TagReason.Equal && elm.location.range.start.line + 1 === loc['end'].line) + { + // name : 被赋值符号名 + if(tagInfo.name && tagInfo.name == elm.searchName){ + this.setTagTypeToSymbolInfo(elm, tagInfo.newType, tagInfo.reason); + break; + } + } + + // 元表标记 + if(reason == Tools.TagReason.MetaTable && elm.searchName == tagInfo.oldType){ + this.setTagTypeToSymbolInfo(elm, tagInfo.newType, tagInfo.reason); + break; + } + } + } + } + } + + //把 require 路径中的.转换为/ + public processRequireArrayPath(){ + let reqArray = this.getRequiresArray(); + for (const reqPath of reqArray) { + reqPath.reqName = reqPath.reqName.replace(/\./g, '/'); + } + } + + // 构建文件返回和函数返回 + private buildSymbolReturns(){ + //设置符号的 requireFile + let reqArray = this.getRequiresArray(); + + for (const element of reqArray) { + let loc = element.loc; + let reqName = element.reqName; + for (let index = 0; index < this.getAllSymbolsArray().length; index++) { + const elm = this.getAllSymbolsArray()[index]; + let aling = elm.location.range.start.line + 1; + let bling = loc['start'].line; + if(aling == bling ) + { + elm.requireFile = reqName; + } + } + } + + //设置符号的 function Return + // let retArray = this.callFunctionRecoder; + for (const element of this.callFunctionRecoder ) { + let loc = element.loc; + let funcName = element.functionName; + for (let index = 0; index < this.getAllSymbolsArray().length; index++) { + const elm = this.getAllSymbolsArray()[index]; + //用户标记 + let aling = elm.location.range.start.line + 1; + let bling = loc['start'].line; + if(aling == bling ) + { + elm.funcRets = funcName; + } + } + } + } + //-------尾处理 + +//----------------------------------------------------------------------------- +//-- 遍历AST主方法 +//----------------------------------------------------------------------------- + //base处理,主要工作是拼接变量名 a.b.c.d,并返回base的isLocal (子元素的isLocal跟随base) + private baseProcess(baseNode) { + if (baseNode['type'] == 'MemberExpression') { + let ret = this.baseProcess(baseNode['base']); + if(!ret){ + // let ret = this.baseProcess(baseNode['base']); + return; + } + let str = ret.name; + let isLocal = ret.isLocal; + let retStr = str + baseNode['indexer'] + baseNode['identifier']['name']; + let retObj = { name: retStr, isLocal: isLocal, origion: baseNode['identifier']['name'] }; + return retObj; + } + else if (baseNode['type'] == 'Identifier') { + return { name: baseNode['name'], isLocal: baseNode['isLocal'] }; + } + else if (baseNode['type'] == 'StringLiteral') { + return { name: baseNode['value'], isLocal: false }; + } + else if (baseNode['type'] == 'NumericLiteral') { + return { name: baseNode['value'], isLocal: false }; + } + + else if (baseNode['type'] == 'IndexExpression') { + let ret = this.baseProcess(baseNode['base']); + let str = ret.name; + let isLocal = ret.isLocal; + let retObj; + if(baseNode['index']['type'] == "NumericLiteral"){ + let retStr = str + '[' + baseNode['index']['raw'] + ']'; + retObj = { name: retStr, isLocal: isLocal, origion: baseNode['index']['raw'] }; + } + + if(baseNode['index']['type'] == "Identifier"){ + let retStr = str + '[' + baseNode['index']['name'] + ']'; + retObj = { name: retStr, isLocal: isLocal, origion: baseNode['index']['name'] }; + } + + if(baseNode['index']['type'] == "MemberExpression"){ + let ret = this.baseProcess(baseNode['index']); + let retStr = str + '[' + ret.name + ']'; + retObj = { name: retStr, isLocal: isLocal, origion: ret.name }; + } + + if(baseNode['index']['type'] == "IndexExpression"){ + let ret = this.baseProcess(baseNode['index']); + let retStr = str + '[' + ret.name + ']'; + retObj = { name: retStr, isLocal: isLocal, origion: ret.name }; + } + + if(baseNode['index']['type'] == "StringLiteral"){ + let ret = this.baseProcess(baseNode['index']); + let retStr = str + '["' + ret.name + '"]'; + retObj = { name: retStr, isLocal: isLocal, origion: ret.name }; + } + + //index 中是一个表达式 + if(baseNode['index']['type'] == "BinaryExpression"){ + let retL = this.baseProcess(baseNode['index']['left']); + let retR = this.baseProcess(baseNode['index']['right']); + + let retStr = str + '[' + retL.name + baseNode['index'].operator + retR.name + ']'; + retObj = { name: retStr, isLocal: isLocal, origion: ret.name }; + } + + return retObj; + } + return { name: '', isLocal: false }; + } + + //base处理,function a.b.c.d. 当用户点击b的时候返回a.b + private MemberExpressionFind(baseNode) { + if(baseNode == null){ + Logger.log("baseNode == null"); + } + + if (baseNode['type'] == 'MemberExpression') { + //递归向内部取base + let ret = this.MemberExpressionFind(baseNode['base']); + // if(ret && ret.isFindout){ + // return ret; + // } + + if(ret == null || ret.isInStat == undefined){ + ret.isInStat = 0; + } + //判断identifier是否符合?一旦符合,后面的基层及全部加上 + let nodeLoc = Location.create(this.docInfo["docUri"], baseNode['identifier']['loc']); + let isIn = this.isInLocation(nodeLoc, this.searchPosition); + + if (isIn === true && ret.isInStat === 0) { + ret.isInStat = 1; + } + if (isIn === false && ret.isInStat === 1) { + //stop + return ret; + } + + let str = ret.name; + let isLocal = ret.isLocal; + let retStr = str + baseNode['indexer'] + baseNode['identifier']['name']; + return { name: retStr, isLocal: isLocal, isInStat: ret.isInStat }; + } + else if (baseNode['type'] == 'IndexExpression') { + //getindex + let ret = this.MemberExpressionFind(baseNode['base']); + // if(ret && ret.isFindout){ + // return ret; + // } + + if(ret == null || ret.isInStat == undefined){ + ret.isInStat = 0; + } + //判断identifier是否符合?一旦符合,后面的基层及全部加上 + let nodeLoc = Location.create(this.docInfo["docUri"], baseNode['index']['loc']); + let isIn = this.isInLocation(nodeLoc, this.searchPosition); + + if (isIn === true && ret.isInStat === 0) { + ret.isInStat = 1; + } + if (isIn === false && ret.isInStat === 1) { + //stop + return ret; + } + + let str = ret.name; + let isLocal = ret.isLocal; + let retStr; + + if(baseNode['index']['value']){ + retStr = str + '.' + baseNode['index']['value']; + } + + if(baseNode['index']['name']){ + retStr = this.MemberExpressionFind(baseNode['index']).name; + } + + return { name: retStr, isLocal: isLocal, isInStat: ret.isInStat }; + } + else if (baseNode['type'] == 'CallExpression') { + this.processCallExpression(baseNode, travelMode.GET_DEFINE , null, "call EXp"); + if (this.posSearchRet && this.posSearchRet.isFindout) { + // return retSymbol; + return {name: this.posSearchRet.retSymbol.name, isLocal:this.posSearchRet.retSymbol.isLocal, isInStat:1} + } + else{ + return { name: '', isLocal: true, isInStat: 0 }; + } + } + else if (baseNode['type'] == 'StringCallExpression') { + this.processStringCallExpression(baseNode, travelMode.GET_DEFINE , null, "call EXp"); + if (this.posSearchRet && this.posSearchRet.isFindout) { + // return retSymbol; + return {name: this.posSearchRet.retSymbol.name, isLocal:this.posSearchRet.retSymbol.isLocal, isInStat:1} + } + else{ + return { name: '', isLocal: true, isInStat: 0 }; + } + } + else if (baseNode['type'] == 'Identifier') { + //base的基层级 + let nodeLoc = Location.create(this.docInfo["docUri"], baseNode['loc']); + + let isIn = this.isInLocation(nodeLoc, this.searchPosition); + if (isIn === true) { + return { name: baseNode['name'], isLocal: baseNode['isLocal'], isInStat: 1 }; + } + else { + return { name: baseNode['name'], isLocal: baseNode['isLocal'], isInStat: 0 }; + } + } + } + + //处理chunk, 通常是遍历AST的第一步 + private processChunk(node, type, deepLayer, prefix){ + if (type === travelMode.BUILD) { + this.processComments(node['comments']); //整理记录comment(---@type) + // 把当前文件压入chunk + let newChunk = new Tools.chunkClass(this.docInfo["docPath"] , this.docInfo.docAST.loc); + this.pushToChunkList(this.docInfo["docPath"], newChunk); + // 压入deep layer + deepLayer.push( newChunk ); //记录chunk名(文件名和起始结束行) + this.traversalAST(node["body"], type, deepLayer, prefix, true); // 遍历body + } + + if (type === travelMode.GET_DEFINE){ + let newChunk = new Tools.chunkClass(this.docInfo["docPath"] , this.docInfo.docAST.loc); + deepLayer.push( newChunk ); //记录chunk名(文件名和起始结束行) + this.traversalAST(node["body"], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + if (type === travelMode.FIND_REFS){ + this.traversalAST(node["body"], type, deepLayer, prefix); + } + } + + // 处理function + private processFunction(node, type, deepLayer: Array, prefix?: string) { + let searchRes = false; //整体检查位置是否在 function 中 + let paraRecoder = new Array(); + // GET_DEFINE 先判断搜索位置是否在 函数location中 + if (type === travelMode.GET_DEFINE) { + let nodeLoc = Location.create(this.docInfo["docUri"], node["loc"]); + searchRes = this.isInLocation(nodeLoc, this.searchPosition); + // 用户点搜索的位置不在本function范围内, 清空数据,返回 + if(searchRes == false) { + this.posSearchRet = new Tools.searchRet(); + } + } + + // 1. 记录函数参数 + let searchHitPara = false; + let searchHitParaIdx = 0; + let paramArray = new Array(); + for (let idx = 0; idx < node["parameters"].length; idx++) { + let paraNode = node["parameters"][idx]; + if(paraNode.type == 'VarargLiteral'){ + //可变参数 + paramArray.push(paraNode['value']); + }else{ + paramArray.push(paraNode['name']); + } + + //搜索模式,且之前未命中 + if (type === travelMode.GET_DEFINE && searchRes === true && searchHitPara === false) { + let nodeLoc1 = Location.create(this.docInfo["docUri"], node["parameters"][idx]["loc"]); + searchHitPara = this.isInLocation(nodeLoc1, this.searchPosition); + if(searchHitPara === true){ + searchHitParaIdx = idx; + continue; + } + } + + if (type === travelMode.BUILD) { + let loc = paraNode["loc"]; + let name; + if(paraNode.type == 'VarargLiteral'){ + name = paraNode.value; + }else{ + name = paraNode["name"]; + } + let isLocal = true; //参数全部是local + let loct = Location.create(this.docInfo["docUri"], Range.create(Position.create(loc["start"]["line"] - 1, loc["start"]["column"]), Position.create(loc["end"]["line"] - 1, loc["end"]["column"]))); + let smbInfo = this.createSymbolInfo(name, name, name, SymbolKind.Variable, loct, isLocal, prefix, deepLayer.concat()); + paraRecoder.push(smbInfo); + } + } + let paramString = "(" + paramArray.join(", ") + ")"; + + // 2. 处理函数名,BUILD时记录 chunk 。 并把信息压入 deepLayer + let newChunk; // 临时记录保存在数据结构中的chunk + let functionName; //函数名(name) + let functionSearchName = "NONAME"; //函数searchName + // 有名函数 普通函数的情况 function a() + if (node["identifier"] && node["identifier"]['type'] == 'Identifier') { + let loc = node["identifier"]["loc"]; + functionSearchName = node["identifier"]["name"]; + functionName = "function " + functionSearchName + paramString; + if (type === travelMode.GET_DEFINE && searchRes === true) { + let nodeLoc1 = Location.create(this.docInfo["docUri"], loc); + let res1 = this.isInLocation(nodeLoc1, this.searchPosition); + if (res1 === true){ + this.posSearchRet = this.createRetSymbol(node["identifier"].name, node["identifier"].isLocal); + return; + } + } + if (type === travelMode.FIND_REFS) { + if (functionSearchName == this.searchInfo.originalName){ + let loc = node["identifier"]["loc"]; + let nodeLoc1 = Location.create(this.docInfo["docUri"], loc); + this.refsLink.push(nodeLoc1); + } + } + if (type === travelMode.BUILD) { + let loct = Location.create(this.docInfo["docUri"], Range.create(Position.create(loc["start"]["line"] - 1, loc["start"]["column"]), Position.create(loc["end"]["line"] - 1, loc["end"]["column"]))); + let pushObj = this.createSymbolInfo(functionSearchName, functionSearchName, functionSearchName, SymbolKind.Function, loct, node["identifier"]["isLocal"], prefix, deepLayer.concat(), paramArray); + newChunk = new Tools.chunkClass(functionSearchName, node.loc); + this.pushToChunkList(newChunk.chunkName, newChunk); + pushObj.chunk = newChunk; + this.pushToAutoList(pushObj); + + } + //有名函数 成员函数的情况 function a.b() + } else if (node["identifier"] && node["identifier"]['type'] == 'MemberExpression') { + let baseInfo = this.baseProcess(node["identifier"]); + functionSearchName = baseInfo.name; + functionName = 'function ' + functionSearchName + paramString; + if (type === travelMode.GET_DEFINE && searchRes === true) { + let bname = this.MemberExpressionFind(node["identifier"]); + if (bname.isInStat && bname.isInStat > 0) { + this.posSearchRet = this.createRetSymbol(bname.name, bname.isLocal); + return; + } + } + + if (type === travelMode.FIND_REFS) { + if (functionSearchName == this.searchInfo.originalName){ + let loc = node["identifier"]["loc"]; + let nodeLoc1 = Location.create(this.docInfo["docUri"], loc); + this.refsLink.push(nodeLoc1); + } + } + + if (type === travelMode.BUILD) { + let bname = this.baseProcess(node["identifier"]); + let originalName = bname.origion; + let loc = node['identifier']['loc']; + let rg = Location.create(this.docInfo["docUri"], Range.create(Position.create(loc["start"]["line"] - 1, loc["start"]["column"]), Position.create(loc["end"]["line"] - 1, loc["end"]["column"]))); + let symbInfo = this.createSymbolInfo(functionName, functionSearchName, originalName, SymbolKind.Function, rg , bname.isLocal, prefix, deepLayer.concat(), paramArray); + newChunk = new Tools.chunkClass(functionSearchName, node.loc); + this.pushToChunkList(newChunk.chunkName, newChunk); + symbInfo.chunk = newChunk; + this.pushToAutoList(symbInfo); + + //a:b , 隐式的self + let sepArr = bname.name.split(':'); + if(sepArr.length > 1){ + let tagname = sepArr[0]; + let funcself = "self"; + let isLocal = true; //参数全部是local + // let rg = Location.create(this.docInfo["docUri"], Range.create(Position.create(-1, -1), Position.create(-1, -1))); + let posChunk = new Tools.chunkClass(functionSearchName, node.loc); + deepLayer.push(posChunk); + let selfInfo = this.createSymbolInfo(funcself, funcself, funcself, SymbolKind.Variable, rg, isLocal, prefix, deepLayer.concat(), null, tagname, Tools.TagReason.Equal); + this.pushToAutoList(selfInfo); + deepLayer.pop(); + + } + } + } + + // 生成chunk信息放入layer + let posChunk = new Tools.chunkClass(functionSearchName, node.loc); + deepLayer.push(posChunk); + + if (type === travelMode.BUILD ){ + //BUILD 向符号列表中写入para信息 + for(let idx = 0 , len = paraRecoder.length;idx < len ; idx++ ){ + let parainfo = paraRecoder.pop(); + parainfo.containerName = functionSearchName; + parainfo.containerList = deepLayer.concat(); + this.pushToAllList(parainfo); + } + } + + // 3. 搜索命中para, 拼完路径后返回 + if (type === travelMode.GET_DEFINE ){ + if (searchHitPara === true) { + this.posSearchRet = this.createRetSymbol(node["parameters"][searchHitParaIdx].name, node["parameters"][searchHitParaIdx].isLocal); + return; + } + } + + //TODO: 暂不支持函数嵌套声明, 可以改为array保存返回值 + this.funcReturnRecoder = null; //准备记录函数返回 + //匿名函数不再加入符号,仅分析内部成员 + this.traversalAST(node["body"], type, deepLayer, functionName); + + // 4. 搜索命中para, 拼完路径后返回 + if (type === travelMode.GET_DEFINE ){ + if (this.posSearchRet && this.posSearchRet.isFindout) { + return; + } + } + + if (type === travelMode.BUILD ){ + //把返回值放入function chunk + if(this.funcReturnRecoder){ + if(newChunk){ + newChunk.returnSymbol = this.funcReturnRecoder; + }else{ + // 无名函数,不会创建newChunk,则不记录返回值 + } + } + } + deepLayer.pop(); + } + + //处理局部变量 + private LocalStatement(node, type, deepLayer, prefix?: string) { + let searchRes = false; + let baseInfo: Tools.baseInfo; + if (type === travelMode.GET_DEFINE) { + //检查变量是否在loc中 + searchRes = this.isInLocation(Location.create(this.docInfo["docUri"], node["loc"]), this.searchPosition); + } + + for (let idx = 0, len = node["variables"].length; idx < len; idx++) { + if (type === travelMode.BUILD) { + baseInfo = this.buildLvalueSymbals(node["variables"][idx], type, deepLayer, prefix); + } + + if (type === travelMode.GET_DEFINE) { + this.searchLvalueSymbals(node["variables"][idx], type, deepLayer, prefix, searchRes); + if (this.posSearchRet && this.posSearchRet.isFindout) return ; + baseInfo = this.posSearchRet.baseinfo; + } + + if (type === travelMode.FIND_REFS) { + this.searchLvalueSymbals(node["variables"][idx], type, deepLayer, prefix, searchRes); + } + } + + //右值 + for (let idx = 0, len = node['init'].length; idx < len; idx++) { + if (type === travelMode.BUILD) { + this.buildRvalueSymbals(node['init'][idx], type, deepLayer, prefix, baseInfo); + } + + if (type === travelMode.GET_DEFINE) { + this.searchRvalueSymbals(node['init'][idx], type, deepLayer, prefix, baseInfo, searchRes); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + if (type === travelMode.FIND_REFS) { + this.searchRvalueSymbals(node['init'][idx], type, deepLayer, prefix, baseInfo, searchRes); + } + } + } + + //检查是否metatable,如果是,记录tag & reason + private processCallExpisSetMetatable(node, type, arg){ + if(type == travelMode.BUILD){ + let len = arg.length; + if(node["base"].type == 'Identifier' && node["base"].name == 'setmetatable' && node["base"].isLocal === false && len == 2) { + //base + let oldName = this.baseProcess(arg[0]); + let newName = this.baseProcess(arg[1]); + + let info = { + reason: Tools.TagReason.MetaTable, + newType: newName.name, + oldType: oldName.name, + location : null + } + this.pushToCommentList(info); + } + } + } + + //检查是否是function call,如果是,记录 tag & reason + private processCallExpisFunctionCall(node, type, arg){ + if(type == travelMode.BUILD){ + // let len = arg.length; + //functionName + let functionName = this.baseProcess(node['base']); + let info = { + functionName: functionName, + loc : node['loc'] + } + this.recordFuncCall(info); + } + } + + //检查是否require,如果是,记录require的文件名 + private processCallExpisRequire(node, type, arg){ + if(type == travelMode.BUILD){ + let len = arg.length; + if(node["base"].type == 'Identifier' && node["base"].name == 'require' && node["base"].isLocal === false && len == 1) { + //读取arg[0] + if(arg[0].type == 'StringLiteral' && arg[0].value){ + //记录 arg[0].value + let info: Tools.requireFileInfo = { reqName: arg[0].value, loc: arg[0].loc }; + // this.docInfo["requireFile"].push(info); + this.docInfo.requires.push(info) + // 记录引用关系 + this.recordReference(this.docInfo["docUri"], arg[0].value); + } + } + } + } + + // + private processStringCallExpisRequire(node, type, arg){ + if(type == travelMode.BUILD){ + if(arg.type == 'StringLiteral' && arg.value){ + let info: Tools.requireFileInfo = { reqName: arg.value, loc: arg.loc }; + this.docInfo["requires"].push(info); + // 记录引用关系 + this.recordReference(this.docInfo["docUri"], arg.value); + } + } + } + + //调用表达式 + private processStringCallExpression(node, type, deepLayer, prefix?: string) { + if(type == travelMode.BUILD){ + this.processStringCallExpisRequire(node, type, node['argument']); + } + + if(type == travelMode.GET_DEFINE){ + let bname = this.MemberExpressionFind(node["base"]); + if (bname.isInStat && bname.isInStat > 0) { + this.posSearchRet = this.createRetSymbol(bname.name, bname.isLocal); + return; + } + } + //base + // if (type === travelMode.FIND_DEFINE) { + // let bname = this.MemberExpressionFind(node["base"]); + // if (bname.isInStat > 0) { + // // let retSearch : Tools.searchSymbolRet = { + // // name: bname.name, + // // containerURI: this.docInfo["docUri"], + // // isLocal:bname.isLocal + // // } + // this.posSearchRet = this.createRetSymbol(bname.name, bname.isLocal); + // } + // } + + } + + //调用表达式 + private processCallExpression(node, type, deepLayer, prefix?: string) { + let varArray = Array.prototype.slice.call(node['arguments']); + let len = varArray.length; + this.processCallExpisRequire(node, type, varArray); + this.processCallExpisSetMetatable(node, type, varArray); + this.processCallExpisFunctionCall(node, type, varArray); + //argument + for (let idx = 0; idx < len; idx++) { + this.traversalAST(node['arguments'][idx], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + //base + if (type === travelMode.GET_DEFINE) { + let bname = this.MemberExpressionFind(node["base"]); + if (bname.isInStat && bname.isInStat > 0) { + this.posSearchRet = this.createRetSymbol(bname.name, bname.isLocal); + return; + } + } + + //base + if (type === travelMode.FIND_REFS) { + let bname = this.MemberExpressionFind(node["base"]); + if (bname == this.searchInfo.name){ + let loc = node["identifier"]["loc"]; + let nodeLoc1 = Location.create(this.docInfo["docUri"], loc); + this.refsLink.push(nodeLoc1); + } + } + } + + //调用语句 + private processCallStatement(node, type, deepLayer, prefix?: string) { + this.traversalAST(node['expression'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + //saved[value] 可以用作定义, 也可以读取值 + private processIndexExpression(node, type, deepLayer, prefix?: string){ + //search + if (type === travelMode.GET_DEFINE) { + let loc = node['index']['loc']; + //先判断index是否命中。如果命中,也要search base. 另外搜索当前元素要把base带上 + let nodeLoc1 = Location.create(this.docInfo["docUri"], loc); + let retBool = this.isInLocation(nodeLoc1, this.searchPosition); + if (retBool === true) { + //取得base + if(node['base'].type == 'MemberExpression'){ + this.posSearchRet = this.processMemberExpression(node['base'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + + }else if(node['base'].type == 'Identifier'){ + this.processIdentifier(node['base'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + this.processIdentifier(node['index'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout == true) return; + }else if(node['base'].type == 'IndexExpression'){ + // c[ a ][ b ] = 9 查找b的定义 + this.processIdentifier(node['index'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout == true) return; + } + } + //递归search base index 不需要递归 + let bname = this.MemberExpressionFind(node['base']); + if (bname.isInStat && bname.isInStat > 0) { + this.posSearchRet = this.createRetSymbol(bname.name, bname.isLocal); + return; + } + //没找到 + return this.createRetBase(bname.name, bname.isLocal, node['index']['value']); + } + } + + //变量赋值, 这里要区分全局变量的赋值和定义 + private processAssignment(node, type, deepLayer, prefix?: string) { + let searchRes = false; + let baseInfo: Tools.baseInfo; + + if (type === travelMode.GET_DEFINE) { + //检查变量是否在loc中 + let nodeLoc = Location.create(this.docInfo["docUri"], node["loc"]); + searchRes = this.isInLocation(nodeLoc, this.searchPosition); + } + + //遍历variables + if (Array.isArray(node['variables']) === true) { + let varArray = Array.prototype.slice.call(node['variables']); + let len = varArray.length; + for (let idx = 0; idx < len; idx++) { + if (type === travelMode.BUILD) { + baseInfo = this.buildLvalueSymbals(node["variables"][idx], type, deepLayer, prefix, null, 1); + } + + if (type === travelMode.GET_DEFINE) { + this.searchLvalueSymbals(node["variables"][idx], type, deepLayer, prefix, searchRes); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + if(this.posSearchRet.baseinfo) baseInfo = this.posSearchRet.baseinfo; + } + + if (type === travelMode.FIND_REFS) { + this.searchLvalueSymbals(node["variables"][idx], type, deepLayer, prefix, searchRes); + } + } + } + + //遍历init (右值) + if (Array.isArray(node['init']) === true) { + let varArray = Array.prototype.slice.call(node['init']); + let len = varArray.length; + for (let idx = 0; idx < len; idx++) { + if (type === travelMode.BUILD) { + //a.b.c = {x = 9} base是 a.b.c + this.buildRvalueSymbals(node['init'][idx], type, deepLayer, prefix, baseInfo); + } + + if (type === travelMode.GET_DEFINE) { + this.searchRvalueSymbals(node['init'][idx], type, deepLayer, prefix, baseInfo, searchRes); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + if (type === travelMode.FIND_REFS) { + this.searchRvalueSymbals(node['init'][idx], type, deepLayer, prefix, baseInfo, searchRes); + } + } + } + } + + //遇到table构建表达式 + private processTableConstructorExpression(node: Object, type, deepLayer: Array, prefix?: string, baseInfo?) { + //遍历fields + for (let idx = 0, len = node['fields'].length; idx < len; idx++) { + let idxNode = node['fields'][idx]; + if (type === travelMode.BUILD) { + if (idxNode['type'] === 'TableKeyString') { + //L + let retInfo = this.buildLvalueSymbals(idxNode['key'], type, deepLayer, prefix, baseInfo); + //R + this.buildRvalueSymbals(idxNode["value"], type, deepLayer, prefix, retInfo); + } + if (idxNode['type'] === 'TableKey') { + if(idxNode['key']['type'] === "StringLiteral"){ + // L + let orgname = idxNode['key']['value']; + let displayName = baseInfo.name + '.' + orgname; + let rg = Location.create(this.docInfo["docUri"], Range.create(Position.create(idxNode['loc']["start"]["line"] - 1, idxNode['loc']["start"]["column"]), Position.create(idxNode['loc']["end"]["line"] - 1, idxNode['loc']["end"]["column"]))); + let symb = this.createSymbolInfo( displayName, displayName, orgname, SymbolKind.Variable, rg, baseInfo.isLocal, prefix, deepLayer.concat()); + this.pushToAutoList(symb); + let retInfo = {name: displayName, isLocal: baseInfo.isLocal}; + // R + this.buildRvalueSymbals(idxNode["value"], type, deepLayer, prefix, retInfo); + } + + if(idxNode['key']['type'] === "NumericLiteral"){ + // L + let orgname = idxNode['key']['raw']; //TODO 【orgname】 + let displayName = baseInfo.name + '[' + orgname + ']'; + let rg = Location.create(this.docInfo["docUri"], Range.create(Position.create(idxNode['loc']["start"]["line"] - 1, idxNode['loc']["start"]["column"]), Position.create(idxNode['loc']["end"]["line"] - 1, idxNode['loc']["end"]["column"]))); + let symb = this.createSymbolInfo( displayName, displayName, orgname, SymbolKind.Variable, rg, baseInfo.isLocal, prefix, deepLayer.concat()); + this.pushToAutoList(symb); + let retInfo = {name: displayName, isLocal: baseInfo.isLocal}; + // R + this.buildRvalueSymbals(idxNode["value"], type, deepLayer, prefix, retInfo); + } + } + } + if (type === travelMode.GET_DEFINE) { + if (idxNode['type'] === 'TableKeyString') { + // string key 的table构造式 + let recBaseName = baseInfo.name; + //L + this.searchLvalueSymbals(idxNode['key'], type, deepLayer, prefix, true, baseInfo); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + //R + this.searchRvalueSymbals(idxNode["value"], type, deepLayer, prefix, this.posSearchRet.baseinfo, true); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + // 记录Base并还原,避免反复处理对Base造成干扰 + //用例 RadioErrorCode ={ Success = 0, NotEnoughScore = 1000} + baseInfo.name = recBaseName; + } + + if (idxNode['type'] === 'TableKey') { + // [] 形式key的table构造式 + if(idxNode['key']['type'] === "NumericLiteral"){ + let recBaseName = baseInfo.name; + baseInfo.name = baseInfo.name + '[' + idxNode['key']['value'] + ']'; + this.searchRvalueSymbals(idxNode["value"], type, deepLayer, prefix, baseInfo, true); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + + baseInfo.name = recBaseName; + } + if(idxNode['key']['type'] === "StringLiteral"){ + let recBaseName = baseInfo.name; + baseInfo.name = baseInfo.name + '.' + idxNode['key']['value']; + this.searchRvalueSymbals(idxNode["value"], type, deepLayer, prefix, baseInfo, true); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + baseInfo.name = recBaseName; + } + } + + if (idxNode['type'] === 'TableValue') { + if (idxNode['value']['type'] === 'TableConstructorExpression') { + let recBaseName = baseInfo.name; + this.processTableConstructorExpression(idxNode['value'], type, deepLayer, prefix, baseInfo); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + baseInfo.name = recBaseName; + } + } + + } + + if (type === travelMode.FIND_REFS) { + if (idxNode['type'] === 'TableKeyString') { + //L + this.searchLvalueSymbals(idxNode['key'], type, deepLayer, prefix, true, baseInfo); + //R + this.searchRvalueSymbals(idxNode["value"], type, deepLayer, prefix, this.posSearchRet.baseinfo, true); + } + } + } + } + + //while + private processWhileStatement(node: Object, type, deepLayer: Array, prefix?: string) { + this.traversalAST(node['body'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + private processRepeatStatement(node: Object, type, deepLayer: Array, prefix?: string) { + this.traversalAST(node['body'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + private processMemberExpression(node, type, deepLayer, prefix: string, baseInfo?, searchRes?) { + if (type === travelMode.GET_DEFINE) { + if (node['type'] === 'MemberExpression') { + let loc = node['identifier']['loc']; + //search 不仅要search当前元素,还要单独search base. 另外搜多当前元素要把base带上 + let nodeLoc1 = Location.create(this.docInfo["docUri"], loc); + let retBool = this.isInLocation(nodeLoc1, this.searchPosition); + if (retBool === true) { + //findout + let bname = this.baseProcess(node); + this.posSearchRet = this.createRetSymbol(bname.name, bname.isLocal); + } + //递归search base + let bname = this.MemberExpressionFind(node['base']); + if (bname.isInStat && bname.isInStat > 0) { + this.posSearchRet = this.createRetSymbol(bname.name, bname.isLocal); + } + //没找到 + return this.createRetBase(bname.name, bname.isLocal, node['identifier']['name']); + } + } + } + // + private processIdentifier(node, type, deepLayer, prefix: string, baseInfo?, searchRes?) { + if (type === travelMode.GET_DEFINE) { + if (node['type'] === 'Identifier') { + if (baseInfo == undefined || baseInfo.name == undefined || baseInfo.name === '') { + baseInfo = { name: node["name"], isLocal: node['isLocal'] }; + } else { + if(baseInfo.identiferStr){ + baseInfo.name = baseInfo.name + '.' + baseInfo.identiferStr + '.' + node["name"]; + }else{ + baseInfo.name = baseInfo.name + '.' + node["name"]; + } + } + //搜索 + let nodeLoc1 = Location.create(this.docInfo["docUri"], node["loc"]); + if ( this.isInLocation(nodeLoc1, this.searchPosition) ) { + this.posSearchRet = this.createRetSymbol(baseInfo.name, baseInfo.isLocal); + }else{ + this.posSearchRet = this.createRetBase(baseInfo.name, baseInfo.isLocal); + } + } + + if (node['type'] === 'BinaryExpression') { + // c[ a + b ] = 9 , 搜索a或者b的定义 + } + } + + if (type === travelMode.FIND_REFS) { + if (node['type'] === 'Identifier') { + if (baseInfo == undefined || baseInfo.name == undefined || baseInfo.name === '') { + baseInfo = { name: node["name"], isLocal: node['isLocal'] }; + } else { + if(baseInfo.identiferStr){ + baseInfo.name = baseInfo.name + '.' + baseInfo.identiferStr + '.' + node["name"]; + }else{ + baseInfo.name = baseInfo.name + '.' + node["name"]; + } + } + + if(baseInfo.name == this.searchInfo.searchName){ + let nodeLoc1 = Location.create(this.docInfo["docUri"], node["loc"]); + this.refsLink.push(nodeLoc1); + } + } + } + } + + //二进制表达式,比如 i == 10000 + private processBinaryExpression(node: Object, type, deepLayer: Array, prefix?: string) { + //search + if (type === travelMode.GET_DEFINE) { + this.traversalAST(node['left'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + + this.traversalAST(node['right'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + //search + if (type === travelMode.FIND_REFS) { + this.traversalAST(node['left'], type, deepLayer, prefix); + this.traversalAST(node['right'], type, deepLayer, prefix); + } + } + + private processUnaryExpression(node: Object, type, deepLayer: Array, prefix?: string) { + if (type === travelMode.GET_DEFINE) { + let argumentType = node['argument']['type']; + switch (argumentType) { + case 'Identifier': + this.processIdentifier(node['argument'], type, deepLayer, prefix); + break; + case 'LogicalExpression': + this.searchRvalueSymbals(node['argument']['left'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) break; + this.searchRvalueSymbals(node['argument']['right'], type, deepLayer, prefix); + break; + case 'IndexExpression': + this.processIndexExpression(node['argument'], type, deepLayer, prefix); + break; + case 'BinaryExpression': + this.processBinaryExpression(node['argument'], type, deepLayer, prefix); + break; + case 'CallExpression': + this.processCallExpression(node['argument'], type, deepLayer, prefix); + break; + case 'MemberExpression': + this.processMemberExpression(node['argument'], type, deepLayer, prefix); + break; + case 'UnaryExpression': + this.processUnaryExpression(node['argument'], type, deepLayer, prefix); + break; + } + if (this.posSearchRet && this.posSearchRet.isFindout) { + return; + } + } + } + + // if中应该不会存在定义(除了body) + private processIfStatement(ASTNode: Object, type, deepLayer: Array, prefix?: string) { + let node = ASTNode['clauses']; + if (Array.isArray(node) === true) { + let ASTArray = Array.prototype.slice.call(node); + for (let idx = 0, len = ASTArray.length; idx < len; idx++) { + if (ASTArray[idx].type == 'IfClause' || ASTArray[idx].type == 'ElseifClause') { + //if ty1 then + if(ASTArray[idx]['condition']['type'] === 'Identifier'){ + this.processIdentifier(ASTArray[idx]['condition'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) { + return; + } + } + + if(ASTArray[idx]['condition']['type'] === 'LogicalExpression'){ + let node = ASTArray[idx]['condition']; + this.searchRvalueSymbals(node['left'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return ; + this.searchRvalueSymbals(node['right'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + if(ASTArray[idx]['condition']['type'] === 'IndexExpression'){ + this.processIndexExpression(ASTArray[idx]['condition'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + //判断二进制表达式有没有符号 + if (ASTArray[idx]['condition']['type'] === 'BinaryExpression') { + this.processBinaryExpression(ASTArray[idx]['condition'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) { + return; + } + } + + if(ASTArray[idx]['condition']['type'] === 'CallExpression'){ + this.processCallExpression(ASTArray[idx]['condition'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) { + return; + } + } + + if(ASTArray[idx]['condition']['type'] === 'MemberExpression'){ + this.processMemberExpression(ASTArray[idx]['condition'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) { + return; + } + } + + if(ASTArray[idx]['condition']['type'] === 'UnaryExpression'){ + this.processUnaryExpression(ASTArray[idx]['condition'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) { + return; + } + } + + //if body + this.traversalAST(ASTArray[idx].body, type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) { + return; + } + } + + if (ASTArray[idx].type == 'ElseClause') { + //if body + this.traversalAST(ASTArray[idx].body, type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) { + return; + } + } + } + } + } + + // 处理return结构 + private processReturnStatement(ASTNode: Object, type, deepLayer: Array, prefix?: string, isBody?:boolean) { + if (type === travelMode.GET_DEFINE) { + let node = ASTNode; + let varArray = Array.prototype.slice.call(node['arguments']); + for (let idx = 0; idx < varArray.length; idx++) { + this.traversalAST(varArray[idx], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + } + + if(type === travelMode.BUILD){ + if(isBody == true){ + //file retSymbol + if(ASTNode['arguments'].length == 1){ + if(ASTNode['arguments'][0]['type'] === 'Identifier' ){ + let name = ASTNode['arguments'][0]['name']; + //记录文件的返回值,因为文件路径不可能重名,直接查找并放置 + this.docInfo.defineSymbols.chunks[this.docInfo.docPath].returnSymbol = name; + } + } + }else{ + //function retSymbol + if(ASTNode['arguments'].length == 1){ + if(ASTNode['arguments'][0]['type'] === 'Identifier' ){ + let name = ASTNode['arguments'][0]['name']; + this.funcReturnRecoder = name; + } + } + } + } + } + + //for k, v in pairs / ipairs + private processForGenericStatement(node: Object, type, deepLayer: Array, prefix?: string) { + // Logger.log('processForGenericStatement'); + //body + this.traversalAST(node['body'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + // for i = 9 ; i + + + private processForNumericStatement(node: Object, type, deepLayer: Array, prefix?: string) { + // Logger.log('processForNumericStatement'); + //body + this.traversalAST(node['body'], type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + //构建左值. 主要是定义. 返回值是base信息 + //source 来源,填写1是来自于assign + private buildLvalueSymbals(node: Object, type, deepLayer: Array, prefix?: string, baseInfo?, isAssign? : number) { + let baseName = ''; + let baseLocal = true; + let displayName = ''; + let searchName = ''; + if (node['type'] === 'Identifier') { + if (baseInfo == undefined) { + baseName = node["name"]; + baseLocal = node["isLocal"]; + displayName = node["name"]; + } else { + baseLocal = baseInfo.isLocal; //设置花括号内的成员函数global / local 随base + baseName = baseInfo.name + '.' + node["name"]; + displayName = baseName; + } + + //区分 a = 9 情况下到底是赋值还是定义 + searchName = baseName; + let isPush = true; //判断是否压入符号列表,因为全局变量赋值也是定义,所以要在这里做一下判断 + if(isAssign == 1){ + //如果来源于赋值操作,而且baseLocal == true , 那么一定是赋值 (local 的assign一定是赋值) + if( baseLocal ){ + isPush = false; + }else{ + // 全局表中是否存在 + if(this.getGlobalSymbolsDic()[searchName] != undefined){ + isPush = false; + } + } + } + + if(isPush === true){ + let loct = Location.create(this.docInfo["docUri"], Range.create(Position.create(node['loc']["start"]["line"] - 1, node['loc']["start"]["column"]), Position.create(node['loc']["end"]["line"] - 1, node['loc']["end"]["column"]))); + let symb = this.createSymbolInfo(displayName, baseName, node["name"], SymbolKind.Variable, loct, baseLocal, prefix, deepLayer.concat()); + this.pushToAutoList(symb); + } + return { name: baseName, isLocal: baseLocal }; + } + + if ('MemberExpression' === node['type']) { + let bname = this.baseProcess(node); + let rg = Location.create(this.docInfo["docUri"], Range.create(Position.create(node['loc']["start"]["line"] - 1, node['loc']["start"]["column"]), Position.create(node['loc']["end"]["line"] - 1, node['loc']["end"]["column"]))); + baseName = bname.name; + baseLocal = bname.isLocal; + //检查符号是否已经存在,如果已存在就不是定义 + if (this.checkIsSymbolExist(bname.name) === false) { + let symb = this.createSymbolInfo( bname.name, bname.name, node['identifier']['name'], SymbolKind.Variable, rg, bname.isLocal, prefix, deepLayer.concat()); + this.pushToAutoList(symb); + + } + return { name: baseName, isLocal: baseLocal }; + } + else if('IndexExpression' === node['type']) { + // saved[valu] = 9; ==> saved.valu + //valu是一个变量,目前并不知道其值,所以无法做build操作,只能查找。 + let baseInfo = this.baseProcess( node['base'] ); + if( node['index'].type == 'StringLiteral' ){ + let rg = Location.create(this.docInfo["docUri"], Range.create(Position.create(node['loc']["start"]["line"] - 1, node['loc']["start"]["column"]), Position.create(node['loc']["end"]["line"] - 1, node['loc']["end"]["column"]))); + let displayName = baseInfo.name + '.' + node['index'].value ; + if (this.checkIsSymbolExist(displayName) === false) { + let symb = this.createSymbolInfo( displayName, displayName, node['index'].value, SymbolKind.Variable, rg, baseInfo.isLocal, prefix, deepLayer.concat()); + this.pushToAutoList(symb); + } + } + return { name: baseInfo.name, isLocal: baseInfo.isLocal }; + } + } + + // 构建右值 | 处理 变量定义,赋值. + // local a = function() end + // a = { b = "c" } + private buildRvalueSymbals(node: Object, type: number, deepLayer: Array, prefix?: string, baseInfo?) { + if (node == undefined) return; + //构造table a = { b = "c" } + if (node['type'] === 'TableConstructorExpression') { + this.processTableConstructorExpression(node, type, deepLayer, prefix, baseInfo); + } + + // 在处理右值的时候,如果是Identifier或者MemberExpression. 标记tag + if(node['type'] === 'Identifier'){ + let info = { + reason: Tools.TagReason.Equal, + newType: node['name'], + location : node['loc'], + name : baseInfo.name + } + this.pushToCommentList(info); + } + + if(node['type'] === 'MemberExpression'){ + let bname = this.baseProcess(node); + let info = { + reason: Tools.TagReason.Equal, + newType: bname.name, + location : node['loc'], + name:baseInfo.name + } + this.pushToCommentList(info); + } + + this.traversalAST(node, type, deepLayer, prefix); + // if (this.posSearchRet && this.posSearchRet.isFindout) { + // return; + // } + } + + //搜索左值 + private searchLvalueSymbals(node: Object, type, deepLayer: Array, prefix?: string, searchRes?, baseInfo?) { + let localBaseInfo = baseInfo; + if (node['type'] === 'Identifier') { + this.processIdentifier(node, type, deepLayer, prefix, localBaseInfo, searchRes); + //找到,返回 + if (this.posSearchRet && this.posSearchRet.isFindout) return; + //未找到,继承baseInfo + if (this.posSearchRet && this.posSearchRet.isFindout === false) localBaseInfo = this.posSearchRet.baseinfo; + } + + if (node['type'] === 'MemberExpression') { + this.processMemberExpression(node, type, deepLayer, prefix, localBaseInfo, searchRes); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + //未找到,继承baseInfo + if (this.posSearchRet && this.posSearchRet.isFindout === false) localBaseInfo = this.posSearchRet.baseinfo; + } + + if(node['type'] === 'CallExpression'){ + this.processCallExpression(node, type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + //未找到,继承baseInfo + if (this.posSearchRet && this.posSearchRet.isFindout === false) localBaseInfo = this.posSearchRet.baseinfo; + + } + if(node['type'] === 'BinaryExpression'){ + this.processBinaryExpression(node, type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + if (this.posSearchRet && this.posSearchRet.isFindout === false) localBaseInfo = this.posSearchRet.baseinfo; + } + + if(node['type'] === 'IndexExpression'){ + this.processIndexExpression(node, type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + if (this.posSearchRet && this.posSearchRet.isFindout === false) localBaseInfo = this.posSearchRet.baseinfo; + } + + } + + //搜索右值 + private searchRvalueSymbals(node, type, deepLayer: Array, prefix?: string, baseInfo?, searchRes?) { + //右侧如果是Identifier, 不需要base + if (node['type'] === 'Identifier') { + this.processIdentifier(node, type, deepLayer, prefix, null, searchRes); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + if(node['type'] === 'MemberExpression'){ + this.processMemberExpression(node, type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) { + return; + } + } + + //右侧如果是{ } , 构造table ,需要base + if (node['type'] === 'TableConstructorExpression') { + this.processTableConstructorExpression(node, type, deepLayer, prefix, baseInfo); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + if (node.type === 'CallExpression') { + this.traversalAST(node, type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + //traversalAST + if (node.type === 'FunctionDeclaration') { + this.traversalAST(node, type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + // indent = indent or "" + if(node.type === 'LogicalExpression'){ + this.searchRvalueSymbals(node['left'], type, deepLayer, prefix, baseInfo, searchRes); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + this.searchRvalueSymbals(node['right'], type, deepLayer, prefix, baseInfo, searchRes); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + // cart = cart .. indent .. field + if(node.type === 'BinaryExpression'){ + this.searchRvalueSymbals(node['left'], type, deepLayer, prefix, baseInfo, searchRes); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + this.searchRvalueSymbals(node['right'], type, deepLayer, prefix, baseInfo, searchRes); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + //a = b['index'] + if(node['type'] === 'IndexExpression'){ + this.processIndexExpression(node, type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) return; + } + + // a = not b + if(node['type'] === 'UnaryExpression'){ + this.processUnaryExpression(node, type, deepLayer, prefix); + if (this.posSearchRet && this.posSearchRet.isFindout) { + return; + } + } + } + +} + + diff --git a/src/code/server/server.ts b/src/code/server/server.ts new file mode 100755 index 0000000..c09e709 --- /dev/null +++ b/src/code/server/server.ts @@ -0,0 +1,461 @@ +// Tencent is pleased to support the open source community by making LuaPanda available. +// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +// https://opensource.org/licenses/BSD-3-Clause +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//第三方npm引用 +import { + createConnection, + TextDocuments, + TextDocument, + // Diagnostic, + // DiagnosticSeverity, + ProposedFeatures, + InitializeParams, + DidChangeConfigurationNotification, + CompletionItem, + SymbolInformation, + TextDocumentPositionParams, + DocumentSymbolParams, + Definition, //定义跳转 + WorkspaceSymbolParams,//folder 符号 + DocumentFormattingParams, + TextEdit, + DocumentHighlight, + ColorInformation, + DocumentColorParams, + ColorPresentationParams, + ColorPresentation, + // SignatureHelp, + ReferenceParams, + DocumentSymbol +} from 'vscode-languageserver'; +let path = require('path'); /*nodejs自带的模块*/ +import * as fs from "fs"; +import * as Tools from "./codeTools"; +import { Logger } from './codeLogManager'; +import { CodeSymbol } from './codeSymbol'; +import { CodeDefinition } from './codeDefinition'; +import { CodeCompletion } from './codeCompletion'; +import { CodeEditor } from './codeEditor'; +import { CodeFormat } from './codeFormat'; +import { CodeLinting } from './codeLinting'; +import { CodeReference } from './codeReference'; +import { NativeCodeExportBase } from './codeExport/nativeCodeExportBase'; + +// Create a connection for the server. The connection uses Node's IPC as a transport. +// Also include all preview / proposed LSP features. +let connection = createConnection(ProposedFeatures.all); +let documents: TextDocuments = new TextDocuments(); +let hasConfigurationCapability: boolean = false; +let hasWorkspaceFolderCapability: boolean = false; +// let hasDiagnosticRelatedInformationCapability: boolean = false; +let analyzerTotalSwitch: boolean = true; +// The settings +export interface LuaAnalyzerSettings { + codeLinting: { + enable : boolean; + luacheckPath : string; + luaVersion : string; + checkWhileTyping : boolean; + checkAfterSave : boolean, + maxNumberOfProblems : number; + maxLineLength : number; + ignoreFolderRegularExpression: string, + ignoreErrorCode : string, + ignoreGlobal : string; + }; +} + +// The global settings, used when the `workspace/configuration` request is not supported by the client. +// Please note that this is not the case when using this server with the client provided in this example +// but could happen with other clients. +const defaultSettings: LuaAnalyzerSettings = { + codeLinting: { + enable : true, + luacheckPath : "", + luaVersion : "5.1", + checkWhileTyping : true, + checkAfterSave : true, + maxNumberOfProblems : 100, + maxLineLength : 120, + ignoreFolderRegularExpression: ".*/res/lua/\\w+\\.lua;.*vscode/LuaPanda/IntelliSenseRes/;", + ignoreErrorCode : "", + ignoreGlobal : "", + } +}; +let globalSettings: LuaAnalyzerSettings = defaultSettings; +// Cache the settings of all open documents +let documentSettings: Map> = new Map(); + +//----------------------------------------------------------------------------- +//-- connection 时序 +//----------------------------------------------------------------------------- +// 建立连接,初始化数据和设置能力项 +connection.onInitialize((initPara: InitializeParams) => { + let capabilities = initPara.capabilities; + Tools.setInitPara(initPara); + Tools.setToolsConnection(connection); + Logger.connection = connection; + // Does the client support the `workspace/configuration` request? + // If not, we will fall back using global settings + hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration); + hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders); + // hasDiagnosticRelatedInformationCapability = + // !!(capabilities.textDocument && + // capabilities.textDocument.publishDiagnostics && + // capabilities.textDocument.publishDiagnostics.relatedInformation); + + // 清空loadedExt, 初始化 name - uri 对应 cache + Tools.setVScodeExtensionPath(path.dirname(path.dirname(path.dirname(__dirname))) ); + Tools.initLoadedExt(); + + // 读取标记文件,如果关闭了标记,那么 + let snippetsPath = Tools.getVScodeExtensionPath() + "/res/snippets/snippets.json"; + let snipContent = fs.readFileSync(snippetsPath); + + // 发送rootFolder + setImmediate(()=>{ + connection.sendNotification("setRootFolders", Tools.getVSCodeOpenedFolders()); + }); + + if(snipContent.toString().trim() == ''){ + analyzerTotalSwitch = false; + setImmediate(()=>{ + connection.sendNotification("showProgress", "LuaPanda"); + }); + Logger.InfoLog("LuaAnalyzer closed!"); + return { + capabilities: {} + } + } + // 创建对应后缀的文件符号 + for (const folder of Tools.getVSCodeOpenedFolders()) { + CodeSymbol.createSymbolswithExt('lua', folder); + CodeSymbol.createSymbolswithExt('lua.bytes', folder); + } + // 异步执行,建立uri -> 完整路径对应表 + setTimeout(Tools.refresh_FileName_Uri_Cache, 0); + // 分析默认位置(扩展中)的lua文件 + let resLuaPath = Tools.getVScodeExtensionPath() + '/res/lua'; //安装插件后地址 + CodeSymbol.createLuaPreloadSymbols(resLuaPath);//更新lua预设符号文件 + NativeCodeExportBase.loadIntelliSenseRes();//更新用户导出符号文件 + Logger.InfoLog("LuaAnalyzer init success!"); + + return { + capabilities: { + //符号分析 + documentSymbolProvider: true, + workspaceSymbolProvider: true, + //定义分析 + definitionProvider: true, + //引用分析 + referencesProvider: false, + //代码格式化 + documentFormattingProvider: true, + documentRangeFormattingProvider: false, + //代码选中高亮 + documentHighlightProvider: false, + //文档同步 + textDocumentSync: documents.syncKind, + //自动补全 + completionProvider: { + triggerCharacters:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:".split(''), + resolveProvider: false + }, + //重命名 + renameProvider : false, + //函数签名提示 + // signatureHelpProvider:{ + // triggerCharacters: [ '(' ], + // }, + //代码上色 + colorProvider : false, + } + }; +}); + + +connection.onNotification("preAnalysisCpp", (message) =>{ + let msgObj = JSON.parse(message); + let anaPath = msgObj['path']; + NativeCodeExportBase.processNativeCodeDir(anaPath); +}); + +connection.onInitialized(() => { + if (hasConfigurationCapability) { + connection.client.register( + DidChangeConfigurationNotification.type, + undefined + ); + } + if (hasWorkspaceFolderCapability) { + connection.workspace.onDidChangeWorkspaceFolders(_event => { + // 在工程中增删文件夹的回调 + Logger.DebugLog('Workspace folder change event received.'); + if(_event.added.length > 0){ + Tools.addOpenedFolder(_event.added); + for (const folder of Tools.getVSCodeOpenedFolders()) { + CodeSymbol.refreshFolderSymbols(folder); + } + } + + if(_event.removed.length > 0){ + Tools.removeOpenedFolder(_event.removed); + } + // 刷新文件名-路径索引 + setTimeout(Tools.refresh_FileName_Uri_Cache, 0); + }); + } +}); + + +// 代码格式化 +connection.onDocumentFormatting( (handler: DocumentFormattingParams) : TextEdit[] =>{ + let uri = Tools.urlDecode(handler.textDocument.uri); + let retCode = CodeFormat.format(uri); + return retCode; +}); + + +connection.onDidChangeConfiguration(change => { + if (hasConfigurationCapability) { + // Reset all cached document settings + documentSettings.clear(); + } else { + globalSettings = ( + (change.settings.lua_analyzer || defaultSettings) + ); + } + // Revalidate all open text documents + documents.all().forEach(validateTextDocument); +}); + +// Only keep settings for open documents +documents.onDidClose(e => { + documentSettings.delete(e.document.uri); +}); + +// 代码自动补全 +connection.onCompletion( + (_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => { + let uri = Tools.urlDecode(_textDocumentPosition.textDocument.uri); + let pos = _textDocumentPosition.position; + try{ + return CodeCompletion.completionEntry(uri, pos); + } catch (error) { + Logger.ErrorLog("[Error] onCompletion " + error.stack); + } + } +); + +// 查找符号引用 +connection.onReferences( + (handler : ReferenceParams): any =>{ + return CodeReference.getSymbalReferences(handler); + } +); + +// 查找符号定义 +connection.onDefinition( + (handler: TextDocumentPositionParams): Definition => { + handler.textDocument.uri = Tools.urlDecode(handler.textDocument.uri); + try{ + return CodeDefinition.getSymbalDefine(handler); + } catch (error) { + Logger.ErrorLog("[Error] onDefinition " + error.stack); + } + } +); + +//获取单文件内的符号 +connection.onDocumentSymbol( + (handler: DocumentSymbolParams): DocumentSymbol[] => { + let uri = handler.textDocument.uri; + let decUri = Tools.urlDecode(uri); + let retSyms = CodeSymbol.getOneDocSymbolsArray(decUri, null, Tools.SearchRange.AllSymbols); + let retSymsArr: any[]; + try { + retSymsArr = Tools.getOutlineSymbol(retSyms); + } + catch(error) { + // 处理层级关系出现错误时退化为原有的不分层显示 + Logger.DebugLog("error detected while processing outline symbols, error: " + error + "\nstack:\n" + error.stack); + retSymsArr = Tools.changeDicSymboltoArray(retSyms); + } + return retSymsArr; + } +); + +//获取整个工程文件夹的符号 +connection.onWorkspaceSymbol( + (handler: WorkspaceSymbolParams): SymbolInformation[] => { + try{ + let userInput = handler.query; + return CodeSymbol.searchSymbolinWorkSpace(userInput); + } catch (error) { + Logger.ErrorLog("[Error] onWorkspaceSymbol " + error.stack); + } + } +); + +// 打开单文件 +documents.onDidOpen(file => { + + // 异步分析工程中同后缀文件 + if (file.document.languageId == "lua" && analyzerTotalSwitch) { //本文件是lua形式 + try { + let uri = Tools.urlDecode(file.document.uri); + let luaExtname = Tools.getPathNameAndExt(uri); + let ext = luaExtname['ext']; + let loadedExt = Tools.getLoadedExt(); + if (loadedExt && loadedExt[ext] === true) { + // VSCode 会自动调用 onDidChangeContent + return; + }else{ + // 处理新的后缀类型 + for (const folder of Tools.getVSCodeOpenedFolders()) { + CodeSymbol.createSymbolswithExt(ext, folder); + } + setTimeout(Tools.refresh_FileName_Uri_Cache, 0); + } + } catch (error) { + Logger.ErrorLog("[Error] onDidOpen " + error.stack); + } + } +}); + +// 文件内容发生改变(首次打开文件时这个方法也会被调用) +documents.onDidChangeContent(change => { + if(change.document.languageId == 'lua' && analyzerTotalSwitch){ + try { + const uri = Tools.urlDecode(change.document.uri); + const text = change.document.getText(); + CodeEditor.saveCode(uri, text); //保存代码 + + // 过滤掉预分析文件 + if(!Tools.isinPreloadFolder(uri)){ + CodeSymbol.refreshOneDocSymbols(uri, text); + }else{ + CodeSymbol.refreshOneUserPreloadDocSymbols( Tools.uriToPath(uri)); + } + + // 运行语法检查 + getDocumentSettings(uri).then( + (settings) => { + if (settings.codeLinting.checkWhileTyping == true) { + validateTextDocument(change.document); + } + } + ); + } catch (error) { + Logger.ErrorLog("[Error] onDidChangeContent " + error.stack); + } + } +}); + +// 保存文件 +documents.onDidSave(change => { + if(!analyzerTotalSwitch) return; + + try { + // 运行语法检查 + getDocumentSettings(change.document.uri).then( + (settings) => { + if (settings.codeLinting.checkAfterSave == true) { + validateTextDocument(change.document); + } + } + ); + } catch (error) { + Logger.ErrorLog("[Error] onDidSave " + error.stack); + } +}); + +//----------------------------------------------------------------------------- +//-- 未实现能力 +//----------------------------------------------------------------------------- +// 代码着色 +connection.onDocumentColor( + (handler: DocumentColorParams): ColorInformation[] =>{ + return new Array(); + } +); + +connection.onColorPresentation( + (handler: ColorPresentationParams):ColorPresentation[] =>{ + return new Array(); + } +); + +//代码单击高亮 +connection.onDocumentHighlight ( + (handler: TextDocumentPositionParams) : DocumentHighlight[] =>{ + return new Array(); + } +); + + +//----------------------------------------------------------------------------- +//-- 工具方法 +//----------------------------------------------------------------------------- +function getDocumentSettings(resource: string): Thenable { + if (!hasConfigurationCapability) { + return Promise.resolve(globalSettings); + } + let result = documentSettings.get(resource); + if (!result) { + result = connection.workspace.getConfiguration({ + scopeUri: resource, + section: 'lua_analyzer' + }); + documentSettings.set(resource, result); + } + return result; +} + +async function validateTextDocument(textDocument: TextDocument): Promise { + let settings = await getDocumentSettings(textDocument.uri); + + // 如果总开关未打开,无论从哪里调用validateTextDocument都不执行语法检查直接返回 + if (settings.codeLinting.enable == false) { + // 清空诊断输出 + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: [] }); + return; + } + + // 处理忽略文件 + let ignoreFolderRegExpArray = settings.codeLinting.ignoreFolderRegularExpression.split(';'); + if (ignoreFolderRegExpArray.length > 0) { + if (Tools.isMatchedIgnoreRegExp(textDocument.uri, ignoreFolderRegExpArray)) { + // 清空诊断输出 + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: [] }); + return; + } + } + + // let globalSymbols = CodeSymbol.getRequireTreeGlobalSymbols(Tools.urlDecode(textDocument.uri)); + let globalSymbols = CodeSymbol.getWorkspaceSymbols(Tools.SearchRange.GlobalSymbols); + let globalVariables: string[] = Object.keys(globalSymbols); + + let luacheckProcess: Promise<{}> = CodeLinting.processLinting(textDocument, settings, globalVariables); + luacheckProcess.then( + () => { + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: [] }); + }, + luaErrorOrWaining => { + const diagnosticArray = CodeLinting.parseLuacheckResult(luaErrorOrWaining, settings); + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: diagnosticArray }); + } + ) + .catch(() => { + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: [] }); + }); +} + +// Make the text document manager listen on the connection +// for open, change and close text document events +documents.listen(connection); +// Listen on the connection +connection.listen(); diff --git a/src/code/server/trieTree.ts b/src/code/server/trieTree.ts new file mode 100644 index 0000000..e20a165 --- /dev/null +++ b/src/code/server/trieTree.ts @@ -0,0 +1,122 @@ + +// Trie树节点,内部类 +class treeNode{ + thisChr; //当前节点指示的字符(为了方便观察) + childrenNode; //字典,指向下一个孩子 + symbols; //当前节点下挂的符号 + constructor(){ + this.childrenNode = new Object(); + this.symbols = new Array(); + } +} + +// Trie树的构造, 查询, 添加(不需要删除) +export class trieTree { + // 构建树,一个文件构建一棵树 + // @symbolArray 传入单个文件的符号列表,array类型 + // @return 返回构建字典树的根节点 + public static createSymbolTree(symbolArray){ + if(!Array.isArray(symbolArray) || symbolArray.length === 0){ + return; + } + let root : treeNode = new treeNode(); + root.thisChr = "TREE_ROOT"; + for (const symbol of symbolArray) { + this.addNodeOnTrieTree( root , symbol); + } + return root; + } + + // 在树上搜寻节点,这里使用的search方案是前缀搜索 + // @root 树根 + // @searchKey 要查找的key,类型是字符串 + // @searchChildren 是否查找子节点 + // @return 搜索到的符号列表 + public static searchOnTrieTree(root , searchKey, searchChildren = true){ + if(!root || !searchKey || searchKey == ''){ + return; + } + + let currentPtr = root; + searchKey = searchKey.toLowerCase() + let searchArray = searchKey.split(''); + + for (let index = 0; index < searchArray.length; index++) { + const it = searchArray[index]; + //遍历,树中没有此节点,说明皮配不上,返回 + if(!currentPtr.childrenNode[it] ){ + return; + } + //移动指针到第一个匹配的节点 + currentPtr = currentPtr.childrenNode[it]; + //当指向最后一个字母的时候,把节点挂上 + if(index === searchArray.length - 1){ + //继续向下遍历所有节点,把结果列出来 + let searchResult = this.travelAllNode(currentPtr, searchChildren); + return searchResult; + } + } + } + + // 在树上搜寻节点,查到对应节点后,不再查找子节点。比如搜索a.b 那么可以查到a.bc ,但是查不出a.bc.d + // @root 树根 + // @searchKey 要查找的key,类型是字符串 + // @return 搜索到的符号列表 + public static searchOnTrieTreeWithoutTableChildren(root , searchKey){ + return this.searchOnTrieTree(root , searchKey, false) + } + + // 内部方法,在树上增加节点 + // @root 树根 + // @symbol 单个符号名 + private static addNodeOnTrieTree(root , symbol){ + let currentPtr = root; + let searchName = symbol.searchName.toLowerCase(); + let searchArray = searchName.split(''); + for (let index = 0; index < searchArray.length; index++) { + const it = searchArray[index]; + //遍历,没有则创建 + if(!currentPtr.childrenNode[it] ){ + let newNode : treeNode = new treeNode(); + newNode.thisChr = it; + currentPtr.childrenNode[it] = newNode; + } + //移动指针 + currentPtr = currentPtr.childrenNode[it]; + //当指向最后一个字母的时候,把节点挂上 + if(index === searchArray.length - 1){ + currentPtr.symbols.push(symbol); + } + } + } + + // 递归遍历节点 + // @node 当前节点 + // @searchChildren 是否查找子节点 + private static travelAllNode(node, searchChildren){ + let retArray; + // 加上自身节点的数据 + if(node.symbols && node.symbols.length > 0){ + retArray = node.symbols; + } + // 去遍历子节点 + for (const key in node.childrenNode) { + const element = node.childrenNode[key]; + let childArray = [] + if (searchChildren === false && (element.thisChr === '.' || element.thisChr === ':')){ + //不再遍历孩子 + }else{ + childArray = this.travelAllNode(element, searchChildren); + } + + if(retArray == undefined){ + retArray = childArray; + }else{ + retArray = retArray.concat(childArray); + } + } + + return retArray; + } +} + diff --git a/src/code/server/typeInfer.ts b/src/code/server/typeInfer.ts new file mode 100644 index 0000000..5b37635 --- /dev/null +++ b/src/code/server/typeInfer.ts @@ -0,0 +1,310 @@ +// Tencent is pleased to support the open source community by making LuaPanda available. +// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +// https://opensource.org/licenses/BSD-3-Clause +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//这个文件中要处理的主要是tag类型查找 +//包含 前缀的去除 , 查找, 补回查找 +import * as Tools from './codeTools'; +import { CodeSymbol } from "./codeSymbol"; +import { CodeSettings } from './codeSettings'; + +//类型推断文件 +export class TypeInfer { +//----------------------------------------------------------------------------- +//-- 对外接口 +//----------------------------------------------------------------------------- + private static retArray = []; //用来记录 Completion 和 Definition 返回数组 + private static startMS; + + private static maxSymbolCount = 1; + // 定义查找接口 + // @symbolInfo 是符号名 + public static SymbolTagForDefinitionEntry(symbolInfo, uri){ + let symbolName = symbolInfo.name; + this.retArray = []; + this.startMS = Date.now(); + this.recursiveProcessSymbolTagForDefinition(uri, symbolName, [], true) + return this.retArray; + } + + // 代码补全接口 + // @searchPrefix 是一个字符串,标记要查找的用户输入,可以是不完全的比如 aactor.el... + // 返回值是symbol数组 + public static SymbolTagForCompletionEntry(uri, searchPrefix){ + this.retArray = []; + this.startMS = Date.now(); + this.recursiveProcessSymbolTagForCompletion(uri, searchPrefix, [], true) + return this.retArray; + } + + // 循环查tag for definition + private static recursiveSearchTagForDefinition(element, uri, searchPrefix, tailListCache, isStripping = true){ + // 找到tag对应的符号。 searchTag函数的本意是找到 findTagRetSymbArray[key] 这个符号的所有对应tag符号,以便继续的合并搜索 + let findoutArr = this.searchTag( element, uri, 0 ) || []; + for (const value of findoutArr) { + let uri = value.containerURI; + //[尝试]向上补全, 所以补全这边的 tailListCache 要拷贝 + this.recursiveProcessSymbolTagForDefinition(uri, value.searchName, tailListCache, false); + if(value.tagType && this.retArray.length === 0){ + this.recursiveSearchTagForDefinition(value, uri, searchPrefix, tailListCache, isStripping); + } + } + } + + + // 循环查tag for conpletion + private static recursiveSearchTagForCompletion(element, uri, searchPrefix, tailListCache, isStripping = true){ + // 找到tag对应的符号。 searchTag函数的本意是找到 findTagRetSymbArray[key] 这个符号的所有对应tag符号,以便继续的合并搜索 + let findoutArr = this.searchTag( element, uri, 1 ) || []; + if (findoutArr.length > this.maxSymbolCount) findoutArr.length = this.maxSymbolCount; + for (const value of findoutArr) { + let uri = value.containerURI; + //[尝试]向上补全, 所以补全这边的 tailListCache 要拷贝 + this.recursiveProcessSymbolTagForCompletion(uri, value.searchName, tailListCache, false); + if(value.tagType){ + this.recursiveSearchTagForCompletion(value, uri, searchPrefix, tailListCache, isStripping); + } + } + } + +//----------------------------------------------------------------------------- +//-- 私有方法 +//----------------------------------------------------------------------------- +// 定义查找的递归接口 + // @uri + // @searchPrefix 搜索前缀,首次进入时传递用户输入的字符串 + // @tailListCache 用来在剥离时,记录被剥离后缀的数组 + // @isStripping 剥离模式/合并模式 + public static recursiveProcessSymbolTagForDefinition(uri, searchPrefix, tailListCache, isStripping = true){ + if(isStripping){ + if(CodeSettings.isOpenDebugCode === false){ + if(this.startMS + 2000 < Date.now()) return; //超时返回 ReleaseCode + } + let searchPrefixArray = Tools.splitToArrayByDot(searchPrefix); + for (let index = searchPrefixArray.length - 1; index >= 0; index--) { + // 切断用户输入 a.b.c => [a,b,c], 拼接, 开始循环(每次pop一个成员){ + tailListCache.push(searchPrefixArray.pop()); + let SCHName = searchPrefixArray.join('.'); + // 先搜索本文件,如果找不到再搜索调用树 + let findTagRetSymbArray = this.searchMethodforDef(uri, SCHName); // 这里的搜索范围? + // 没找到,继续pop循环 + if(!findTagRetSymbArray || findTagRetSymbArray.length == 0) continue; + // 找到了。遍历查tag(循环) + if(findTagRetSymbArray.length > this.maxSymbolCount) findTagRetSymbArray.length = this.maxSymbolCount; // 同一个符号,处理太多结果会很慢,这里限制数量20 + for (const key in findTagRetSymbArray) { + let uri = findTagRetSymbArray[key].containerURI; + this.recursiveSearchTagForDefinition(findTagRetSymbArray[key] , uri, searchPrefix, tailListCache, isStripping); + } + } + }else{ + // up + // 从 tailListCache 取出变量忘 searchPrefix上拼接,并查找结果有没有符号,符号有没有tag + let temptailCache = tailListCache.concat(); + let newName = searchPrefix + '.' + temptailCache.pop(); + let addPrefixSearchArray = this.searchMethodforComp(uri, newName, Tools.SearchMode.ExactlyEqual);// prefix search with no children + if(addPrefixSearchArray.length > this.maxSymbolCount) addPrefixSearchArray.length = this.maxSymbolCount; + for (const element of addPrefixSearchArray) { + if(element.tagType) { + // TODO 如果有符号,有tag,切换成符号,递归 + this.retArray.push(element); + }else{ + // 如果有符号,不论有无tag,继续合并 + if(temptailCache.length > 0){ + this.recursiveProcessSymbolTagForDefinition(uri, newName, temptailCache, false); + }else{ + this.retArray.push(element); + } + } + } + } + } + + // 自动补全的递归接口 + // @uri + // @searchPrefix 搜索前缀,首次进入时传递用户输入的字符串 + // @tailListCache 用来在剥离时,记录被剥离后缀的数组 + // @isStripping 剥离模式/合并模式 + public static recursiveProcessSymbolTagForCompletion(uri, searchPrefix, tailListCache, isStripping = true){ + // 防止循环遍历,记录[文件名] -- [符号名] + if(isStripping){ + if(CodeSettings.isOpenDebugCode === false){ + if(this.startMS + 2000 < Date.now()) return; //超时返回 ReleaseCode + } + let searchPrefixArray = Tools.splitToArrayByDot(searchPrefix); + for (let index = searchPrefixArray.length - 1; index > 0; index--) { + // 切断用户输入 a.b.c => [a,b,c], 拼接, 开始循环(每次pop一个成员){ + tailListCache.push(searchPrefixArray.pop()); + let SCHName = searchPrefixArray.join('.'); + // 先搜索本文件,如果找不到再搜索调用树 + let findTagRetSymbArray = this.searchMethodforComp(uri, SCHName); // 这里的搜索范围? + // 没找到,继续pop循环 + if(!findTagRetSymbArray || findTagRetSymbArray.length == 0) continue; + // 找到了。遍历查tag(循环) + if(findTagRetSymbArray.length > this.maxSymbolCount) findTagRetSymbArray.length = this.maxSymbolCount; // 同一个符号,处理太多结果会很慢,这里限制数量20 + for (const key in findTagRetSymbArray) { + let uri = findTagRetSymbArray[key].containerURI; + this.recursiveSearchTagForCompletion(findTagRetSymbArray[key] , uri, searchPrefix, tailListCache, isStripping); + } + } + }else{ + // up + // 从 tailListCache 取出变量忘 searchPrefix上拼接,并查找结果有没有符号,符号有没有tag + let temptailCache = tailListCache.concat(); + let newName = searchPrefix + '.' + temptailCache.pop(); + let addPrefixSearchArray = this.searchMethodforComp(uri, newName, Tools.SearchMode.PrefixMatch);// prefix search with no children + // if(addPrefixSearchArray.length > this.maxSymbolCount) addPrefixSearchArray.length = this.maxSymbolCount; 补全这里不能做限制,否则会影响到输出的结果 + for (const element of addPrefixSearchArray) { + if(element.tagType) { + // TODO 如果有符号,有tag,切换成符号,递归 + this.retArray.push(element); + }else{ + // 如果有符号,不论有无tag,继续合并 + if(temptailCache.length > 0){ + this.recursiveProcessSymbolTagForCompletion(uri, newName, temptailCache, false); + }else{ + this.retArray.push(element); + } + } + } + // 如果没有符号,结束 + } + } + + private static searchMethodCommon(uri, SCHName , method = Tools.SearchMode.ExactlyEqual,operation) { + if(operation === 0){ + return this.searchMethodforDef(uri, SCHName, method) || []; + }else if(operation === 1){ + return this.searchMethodforComp(uri, SCHName, method)|| []; + } + } + + // 普通搜索, local => global, 用什么样的搜索方式ExactlyEqual ,在哪个范围(含预制) + private static searchMethodforComp(uri, SCHName , method = Tools.SearchMode.ExactlyEqual){ + let findTagRetSymbArray = CodeSymbol.searchSymbolinDoc(uri , SCHName ,method); + if (findTagRetSymbArray == null || (findTagRetSymbArray &&findTagRetSymbArray.length <= 0)){ + findTagRetSymbArray = CodeSymbol.searchSymbolforCompletion(uri ,SCHName, method, Tools.SearchRange.GlobalSymbols) || []; + } + return findTagRetSymbArray; + } + + // 普通搜索, local => global, 用什么样的搜索方式ExactlyEqual ,在哪个范围(含预制) + private static searchMethodforDef(uri, SCHName , method = Tools.SearchMode.ExactlyEqual){ + let findTagRetSymbArray = CodeSymbol.searchSymbolinDoc(uri , SCHName ,method); + if (findTagRetSymbArray == null || (findTagRetSymbArray &&findTagRetSymbArray.length <= 0)){ + findTagRetSymbArray = CodeSymbol.searchSymbolforGlobalDefinition(uri ,SCHName, method, Tools.SearchRange.GlobalSymbols) || []; + } + return findTagRetSymbArray; + } + + // 搜索tag + // DEF = 0 comp = 1 + private static searchTag(element, uri, operation){ + let findoutSymbs; + if(element.tagType && (element.tagReason === Tools.TagReason.UserTag || element.tagReason === Tools.TagReason.Equal) ){ //USERTag + findoutSymbs = this.searchUserTag(uri, element, operation); + }else if(element.tagType && element.tagReason == Tools.TagReason.MetaTable ){ + findoutSymbs = this.searchMetaTable(uri, element, operation); + }else if(element.requireFile && element.requireFile.length > 0){ // 符号源于文件返回值 + findoutSymbs = this.searchRequire(element); + }else if(element.funcRets){ // 符号源于函数返回值 + findoutSymbs = this.searchFunctionReturn(element); + }else if(element.chunk && element.chunk.returnSymbol){ + let chunkRet = element.chunk.returnSymbol; + findoutSymbs = this.searchMethodCommon(uri ,chunkRet, Tools.SearchMode.ExactlyEqual, operation); + } + + // 为了避免 local common = common 导致搜索到自己的定义,最终无限循环。 + for (const iterator in findoutSymbs) { + if(findoutSymbs[iterator] === element){ + // delete findoutSymbs[iterator]; + findoutSymbs.splice(iterator, 1); + break; + } + } + + return findoutSymbs; + } + + // 处理引用标记 + private static searchRequire(element){ + let beRequiredUri = Tools.transFileNameToUri(element.requireFile); + if(beRequiredUri.length === 0) return; + let beRequiredFilesRet = CodeSymbol.getOneDocReturnSymbol(beRequiredUri); + if(beRequiredFilesRet && beRequiredFilesRet.length > 0){ + let searchReturnSymbolInBeReqFile = CodeSymbol.searchSymbolinDoc(beRequiredUri, beRequiredFilesRet, Tools.SearchMode.ExactlyEqual); + return searchReturnSymbolInBeReqFile; + } + return []; + } + + private static searchFunctionReturn(element){ + let uri = element.containerURI; + let searchName = element.funcRets.name; + //优先本文件,之后调用树,在之后全工程。 应该封装对应的搜索接口 + let returnFuncList = CodeSymbol.searchSymbolinDoc(uri , searchName , Tools.SearchMode.ExactlyEqual); + if (returnFuncList == null || ( returnFuncList && returnFuncList.length <= 0) ){ + returnFuncList = CodeSymbol.searchSymbolforCompletion(uri ,searchName ,Tools.SearchMode.ExactlyEqual); + } + // + let retrunSymbol = new Array(); + if(returnFuncList && returnFuncList.length == 0){ + //等式右边的符号无法被直接搜索到,使用递归搜索 + let tempRetArray = this.retArray; + this.retArray = []; + this.recursiveProcessSymbolTagForDefinition(uri, searchName, []); + if(this.retArray.length>0){ + returnFuncList = this.retArray; + this.retArray = tempRetArray; + } + } + if(returnFuncList && returnFuncList.length > 0){ + // 遍历所有的函数,并在函数中查找返回类型 + const retFuncSymbol = returnFuncList[0]; + let chunks =CodeSymbol.getCretainDocChunkDic(retFuncSymbol.containerURI); + if( chunks[retFuncSymbol.searchName] ){ + // 找到函数符号的 + let chunkRetSymbolName = chunks[retFuncSymbol.searchName].returnSymbol; + // 优先使用@return标记的类型 + if (retFuncSymbol.tagType !== undefined && retFuncSymbol.tagReason === Tools.TagReason.UserTag) { + chunkRetSymbolName = retFuncSymbol.tagType; + } + // 然后再chunk 所在文件,查找chunkRetSymbolName + retrunSymbol = CodeSymbol.searchSymbolinDoc(retFuncSymbol.containerURI , chunkRetSymbolName , Tools.SearchMode.ExactlyEqual); + if (retrunSymbol == null || ( retrunSymbol && retrunSymbol.length <= 0) ){ + retrunSymbol = CodeSymbol.searchSymbolforCompletion(retFuncSymbol.containerURI ,chunkRetSymbolName ,Tools.SearchMode.ExactlyEqual); + } + return retrunSymbol; + } + } + } + + private static searchUserTag(uri, element, operation){ + let tag_type = element.tagType; + if(tag_type){ + return this.searchMethodCommon(uri, tag_type, Tools.SearchMode.ExactlyEqual, operation); + }else{ + return []; + } + } + + private static searchMetaTable(uri, element , operation){ + let tag_type = element.tagType + ".__index"; + if(tag_type){ + // 得到 a.__index 的符号 + let index_symbol = this.searchMethodCommon(uri, tag_type, Tools.SearchMode.ExactlyEqual, operation); + for (const element of index_symbol) { + if(!element.tagType){ + continue; + } + let searchName = element.tagType; // a.__index 对应的tag + let tagRes = this.searchMethodCommon(element.containerURI, searchName, Tools.SearchMode.ExactlyEqual, operation); + if(tagRes){ + return tagRes; + } + } + } + return []; + } +} diff --git a/src/LogManager.ts b/src/common/logManager.ts similarity index 77% rename from src/LogManager.ts rename to src/common/logManager.ts index be1451a..2f0a692 100644 --- a/src/LogManager.ts +++ b/src/common/logManager.ts @@ -5,11 +5,11 @@ export class DebugLogger { private static Dinfo; public static init() { - DebugLogger.Ainfo = vscode.window.createOutputChannel("Adapter/log"); - DebugLogger.Ainfo.show(); + DebugLogger.Ainfo = vscode.window.createOutputChannel("LuaPanda Adapter"); + // DebugLogger.Ainfo.show(); - DebugLogger.Dinfo = vscode.window.createOutputChannel("Debugger/log"); - DebugLogger.Dinfo.show(); + DebugLogger.Dinfo = vscode.window.createOutputChannel("LuaPanda Debugger"); + // DebugLogger.Dinfo.show(); DebugLogger.Dinfo.appendLine("LuaPanda initializing..."); } diff --git a/src/common/pathManager.ts b/src/common/pathManager.ts new file mode 100644 index 0000000..b4f81c2 --- /dev/null +++ b/src/common/pathManager.ts @@ -0,0 +1,219 @@ +import { DebugLogger } from './logManager'; +import { Tools } from './tools'; +import { isArray } from 'util'; +import * as vscode from 'vscode'; + +let pathReader = require('path-reader'); +// let path = require("path"); + +export class PathManager { + public fileNameToPathMap; // 文件名-路径 Map + public useAutoPathMode = false; + public pathCaseSensitivity = false; + public VSCodeOpenedFolder; // VSCode当前打开的用户工程路径。打开文件夹后,由languageServer赋值 + public LuaPandaPath; // 用户工程中luapanda.lua文件所在的路径,它在调试器启动时赋值。但也可能工程中不存在luapanda文件导致路径为空 + public CWD; + public rootFolder; + public static rootFolderArray = {}; // name uri + private consoleLog; + private luaDebugInstance; + public constructor(_luaDebugInstance, _consoleLog){ + this.luaDebugInstance = _luaDebugInstance; + this.consoleLog = _consoleLog; + } + + // 建立/刷新 工程下文件名-路径Map + public rebuildWorkspaceNamePathMap(rootPath : string){ + let beginMS = Tools.getCurrentMS();//启动时毫秒数 + let _fileNameToPathMap = new Array(); // 文件名-路径 cache + let workspaceFiles = pathReader.files(rootPath, {sync:true}); //同步读取工程中所有文件名 + let workspaceFileCount = workspaceFiles.length; + let processFilNum = 0; //记录最终处理了多少个文件 + for(let processingFileIdx = 0; processingFileIdx < workspaceFileCount ; processingFileIdx++){ + let formatedPath = Tools.genUnifiedPath(workspaceFiles[processingFileIdx]); + let nameExtObject = Tools.getPathNameAndExt(formatedPath); + if( !Tools.extMap[nameExtObject['ext']] ){ + // 文件类型不在可处理列表中 + continue; + } + processFilNum = processFilNum + 1; + let fileNameKey = nameExtObject['name']; // key是文件名,不包含路径和文件后缀 + if(_fileNameToPathMap[fileNameKey]){ + //存在同名文件 + if(isArray(_fileNameToPathMap[fileNameKey])){ + _fileNameToPathMap[fileNameKey].push(formatedPath); + }else if(typeof _fileNameToPathMap[fileNameKey] === "string"){ + //冲突, 对应的key已有值(存在同名文件), 使用数组保存数据 + let tempSaveValue = _fileNameToPathMap[fileNameKey]; + let tempArray = new Array(); + tempArray.push(tempSaveValue); + tempArray.push(formatedPath); + _fileNameToPathMap[fileNameKey] = tempArray; + }else{ + // 可能和元方法冲突, 此时key是一个function + _fileNameToPathMap[fileNameKey] = formatedPath; + } + }else{ + _fileNameToPathMap[fileNameKey] = formatedPath; + } + // 显示进度 + let processingRate = Math.floor( processingFileIdx / workspaceFileCount * 100 ); + let completePath = ''; + if(isArray(_fileNameToPathMap[fileNameKey])){ + completePath = _fileNameToPathMap[fileNameKey][_fileNameToPathMap[fileNameKey].length-1]; + }else if(typeof _fileNameToPathMap[fileNameKey] === "string"){ + completePath = _fileNameToPathMap[fileNameKey]; + } + DebugLogger.AdapterInfo(processingRate + "% | " + fileNameKey + " " + completePath); + // record LuaPanda.lua Path + if(fileNameKey === "LuaPanda"){ + this.LuaPandaPath = completePath; + } + } + let endMS = Tools.getCurrentMS();//文件分析结束时毫秒数 + DebugLogger.AdapterInfo("文件Map刷新完毕,使用了" + (endMS - beginMS) + "毫秒。检索了"+ workspaceFileCount +"个文件, 其中" + processFilNum + "个lua类型文件"); + if(processFilNum <= 0){ + vscode.window.showErrorMessage("没有在工程中检索到lua文件。请检查launch.json文件中lua后缀(luaFileExtension)是否配置正确, 以及VSCode打开的工程是否正确", "确定") + let noLuaFileTip = "[!] 没有在VSCode打开的工程中检索到lua文件,请进行如下检查\n 1. VSCode打开的文件夹是否正确 \n 2. launch.json 文件中 luaFileExtension 选项配置是否正确" + DebugLogger.DebuggerInfo(noLuaFileTip); + DebugLogger.AdapterInfo(noLuaFileTip); + } + this.fileNameToPathMap = _fileNameToPathMap; + } + + // 检查同名文件, 如果存在,通过日志输出 + public checkSameNameFile(distinguishSameNameFile){ + let sameNameFileStr; + for (const nameKey in this.fileNameToPathMap) { + let completePath = this.fileNameToPathMap[nameKey] + if(isArray(completePath)){ + //初始化语句 + if(sameNameFileStr === undefined){ + sameNameFileStr = "\nVSCode打开工程中存在以下同名lua文件: \n"; + } + sameNameFileStr = sameNameFileStr + " + " + completePath.join("\n + ") + "\n\n" + } + } + + if(sameNameFileStr){ + if(distinguishSameNameFile){ + sameNameFileStr = sameNameFileStr + "distinguishSameNameFile 已开启。调试器[可以区分]同名文件中的断点。\n" + }else{ + let sameNameFileTips = "[Tips] VSCode 打开目录中存在同名 lua 文件,请避免在这些文件中打断点。如确定需要区分同名文件中的断点,可按以下选择适合自己项目的操作:\n"; + sameNameFileTips += "方法1: LuaPanda启动时会索引 cwd 目录中的 lua 文件, 修改 launch.json 中的 cwd 配置路径, 过滤掉不参与运行的文件夹, 缩小索引范围来避免重复文件;\n"; + sameNameFileTips += "方法2: 在 launch.json 中加入 distinguishSameNameFile:true , 开启同名文件区分 (会采用更严格的路径校验方式区分同名文件);\n"; + sameNameFileTips += "方法3: 同名文件信息展示在 VSCode 控制台 OUTPUT - LuaPanda Debugger 中, 也可以尝试修改文件名;\n"; + this.consoleLog(sameNameFileTips, this.luaDebugInstance); + } + + DebugLogger.DebuggerInfo(sameNameFileStr); + DebugLogger.AdapterInfo(sameNameFileStr); + } + } + + // 传入局部路径,返回完整路径 + public checkFullPath( shortPath: string , oPath?: string): string{ + if(this.useAutoPathMode === false){ + return shortPath; + } + + //如果首字符是@,去除@ + if('@' === shortPath.substr(0,1)){ + shortPath = shortPath.substr(1); + } + + let nameExtObject = Tools.getPathNameAndExt(shortPath); + let fileName = nameExtObject['name']; + + let fullPath; + if(this.pathCaseSensitivity){ + fullPath = this.fileNameToPathMap[fileName]; + }else{ + for (const keyPath in this.fileNameToPathMap) { + if(keyPath.toLowerCase() === fileName){ + fullPath = this.fileNameToPathMap[keyPath]; + break; + } + } + } + + if(fullPath){ + if(isArray(fullPath)){ + // 存在同名文件 + if(oPath){ + return this.checkRightPath( shortPath , oPath , fullPath); + }else{ + // 如果lua文件没有更新,没有传过来oPath,则打开第一个文件 + for (const element of fullPath) { + // @ts-ignore + if(element.indexOf(shortPath)){ + // @ts-ignore + return element; // 这里固定返回第一个元素 + } + } + } + }else if(typeof fullPath === "string"){ + return fullPath; + } + } + //最终没有找到,返回输入的地址 + DebugLogger.showTips("调试器没有找到文件 " + shortPath + " 。 请检查launch.json文件中lua后缀是否配置正确, 以及VSCode打开的工程是否正确", 2); + return shortPath; + } + + // 存在同名文件的情况下, 根据lua虚拟机传来的 fullPath , 判断断点处具体是哪一个文件 + public checkRightPath( fileName: string , oPath: string, fullPathArray): string{ + //------ 这部分还需要么? + //如果首字符是@,去除@ + if('@' === oPath.substr(0,1)){ + oPath = oPath.substr(1); + } + //如果是相对路径,把 ./ 替换成 / + if('./' === oPath.substr(0,2)){ + oPath = oPath.substr(1); + } + + //标准化路径, 盘符变成小写 + oPath = Tools.genUnifiedPath(oPath); + + if(!this.pathCaseSensitivity){ + oPath = oPath.toLowerCase(); + } + + //因为 filename 存在不确定性(是否包含后缀),这里把后缀去掉进行对比 + let nameExtObject = Tools.getPathNameAndExt(fileName); + fileName = nameExtObject['name']; + + // 从oPath中把文件名截取掉 + let idx = oPath.lastIndexOf(fileName); + oPath = oPath.substring(0, idx - 1); // 此时opath是dir + oPath = oPath + '/' + fileName; + // oPath中的. 替换成 / + oPath = oPath.replace(/\./g, "/"); + //------ + + for (const iteratorPath of fullPathArray) { + let pathForCompare = iteratorPath; + if(!this.pathCaseSensitivity){ + pathForCompare = iteratorPath.toLowerCase() + } + if(pathForCompare.indexOf(oPath) >= 0){ + // fullPathArray 中包含oPath, 命中 + return iteratorPath; + } + } + // 如果最终都无法命中, 默认第一条。这种情况要避免,否则二次验证也通不过 + if(Tools.developmentMode === true){ + // 开发模式下提示 + let str = "file_name:" + fileName + " opath:" + oPath + "无法命中任何文件路径!" + DebugLogger.showTips(str); + let Adapterlog = "同名文件无法命中!\n"; + for (const iteratorPath of fullPathArray) { + Adapterlog += " + " + iteratorPath + "\n"; + } + Adapterlog += str; + DebugLogger.AdapterInfo(Adapterlog); + } + return fullPathArray[0]; + } +} diff --git a/src/StatusBarManager.ts b/src/common/statusBarManager.ts similarity index 58% rename from src/StatusBarManager.ts rename to src/common/statusBarManager.ts index 938495f..fd175f9 100644 --- a/src/StatusBarManager.ts +++ b/src/common/statusBarManager.ts @@ -3,10 +3,17 @@ import * as vscode from 'vscode'; export class StatusBarManager { private static MemStateBar; + private static Setting; + public static init() { this.MemStateBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 5.0); this.MemStateBar.tooltip = "Click to collect garbage"; this.MemStateBar.command = 'luapanda.LuaGarbageCollect'; + + this.Setting = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 6.0); + this.Setting.tooltip = "Click open setting page"; + this.Setting.command = 'luapanda.openSettingsPage'; + this.Setting.hide(); } //刷新内存数据显示区的值 @@ -15,6 +22,12 @@ export class StatusBarManager { this.MemStateBar.show(); } + //刷新内存数据显示区的值 + public static showSetting(message: string) { + this.Setting.text = message; + this.Setting.show(); + } + //按钮恢复到初始状态 public static reset() { diff --git a/src/common/threadManager.ts b/src/common/threadManager.ts new file mode 100644 index 0000000..a768a8e --- /dev/null +++ b/src/common/threadManager.ts @@ -0,0 +1,24 @@ +// import { DebugLogger } from './logManager'; + +export class ThreadManager { + static THREAD_ID_COUNTER = 0; // 线程计数器 + static NEXT_THREAD_ID = 0; // 指示下一个待分配 thread id + private _CUR_THREAD_ID; // 当前线程号,从 0 开始 + get CUR_THREAD_ID(){ + return this._CUR_THREAD_ID; + } + + public constructor() { + this._CUR_THREAD_ID = ThreadManager.NEXT_THREAD_ID; + ThreadManager.NEXT_THREAD_ID ++; + ThreadManager.THREAD_ID_COUNTER ++; + } + + // 析构函数 如果线程数为0, 待分配线程号也置0 + public destructor() { + ThreadManager.THREAD_ID_COUNTER--; + if(ThreadManager.THREAD_ID_COUNTER === 0){ + ThreadManager.NEXT_THREAD_ID = 0; + } + } +} diff --git a/src/common/tools.ts b/src/common/tools.ts new file mode 100644 index 0000000..e0e6b58 --- /dev/null +++ b/src/common/tools.ts @@ -0,0 +1,175 @@ +import * as vscode from 'vscode'; +import * as fs from "fs"; +import URI from 'vscode-uri'; +let path = require("path"); + +export class Tools { + public static extMap; // 可处理的文件后缀列表 + public static adapterVersion; //赋值放在了插件初始化时 + public static VSCodeExtensionPath; // VSCode插件所在路径,插件初始化时就会被赋值. + public static client; + public static developmentMode = false; + + // 路径相关函数 + // 获取扩展中预置的lua文件位置 + public static getLuaPathInExtension() : string{ + let luaPathInVSCodeExtension = this.VSCodeExtensionPath + "/Debugger/LuaPanda.lua"; + return luaPathInVSCodeExtension; + } + + // 获取扩展中预置的lua文件位置 + public static getClibPathInExtension() : string{ + let ClibPathInVSCodeExtension = this.VSCodeExtensionPath + "/Debugger/debugger_lib/plugins/"; + return ClibPathInVSCodeExtension; + } + + // 读文本文件内容 + // @path 文件路径 + // @return 文件内容 + public static readFileContent(path: string): string { + if(path === '' || path == undefined){ + return ''; + } + let data = fs.readFileSync(path); + let dataStr = data.toString(); + return dataStr; + } + + // 写文件内容 + // @path 文件路径 + // @return 文件内容 + public static writeFileContent(path: string, content:string) { + if(path === '' || path == undefined){ + return; + } + fs.writeFileSync(path, content); + } + + // 把传入的路径转为标准路径 + public static genUnifiedPath(beProcessPath) : string{ + //全部使用 / + beProcessPath = beProcessPath.replace(/\\/g, '/'); + while(beProcessPath.match(/\/\//)){ + beProcessPath = beProcessPath.replace(/\/\//g, '/'); + } + //win盘符转为小写 + beProcessPath = beProcessPath.replace(/^\w:/, function($1){return $1.toLocaleLowerCase()}); + return beProcessPath; + } + + // 获取当前VScode活跃窗口的文件路径 + public static getVSCodeAvtiveFilePath(): Object{ + let retObject = {retCode : 0, retMsg : "", filePath: "" }; + + let activeWindow = vscode.window.activeTextEditor; + if (activeWindow){ + let activeFileUri = ''; + // 先判断当前活动窗口的 uri 是否有效 + let activeScheme = activeWindow.document.uri.scheme; + if( activeScheme !== "file" ){ + // 当前活动窗口不是file类型,遍历 visibleTextEditors,取出file类型的窗口 + let visableTextEditorArray = vscode.window.visibleTextEditors; + for (const key in visableTextEditorArray) { + const editor = visableTextEditorArray[key]; + let editScheme = editor.document.uri.scheme; + if(editScheme === "file"){ + activeFileUri = editor.document.uri.fsPath; + break; + } + } + }else{ + // 使用 activeWindow + activeFileUri = activeWindow.document.uri.fsPath + } + if(activeFileUri === ''){ + retObject.retMsg = "[Error]: adapter start file debug, but file Uri is empty string"; + retObject.retCode = -1; + return retObject; + } + + let pathArray = activeFileUri.split(path.sep); + let filePath = pathArray.join('/'); + filePath = '"' + filePath + '"'; //给路径加上"" + + retObject.filePath = filePath; + return retObject; + + }else{ + retObject.retMsg = "[Error]: can not get vscode activeWindow"; + retObject.retCode = -1; + return retObject; + } + } + + // 构建可接受的后缀列表 + public static rebuildAcceptExtMap(userSetExt? : string){ + Tools.extMap = new Object(); + Tools.extMap['lua'] = true; + Tools.extMap['lua.txt'] = true; + Tools.extMap['lua.bytes'] = true; + if(typeof userSetExt == 'string' && userSetExt != ''){ + Tools.extMap[userSetExt] = true; + } + } + + // 获取当前毫秒数 + public static getCurrentMS(){ + let currentMS = new Date();//获取当前时间 + return currentMS.getTime(); + } + + // 从URI分析出文件名和后缀 + public static getPathNameAndExt(UriOrPath): Object{ + let name_and_ext = path.basename(UriOrPath).split('.'); + let name = name_and_ext[0]; //文件名 + let ext = name_and_ext[1] || ''; //文件后缀 + for (let index = 2; index < name_and_ext.length; index++) { + ext = ext + '.' + name_and_ext[index]; + } + return { name, ext }; + } + + // 从URI分析出文件路径,文件名,后缀 + // 注意:如果dirname 取不到,默认是 . + public static getDirAndFileName(UriOrPath): Object{ + let retObj = this.getPathNameAndExt(UriOrPath) + let _dir = path.dirname(UriOrPath) + retObj["dir"] = _dir; + return retObj; + } + + public static removeDir(dir): boolean { + let files; + try{ + files = fs.readdirSync(dir) + }catch(err){ + if (err.code === 'ENOENT') { + return false; + } else { + throw err; + } + } + + for(var i=0;i< files.length;i++){ + let newPath = path.join(dir,files[i]); + let stat = fs.statSync(newPath) + if(stat.isDirectory()){ + //如果是文件夹就递归 + this.removeDir(newPath); + } + else{ + //删除文件 + fs.unlinkSync(newPath); + } + } + fs.rmdirSync(dir); + return true; + } + + // uri string -> path + public static uriToPath(uri: string): string { + let pathStr = URI.parse(uri).fsPath; + return pathStr; + } + +} \ No newline at end of file diff --git a/src/BreakPoint.ts b/src/debug/breakPoint.ts similarity index 100% rename from src/BreakPoint.ts rename to src/debug/breakPoint.ts diff --git a/src/dataProcesser.ts b/src/debug/dataProcessor.ts similarity index 53% rename from src/dataProcesser.ts rename to src/debug/dataProcessor.ts index 3f5ee1e..d15d03d 100644 --- a/src/dataProcesser.ts +++ b/src/debug/dataProcessor.ts @@ -1,58 +1,58 @@ import { LuaDebugRuntime } from './luaDebugRuntime'; import { Socket } from 'net'; -import { DebugLogger } from './LogManager'; -import { LuaDebugSession } from './luaDebug'; +import { DebugLogger } from '../common/logManager'; //网络收发消息,记录回调 -export class DataProcesser { +export class DataProcessor { + public _runtime: LuaDebugRuntime; //RunTime句柄 + public _socket: Socket; + public isNeedB64EncodeStr: boolean = true; + private orderList: Array = new Array(); //记录随机数和它对应的回调 + private recvMsgQueue: Array = new Array(); //记录粘包的多条指令 + private cutoffString: string = ""; + private getDataJsonCatch: string = ""; //解析缓存,防止用户信息中含有分隔符 - public static _runtime: LuaDebugRuntime; //RunTime句柄 - public static _socket: Socket; - private static orderList: Array = new Array(); //记录随机数和它对应的回调 - private static recvMsgQueue: Array = new Array(); //记录粘包的多条指令 - private static cutoffString: string = ""; - private static getDataJsonCatch: string = ""; //解析缓存,防止用户信息中含有分隔符 /** * 接收从Debugger发来的消息 * @param orgData: 消息串 */ - public static processMsg(orgData: string) { + public processMsg(orgData: string) { let data = orgData.trim(); if (this.cutoffString.length > 0) { data = this.cutoffString + data; this.cutoffString = ""; } - let pos = data.indexOf(DataProcesser._runtime.TCPSplitChar); + let pos = data.indexOf(this._runtime.TCPSplitChar); if (pos < 0) { //没有分隔符,做截断判断 - DataProcesser.processCutoffMsg(data); + this.processCutoffMsg(data); } else { do { let data_save = data.substring(0, pos); //保存的命令 - data = data.substring(pos + DataProcesser._runtime.TCPSplitChar.length, data.length); - DataProcesser.recvMsgQueue.push(data_save); - pos = data.indexOf(DataProcesser._runtime.TCPSplitChar); + data = data.substring(pos + this._runtime.TCPSplitChar.length, data.length); + this.recvMsgQueue.push(data_save); + pos = data.indexOf(this._runtime.TCPSplitChar); if (pos < 0) { //没有分隔符时,剩下的字符串不为空 - DataProcesser.processCutoffMsg(data); + this.processCutoffMsg(data); } } while (pos > 0); - while (DataProcesser.recvMsgQueue.length > 0) { - let dt1 = DataProcesser.recvMsgQueue.shift(); //从头部取元素,保证是一个队列形式 - DataProcesser.getData(String(dt1)); + while (this.recvMsgQueue.length > 0) { + let dt1 = this.recvMsgQueue.shift(); //从头部取元素,保证是一个队列形式 + this.getData(String(dt1)); } } //最后处理一下超时回调 - for (let index = 0; index < DataProcesser.orderList.length; index++) { - const element = DataProcesser.orderList[index]; + for (let index = 0; index < this.orderList.length; index++) { + const element = this.orderList[index]; if ( element["timeOut"] && Date.now() > element["timeOut"] ){ - // dataProcesser._runtime.showError(element["callbackId"] + " 请求超时! 详细请求信息可在 Adapter/log 中搜索此id查看"); + // dataProcessor._runtime.showError(element["callbackId"] + " 请求超时! 详细请求信息可在 LuaPanda Adapter 中搜索此id查看"); let cb = element["callback"]; cb(element["callbackArgs"]); - DataProcesser.orderList.splice(index, 1); + this.orderList.splice(index, 1); } } } @@ -61,10 +61,10 @@ export class DataProcesser { * 切割消息 * @param orgData: 消息串 */ - private static processCutoffMsg(orgData: string) { + private processCutoffMsg(orgData: string) { let data = orgData.trim(); if (data.length > 0) { - DataProcesser.cutoffString = DataProcesser.cutoffString + data; //被截断的部分 + this.cutoffString = this.cutoffString + data; //被截断的部分 } } @@ -72,14 +72,14 @@ export class DataProcesser { * 处理单条消息。主要包括解析json,分析命令,做相应处理 * @param data 消息json */ - private static getData(data: string) { + private getData(data: string) { let cmdInfo; try{ if(this.getDataJsonCatch != ""){ data = this.getDataJsonCatch + data; } cmdInfo = JSON.parse(data); - if (LuaDebugSession.isNeedB64EncodeStr && cmdInfo.info !== undefined) { + if (this.isNeedB64EncodeStr && cmdInfo.info !== undefined) { for (let i = 0, len = cmdInfo.info.length; i < len; i++) { if (cmdInfo.info[i].type === "string") { cmdInfo.info[i].value = Buffer.from(cmdInfo.info[i].value, 'base64').toString() @@ -89,8 +89,8 @@ export class DataProcesser { this.getDataJsonCatch = ""; } catch(e){ - if(LuaDebugSession.isNeedB64EncodeStr){ - DataProcesser._runtime.showError(" JSON 解析失败! " + data); + if(this.isNeedB64EncodeStr){ + this._runtime.showError(" JSON 解析失败! " + data); DebugLogger.AdapterInfo("[Adapter Error]: JSON 解析失败! " + data); }else{ this.getDataJsonCatch = data + "|*|"; @@ -98,21 +98,21 @@ export class DataProcesser { return; } - if (DataProcesser._runtime != null) { + if (this._runtime != null) { if (cmdInfo == null) { - DataProcesser._runtime.showError("JSON 解析失败! no cmdInfo:" + data); + this._runtime.showError("JSON 解析失败! no cmdInfo:" + data); DebugLogger.AdapterInfo("[Adapter Error]:JSON解析失败 no cmdInfo:"+ data); return; } if (cmdInfo["cmd"] == undefined) { - DataProcesser._runtime.showError("JSON 解析失败! no cmd:" + data); + this._runtime.showError("JSON 解析失败! no cmd:" + data); DebugLogger.AdapterInfo("[Adapter Warning]:JSON 解析失败 no cmd:"+ data); } if (cmdInfo["callbackId"] != undefined && cmdInfo["callbackId"] != "0") { //进入回调(如增加断点) - for (let index = 0; index < DataProcesser.orderList.length; index++) { - const element = DataProcesser.orderList[index]; + for (let index = 0; index < this.orderList.length; index++) { + const element = this.orderList[index]; if (element["callbackId"] == cmdInfo["callbackId"]) { let cb = element["callback"]; if (cmdInfo["info"] != null) { @@ -120,36 +120,43 @@ export class DataProcesser { } else { cb(element["callbackArgs"]); } - DataProcesser.orderList.splice(index, 1); + this.orderList.splice(index, 1); return; } } DebugLogger.AdapterInfo("[Adapter Error]: 没有在列表中找到回调"); } else { - if (cmdInfo["cmd"] == "refreshLuaMemory") { - DataProcesser._runtime.refreshLuaMemoty(cmdInfo["info"]["memInfo"]); - return; - } - - if (cmdInfo["cmd"] == "tip") { - DataProcesser._runtime.showTip(cmdInfo["info"]["logInfo"]); - return; - } - - if (cmdInfo["cmd"] == "tipError") { - DataProcesser._runtime.showError(cmdInfo["info"]["logInfo"]); - return; - } - - if (cmdInfo["cmd"] == "stopOnBreakpoint" || cmdInfo["cmd"] == "stopOnEntry" || cmdInfo["cmd"] == "stopOnStep" || cmdInfo["cmd"] == "stopOnStepIn" || cmdInfo["cmd"] == "stopOnStepOut") { - // 进入断点/step停止 - let stackInfo = cmdInfo["stack"]; - DataProcesser._runtime.stop(stackInfo, cmdInfo["cmd"]); - } else if (cmdInfo["cmd"] == "log") { - let logStr = cmdInfo["info"]["logInfo"]; - if (logStr != null) { - DataProcesser._runtime.printLog(logStr); - } + switch (cmdInfo["cmd"]) { + case "refreshLuaMemory": + this._runtime.refreshLuaMemoty(cmdInfo["info"]["memInfo"]); + break; + case "tip": + this._runtime.showTip(cmdInfo["info"]["logInfo"]); + break; + case "tipError": + this._runtime.showError(cmdInfo["info"]["logInfo"]); + break; + case "stopOnCodeBreakpoint": + case "stopOnBreakpoint": + case "stopOnEntry": + case "stopOnStep": + case "stopOnStepIn": + case "stopOnStepOut": + let stackInfo = cmdInfo["stack"]; + this._runtime.stop(stackInfo, cmdInfo["cmd"]); + break; + case "output": + let outputLog = cmdInfo["info"]["logInfo"]; + if (outputLog != null) { + this._runtime.printLog(outputLog); + } + break; + case "debug_console": + let consoleLog = cmdInfo["info"]["logInfo"]; + if (consoleLog != null) { + this._runtime.logInDebugConsole(consoleLog); + } + break; } } } @@ -162,7 +169,7 @@ export class DataProcesser { * @param callbackFunc: 回调函数 * @param callbackArgs: 回调参数 */ - public static commandToDebugger(cmd: string, sendObject: Object, callbackFunc = null, callbackArgs = null, timeOutSec = 0) { + public commandToDebugger(cmd: string, sendObject: Object, callbackFunc = null, callbackArgs = null, timeOutSec = 0) { //生成随机数 let max = 999999999; let min = 10; //10以内是保留位 @@ -176,7 +183,7 @@ export class DataProcesser { isSame = false; ranNum = Math.floor(Math.random() * (max - min + 1) + min); //检查随机数唯一性 - DataProcesser.orderList.forEach(element => { + this.orderList.forEach(element => { if (element["callbackId"] == ranNum) { //若遍历后isSame依然是false,说明没有重合 isSame = true; @@ -194,17 +201,17 @@ export class DataProcesser { if (callbackArgs != null) { dic["callbackArgs"] = callbackArgs; } - DataProcesser.orderList.push(dic); + this.orderList.push(dic); sendObj["callbackId"] = ranNum.toString(); } sendObj["cmd"] = cmd; sendObj["info"] = sendObject; - const str = JSON.stringify(sendObj) + " " + DataProcesser._runtime.TCPSplitChar + "\n"; + const str = JSON.stringify(sendObj) + " " + this._runtime.TCPSplitChar + "\n"; //记录随机数和回调的对应关系 - if (DataProcesser._socket != undefined) { + if (this._socket != undefined) { DebugLogger.AdapterInfo("[Send Msg]:" + str); - DataProcesser._socket.write(str); + this._socket.write(str); } else { DebugLogger.AdapterInfo("[Send Msg but socket deleted]:" + str); } diff --git a/src/debugAdapter.ts b/src/debug/debugAdapter.ts similarity index 100% rename from src/debugAdapter.ts rename to src/debug/debugAdapter.ts diff --git a/src/luaDebug.ts b/src/debug/luaDebug.ts similarity index 52% rename from src/luaDebug.ts rename to src/debug/luaDebug.ts index fc088b6..b9380d0 100755 --- a/src/luaDebug.ts +++ b/src/debug/luaDebug.ts @@ -6,7 +6,6 @@ import * as vscode from 'vscode'; import { - Logger, logger, LoggingDebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, BreakpointEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles @@ -14,88 +13,133 @@ import { import { DebugProtocol } from 'vscode-debugprotocol'; import { basename } from 'path'; import { LuaDebugRuntime, LuaBreakpoint } from './luaDebugRuntime'; -const { Subject } = require('await-notify'); import * as Net from 'net'; -import { DataProcesser } from './dataProcesser'; -import { DebugLogger } from './LogManager'; -import { StatusBarManager } from './StatusBarManager'; -import { LineBreakpoint, ConditionBreakpoint, LogPoint } from './BreakPoint'; -import { Tools } from './Tools'; +import { DataProcessor } from './dataProcessor'; +import { DebugLogger } from '../common/logManager'; +import { StatusBarManager } from '../common/statusBarManager'; +import { LineBreakpoint, ConditionBreakpoint, LogPoint } from './breakPoint'; +import { Tools } from '../common/tools'; +import { UpdateManager } from './updateManager'; +import { ThreadManager } from '../common/threadManager'; +import { PathManager } from '../common/pathManager'; +import { VisualSetting } from './visualSetting' +const { Subject } = require('await-notify'); +let fs = require('fs'); + export class LuaDebugSession extends LoggingDebugSession { - public static isNeedB64EncodeStr: boolean = true; - private static THREAD_ID = 1; //调试器不支持多线程,硬编码THREAD_ID为1 - public static TCPPort = 0; //和客户端连接的端口号,通过VScode的设置赋值 - private static breakpointsArray; //在socket连接前临时保存断点的数组 - private static autoReconnect; + public TCPPort; //和客户端连接的端口号,通过VScode的设置赋值 + public connectionIP; + public _server; // adapter 作为server + public _client; // adapter 作为client + private VSCodeAsClient; + private breakpointsArray; //在socket连接前临时保存断点的数组 + private autoReconnect; private _configurationDone = new Subject(); private _variableHandles = new Handles(50000);//Handle编号从50000开始 - private static replacePath; //替换路径数组 - //自身单例 - private static instance: LuaDebugSession; - public static userConnectionFlag; //这个标记位的作用是标记Adapter停止连接,因为Adapter是Server端,要等Client发来请求才能断开 - public static isListening; - public static _server; - - public static getInstance(): LuaDebugSession { - return LuaDebugSession.instance; - } - + private replacePath; //替换路径数组 + private connectInterval; // client 循环连接的句柄 //luaDebugRuntime实例 - private _runtime: LuaDebugRuntime; + private _runtime: LuaDebugRuntime; + private _dataProcessor: DataProcessor; + private _threadManager:ThreadManager; + private _pathManager: PathManager; private UseLoadstring: boolean = false; - - public getRuntime() { - return this._runtime; - } + private _dbCheckBreakpoint = true; + //terminal实例,便于销毁 + private _debugFileTermianl; + private _programTermianl; + //保存所有活动的LuaDebugSession实例 + private static _debugSessionArray:Map = new Map(); + static get debugSessionArray(){ return LuaDebugSession._debugSessionArray; } + private connectionFlag = false; //连接成功的标志位 public constructor() { super("lua-debug.txt"); - //设置自身实例 - LuaDebugSession.instance = this; this.setDebuggerLinesStartAt1(true); this.setDebuggerColumnsStartAt1(true); - //设置runtime实例 - this._runtime = new LuaDebugRuntime(); - DataProcesser._runtime = this._runtime; + this._threadManager = new ThreadManager(); // 线程实例 调用this._threadManager.CUR_THREAD_ID可以获得当前线程号 + this._pathManager = new PathManager(this, this.printLogInDebugConsole); + this._runtime = new LuaDebugRuntime(); // _runtime and _dataProcessor 相互持有实例 + this._dataProcessor = new DataProcessor(); + this._dataProcessor._runtime = this._runtime; + this._runtime._dataProcessor = this._dataProcessor; + this._runtime._pathManager = this._pathManager; + + LuaDebugSession._debugSessionArray.set(this._threadManager.CUR_THREAD_ID, this); this._runtime.TCPSplitChar = "|*|"; - //给状态绑定监听方法 this._runtime.on('stopOnEntry', () => { - this.sendEvent(new StoppedEvent('entry', LuaDebugSession.THREAD_ID)); + this.sendEvent(new StoppedEvent('entry', this._threadManager.CUR_THREAD_ID)); }); this._runtime.on('stopOnStep', () => { - this.sendEvent(new StoppedEvent('step', LuaDebugSession.THREAD_ID)); + this.sendEvent(new StoppedEvent('step', this._threadManager.CUR_THREAD_ID)); }); this._runtime.on('stopOnStepIn', () => { - this.sendEvent(new StoppedEvent('step', LuaDebugSession.THREAD_ID)); + this.sendEvent(new StoppedEvent('step', this._threadManager.CUR_THREAD_ID)); }); this._runtime.on('stopOnStepOut', () => { - this.sendEvent(new StoppedEvent('step', LuaDebugSession.THREAD_ID)); + this.sendEvent(new StoppedEvent('step', this._threadManager.CUR_THREAD_ID)); + }); + + this._runtime.on('stopOnCodeBreakpoint', () => { + // stopOnCodeBreakpoint 指的是遇到 LuaPanda.BP(),因为是代码中的硬断点,VScode中不会保存这个断点信息,故不做校验 + this.sendEvent(new StoppedEvent('breakpoint', this._threadManager.CUR_THREAD_ID)); }); - this._runtime.on('stopOnBreakpoint', () => { - this.sendEvent(new StoppedEvent('breakpoint', LuaDebugSession.THREAD_ID)); + this._runtime.on('stopOnBreakpoint', () => { + // 因为lua端所做的断点命中可能出现同名文件错误匹配,这里要再次校验lua端命中的行列号是否在 breakpointsArray 中 + if(this.checkIsRealHitBreakpoint()){ + this.sendEvent(new StoppedEvent('breakpoint', this._threadManager.CUR_THREAD_ID)); + }else{ + // go on running + this._runtime.continueWithFakeHitBk(() => { + DebugLogger.AdapterInfo("命中同名文件中的断点, 确认继续运行"); + }); + } }); this._runtime.on('stopOnException', () => { - this.sendEvent(new StoppedEvent('exception', LuaDebugSession.THREAD_ID)); + this.sendEvent(new StoppedEvent('exception', this._threadManager.CUR_THREAD_ID)); }); this._runtime.on('stopOnPause', () => { - this.sendEvent(new StoppedEvent('exception', LuaDebugSession.THREAD_ID)); + this.sendEvent(new StoppedEvent('exception', this._threadManager.CUR_THREAD_ID)); }); this._runtime.on('breakpointValidated', (bp: LuaBreakpoint) => { this.sendEvent(new BreakpointEvent('changed', { verified: bp.verified, id: bp.id })); }); - this._runtime.on('output', (text, filePath, line, column) => { - const e: DebugProtocol.OutputEvent = new OutputEvent(`${text}\n`); - e.body.source = this.createSource(filePath); - e.body.line = this.convertDebuggerLineToClient(line); - e.body.column = this.convertDebuggerColumnToClient(column); - this.sendEvent(e); + this._runtime.on('logInDebugConsole', (message) => { + this.printLogInDebugConsole(message); }); } + // 在有同名文件的情况下,需要再次进行命中判断。 + private checkIsRealHitBreakpoint(){ + if( !this._dbCheckBreakpoint ){ + // 用户关闭了二次断点校验,直接返回成功 + return true; + } + + let steak = this._runtime.breakStack; + let steakPath = steak[0].file; + let steakLine = steak[0].line; + for (let bkMap of this.breakpointsArray) { + if(bkMap.bkPath === steakPath){ + for (const node of bkMap.bksArray) { + if(node.line == steakLine){ + return true; + } + } + } + } + return false; + } + + // 在调试控制台打印日志. 从非luaDebug.ts文件调用这个函数时,要传instance实例 + public printLogInDebugConsole(message, instance = this){ + instance.sendEvent(new OutputEvent(message + '\n', 'console')); + } + /** * VScode前端的首个请求,询问debug adapter所能提供的特性 * 这个方法是VSCode调过来的,adapter拿到其中的参数进行填充. 再回给VSCode,VSCode根据这些设置做不同的显示 @@ -111,8 +155,10 @@ export class LuaDebugSession extends LoggingDebugSession { response.body.supportsSetVariable = true;//修改变量的值 response.body.supportsFunctionBreakpoints = false; response.body.supportsConditionalBreakpoints = true; - response.body.supportsHitConditionalBreakpoints = false; + response.body.supportsHitConditionalBreakpoints = true; response.body.supportsLogPoints = true; + // response.body.supportsRestartRequest = false; + // response.body.supportsRestartFrame = false; this.sendResponse(response); } @@ -125,26 +171,85 @@ export class LuaDebugSession extends LoggingDebugSession { } /** - * launchRequest的args会把在Launch.json中的配置读取出来, 在这里通过socket传给Debugger + * Attach 模式初始化代码 + */ + protected async attachRequest(response: DebugProtocol.AttachResponse, args) { + await this._configurationDone.wait(1000); + this.initProcess(response, args); + this.sendResponse(response); + } + + /** + * Launch 模式初始化代码 */ protected async launchRequest(response: DebugProtocol.LaunchResponse, args) { - logger.setup(args.trace ? Logger.LogLevel.Verbose : Logger.LogLevel.Stop, false); - // 等待configurationDoneRequest的通知 await this._configurationDone.wait(1000); + this.initProcess(response, args); + this.sendResponse(response); + } + + private copyAttachConfig(args){ + if(args.tag === "attach"){ + if(args.rootFolder){ + // 把launch中的配置拷贝到attach. 判断attach中是否有,如果有的话不再覆盖,没有的话覆盖。 + let settings = VisualSetting.readLaunchjson(args.rootFolder); + for (const launchValue of settings.configurations) { + if(launchValue["tag"] === "normal" || launchValue["name"] === "LuaPanda"){ + for (const key in launchValue) { + if(key === "name" || key === "program" || args[key]){ + continue; + } + + if(key === "cwd"){ + args[key] = launchValue[key].replace(/\${workspaceFolder}/, args.rootFolder ); + continue; + } + + args[key] = launchValue[key]; + } + } + } + } + } + return args; + } + + private initProcess(response, args){ //1. 配置初始化信息 let os = require("os"); let path = require("path"); - Tools.useAutoPathMode = !!args.autoPathMode; - //1.1.生成文件map - if(Tools.useAutoPathMode === true){ + this.copyAttachConfig(args) + this.VSCodeAsClient = args.VSCodeAsClient; + this.connectionIP = args.connectionIP; + this.TCPPort = args.connectionPort; + this._pathManager.CWD = args.cwd; + this._pathManager.rootFolder = args.rootFolder; + this._pathManager.useAutoPathMode = !!args.autoPathMode; + this._pathManager.pathCaseSensitivity = !!args.pathCaseSensitivity; + this._dbCheckBreakpoint = !!args.dbCheckBreakpoint; + + if(this._pathManager.useAutoPathMode === true){ Tools.rebuildAcceptExtMap(args.luaFileExtension); - Tools.rebuildWorkspaceNamePathMap(args.cwd); - Tools.checkSameNameFile(); + // 判断 args.cwd 是否存在, 如果不存在给出提示,并停止运行 + let isCWDExist = fs.existsSync(args.cwd); + if(!isCWDExist){ + vscode.window.showErrorMessage("[Error] launch.json 文件中 cwd 指向的路径 " + args.cwd + " 不存在,请修改后再次运行!" , "好的"); + return; + } + this._pathManager.rebuildWorkspaceNamePathMap(args.cwd); + this._pathManager.checkSameNameFile(!!args.distinguishSameNameFile); + } + + // 普通模式下才需要检查升级,单文件调试不用 + if(args.tag != "independent_file"){ + try { + new UpdateManager().checkIfLuaPandaNeedUpdate(this._pathManager.LuaPandaPath, args.cwd); + } catch (error) { + DebugLogger.AdapterInfo("[Error] 检查升级信息失败,可选择后续手动升级。 https://github.com/Tencent/LuaPanda/blob/master/Docs/Manual/update.md "); + } } - //去除out, Debugger/debugger_lib/plugins/Darwin/ libpdebug_版本号.so - let clibPath = path.dirname(__dirname) + '/Debugger/debugger_lib/plugins/' let sendArgs = new Array(); sendArgs["stopOnEntry"] = !!args.stopOnEntry; sendArgs["luaFileExtension"] = args.luaFileExtension; @@ -152,107 +257,40 @@ export class LuaDebugSession extends LoggingDebugSession { sendArgs["isNeedB64EncodeStr"] = !!args.isNeedB64EncodeStr; sendArgs["TempFilePath"] = args.TempFilePath; sendArgs["logLevel"] = args.logLevel; - sendArgs["debugMode"] = args.DebugMode; sendArgs["pathCaseSensitivity"] = args.pathCaseSensitivity; sendArgs["OSType"] = os.type(); - sendArgs["clibPath"] = clibPath; + sendArgs["clibPath"] = Tools.getClibPathInExtension(); sendArgs["useCHook"] = args.useCHook; - let pkg = require("../package.json"); - let adapterVersion = pkg.version; - sendArgs["adapterVersion"] = String(adapterVersion); - sendArgs["autoPathMode"] = Tools.useAutoPathMode; + sendArgs["adapterVersion"] = String(Tools.adapterVersion); + sendArgs["autoPathMode"] = this._pathManager.useAutoPathMode; + sendArgs["distinguishSameNameFile"] = !!args.distinguishSameNameFile; + sendArgs["truncatedOPath"] = String(args.truncatedOPath); + sendArgs["DevelopmentMode"] = String(args.DevelopmentMode); + Tools.developmentMode = args.DevelopmentMode; if(args.docPathReplace instanceof Array && args.docPathReplace.length === 2 ){ - LuaDebugSession.replacePath = new Array( Tools.genUnifiedPath(String(args.docPathReplace[0])), Tools.genUnifiedPath(String(args.docPathReplace[1]))); + this.replacePath = new Array( Tools.genUnifiedPath(String(args.docPathReplace[0])), Tools.genUnifiedPath(String(args.docPathReplace[1]))); }else{ - LuaDebugSession.replacePath = null; + this.replacePath = null; } - LuaDebugSession.autoReconnect = args.autoReconnect; + this.autoReconnect = args.autoReconnect; //2. 初始化内存分析状态栏 StatusBarManager.reset(); - //3. 把response装入回调 - let callbackArgs = new Array(); - callbackArgs.push(this); - callbackArgs.push(response); - //4. 启动Adapter的socket | VSCode = Server ; Debugger = Client - LuaDebugSession._server = Net.createServer(socket => { - //--connect-- - DebugLogger.AdapterInfo("Debugger " + socket.remoteAddress + ":" + socket.remotePort + " connect!"); - DataProcesser._socket = socket; - //向debugger发送含配置项的初始化协议 - this._runtime.start((arr, info) => { - DebugLogger.AdapterInfo("已建立连接,发送初始化协议和断点信息!"); - //对luapanda.lua的版本控制,低于一定版本要提示升级 - if (typeof info.debuggerVer == "string"){ - //转数字 - let DVerArr = info.debuggerVer.split("."); - let AVerArr = String(adapterVersion).split("."); - if (DVerArr.length === AVerArr.length && DVerArr.length === 3 ){ - //在adapter和debugger版本号长度相等的前提下,比较大版本,大版本 <2 或者 小版本 < 1 就提示. 2.1.0以下会提示 - if ( parseInt(DVerArr[0]) < 2 || parseInt(DVerArr[1]) < 1 ){ - DebugLogger.showTips("当前调试器的lua文件版本过低,可能无法正常使用,请升级到最新版本。帮助文档 https://github.com/Tencent/LuaPanda/blob/master/Docs/Manual/update.md ", 2); - } - }else{ - DebugLogger.showTips("调试器版本号异常:" + info.debuggerVer + ". 建议升级至最新版本。帮助文档 https://github.com/Tencent/LuaPanda/blob/master/Docs/Manual/update.md ", 1); - } - } - if (info.UseLoadstring === "1") { - this.UseLoadstring = true; - } else { - this.UseLoadstring = false; - } - if (info.isNeedB64EncodeStr === "true") { - LuaDebugSession.isNeedB64EncodeStr = true; - } else { - LuaDebugSession.isNeedB64EncodeStr = false; - } - if (info.UseHookLib === "1") { } - //已建立连接,并完成初始化 - let ins = arr[0]; - ins.sendResponse(arr[1]); - LuaDebugSession.userConnectionFlag = true; - LuaDebugSession.isListening = false; - //发送断点信息 - for (let bkMap of LuaDebugSession.breakpointsArray) { - this._runtime.setBreakPoint(bkMap.bkPath, bkMap.bksArray, null, null); - } - }, callbackArgs, sendArgs); - //--connect end-- - socket.on('end', () => { - DebugLogger.AdapterInfo('socket end'); - }); - - socket.on('close', () => { - if (LuaDebugSession.isListening == true) { - DebugLogger.AdapterInfo('close socket when listening!'); - return; - } - DebugLogger.AdapterInfo('Socket close!'); - vscode.window.showInformationMessage('Stop connecting!'); - //停止连接 - LuaDebugSession._server.close(); - LuaDebugSession.userConnectionFlag = false; - delete DataProcesser._socket; - //停止VSCode的调试模式 - this.sendEvent(new TerminatedEvent(LuaDebugSession.autoReconnect)); - }); - - socket.on('data', (data) => { - DebugLogger.AdapterInfo('[Get Msg]:' + data); - DataProcesser.processMsg(data.toString()); - }); - }).listen(LuaDebugSession.TCPPort, function () { - DebugLogger.AdapterInfo("listening..."); - DebugLogger.DebuggerInfo("listening..."); + if(this.VSCodeAsClient){ + // VSCode = Client ; Debugger = Server + this.printLogInDebugConsole("[Connecting] 调试器 VSCode Client 已启动,正在尝试连接。 TargetName:" + args.name + " Port:" + args.connectionPort ); + this.startClient(sendArgs); + }else{ + this.printLogInDebugConsole("[Listening] 调试器 VSCode Server 已启动,正在等待连接。 TargetName:" + args.name + " Port:" + args.connectionPort ); + this.startServer(sendArgs); + } - }); - LuaDebugSession.isListening = true; - LuaDebugSession.breakpointsArray = new Array(); + this.breakpointsArray = new Array(); this.sendEvent(new InitializedEvent()); //收到返回后,执行setbreakpoint - //单文件调试模式 - if(args.name === 'LuaPanda-DebugFile'){ + //单文件调试模式。 + if( args.tag === "independent_file" ){ // 获取活跃窗口 let retObject = Tools.getVSCodeAvtiveFilePath(); if( retObject["retCode"] !== 0 ){ @@ -261,21 +299,24 @@ export class LuaDebugSession extends LoggingDebugSession { } let filePath = retObject["filePath"]; - const terminal = vscode.window.createTerminal({ - name: "Run Lua File (LuaPanda)", + if(this._debugFileTermianl){ + this._debugFileTermianl.dispose(); + } + this._debugFileTermianl = vscode.window.createTerminal({ + name: "Debug Lua File (LuaPanda)", env: {}, }); // 把路径加入package.path let pathCMD = "'"; - let pathArr = path.dirname(__dirname).split( path.sep ); - let stdPath = pathArr.join('/'); + let pathArr = Tools.VSCodeExtensionPath.split( path.sep ); + let stdPath = pathArr.join('/'); pathCMD = pathCMD + stdPath + "/Debugger/?.lua;" pathCMD = pathCMD + args.packagePath.join(';') pathCMD = pathCMD + "'"; //拼接命令 pathCMD = " \"package.path = " + pathCMD + ".. package.path; "; - let reqCMD = "require('LuaPanda').start('127.0.0.1'," + LuaDebugSession.TCPPort + ");\" "; + let reqCMD = "require('LuaPanda').start('127.0.0.1'," + this.TCPPort + ");\" "; let doFileCMD = filePath; let runCMD = pathCMD + reqCMD + doFileCMD; @@ -285,30 +326,165 @@ export class LuaDebugSession extends LoggingDebugSession { }else{ LuaCMD = "lua -e "; } - terminal.sendText( LuaCMD + runCMD , true); - terminal.show(); + this._debugFileTermianl.sendText( LuaCMD + runCMD , true); + this._debugFileTermianl.show(); } else{ // 非单文件调试模式下,拉起program - let fs = require('fs'); - if(fs.existsSync(args.program) && fs.statSync(args.program).isFile()){ - //program 和 args 分开 - const terminal = vscode.window.createTerminal({ - name: "Run program file (LuaPanda)", - env: {}, - }); - - let progaamCmdwithArgs = args.program; - for (const arg of args.args) { - progaamCmdwithArgs = progaamCmdwithArgs + " " + arg; + if(args.program != undefined && args.program.trim() != ''){ + if(fs.existsSync(args.program) && fs.statSync(args.program).isFile()){ + //program 和 args 分开 + if(this._programTermianl){ + this._programTermianl.dispose(); + } + this._programTermianl = vscode.window.createTerminal({ + name: "Run Program File (LuaPanda)", + env: {}, + }); + + let progaamCmdwithArgs = '"' + args.program + '"'; + if (os.type() === "Windows_NT") { + progaamCmdwithArgs = '& ' + progaamCmdwithArgs; + } + + for (const arg of args.args) { + progaamCmdwithArgs = progaamCmdwithArgs + " " + arg; + } + + this._programTermianl.sendText(progaamCmdwithArgs , true); + this._programTermianl.show(); + }else{ + let progError = "[Warning] 配置文件 launch.json 中的 program 路径有误: \n"; + progError += " + program 配置项的作用是,在调试器开始运行时拉起一个可执行文件(注意不是lua文件)。"; + progError += "如无需此功能,建议 program 设置为 \"\" 或从 launch.json 中删除 program 项。\n"; + progError += " + 当前设置的 " + args.program + " 不存在或不是一个可执行文件。"; + this.printLogInDebugConsole(progError); } - - terminal.sendText(progaamCmdwithArgs , true); - terminal.show(); } } } + private startServer(sendArgs){ + this.connectionFlag = false; + //3. 启动Adapter的socket | VSCode = Server ; Debugger = Client + this._server = Net.createServer(socket => { + //--connect-- + this._dataProcessor._socket = socket; + //向debugger发送含配置项的初始化协议 + this._runtime.start(( _ , info) => { + //之所以使用 connectionFlag 连接成功标志位, 是因为代码进入 Net.createServer 的回调后,仍然可能被client超时断开连接。所以标志位被放入了 + //_runtime.start 初始化消息发送成功之后。 + this.connectionFlag = true; + this._server.close(); //_server 已建立连接,不再接受新的连接 + let connectMessage = "[Connected] VSCode Server 已建立连接! Remote device info " + socket.remoteAddress + ":" + socket.remotePort ; + DebugLogger.AdapterInfo(connectMessage); + this.printLogInDebugConsole(connectMessage); + this.printLogInDebugConsole("[Tips] 当停止在断点处时,可在调试控制台输入要观察变量或执行表达式. " ); + + if (info.UseLoadstring === "1") { + this.UseLoadstring = true; + } else { + this.UseLoadstring = false; + } + if (info.isNeedB64EncodeStr === "true") { + this._dataProcessor.isNeedB64EncodeStr = true; + } else { + this._dataProcessor.isNeedB64EncodeStr = false; + } + if (info.UseHookLib === "1") { } + //已建立连接,并完成初始化 + //发送断点信息 + for (let bkMap of this.breakpointsArray) { + this._runtime.setBreakPoint(bkMap.bkPath, bkMap.bksArray, null, null); + } + }, sendArgs); + //--connect end-- + socket.on('end', () => { + DebugLogger.AdapterInfo('socket end'); + }); + + socket.on('close', () => { + if( this.connectionFlag ){ + this.connectionFlag = false; + DebugLogger.AdapterInfo('Socket close!'); + vscode.window.showInformationMessage('[LuaPanda] 调试器已断开连接'); + // this._dataProcessor._socket 是在建立连接后赋值,所以在断开连接时删除 + delete this._dataProcessor._socket; + this.sendEvent(new TerminatedEvent(this.autoReconnect)); + } + }); + + socket.on('data', (data) => { + DebugLogger.AdapterInfo('[Get Msg]:' + data); + this._dataProcessor.processMsg(data.toString()); + }); + }).listen(this.TCPPort, 0 , function () { + DebugLogger.AdapterInfo("listening..."); + DebugLogger.DebuggerInfo("listening..."); + + }); + + } + + private startClient(sendArgs){ + // 循环发送connect请求,每次请求持续1s。 + // 停止循环的时机 : 1建立连接后 2未建立连接,但是用户点击VScode stop按钮 + this.connectInterval = setInterval(begingConnect, 1000, this); + + function begingConnect(instance){ + instance._client = Net.createConnection(instance.TCPPort, instance.connectionIP); + //设置超时时间 + instance._client.setTimeout(800); + + instance._client.on('connect', () => { + clearInterval(instance.connectInterval); //连接后清除循环请求 + instance._dataProcessor._socket = instance._client; + instance._runtime.start(( _ , info) => { + let connectMessage = "[Connected] VSCode Client 已建立连接!"; + DebugLogger.AdapterInfo(connectMessage); + instance.printLogInDebugConsole(connectMessage); + instance.printLogInDebugConsole("[Tips] 当停止在断点处时,可在调试控制台输入要观察变量或执行表达式." ); + //已建立连接,并完成初始化 + if (info.UseLoadstring === "1") { + instance.UseLoadstring = true; + } else { + instance.UseLoadstring = false; + } + if (info.isNeedB64EncodeStr === "true") { + instance._dataProcessor.isNeedB64EncodeStr = true; + } else { + instance._dataProcessor.isNeedB64EncodeStr = false; + } + if (info.UseHookLib === "1") { } + //已建立连接,并完成初始化 + //发送断点信息 + for (let bkMap of instance.breakpointsArray) { + instance._runtime.setBreakPoint(bkMap.bkPath, bkMap.bksArray, null, null); + } + }, sendArgs); + }); + + instance._client.on('end', () => { + // VScode client 主动发起断开连接 + DebugLogger.AdapterInfo("client end"); + vscode.window.showInformationMessage('[LuaPanda] 调试器已断开连接'); + // this._dataProcessor._socket 是在建立连接后赋值,所以在断开连接时删除 + delete instance._dataProcessor._socket; + instance.sendEvent(new TerminatedEvent(instance.autoReconnect)); + }); + + instance._client.on('close', () => { + // 可能是连接后断开,也可能是超时关闭socket + // DebugLogger.AdapterInfo('client close!'); + }); + //接收消息 + instance._client.on('data', (data) => { + DebugLogger.AdapterInfo('[Get Msg]:' + data); + instance._dataProcessor.processMsg(data.toString()); + }); + } + } + /** * VSCode -> Adapter 设置(删除)断点 */ @@ -317,8 +493,8 @@ export class LuaDebugSession extends LoggingDebugSession { let path = args.source.path; path = Tools.genUnifiedPath(path); - if(LuaDebugSession.replacePath && LuaDebugSession.replacePath.length === 2){ - path = path.replace(LuaDebugSession.replacePath[1], LuaDebugSession.replacePath[0]); + if(this.replacePath && this.replacePath.length === 2){ + path = path.replace(this.replacePath[1], this.replacePath[0]); } let vscodeBreakpoints = new Array(); //VScode UI识别的断点(起始行号1) @@ -345,12 +521,12 @@ export class LuaDebugSession extends LoggingDebugSession { }; // 更新记录数据中的断点 - if (LuaDebugSession.breakpointsArray == undefined) { - LuaDebugSession.breakpointsArray = new Array(); + if (this.breakpointsArray == undefined) { + this.breakpointsArray = new Array(); } let isbkPathExist = false; //断点路径已经存在于断点列表中 - for (let bkMap of LuaDebugSession.breakpointsArray) { + for (let bkMap of this.breakpointsArray) { if (bkMap.bkPath === path) { bkMap["bksArray"] = vscodeBreakpoints; isbkPathExist = true; @@ -361,10 +537,10 @@ export class LuaDebugSession extends LoggingDebugSession { let bk = new Object(); bk["bkPath"] = path; bk["bksArray"] = vscodeBreakpoints; - LuaDebugSession.breakpointsArray.push(bk); + this.breakpointsArray.push(bk); } - if (DataProcesser._socket && LuaDebugSession.userConnectionFlag) { + if (this._dataProcessor._socket) { //已建立连接 let callbackArgs = new Array(); callbackArgs.push(this); @@ -391,8 +567,8 @@ export class LuaDebugSession extends LoggingDebugSession { response.body = { stackFrames: stk.frames.map(f => { let source = f.file; - if(LuaDebugSession.replacePath && LuaDebugSession.replacePath.length === 2){ - source = source.replace(LuaDebugSession.replacePath[0], LuaDebugSession.replacePath[1]); + if(this.replacePath && this.replacePath.length === 2){ + source = source.replace(this.replacePath[0], this.replacePath[1]); } return new StackFrame(f.index, f.name, this.createSource(source), f.line); } @@ -628,21 +804,36 @@ export class LuaDebugSession extends LoggingDebugSession { } /** - * disconnect + * 断开和lua的连接 + * 关闭连接的调用顺序 停止连接时的公共方法要放入 disconnectRequest. + * 未建立连接 : disconnectRequest + * 当VScode主动停止连接 : disconnectRequest - > socket end -> socket close + * 当lua进程主动停止连接 : socket end -> socket close -> disconnectRequest */ protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args): void { - DebugLogger.AdapterInfo("disconnectRequest"); + let disconnectMessage = "[Disconnect Request] 调试器已断开连接."; + DebugLogger.AdapterInfo(disconnectMessage); + this.printLogInDebugConsole(disconnectMessage); + let restart = args.restart; - //给lua发消息,让lua停止运行 - let callbackArgs = new Array(); - callbackArgs.push(restart); - this._runtime.stopRun(arr => { - //客户端主动断开连接,这里仅做确认 - DebugLogger.AdapterInfo("确认stop"); - }, callbackArgs, 'stopRun'); - LuaDebugSession.userConnectionFlag = false; + if(this.VSCodeAsClient){ + clearInterval(this.connectInterval);// 在未建立连接的情况下清除循环 + this._client.end(); // 结束连接 + }else{ + // 给lua发消息,让lua client停止运行 + let callbackArgs = new Array(); + callbackArgs.push(restart); + this._runtime.stopRun(arr => { + //客户端主动断开连接,这里仅做确认 + DebugLogger.AdapterInfo("确认stop"); + }, callbackArgs, 'stopRun'); + this._server.close(); // 关闭 server, 停止 listen. 放在这里的原因是即使未建立连接,也可以停止listen. + } + + // 删除自身的线程id, 并从LuaDebugSession实例列表中删除自身 + this._threadManager.destructor(); + LuaDebugSession._debugSessionArray.delete(this._threadManager.CUR_THREAD_ID); this.sendResponse(response); - LuaDebugSession._server.close(); } protected restartRequest(response: DebugProtocol.RestartResponse, args: DebugProtocol.RestartArguments): void { @@ -660,7 +851,7 @@ export class LuaDebugSession extends LoggingDebugSession { protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { response.body = { threads: [ - new Thread(LuaDebugSession.THREAD_ID, "thread 1") + new Thread(this._threadManager.CUR_THREAD_ID, "thread " + this._threadManager.CUR_THREAD_ID) ] }; this.sendResponse(response); diff --git a/src/luaDebugRuntime.ts b/src/debug/luaDebugRuntime.ts similarity index 76% rename from src/luaDebugRuntime.ts rename to src/debug/luaDebugRuntime.ts index 00bd7ae..a6c0c88 100755 --- a/src/luaDebugRuntime.ts +++ b/src/debug/luaDebugRuntime.ts @@ -1,10 +1,10 @@ import * as vscode from 'vscode'; import { EventEmitter } from 'events'; -import { DataProcesser } from './dataProcesser'; +import { DataProcessor } from './dataProcessor'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { DebugLogger } from './LogManager'; -import { StatusBarManager } from './StatusBarManager'; -import { Tools } from './Tools'; +import { DebugLogger } from '../common/logManager'; +import { StatusBarManager } from '../common/statusBarManager'; +import { PathManager } from '../common/pathManager'; export interface LuaBreakpoint { @@ -16,6 +16,8 @@ export interface LuaBreakpoint { export class LuaDebugRuntime extends EventEmitter { //当前读取的文件 private _sourceFile: string; + public _dataProcessor: DataProcessor; + public _pathManager: PathManager; public get sourceFile() { return this._sourceFile; } @@ -47,12 +49,12 @@ export class LuaDebugRuntime extends EventEmitter { * @param callbackArgs:回调参数 * @param sendArgs:发给debugger的参数 */ - public start(callback, callbackArgs, sendArgs) { + public start(callback, sendArgs) { let arrSend = new Object(); for (let key in sendArgs) { arrSend[key] = String(sendArgs[key]); } - DataProcesser.commandToDebugger('initSuccess', arrSend, callback, callbackArgs); + this._dataProcessor.commandToDebugger('initSuccess', arrSend, callback); } /** @@ -64,7 +66,22 @@ export class LuaDebugRuntime extends EventEmitter { public continue(callback, callbackArgs, event = 'continue') { DebugLogger.AdapterInfo("continue"); let arrSend = new Object(); - DataProcesser.commandToDebugger(event, arrSend, callback, callbackArgs); + this._dataProcessor.commandToDebugger(event, arrSend, callback, callbackArgs); + } + + /** + * 通知Debugger继续执行 + * @param callback: 收到请求返回后的回调函数 + * @param callbackArgs:回调参数 + * @param event:事件名 + */ + public continueWithFakeHitBk(callback ,callbackArgs = null, event = 'continue') { + DebugLogger.AdapterInfo("continue"); + let arrSend = new Object(); + arrSend["fakeBKPath"] = String(this.breakStack[0].oPath); + arrSend["fakeBKLine"] = String(this.breakStack[0].line); + arrSend["isFakeHit"] = String(true); + this._dataProcessor.commandToDebugger(event, arrSend, callback, callbackArgs); } /** @@ -80,7 +97,7 @@ export class LuaDebugRuntime extends EventEmitter { let arrSend = new Object(); arrSend["varName"] = String(varName); arrSend["stackId"] = String(frameId); - DataProcesser.commandToDebugger(event, arrSend, callback, callbackArgs); + this._dataProcessor.commandToDebugger(event, arrSend, callback, callbackArgs); } /** @@ -96,7 +113,7 @@ export class LuaDebugRuntime extends EventEmitter { let arrSend = new Object(); arrSend["Expression"] = String(expression); arrSend["stackId"] = String(frameId); - DataProcesser.commandToDebugger(event, arrSend, callback, callbackArgs); + this._dataProcessor.commandToDebugger(event, arrSend, callback, callbackArgs); } /** @@ -116,7 +133,7 @@ export class LuaDebugRuntime extends EventEmitter { arrSend["stackId"] = String(frameId); arrSend["newValue"] = String(newValue); arrSend["varName"] = String(name); - DataProcesser.commandToDebugger(event, arrSend, callback, callbackArgs); + this._dataProcessor.commandToDebugger(event, arrSend, callback, callbackArgs); } /** @@ -133,7 +150,7 @@ export class LuaDebugRuntime extends EventEmitter { let arrSend = new Object(); arrSend["varRef"] = String(variableRef); arrSend["stackId"] = String(frameId); - DataProcesser.commandToDebugger(event, arrSend, callback, callbackArgs, 3); + this._dataProcessor.commandToDebugger(event, arrSend, callback, callbackArgs, 3); } /** @@ -141,7 +158,7 @@ export class LuaDebugRuntime extends EventEmitter { */ public stopRun(callback, callbackArgs, event = 'stopRun') { let arrSend = new Object(); - DataProcesser.commandToDebugger(event, arrSend, callback, callbackArgs); + this._dataProcessor.commandToDebugger(event, arrSend, callback, callbackArgs); } /** @@ -150,7 +167,7 @@ export class LuaDebugRuntime extends EventEmitter { public step(callback, callbackArgs, event = 'stopOnStep') { DebugLogger.AdapterInfo("step:" + event); let arrSend = new Object(); - DataProcesser.commandToDebugger(event, arrSend, callback, callbackArgs); + this._dataProcessor.commandToDebugger(event, arrSend, callback, callbackArgs); } /** @@ -158,7 +175,7 @@ export class LuaDebugRuntime extends EventEmitter { */ public luaGarbageCollect(event = "LuaGarbageCollect") { let arrSend = new Object(); - DataProcesser.commandToDebugger(event, arrSend); + this._dataProcessor.commandToDebugger(event, arrSend); } /** @@ -173,7 +190,7 @@ export class LuaDebugRuntime extends EventEmitter { let arrSend = new Object(); arrSend["path"] = path; arrSend["bks"] = bks; - DataProcesser.commandToDebugger("setBreakPoint", arrSend, callback, callbackArgs); + this._dataProcessor.commandToDebugger("setBreakPoint", arrSend, callback, callbackArgs); } /** @@ -214,6 +231,13 @@ export class LuaDebugRuntime extends EventEmitter { vscode.window.showErrorMessage(tip); } + /** + * 在调试控制台中打印日志 + */ + public logInDebugConsole(message: string) { + this.sendEvent('logInDebugConsole', message); + } + /** * 命中断点 */ @@ -222,7 +246,8 @@ export class LuaDebugRuntime extends EventEmitter { let linenum: string = element.line; element.line = parseInt(linenum); //转为VSCode行号(int) let getinfoPath : string = element.file; - element.file = Tools.checkFullPath(getinfoPath); + let oPath = element.oPath; + element.file = this._pathManager.checkFullPath(getinfoPath, oPath); }); //先保存堆栈信息,再发暂停请求 this.breakStack = stack; diff --git a/src/debug/updateManager.ts b/src/debug/updateManager.ts new file mode 100644 index 0000000..b58728c --- /dev/null +++ b/src/debug/updateManager.ts @@ -0,0 +1,75 @@ +import { Tools } from '../common/tools'; +import { DebugLogger } from '../common/logManager'; +import { VisualSetting } from './visualSetting'; +import * as vscode from 'vscode'; +import * as fs from "fs"; + +export class UpdateManager{ + private static checkUpdate = true; + + public setCheckUpdate(state){ + UpdateManager.checkUpdate = state; + } + + // 获取调试器lua文件的版本号,并提示用户升级 + public checkIfLuaPandaNeedUpdate(LuaPandaPath, rootFolder){ + if(!UpdateManager.checkUpdate || !LuaPandaPath){ + return; + } + + let luapandaTxt = Tools.readFileContent(LuaPandaPath); + let dver = luapandaTxt.match(/(?<=local debuggerVer = )("(.*?)")/); + if(dver && dver.length === 3){ + let DVerArr = dver[2].split('.'); + let AVerArr = String(Tools.adapterVersion).split("."); + if (DVerArr.length === AVerArr.length && DVerArr.length === 3 ){ + let intDVer = parseInt(DVerArr[0]) * 10000 + parseInt(DVerArr[1]) * 100 + parseInt(DVerArr[2]); + let intAVer = parseInt(AVerArr[0]) * 10000 + parseInt(AVerArr[1]) * 100 + parseInt(AVerArr[2]); + + let updateTipSetting = VisualSetting.getLaunchjson(rootFolder , "updateTips"); + if ( intDVer < intAVer && updateTipSetting !== false){ + // if ( intDVer < intAVer){ + vscode.window.showInformationMessage('LuaPanda VSCode 插件已升级 ' + Tools.adapterVersion + ' 版本, 建议同时升级 LuaPanda.lua 文件。首次开始调试前请重建一下 launch.json 文件, 避免产生兼容问题。launch.json 配置项目参考 https://github.com/Tencent/LuaPanda/blob/master/Docs/Manual/launch-json-introduction.md', "好的"); + + vscode.window.showInformationMessage('当前工程中的 LuaPanda.lua 文件版本较低,是否自动替换为最新版本?', 'Yes', 'No', 'Never').then(value => { + if(value === "Yes"){ + let confirmButton = "立刻升级"; + vscode.window.showInformationMessage('已准备好更新 ' + LuaPandaPath+ '。如用户对此文件有修改, 建议备份后再升级, 避免修改内容被覆盖', confirmButton, '稍后再试').then(value => { + if(value === confirmButton){ + this.updateLuaPandaFile(LuaPandaPath) + } + }); + } + else if(value === "No"){ + // 本次插件运行期间不再提示 + vscode.window.showInformationMessage('本次运行期间 LuaPanda 将不再弹出升级提示', "好的"); + this.setCheckUpdate(false); + }else if(value === "Never"){ + // 永久不再提示升级 + vscode.window.showInformationMessage('本项目调试时将不会再弹出调试器升级提示,需要升级请参考 https://github.com/Tencent/LuaPanda/blob/master/Docs/Manual/update.md', "好的"); + this.setCheckUpdate(false); + // 把信息标记在 launch.json上 + VisualSetting.setLaunchjson(rootFolder, "updateTips", false); + }; + }); + } + }else{ + //版本号异常,不做处理 + } + } + } + + // 更新调试器lua文件(读取预置文件,写入工程的目标文件中) + public updateLuaPandaFile(LuaPandaPath) { + //文件替换 + let luapandaContent = fs.readFileSync(Tools.getLuaPathInExtension()); + try { + fs.writeFileSync(LuaPandaPath, luapandaContent); + DebugLogger.showTips("升级成功, " + LuaPandaPath + " 已升级到 " + Tools.adapterVersion , 0); + } catch (error) { + DebugLogger.showTips("升级失败, " + LuaPandaPath + "写入失败! 可以手动替换此文件到github最新版", 1); + } finally { + this.setCheckUpdate(false); + } + } +} \ No newline at end of file diff --git a/src/debug/visualSetting.ts b/src/debug/visualSetting.ts new file mode 100644 index 0000000..7628839 --- /dev/null +++ b/src/debug/visualSetting.ts @@ -0,0 +1,233 @@ +// 可视化配置部分 +import { Tools } from '../common/tools'; +import * as fs from "fs"; +import * as vscode from 'vscode'; +import { DebugLogger } from '../common/logManager'; + +export class VisualSetting { + private static ADBRevTerminal; + + // 修改launch.json中的一项 + public static setLaunchjson(rootFolder, key, value, tag = ''){ + let settings = this.readLaunchjson(rootFolder); + for (const keyLaunch in settings.configurations) { + let valueLaunch = settings.configurations[keyLaunch] + if(tag === '' || valueLaunch["tag"] === tag){ + valueLaunch[key] = value; + } + } + + //序列化并写入 + let launchJson = JSON.stringify(settings, null, 4); + Tools.writeFileContent(rootFolder + "/.vscode/launch.json" ,launchJson); + } + + // 获取launch.json中的一项 + public static getLaunchjson(rootFolder, key, tag = ''){ + let settings = this.readLaunchjson(rootFolder); + for (const keyLaunch in settings.configurations) { + let valueLaunch = settings.configurations[keyLaunch] + if(tag === '' || valueLaunch["tag"] === tag){ + return valueLaunch[key]; + } + } + } + + public static readLaunchjson(rootFolder){ + let launchPath = rootFolder + "/.vscode/launch.json"; + //如果文件不存在,就创建一个 + let launchExist = fs.existsSync(launchPath); + let jsonStr; + if(!launchExist){ + let dotVScodeDirExist = fs.existsSync(rootFolder + "/.vscode"); + if(!dotVScodeDirExist){ + //创建.vscode目录 + fs.mkdirSync(rootFolder + "/.vscode"); + } + // 文件不存在,读取预制文件,创建launch + let launchTemplate = Tools.readFileContent(Tools.VSCodeExtensionPath + "/res/others/launch.json"); + Tools.writeFileContent(rootFolder + "/.vscode/launch.json" ,launchTemplate); + jsonStr = launchTemplate; + }else{ + // 文件存在,读取launch.json的信息 + jsonStr = Tools.readFileContent(launchPath); + } + + if(jsonStr == null || jsonStr == ''){ + // 没有找到launch.json 文件,生成一份(读取预制内容,拷贝到其中) + return null; + } + + //去除注释行 + let reg = /[^:]((\/\/.*)|(\/\*[\s\S]*?\*\/))/g;// 正则表达式 + jsonStr = jsonStr.replace(reg, ''); + let launchSettings = JSON.parse(jsonStr); + return launchSettings; + } + + // 读取launch.json中的信息,并序列化 + public static getLaunchData(rootFolderArray){ + let jsonObj = new Object(); + + let snippetsPath = Tools.VSCodeExtensionPath + "/res/snippets/snippets.json"; + let isOpenAnalyzer = true; + let snipContent = fs.readFileSync(snippetsPath); + if(snipContent.toString().trim() == ''){ + isOpenAnalyzer = false; + } + jsonObj["command"] = "init_setting"; + jsonObj["isOpenAnalyzer"] = isOpenAnalyzer; + jsonObj["configs"] = []; + let index = 0; + for (const forderName in rootFolderArray) { + let rootFolder = rootFolderArray[forderName]; + jsonObj["configs"][index] = {"path": rootFolder, "launch.json": {}}; + // jsonObj["configs"][index]["path"] = rootFolder; + // jsonObj["configs"][index]["launch.json"] = new Object(); + let settings = this.readLaunchjson(rootFolder); + for (const key in settings.configurations) { + const v = settings.configurations[key]; + + if(v["tag"] === "normal" || v["name"] === "LuaPanda" ){ + jsonObj["configs"][index]["launch.json"][v["name"]] = v; + } + else if(v["tag"] === "attach" || v["name"] === "LuaPanda-Attach"){ + jsonObj["configs"][index]["launch.json"][v["name"]] = v; + } + else if(v["tag"] === "independent_file" || v["name"] === "LuaPanda-IndependentFile"){ + jsonObj["configs"][index]["launch.json"][v["name"]] = v; + } + } + + if(Object.keys(jsonObj["configs"][index]["launch.json"]).length === 0 ){ + //读取预制内容,传给页面 + let launchTemplate = Tools.readFileContent(Tools.VSCodeExtensionPath + "/res/others/launch.json"); + let settings = JSON.parse(launchTemplate); + for (const key in settings.configurations) { + const v = settings.configurations[key]; + if(v["tag"] === "normal" || v["name"] === "LuaPanda"){ + jsonObj["configs"][index]["launch.json"][v["name"]] = v; + } + + if(v["tag"] === "attach" || v["name"] === "LuaPanda-Attach"){ + jsonObj["configs"][index]["launch.json"][v["name"]] = v; + } + + if(v["tag"] === "independent_file" || v["name"] === "LuaPanda-IndependentFile"){ + jsonObj["configs"][index]["launch.json"][v["name"]] = v; + } + } + } + index ++; + } + + //setting反馈到html中 + return JSON.stringify(jsonObj); + } + + public static getWebMessage(message) { + let messageObj = JSON.parse(message.webInfo); + switch (messageObj.command) { + case 'save_settings': + this.processSaveSettings(messageObj); + break; + case 'adb_reverse': + this.processADBReverse(messageObj); + break; + case 'on_off_analyzer': + this.on_off_analyzer(messageObj); + break; + case 'preAnalysisCpp': + if(!messageObj.path || messageObj.path.trim() == ''){ + DebugLogger.showTips("C++ 文件分析失败,传入路径为空!",2); + }else{ + if (!fs.existsSync(messageObj.path.trim())) { + DebugLogger.showTips("输入了不存在的路径!", 2); + return; + } + + Tools.client.sendNotification('preAnalysisCpp', message.webInfo); + } + break; + case 'clearPreProcessFile': + //清除文件夹 + let removePath = messageObj.rootFolder + "/.vscode/LuaPanda/"; + let res =Tools.removeDir(removePath); + if(res){ + DebugLogger.showTips("文件夹已经清除"); + }else{ + DebugLogger.showTips("文件不存在", 2); + } + break; + } + } + + private static on_off_analyzer(messageObj) { + let userControlBool = messageObj.switch; + //读文件判断当前是on或者off,如果文件不存在,按on处理 + let snippetsPath = Tools.VSCodeExtensionPath + "/res/snippets/snippets.json"; + let snippetsPathBackup = Tools.VSCodeExtensionPath + "/res/snippets/snippets_backup.json"; + + if(!userControlBool){ + // 用户关闭, 清空snippets + fs.writeFileSync(snippetsPath, ''); + DebugLogger.showTips("您已关闭了代码辅助功能,重启VScode后将不再有代码提示!"); + return; + } + + if(userControlBool){ + // 用户打开 + if(fs.existsSync(snippetsPathBackup)){ + // 读取snippetsPathBackup中的内容,写入snippets + fs.writeFileSync(snippetsPath, fs.readFileSync(snippetsPathBackup)); + } + DebugLogger.showTips("您已打开了代码辅助功能,重启VScode后将会启动代码提示!"); + return; + } + } + + private static processADBReverse(messageObj) { + let connectionPort = messageObj["connectionPort"]; + if(this.ADBRevTerminal){ + this.ADBRevTerminal.dispose(); + } + this.ADBRevTerminal = vscode.window.createTerminal({ + name: "ADB Reverse (LuaPanda)", + env: {}, + }); + + let cmd = "adb reverse tcp:" + connectionPort + " tcp:" + connectionPort; + this.ADBRevTerminal.sendText(cmd , true); + this.ADBRevTerminal.show(); + } + + // 可视化界面保存配置 + private static processSaveSettings(messageObj) { + try { + const element = messageObj.configs; + let rootFolder = element.path; + let settings = this.readLaunchjson(rootFolder); + let newConfig = element["launch.json"]; + // let alreadyWriteIn = false; + for (const key in settings.configurations) { + let target_name = settings.configurations[key]["name"]; + if(newConfig[target_name]){ + settings.configurations[key] = newConfig[target_name]; + } + + } + // if(!alreadyWriteIn){ + // //launch.json中不存在luapanda项目 + // settings.configurations.push(newConfig); + // } + + //序列化并写入 + let launchJson = JSON.stringify(settings, null, 4); + Tools.writeFileContent(rootFolder + "/.vscode/launch.json" ,launchJson); + + DebugLogger.showTips("配置保存成功!"); + } catch (error) { + DebugLogger.showTips("配置保存失败, 可能是由于 launch.json 文件无法写入. 请手动修改 launch.json 中的配置项来完成配置!", 2); + } + } +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index bd8d508..0c636fb 100755 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,39 +1,145 @@ 'use strict'; import * as vscode from 'vscode'; import * as Net from 'net'; +import * as path from 'path'; +import { LuaDebugSession } from './debug/luaDebug'; +import { DebugLogger } from './common/logManager'; +import { StatusBarManager } from './common/statusBarManager'; +import { Tools } from './common/tools'; +import { + LanguageClient, + LanguageClientOptions, + ServerOptions, + TransportKind +} from 'vscode-languageclient'; +import { workspace, ExtensionContext } from 'vscode'; +import { VisualSetting } from './debug/visualSetting' +import { PathManager } from './common/pathManager'; -import { LuaDebugSession } from './luaDebug'; -import { DebugLogger } from './LogManager'; -import { StatusBarManager } from './StatusBarManager'; -import { Tools } from './Tools'; +let client: LanguageClient; -export function activate(context: vscode.ExtensionContext) { - //reloadWindow +export function activate(context: ExtensionContext) { + // reloadWindow let reloadWindow = vscode.commands.registerCommand('luapanda.reloadLuaDebug', function () { vscode.commands.executeCommand("workbench.action.reloadWindow") }); context.subscriptions.push(reloadWindow); - //force garbage collect + // force garbage collect let LuaGarbageCollect = vscode.commands.registerCommand('luapanda.LuaGarbageCollect', function () { - LuaDebugSession.getInstance().LuaGarbageCollect(); + for (var [ , instance] of LuaDebugSession.debugSessionArray) { + instance.LuaGarbageCollect(); + } vscode.window.showInformationMessage('Lua Garbage Collect!'); }); context.subscriptions.push(LuaGarbageCollect); + let openSettingsPage = vscode.commands.registerCommand('luapanda.openSettingsPage', function () { + //先尝试获取数据,如果数据获取失败,给错误提示。 + try{ + let launchData = VisualSetting.getLaunchData(PathManager.rootFolderArray); + // 和VSCode的交互 + let panel: vscode.WebviewPanel = vscode.window.createWebviewPanel( + 'LuaPanda Setting', + 'LuaPanda Setting', + vscode.ViewColumn.One, + { + retainContextWhenHidden: true, + enableScripts: true + } + ); + + panel.webview.html = Tools.readFileContent(Tools.VSCodeExtensionPath + '/res/web/settings.html'); + // Handle messages from the webview + panel.webview.onDidReceiveMessage(message => { + VisualSetting.getWebMessage(message) + }, + undefined, + context.subscriptions + ); + + panel.webview.postMessage(launchData); + }catch (error) { + DebugLogger.showTips("解析 launch.json 文件失败, 请检查此文件配置项是否异常, 或手动修改 launch.json 中的项目来完成配置!", 2); + } + + }); + context.subscriptions.push(openSettingsPage); + const provider = new LuaConfigurationProvider() context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('lua', provider)); context.subscriptions.push(provider); - //init log + + // 静态公共变量赋值 + let pkg = require( context.extensionPath + "/package.json"); + Tools.adapterVersion = pkg.version; + Tools.VSCodeExtensionPath = context.extensionPath; + // init log DebugLogger.init(); StatusBarManager.init(); + + // language server 相关 + // The server is implemented in node + let serverModule = context.asAbsolutePath( + path.join('out', 'code', 'server', 'server.js') + ); + // The debug options for the server + // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging + let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] }; + + // If the extension is launched in debug mode then the debug server options are used + // Otherwise the run options are used + let serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions + } + }; + + // Options to control the language client + let clientOptions: LanguageClientOptions = { + // Register the server for plain text documents + documentSelector: [{ scheme: 'file', language: 'lua' }], + synchronize: { + // Notify the server about file changes to '.clientrc files contained in the workspace + fileEvents: workspace.createFileSystemWatcher('**/.clientrc') + } + }; + + // Create the language client and start the client. + client = new LanguageClient( + 'lua_analyzer', + 'Lua Analyzer', + serverOptions, + clientOptions + ); + + // Start the client. This will also launch the server + client.start(); + client.onReady().then(() => { + Tools.client = client; + client.onNotification("setRootFolders", setRootFolders); + client.onNotification("showProgress", showProgress); // 初始化进度 + client.onNotification("showErrorMessage", showErrorMessage); + client.onNotification("showWarningMessage", showWarningMessage); + client.onNotification("showInformationMessage", showInformationMessage); + }); + } export function deactivate() { - // nothing to do + if (!client) { + return undefined; + } + Tools.client = undefined; + return client.stop(); } +// debug启动时的配置项处理 class LuaConfigurationProvider implements vscode.DebugConfigurationProvider { private _server?: Net.Server; + private static RunFileTerminal; resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): vscode.ProviderResult { // if launch.json is missing or empty if (!config.type && !config.name) { @@ -56,7 +162,10 @@ class LuaConfigurationProvider implements vscode.DebugConfigurationProvider { } let filePath = retObject["filePath"]; - const terminal = vscode.window.createTerminal({ + if(LuaConfigurationProvider.RunFileTerminal){ + LuaConfigurationProvider.RunFileTerminal.dispose(); + } + LuaConfigurationProvider.RunFileTerminal = vscode.window.createTerminal({ name: "Run Lua File (LuaPanda)", env: {}, }); @@ -64,8 +173,8 @@ class LuaConfigurationProvider implements vscode.DebugConfigurationProvider { // 把路径加入package.path let path = require("path"); let pathCMD = "'"; - let pathArr = path.dirname(__dirname).split( path.sep ); - let stdPath = pathArr.join('/'); + let pathArr = Tools.VSCodeExtensionPath.split( path.sep ); + let stdPath = pathArr.join('/'); pathCMD = pathCMD + stdPath + "/Debugger/?.lua;" pathCMD = pathCMD + config.packagePath.join(';') pathCMD = pathCMD + "'"; @@ -80,95 +189,133 @@ class LuaConfigurationProvider implements vscode.DebugConfigurationProvider { }else{ LuaCMD = "lua -e "; } - terminal.sendText( LuaCMD + runCMD , true); - terminal.show(); + LuaConfigurationProvider.RunFileTerminal.sendText( LuaCMD + runCMD , true); + LuaConfigurationProvider.RunFileTerminal.show(); return ; } - // 关于打开调试控制台的自动设置 - if(config.name === "LuaPanda"){ - if(!config.internalConsoleOptions){ - config.internalConsoleOptions = "openOnFirstSessionStart"; + // 旧版本的launch.json中没有tag, 利用name给tag赋值 + if(config.tag == undefined){ + if(config.name === "LuaPanda"){ + config.tag = "normal" } - }else if(config.name === "LuaPanda-DebugFile"){ - if(!config.internalConsoleOptions){ - config.internalConsoleOptions = "neverOpen"; + else if(config.name === "LuaPanda-Attach"){ + config.tag = "attach" + } + // config.name === "LuaPanda-DebugFile" 是对 3.1.0 版本的兼容 + else if(config.name === "LuaPanda-IndependentFile" || config.name === "LuaPanda-DebugFile" ){ + config.tag = "independent_file" } - } - - if(!config.program){ - config.program = ''; - } - - if(!config.autoPathMode){ - config.autoPathMode = false; - } - if(!config.args){ - config.args = new Array(); } - if (!config.request) { - vscode.window.showInformationMessage("请在launch中配置request方式!"); - config.request = 'launch'; + // 关于打开调试控制台的自动设置 + if(config.tag === "independent_file"){ + if(!config.internalConsoleOptions){ + config.internalConsoleOptions = "neverOpen"; + } + }else{ + if(!config.internalConsoleOptions){ + config.internalConsoleOptions = "openOnSessionStart"; + } } - if (!config.cwd) { - vscode.window.showInformationMessage("请在launch中配置cwd工作路径!"); - config.cwd = '${workspaceFolder}'; - } + // rootFolder 固定为 ${workspaceFolder}, 用来查找本项目的launch.json. + config.rootFolder = '${workspaceFolder}'; if (!config.TempFilePath) { config.TempFilePath = '${workspaceFolder}'; } - if (!config.luaFileExtension) { - config.luaFileExtension = ''; - }else{ - let firseLetter = config.luaFileExtension.substr(0, 1); - if(firseLetter === '.'){ - config.luaFileExtension = config.luaFileExtension.substr(1); - } + // 开发模式设置 + if( config.DevelopmentMode !== true ){ + config.DevelopmentMode = false; } - if (config.stopOnEntry == undefined) { - vscode.window.showInformationMessage("请在launch中配置是否stopOnEntry") - config.stopOnEntry = true; - } + // attach 模式这里不用赋初值,后面会拷贝luapanda模式的配置信息 + if(config.tag !== "attach"){ + if(!config.program){ + config.program = ''; + } - if (config.pathCaseSensitivity == undefined) { - config.pathCaseSensitivity = true; - } + if(config.packagePath == undefined){ + config.packagePath = []; + } + + if(config.truncatedOPath == undefined){ + config.truncatedOPath = ""; + } - if (config.trace == undefined) { - config.trace = false; - } + if(config.distinguishSameNameFile == undefined){ + config.distinguishSameNameFile = false; + } - if (config.connectionPort == undefined) { - LuaDebugSession.TCPPort = 8818; - } else { - LuaDebugSession.TCPPort = config.connectionPort; - } + if(config.dbCheckBreakpoint == undefined){ + config.dbCheckBreakpoint = false; + } - if (config.logLevel == undefined) { - config.logLevel = 1; - } + if(!config.args){ + config.args = new Array(); + } - if (config.autoReconnect != true) { - config.autoReconnect = false; - } + if(config.autoPathMode == undefined){ + // 默认使用自动路径模式 + config.autoPathMode = true; + } + + if (!config.cwd) { + config.cwd = '${workspaceFolder}'; + } - //隐藏属性 - if (config.DebugMode == undefined) { - config.DebugMode = false; - } + if (!config.luaFileExtension) { + config.luaFileExtension = ''; + }else{ + // luaFileExtension 兼容 ".lua" or "lua" + let firseLetter = config.luaFileExtension.substr(0, 1); + if(firseLetter === '.'){ + config.luaFileExtension = config.luaFileExtension.substr(1); + } + } - if (config.useCHook == undefined) { - config.useCHook = true; - } + if (config.stopOnEntry == undefined) { + config.stopOnEntry = true; + } + + if (config.pathCaseSensitivity == undefined) { + config.pathCaseSensitivity = false; + } + + if (config.connectionPort == undefined) { + config.connectionPort = 8818; + } + + if (config.logLevel == undefined) { + config.logLevel = 1; + } + + if (config.autoReconnect != true) { + config.autoReconnect = false; + } + + if (config.updateTips == undefined) { + config.updateTips = true; + } + + if (config.useCHook == undefined) { + config.useCHook = true; + } + + if (config.isNeedB64EncodeStr == undefined) { + config.isNeedB64EncodeStr = true; + } - if (config.isNeedB64EncodeStr == undefined) { - config.isNeedB64EncodeStr = true; + if (config.VSCodeAsClient == undefined) { + config.VSCodeAsClient = false; + } + + if (config.connectionIP == undefined) { + config.connectionIP = "127.0.0.1"; + } } if (!this._server) { @@ -179,7 +326,7 @@ class LuaConfigurationProvider implements vscode.DebugConfigurationProvider { }).listen(0); } // make VS Code connect to debug server instead of launching debug adapter - config.debugServer = this._server.address().port; + config.debugServer = this._server.address()['port']; return config; } @@ -189,3 +336,24 @@ class LuaConfigurationProvider implements vscode.DebugConfigurationProvider { } } } + +// code server 消息回调函数 +function showProgress(message: string) { + StatusBarManager.showSetting(message); +} + +function setRootFolders(...rootFolders) { + PathManager.rootFolderArray = rootFolders; +} + +function showErrorMessage(str: string) { + vscode.window.showErrorMessage(str); +} + +function showWarningMessage(str: string) { + vscode.window.showWarningMessage(str); +} + +function showInformationMessage(str: string) { + vscode.window.showInformationMessage(str); +} diff --git a/src/tsconfig.json b/src/tsconfig.json index d5421db..c5c1dc4 100755 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,17 +1,18 @@ { "compilerOptions": { "module": "commonjs", + "lib": ["es6", "dom", "es2017"], "target": "es6", - "noImplicitAny": false, - "removeComments": false, - "noUnusedLocals": true, + "removeComments": true, + "noUnusedLocals": false, "noImplicitThis": true, "inlineSourceMap": false, "sourceMap": true, "outDir": "../out", "preserveConstEnums": true, - "strictNullChecks": true, - "noUnusedParameters": false + "strictNullChecks": false, + "noUnusedParameters": false, + "strict": false /* enable all strict type-checking options */ } } diff --git a/vsix/luapanda-3.1.1-for-unlua.vsix b/vsix/luapanda-3.1.1-for-unlua.vsix new file mode 100644 index 0000000..f4028e4 Binary files /dev/null and b/vsix/luapanda-3.1.1-for-unlua.vsix differ diff --git a/vsix/luapanda-3.3.0.vsix b/vsix/luapanda-3.3.0.vsix new file mode 100644 index 0000000..e5d7d67 Binary files /dev/null and b/vsix/luapanda-3.3.0.vsix differ diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..8f5dce2 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1537 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/commander@^2.3.31": + version "2.12.2" + resolved "https://registry.yarnpkg.com/@types/commander/-/commander-2.12.2.tgz#183041a23842d4281478fa5d23c5ca78e6fd08ae" + integrity sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q== + dependencies: + commander "*" + +"@types/diff@^3.2.0": + version "3.5.6" + resolved "https://registry.yarnpkg.com/@types/diff/-/diff-3.5.6.tgz#2524928a13888cebb59dc18e0c793022e7d02dfd" + integrity sha512-5BV7iGX/NmFGqAQn+YDBK++kO7IbZf0mIn8mwdJACIpZsMUqJvEin0riqNDbmS3SQL8u00dGnbC0FFJQptTSWw== + +"@types/get-stdin@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/get-stdin/-/get-stdin-5.0.1.tgz#46afbcaf09e94fe025afa07ae994ac3168adbdf3" + integrity sha512-hkShVHsKPKK4OulBjeYi15m5RQRR2ZV7ItFEa3C+QEay81MenD5ZfcTxzlo3ZaUTkbCbpw5XIW/NWKQNWq+Q/A== + dependencies: + "@types/node" "*" + +"@types/mocha@2.2.48": + version "2.2.48" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.48.tgz#3523b126a0b049482e1c3c11877460f76622ffab" + integrity sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw== + +"@types/node@*": + version "20.6.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.5.tgz#4c6a79adf59a8e8193ac87a0e522605b16587258" + integrity sha512-2qGq5LAOTh9izcc0+F+dToFigBWiK1phKPt7rNhOqJSr35y8rlIBjDwGtFSgAI6MGIhjwOVNSQZVdJsZJ2uR1w== + +"@types/node@^16.13.2": + version "16.18.54" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.54.tgz#4a63bdcea5b714f546aa27406a1c60621236a132" + integrity sha512-oTmGy68gxZZ21FhTJVVvZBYpQHEBZxHKTsGshobMqm9qWpbqdZsA5jvsuPZcHu0KwpmLrOHWPdEfg7XDpNT9UA== + +"@types/vscode@^1.25.0": + version "1.82.0" + resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.82.0.tgz#89b0b21179dcf5e8cee1664a9a05c5f6c60d38d0" + integrity sha512-VSHV+VnpF8DEm8LNrn8OJ8VuUNcBzN3tMvKrNpbhhfuVjFm82+6v44AbDhLvVFgCzn6vs94EJNTp7w8S6+Q1Rw== + +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +antlr4@^4.7.2: + version "4.13.1" + resolved "https://registry.yarnpkg.com/antlr4/-/antlr4-4.13.1.tgz#1e0a1830a08faeb86217cb2e6c34716004e4253d" + integrity sha512-kiXTspaRYvnIArgE97z5YVVf/cDVQABr3abFRR6mE7yesLMkgu4ujuyV/sgxafQ8wgve0DJQUJ38Z8tkgA2izA== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +await-notify@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/await-notify/-/await-notify-1.0.1.tgz#0b48133b22e524181e11557665185f2a2f3ce47c" + integrity sha512-eT6XN2ycPKvuiffzUNmU0dnGmmLw+TexMW7UKOyf5utdVrWx14PR2acRIfy6ZfFWRAv8twt1X74VUgd9RnDmfQ== + +azure-devops-node-api@^11.0.1: + version "11.2.0" + resolved "https://registry.yarnpkg.com/azure-devops-node-api/-/azure-devops-node-api-11.2.0.tgz#bf04edbef60313117a0507415eed4790a420ad6b" + integrity sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA== + dependencies: + tunnel "0.0.6" + typed-rest-client "^1.8.4" + +babel-code-frame@^6.22.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + integrity sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g== + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ== + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +camelcase@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.3.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +cheerio-select@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" + integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== + dependencies: + boolbase "^1.0.0" + css-select "^5.1.0" + css-what "^6.1.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + +cheerio@^1.0.0-rc.9: + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" + integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.0.1" + htmlparser2 "^8.0.1" + parse5 "^7.0.0" + parse5-htmlparser2-tree-adapter "^7.0.0" + +chokidar@3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@*: + version "11.0.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-11.0.0.tgz#43e19c25dbedc8256203538e8d7e9346877a6f67" + integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ== + +commander@^2.12.1, commander@^2.9.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +debug@4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +detect-libc@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== + +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +diff@^3.2.0, diff@^3.3.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +entities@^4.2.0, entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +entities@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + +get-stdin@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" + integrity sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA== + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.6, glob@^7.1.1, glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== + dependencies: + ansi-regex "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +hosted-git-info@^4.0.2: + version "4.1.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" + integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== + dependencies: + lru-cache "^6.0.0" + +htmlparser2@^8.0.1: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + +iconv-lite@~0.4.11: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg== + +js-yaml@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +js-yaml@^3.7.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +keytar@^7.7.0: + version "7.9.0" + resolved "https://registry.yarnpkg.com/keytar/-/keytar-7.9.0.tgz#4c6225708f51b50cbf77c5aae81721964c2918cb" + integrity sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ== + dependencies: + node-addon-api "^4.3.0" + prebuild-install "^7.0.1" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +linkify-it@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" + integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== + dependencies: + uc.micro "^1.0.1" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +log-symbols@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lua-fmt@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/lua-fmt/-/lua-fmt-2.6.0.tgz#ef9ac0573d1da7330dca09c022c39a33aed347a3" + integrity sha512-J4D7TK+BhSJ9ePQPpZPlu/aHE3X9ojN+D7DNv1HXZPryBJ74XWVmXpRX+qceXdE15TUsQRCj9w81waVOOFhfWA== + dependencies: + "@types/commander" "^2.3.31" + "@types/diff" "^3.2.0" + "@types/get-stdin" "^5.0.0" + commander "^2.9.0" + diff "^3.3.0" + get-stdin "^5.0.1" + luaparse oxyc/luaparse#ac42a00ebf4020b8c9d3219e4b0f84bf7ce6e802 + +luaparse@^0.2.1, luaparse@oxyc/luaparse#ac42a00ebf4020b8c9d3219e4b0f84bf7ce6e802: + version "0.2.1" + resolved "https://codeload.github.com/oxyc/luaparse/tar.gz/ac42a00ebf4020b8c9d3219e4b0f84bf7ce6e802" + +markdown-it@^12.3.2: + version "12.3.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" + integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== + dependencies: + argparse "^2.0.1" + entities "~2.1.0" + linkify-it "^3.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + +mdurl@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== + +mime@^1.3.4: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.3: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +misc-utils-of-mine-generic@^0.2.29: + version "0.2.45" + resolved "https://registry.yarnpkg.com/misc-utils-of-mine-generic/-/misc-utils-of-mine-generic-0.2.45.tgz#db1b76db1ab5a04771b24173e38957182e9e292d" + integrity sha512-WsG2zYiui2cdEbHF2pXmJfnjHb4zL+cy+PaYcLgIpMju98hwX89VbjlvGIfamCfEodbQ0qjCEvD3ocgkCXfMOQ== + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +mocha@10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8" + integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg== + dependencies: + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.2.0" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + nanoid "3.3.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mute-stream@~0.0.4: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== + +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + +node-abi@^3.3.0: + version "3.47.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.47.0.tgz#6cbfa2916805ae25c2b7156ca640131632eb05e8" + integrity sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A== + dependencies: + semver "^7.3.5" + +node-addon-api@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" + integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parse-semver@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/parse-semver/-/parse-semver-1.1.1.tgz#9a4afd6df063dc4826f93fba4a99cf223f666cb8" + integrity sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ== + dependencies: + semver "^5.1.0" + +parse5-htmlparser2-tree-adapter@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" + integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + dependencies: + domhandler "^5.0.2" + parse5 "^7.0.0" + +parse5@^7.0.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-reader@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-reader/-/path-reader-1.1.0.tgz#fbc7302485f83030c1ec75067fe5cba47f4c48f3" + integrity sha512-RT7wPCyH2AqncAMi4L1h+LQAvMBrZU8cnypGqgLzuIfIINT7w6OWm7KLH1tT8AQ+6p6tuujgSm3K90EMmRDTAA== + dependencies: + minimatch "^3.0.4" + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +prebuild-install@^7.0.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +qs@^6.9.1: + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== + dependencies: + side-channel "^1.0.4" + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + integrity sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ== + dependencies: + mute-stream "~0.0.4" + +readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve@^1.3.2: + version "1.22.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" + integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +semver@^5.1.0, semver@^5.3.0, semver@^5.5.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^7.3.5: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +serialize-javascript@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tar-fs@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tslib@^1.8.0, tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslint@5.9.1: + version "5.9.1" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.9.1.tgz#1255f87a3ff57eb0b0e1f0e610a8b4748046c9ae" + integrity sha512-EDEyKYflb79XSj/rX9abiYrpBJtdHvhYGnLaHNf3fW0KPlByePKwhlAmBtH4Y0PYQVkwPsrYSE6Fg1s8gDqucA== + dependencies: + babel-code-frame "^6.22.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^3.2.0" + glob "^7.1.1" + js-yaml "^3.7.0" + minimatch "^3.0.4" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.8.0" + tsutils "^2.12.1" + +tsutils@^2.12.1: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tunnel@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + +typed-rest-client@^1.8.4: + version "1.8.11" + resolved "https://registry.yarnpkg.com/typed-rest-client/-/typed-rest-client-1.8.11.tgz#6906f02e3c91e8d851579f255abf0fd60800a04d" + integrity sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA== + dependencies: + qs "^6.9.1" + tunnel "0.0.6" + underscore "^1.12.1" + +typescript@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== + +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + +underscore@^1.12.1: + version "1.13.6" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" + integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== + +univac@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/univac/-/univac-0.0.8.tgz#f2620bc44ec483ffda9a08c5549fdae0e08f28d2" + integrity sha512-2Wk5TnfP/0UMAoymXfgoluhGp5VAv4XSRzlRdYOX6YjZTgHq+4a/dXNUFAzbcWfHeSi2YmlQyEfw5jlH/8+oIA== + dependencies: + antlr4 "^4.7.2" + minimist "^1.2.0" + misc-utils-of-mine-generic "^0.2.29" + web-tree-sitter "^0.15.9" + +url-join@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" + integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== + +urlencode@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/urlencode/-/urlencode-1.1.0.tgz#1f2ba26f013c85f0133f7a3ad6ff2730adf7cbb7" + integrity sha512-OOAOh9owHXr/rCN1tteSnYwIvsrGHamSz0hafMhmQa7RcS4+Ets6/2iVClVGjt9jkDW84UqoMw/Gmpc7QolX6A== + dependencies: + iconv-lite "~0.4.11" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +vsce@^2.15.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/vsce/-/vsce-2.15.0.tgz#4a992e78532092a34a755143c6b6c2cabcb7d729" + integrity sha512-P8E9LAZvBCQnoGoizw65JfGvyMqNGlHdlUXD1VAuxtvYAaHBKLBdKPnpy60XKVDAkQCfmMu53g+gq9FM+ydepw== + dependencies: + azure-devops-node-api "^11.0.1" + chalk "^2.4.2" + cheerio "^1.0.0-rc.9" + commander "^6.1.0" + glob "^7.0.6" + hosted-git-info "^4.0.2" + keytar "^7.7.0" + leven "^3.1.0" + markdown-it "^12.3.2" + mime "^1.3.4" + minimatch "^3.0.3" + parse-semver "^1.1.1" + read "^1.0.7" + semver "^5.1.0" + tmp "^0.2.1" + typed-rest-client "^1.8.4" + url-join "^4.0.1" + xml2js "^0.4.23" + yauzl "^2.3.1" + yazl "^2.2.2" + +vscode-debugadapter-testsupport@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/vscode-debugadapter-testsupport/-/vscode-debugadapter-testsupport-1.27.0.tgz#bab26880ea2f13efb5a120964c4c48ed75d3d15d" + integrity sha512-rhNVFSeEtSfHZ8ZES1AKaY3vjfEgRbnikCsoDV0/Vu/jfuGnUlNMgWm+vbvtTlTIBPNjn2KKXU6ymYDjMwuW2Q== + dependencies: + vscode-debugprotocol "1.27.0" + +vscode-debugadapter@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/vscode-debugadapter/-/vscode-debugadapter-1.27.0.tgz#0688f7d03d7568efd653003ecdb402b7ba37231e" + integrity sha512-JwE3fWmKnpjYnFqhff0umqIJi4c26gh/CXZ5LNb4gLIuPd5sEAEoEbGeCcAaajuTrVxFw6FlYEep9y+IQCf+ww== + dependencies: + vscode-debugprotocol "1.27.0" + +vscode-debugprotocol@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.27.0.tgz#735a43a3cc1235fe587c0ef93fe4e328def7b17c" + integrity sha512-cg3lKqVwxNpO2pLBxSwkBvE7w06+bHfbA/s14u8izSWyhJtPgRu1lQwi5tEyTRuwfEugfoPwerYL4vtY6teQDw== + +vscode-jsonrpc@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz#a7bf74ef3254d0a0c272fab15c82128e378b3be9" + integrity sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg== + +vscode-languageclient@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-5.2.1.tgz#7cfc83a294c409f58cfa2b910a8cfeaad0397193" + integrity sha512-7jrS/9WnV0ruqPamN1nE7qCxn0phkH5LjSgSp9h6qoJGoeAKzwKz/PF6M+iGA/aklx4GLZg1prddhEPQtuXI1Q== + dependencies: + semver "^5.5.0" + vscode-languageserver-protocol "3.14.1" + +vscode-languageserver-protocol@3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.14.1.tgz#b8aab6afae2849c84a8983d39a1cf742417afe2f" + integrity sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g== + dependencies: + vscode-jsonrpc "^4.0.0" + vscode-languageserver-types "3.14.0" + +vscode-languageserver-types@3.14.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz#d3b5952246d30e5241592b6dde8280e03942e743" + integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A== + +vscode-languageserver@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-5.2.1.tgz#0d2feddd33f92aadf5da32450df498d52f6f14eb" + integrity sha512-GuayqdKZqAwwaCUjDvMTAVRPJOp/SLON3mJ07eGsx/Iq9HjRymhKWztX41rISqDKhHVVyFM+IywICyZDla6U3A== + dependencies: + vscode-languageserver-protocol "3.14.1" + vscode-uri "^1.0.6" + +vscode-uri@^1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.8.tgz#9769aaececae4026fb6e22359cb38946580ded59" + integrity sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ== + +web-tree-sitter@^0.15.9: + version "0.15.11" + resolved "https://registry.yarnpkg.com/web-tree-sitter/-/web-tree-sitter-0.15.11.tgz#2fa29820f4b6273f936e23de9ab1a1a62be347c3" + integrity sha512-7Sr26MV8bPPvKU+4VMpOoLYwaAFBsCsxRGtxYPSxClWIaR2KIciqg6zUeG14W9QrdlpBt7VHHzhd1CBb0e6i8w== + +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +xml2js@^0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" + integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yauzl@^2.3.1: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + +yazl@^2.2.2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" + integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== + dependencies: + buffer-crc32 "~0.2.3" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==