├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── bug_report.yml └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── LICENSE ├── README.md ├── build-settings.lua ├── buildClient.bat ├── buildClient.sh ├── changelog.md ├── client ├── .eslintrc.json ├── .prettierrc.yml ├── package-lock.json ├── package.json ├── src │ ├── addon_manager │ │ ├── commands │ │ │ ├── disable.ts │ │ │ ├── enable.ts │ │ │ ├── getAddons.ts │ │ │ ├── index.ts │ │ │ ├── open.ts │ │ │ ├── openLog.ts │ │ │ ├── refreshAddons.ts │ │ │ ├── setVersion.ts │ │ │ ├── uninstall.ts │ │ │ └── update.ts │ │ ├── config.ts │ │ ├── models │ │ │ └── addon.ts │ │ ├── panels │ │ │ └── WebVue.ts │ │ ├── registration.ts │ │ ├── services │ │ │ ├── addonManager.service.ts │ │ │ ├── filesystem.service.ts │ │ │ ├── git.service.ts │ │ │ ├── logging.service.ts │ │ │ ├── logging │ │ │ │ ├── vsCodeLogFileTransport.ts │ │ │ │ └── vsCodeOutputTransport.ts │ │ │ ├── settings.service.ts │ │ │ └── string.service.ts │ │ └── types │ │ │ ├── addon.d.ts │ │ │ └── webvue.ts │ ├── extension.ts │ ├── languageserver.ts │ ├── psi │ │ └── psiViewer.ts │ └── vscode.proposed │ │ └── editorHoverVerbosityLevel.d.ts ├── tsconfig.json └── web │ └── dist │ ├── 3rdpartylicenses.txt │ ├── assets │ ├── fonts │ │ └── mat-icon-font.woff2 │ └── scss │ │ └── mat-icon.scss │ ├── favicon.ico │ ├── index.html │ ├── main.0ac5708dde926afc.js │ ├── mat-icon-font.d36bf6bfd46ff3bb.woff2 │ ├── polyfills.fdaa14aa9967abe5.js │ ├── runtime.16fa3418f03cd751.js │ └── styles.4321c6214ef1a9a7.css ├── images ├── Auto Completion.gif ├── Diagnostics.gif ├── Emmy Annotation.gif ├── Find All References.gif ├── Goto Definition.gif ├── Hover.gif ├── Install In VSCode.gif ├── Rename.gif ├── Signature Help.gif ├── icon.ico ├── logo.png ├── plugin-diff.gif ├── setting-without-vscode.gif ├── tokens │ ├── comment.block.lua.jpg │ ├── comment.line.double-dash.lua.jpg │ ├── constant.character.escape.byte.lua.jpg │ ├── constant.character.escape.lua.jpg │ ├── constant.character.escape.unicode.lua.jpg │ ├── constant.language.lua.jpg │ ├── constant.numeric.float.hexadecimal.lua.jpg │ ├── constant.numeric.float.lua.jpg │ ├── constant.numeric.integer.hexadecimal.lua.jpg │ ├── constant.numeric.integer.lua.jpg │ ├── entity.name.class.lua.jpg │ ├── entity.name.function.lua.jpg │ ├── interface.declaration.jpg │ ├── invalid.illegal.character.escape.lua.jpg │ ├── keyword.control.goto.lua.jpg │ ├── keyword.control.lua.jpg │ ├── keyword.local.lua.jpg │ ├── keyword.operator.lua.jpg │ ├── namespace.deprecated.jpg │ ├── namespace.readonly.jpg │ ├── namespace.static.jpg │ ├── parameter.declaration.jpg │ ├── property.declaration.jpg │ ├── punctuation.definition.comment.begin.lua.jpg │ ├── punctuation.definition.comment.end.lua.jpg │ ├── punctuation.definition.comment.lua.jpg │ ├── punctuation.definition.parameters.begin.lua.jpg │ ├── punctuation.definition.parameters.finish.lua.jpg │ ├── punctuation.definition.string.begin.lua.jpg │ ├── punctuation.definition.string.end.lua.jpg │ ├── punctuation.section.embedded.begin.lua.jpg │ ├── punctuation.section.embedded.end.lua.jpg │ ├── punctuation.separator.arguments.lua.jpg │ ├── string.quoted.double.lua.jpg │ ├── string.quoted.other.multiline.lua.jpg │ ├── string.quoted.single.lua.jpg │ ├── string.tag.lua.jpg │ ├── support.function.library.lua.jpg │ ├── support.function.lua.jpg │ ├── variable.jpg │ ├── variable.language.self.lua.jpg │ ├── variable.other.lua.jpg │ └── variable.parameter.function.lua.jpg └── wiki-workspace.png ├── make └── copy.lua ├── package-lock.json ├── package.json ├── package.nls.es-419.json ├── package.nls.ja-jp.json ├── package.nls.json ├── package.nls.pt-br.json ├── package.nls.zh-cn.json ├── package.nls.zh-tw.json ├── package ├── build.lua └── semanticTokenScope.lua ├── publish.lua ├── setting ├── schema-es-419.json ├── schema-ja-jp.json ├── schema-pt-br.json ├── schema-zh-cn.json ├── schema-zh-tw.json ├── schema.json └── setting.json └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://paypal.me/sumneko", "https://github.com/LuaLS/lua-language-server/issues/484"] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report something behaving in an unexpected manor. 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: > 7 | **Please check for similar issues before continuing!** 8 | - type: dropdown 9 | id: OS 10 | attributes: 11 | label: Which OS are you using? 12 | options: 13 | - Windows 14 | - Linux 15 | - MacOS 16 | - Windows WSL 17 | - Other 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: expected 22 | attributes: 23 | label: Expected Behaviour 24 | description: What is the expected behaviour? 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: actual 29 | attributes: 30 | label: Actual Behaviour 31 | description: What is actually happening that is unexpected? 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: reproduction 36 | attributes: 37 | label: Reproduction steps 38 | description: > 39 | Please provide detailed steps to reproduce the error. This will help us 40 | to diagnose, test fixes, and fix the issue. 41 | value: | 42 | 1. Go to '...' 43 | 2. Click '...' 44 | 3. See error '...' 45 | validations: 46 | required: true 47 | - type: textarea 48 | id: additional-notes 49 | attributes: 50 | label: Additional Notes 51 | description: > 52 | Please provide any additional notes, context, and media you have. 53 | - type: textarea 54 | id: log 55 | attributes: 56 | label: Log 57 | description: > 58 | Please provide your log. The log can be found in VS Code by opening the 59 | `OUTPUT` panel and selecting `Lua Addon Manager` from the dropdown. 60 | - type: markdown 61 | attributes: 62 | value: | 63 | Thank you very much for helping us improve! ❤️ 64 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | tags: 11 | - "*" 12 | pull_request: 13 | branches: 14 | - master 15 | 16 | env: 17 | PROJECT: vscode-lua 18 | BIN_DIR: server/bin 19 | PKG_SUFFIX: vsix 20 | 21 | jobs: 22 | compile: 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | include: 27 | - { os: ubuntu-20.04, target: linux, platform: linux-x64 } 28 | - { os: ubuntu-20.04, target: linux, platform: linux-arm64 } 29 | - { os: macos-latest, target: darwin, platform: darwin-x64 } 30 | - { os: macos-latest, target: darwin, platform: darwin-arm64 } 31 | #- { os: windows-latest, target: windows, platform: win32-ia32 } # 不再支持32位 32 | - { os: windows-latest, target: windows, platform: win32-x64 } 33 | runs-on: ${{ matrix.os }} 34 | steps: 35 | - name: Install aarch64-linux-gnu 36 | if: ${{ matrix.platform == 'linux-arm64' }} 37 | run: | 38 | sudo apt-get update 39 | sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu 40 | 41 | - uses: actions/checkout@v4 42 | with: 43 | submodules: recursive 44 | 45 | - name: Set up Node.js 46 | uses: actions/setup-node@v2 47 | with: 48 | node-version: '22' # 指定要安装的 Node.js 版本 49 | 50 | - name: Build for Windows 51 | if: ${{ matrix.target == 'windows' }} 52 | working-directory: ./server 53 | run: | 54 | .\make.bat ${{ matrix.platform }} 55 | rm -r ./build 56 | 57 | - name: Build for Linux 58 | if: ${{ matrix.target == 'linux' }} 59 | working-directory: ./server 60 | run: | 61 | sudo apt update 62 | sudo apt install ninja-build 63 | ./make.sh ${{ matrix.platform }} 64 | rm -r ./build 65 | 66 | - name: Build for macOS 67 | if: ${{ matrix.target == 'darwin' }} 68 | working-directory: ./server 69 | run: | 70 | brew install ninja 71 | ./make.sh ${{ matrix.platform }} 72 | rm -r ./build 73 | 74 | - name: Setting up workflow variables 75 | id: vars 76 | shell: bash 77 | run: | 78 | # Package version 79 | if [[ $GITHUB_REF = refs/tags/* ]]; then 80 | PKG_VERSION=${GITHUB_REF##*/} 81 | else 82 | PKG_VERSION=${GITHUB_SHA:0:7} 83 | fi 84 | 85 | # Package name w/ version 86 | PKG_BASENAME="${{ env.PROJECT }}-${PKG_VERSION}-${{ matrix.platform }}" 87 | 88 | # Full name of the tarball asset 89 | PKG_NAME="${PKG_BASENAME}.${PKG_SUFFIX}" 90 | 91 | echo PKG_BASENAME=${PKG_BASENAME} >> $GITHUB_OUTPUT 92 | echo PKG_NAME=${PKG_NAME} >> $GITHUB_OUTPUT 93 | 94 | - name: Compile client 95 | shell: bash 96 | run: | 97 | npm install -g typescript 98 | cd client 99 | npm ci 100 | npm run build 101 | cd .. 102 | 103 | - name: Build Addon Manager WebVue 104 | shell: bash 105 | run: | 106 | cd client/webvue 107 | npm ci 108 | npm run build 109 | cd ../.. 110 | 111 | - name: Pack vsix 112 | id: pack 113 | shell: bash 114 | run: | 115 | npm install -g @vscode/vsce 116 | vsce package -o ${{ steps.vars.outputs.PKG_NAME }} -t ${{ matrix.platform }} 117 | 118 | - uses: actions/upload-artifact@v4 119 | with: 120 | name: ${{ steps.vars.outputs.PKG_BASENAME }} 121 | path: ${{ steps.vars.outputs.PKG_NAME }} 122 | 123 | - name: Publish release assets 124 | uses: softprops/action-gh-release@v1 125 | if: startsWith(github.ref, 'refs/tags/') 126 | with: 127 | generate_release_notes: true 128 | files: | 129 | ${{ steps.vars.outputs.PKG_NAME }} 130 | 131 | - name: Publish to VSCode Market 132 | if: startsWith(github.ref, 'refs/tags/') 133 | run: vsce publish -i ${{ steps.vars.outputs.PKG_NAME }} -p ${{ secrets.VSCE_TOKEN }} 134 | 135 | - name: Publish to Open VSX Registry 136 | if: startsWith(github.ref, 'refs/tags/') 137 | run: | 138 | npm install -g ovsx 139 | ovsx publish -i ${{ steps.vars.outputs.PKG_NAME }} -p ${{ secrets.OVSX_TOKEN }} 140 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /client/node_modules 2 | /client/out/ 3 | /publish/ 4 | /luadoc/out/ 5 | /ovsx-token 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "server"] 2 | path = server 3 | url = https://github.com/LuaLS/lua-language-server 4 | [submodule "client/3rd/vscode-lua-doc"] 5 | path = client/3rd/vscode-lua-doc 6 | url = https://github.com/LuaLS/vscode-lua-doc 7 | [submodule "client/webvue"] 8 | path = client/webvue 9 | url = https://github.com/LuaLS/vscode-lua-webvue 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "发布", 7 | "type": "lua", 8 | "request": "launch", 9 | "stopOnEntry": false, 10 | "program": "${workspaceRoot}/publish.lua", 11 | "arg": [ 12 | ], 13 | "sourceCoding": "utf8", 14 | "luaexe": "${workspaceFolder}/server/bin/lua-language-server.exe", 15 | "outputCapture": [ 16 | "print", 17 | "stderr", 18 | ], 19 | }, 20 | { 21 | "name": "导出配置文件", 22 | "type": "lua", 23 | "request": "launch", 24 | "stopOnEntry": false, 25 | "program": "${workspaceRoot}/build-settings.lua", 26 | "arg": [ 27 | ], 28 | "luaexe": "${workspaceFolder}/server/bin/lua-language-server.exe", 29 | "sourceCoding": "utf8", 30 | "outputCapture": [ 31 | "print", 32 | "stderr", 33 | ], 34 | }, 35 | { 36 | "type": "extensionHost", 37 | "request": "launch", 38 | "name": "Launch Client", 39 | "runtimeExecutable": "${execPath}", 40 | "args": ["--extensionDevelopmentPath=${workspaceRoot}"], 41 | "outFiles": ["${workspaceRoot}/client/out/**/*.js"], 42 | }, 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Lua.runtime.version": "Lua 5.4", 3 | "Lua.workspace.library": { 4 | "server/script-beta": true 5 | }, 6 | "Lua.workspace.checkThirdParty": false 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "shell", 6 | "command": "./buildClient.sh", 7 | "windows": { 8 | "command": ".\\buildClient.bat" 9 | }, 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | }, 14 | "presentation": { 15 | "echo": true, 16 | "reveal": "always", 17 | "focus": false, 18 | "panel": "dedicated", 19 | "showReuseMessage": false, 20 | "clear": true 21 | }, 22 | "icon": { "color": "terminal.ansiCyan", "id": "server-process" }, 23 | "label": "Build Client" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | **/* 2 | 3 | !client/node_modules 4 | !client/out 5 | !client/package.json 6 | !client/3rd/vscode-lua-doc/doc 7 | !client/3rd/vscode-lua-doc/extension.js 8 | !client/webvue/build 9 | 10 | !server/bin 11 | !server/locale 12 | !server/script 13 | !server/main.lua 14 | !server/main.lua 15 | !server/debugger.lua 16 | !server/changelog.md 17 | !server/LICENSE 18 | !server/meta/3rd 19 | !server/meta/template 20 | !server/meta/spell 21 | 22 | !images/logo.png 23 | 24 | !setting 25 | !syntaxes 26 | !package.json 27 | !README.md 28 | !changelog.md 29 | !package.nls.json 30 | !package.nls.*.json 31 | !LICENSE 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 最萌小汐 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-language-server 2 | 3 |  4 |  5 |  6 |  7 | 8 | 9 | ***Lua development just got a whole lot better*** 🧠 10 | 11 | The Lua language server provides various language features for Lua to make development easier and faster. With nearly a million installs in Visual Studio Code, it is the most popular extension for Lua language support. 12 | 13 | [See our website for more info](https://luals.github.io). 14 | 15 | ## Features 16 | 17 | - ⚙️ Supports `Lua 5.4`, `Lua 5.3`, `Lua 5.2`, `Lua 5.1`, and `LuaJIT` 18 | - 📄 Over 20 supported [annotations](https://luals.github.io/wiki/annotations/) for documenting your code 19 | - ↪ Go to definition 20 | - 🦺 Dynamic [type checking](https://luals.github.io/wiki/type-checking/) 21 | - 🔍 Find references 22 | - ⚠️ [Diagnostics/Warnings](https://luals.github.io/wiki/diagnostics/) 23 | - 🕵️ [Syntax checking](https://luals.github.io/wiki/syntax-errors/) 24 | - 📝 Element renaming 25 | - 🗨️ Hover to view details on variables, functions, and more 26 | - 🖊️ Autocompletion 27 | - 📚 Support for [libraries](https://luals.github.io/wiki/settings/#workspacelibrary) 28 | - 💅 [Code formatting](https://luals.github.io/wiki/formatter/) 29 | - 💬 [Spell checking](https://luals.github.io/wiki/diagnostics/#spell-check) 30 | - 🛠️ Custom [plugins](https://luals.github.io/wiki/plugins/) 31 | - 📖 [Documentation Generation](https://luals.github.io/wiki/export-docs/) 32 | 33 | ## Install 34 | The language server can be installed for use in Visual Studio Code, NeoVim, and any [other clients](https://microsoft.github.io/language-server-protocol/implementors/tools/) that support the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/). 35 | 36 | See [installation instructions on our website](https://luals.github.io/#install). 37 | 38 | [](https://luals.github.io/#vscode-install) 39 | [](https://luals.github.io/#neovim-install) 40 | [](https://luals.github.io/#other-install) 41 | 42 | ### Community Install Methods 43 | The install methods below are maintained by community members. 44 | 45 | [asdf plugin](https://github.com/bellini666/asdf-lua-language-server) 46 | 47 | ## Links 48 | - [Changelog](https://github.com/LuaLS/lua-language-server/blob/master/changelog.md) 49 | - [Wiki](https://luals.github.io/wiki) 50 | - [FAQ](https://luals.github.io/wiki/faq) 51 | - [Report an issue][issues] 52 | - [Suggest a feature][issues] 53 | - [Discuss](https://github.com/LuaLS/lua-language-server/discussions) 54 | 55 | > If you find any mistakes, please [report it][issues] or open a [pull request][pulls] if you have a fix of your own ❤️ 56 | > 57 | > 如果你发现了任何错误,请[告诉我][issues]或使用[Pull Requests][pulls]来直接修复。❤️ 58 | 59 | [issues]: https://github.com/LuaLS/lua-language-server/issues 60 | [pulls]: https://github.com/LuaLS/lua-language-server/pulls 61 | 62 | ## Available Languages 63 | 64 | - `en-us` 🇺🇸 65 | - `zh-cn` 🇨🇳 66 | - `zh-tw` 🇹🇼 67 | - `pt-br` 🇧🇷 68 | 69 | 70 | > **Note** 71 | > All translations are provided and collaborated on by the community. If you find an inappropriate or harmful translation, [please report it immediately](https://github.com/LuaLS/lua-language-server/issues). 72 | 73 | Are you able to [provide a translation](https://luals.github.io/wiki/translations)? It would be greatly appreciated! 74 | 75 | Thank you to [all contributors of translations](https://github.com/LuaLS/lua-language-server/commits/master/locale)! 76 | 77 | 78 | ## Privacy 79 | The language server had **opt-in** telemetry that collected usage data and sent it to the development team to help improve the extension. Read our [privacy policy](https://luals.github.io/privacy#language-server) to learn more. Telemetry was removed in `v3.6.5` and is no longer part of the language server. 80 | 81 | 82 | ## Contributors 83 |  84 | 85 | ## Credit 86 | Software that the language server (or the development of it) uses: 87 | 88 | * [bee.lua](https://github.com/actboy168/bee.lua) 89 | * [luamake](https://github.com/actboy168/luamake) 90 | * [LPegLabel](https://github.com/sqmedeiros/lpeglabel) 91 | * [LuaParser](https://github.com/LuaLS/LuaParser) 92 | * [ScreenToGif](https://github.com/NickeManarin/ScreenToGif) 93 | * [vscode-languageclient](https://github.com/microsoft/vscode-languageserver-node) 94 | * [lua.tmbundle](https://github.com/textmate/lua.tmbundle) 95 | * [EmmyLua](https://emmylua.github.io) 96 | * [lua-glob](https://github.com/LuaLS/lua-glob) 97 | * [utility](https://github.com/LuaLS/utility) 98 | * [vscode-lua-doc](https://github.com/actboy168/vscode-lua-doc) 99 | * [json.lua](https://github.com/actboy168/json.lua) 100 | * [EmmyLuaCodeStyle](https://github.com/CppCXY/EmmyLuaCodeStyle) 101 | * [inspect.lua](https://github.com/kikito/inspect.lua) 102 | -------------------------------------------------------------------------------- /build-settings.lua: -------------------------------------------------------------------------------- 1 | local fs = require 'bee.filesystem' 2 | 3 | local currentPath = debug.getinfo(1, 'S').source:sub(2) 4 | local rootPath = currentPath:gsub('[^/\\]-$', '') 5 | package.path = package.path 6 | .. ';' .. rootPath .. '?.lua' 7 | .. ';' .. rootPath .. 'server/script/?.lua' 8 | 9 | local json = require 'json-beautify' 10 | local configuration = require 'server.tools.configuration' 11 | local fsu = require 'fs-utility' 12 | local lloader = require 'locale-loader' 13 | local diagd = require 'proto.diagnostic' 14 | local util = require 'utility' 15 | 16 | local function addSplited(t, key, value) 17 | t[key] = value 18 | for pos in key:gmatch '()%.' do 19 | local left = key:sub(1, pos - 1) 20 | local right = key:sub(pos + 1) 21 | local nt = t[left] or { 22 | properties = {} 23 | } 24 | t[left] = nt 25 | addSplited(nt.properties, right, value) 26 | end 27 | end 28 | 29 | local function copyWithNLS(t, callback) 30 | local nt = {} 31 | local mt = getmetatable(t) 32 | if mt then 33 | setmetatable(nt, mt) 34 | end 35 | for k, v in pairs(t) do 36 | if type(v) == 'string' then 37 | v = callback(v) or v 38 | elseif type(v) == 'table' then 39 | v = copyWithNLS(v, callback) 40 | end 41 | nt[k] = v 42 | if type(k) == 'string' and k:sub(1, #'Lua.') == 'Lua.' then 43 | local shortKey = k:sub(#'Lua.' + 1) 44 | local ref = { 45 | ['$ref'] = '#/properties/' .. shortKey 46 | } 47 | addSplited(nt, shortKey, ref) 48 | nt[k] = nil 49 | nt[shortKey] = v 50 | end 51 | end 52 | return nt 53 | end 54 | 55 | local encodeOption = { 56 | newline = '\r\n', 57 | indent = ' ', 58 | } 59 | local function mergeDiagnosticGroupLocale(locale) 60 | for groupName, names in pairs(diagd.diagnosticGroups) do 61 | local key = ('config.diagnostics.%s'):format(groupName) 62 | local list = {} 63 | for name in util.sortPairs(names) do 64 | list[#list+1] = ('* %s'):format(name) 65 | end 66 | local desc = table.concat(list, '\n') 67 | locale[key] = desc 68 | end 69 | end 70 | 71 | for dirPath in fs.pairs(fs.path 'server/locale') do 72 | local lang = dirPath:filename():string() 73 | local nlsPath = dirPath / 'setting.lua' 74 | local text = fsu.loadFile(nlsPath) 75 | if not text then 76 | goto CONTINUE 77 | end 78 | local nls = lloader(text, nlsPath:string()) 79 | -- add `config.diagnostics.XXX` 80 | mergeDiagnosticGroupLocale(nls) 81 | 82 | local setting = { 83 | title = 'setting', 84 | description = 'Setting of sumneko.lua', 85 | type = 'object', 86 | properties = copyWithNLS(configuration, function (str) 87 | return str:gsub('^%%(.+)%%$', function (key) 88 | if not nls[key] then 89 | nls[key] = "TODO: Needs documentation" 90 | end 91 | return nls[key] 92 | end) 93 | end), 94 | } 95 | 96 | local schemaName, nlsName 97 | if lang == 'en-us' then 98 | schemaName = 'setting/schema.json' 99 | nlsName = 'package.nls.json' 100 | else 101 | schemaName = 'setting/schema-' .. lang .. '.json' 102 | nlsName = 'package.nls.' .. lang .. '.json' 103 | end 104 | 105 | fsu.saveFile(fs.path(schemaName), json.beautify(setting, encodeOption)) 106 | fsu.saveFile(fs.path(nlsName), json.beautify(nls, encodeOption)) 107 | ::CONTINUE:: 108 | end 109 | -------------------------------------------------------------------------------- /buildClient.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | echo Building VS Code Extension Client... 4 | 5 | echo Compiling TypeScript... 6 | cd client 7 | call npm i 8 | call npm run build 9 | 10 | echo Building Addon Manager WebVue... 11 | cd webvue 12 | call npm i 13 | call npm run build 14 | 15 | echo Build complete! 16 | -------------------------------------------------------------------------------- /buildClient.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | bold=$(tput bold) 6 | normal=$(tput sgr0) 7 | 8 | black='\e[0;30m' 9 | red='\e[0;31m' 10 | green='\e[0;32m' 11 | cyan='\e[0;36m' 12 | 13 | echo -e "${red}${bold}Building VS Code Extension Client..." 14 | 15 | echo -e "${cyan}${bold}Compiling TypeScript...${black}${normal}" 16 | cd client 17 | npm i 18 | npm run build 19 | 20 | echo -e "${green}${bold}Building Addon Manager WebVue...${black}${normal}" 21 | cd webvue 22 | npm i 23 | npm run build 24 | 25 | echo -e "${green}${bold}Build complete!${black}${normal}" 26 | -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 6 | "rules": { 7 | "@typescript-eslint/no-namespace": "off", 8 | "linebreak-style": "off", 9 | "no-duplicate-imports": "warn", 10 | "semi": "error", 11 | "default-case": "error", 12 | "default-case-last": "error", 13 | "eqeqeq": "error" 14 | }, 15 | "ignorePatterns": ["out", "dist", "**/*.d.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /client/.prettierrc.yml: -------------------------------------------------------------------------------- 1 | tabWidth: 4 2 | endOfLine: auto 3 | -------------------------------------------------------------------------------- /client/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lsp-sample-client", 3 | "version": "0.0.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "lsp-sample-client", 9 | "version": "0.0.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "axios": "^1.7.4", 13 | "dayjs": "^1.11.7", 14 | "simple-git": "^3.16.0", 15 | "triple-beam": "^1.3.0", 16 | "vscode-languageclient": "9.0.1", 17 | "winston": "^3.8.2" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^18.19.18", 21 | "@types/vscode": "1.85.0", 22 | "typescript": "^5.5.0" 23 | }, 24 | "engines": { 25 | "vscode": "^1.85.0" 26 | } 27 | }, 28 | "node_modules/@colors/colors": { 29 | "version": "1.6.0", 30 | "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", 31 | "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", 32 | "engines": { 33 | "node": ">=0.1.90" 34 | } 35 | }, 36 | "node_modules/@dabh/diagnostics": { 37 | "version": "2.0.3", 38 | "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", 39 | "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", 40 | "dependencies": { 41 | "colorspace": "1.1.x", 42 | "enabled": "2.0.x", 43 | "kuler": "^2.0.0" 44 | } 45 | }, 46 | "node_modules/@kwsites/file-exists": { 47 | "version": "1.1.1", 48 | "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", 49 | "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", 50 | "dependencies": { 51 | "debug": "^4.1.1" 52 | } 53 | }, 54 | "node_modules/@kwsites/promise-deferred": { 55 | "version": "1.1.1", 56 | "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", 57 | "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" 58 | }, 59 | "node_modules/@types/node": { 60 | "version": "18.19.42", 61 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.42.tgz", 62 | "integrity": "sha512-d2ZFc/3lnK2YCYhos8iaNIYu9Vfhr92nHiyJHRltXWjXUBjEE+A4I58Tdbnw4VhggSW+2j5y5gTrLs4biNnubg==", 63 | "dev": true, 64 | "dependencies": { 65 | "undici-types": "~5.26.4" 66 | } 67 | }, 68 | "node_modules/@types/triple-beam": { 69 | "version": "1.3.5", 70 | "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", 71 | "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" 72 | }, 73 | "node_modules/@types/vscode": { 74 | "version": "1.85.0", 75 | "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.85.0.tgz", 76 | "integrity": "sha512-CF/RBon/GXwdfmnjZj0WTUMZN5H6YITOfBCP4iEZlOtVQXuzw6t7Le7+cR+7JzdMrnlm7Mfp49Oj2TuSXIWo3g==", 77 | "dev": true 78 | }, 79 | "node_modules/async": { 80 | "version": "3.2.5", 81 | "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", 82 | "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" 83 | }, 84 | "node_modules/asynckit": { 85 | "version": "0.4.0", 86 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 87 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 88 | }, 89 | "node_modules/axios": { 90 | "version": "1.7.4", 91 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", 92 | "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", 93 | "dependencies": { 94 | "follow-redirects": "^1.15.6", 95 | "form-data": "^4.0.0", 96 | "proxy-from-env": "^1.1.0" 97 | } 98 | }, 99 | "node_modules/balanced-match": { 100 | "version": "1.0.2", 101 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 102 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 103 | }, 104 | "node_modules/brace-expansion": { 105 | "version": "2.0.1", 106 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 107 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 108 | "dependencies": { 109 | "balanced-match": "^1.0.0" 110 | } 111 | }, 112 | "node_modules/color": { 113 | "version": "3.2.1", 114 | "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", 115 | "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", 116 | "dependencies": { 117 | "color-convert": "^1.9.3", 118 | "color-string": "^1.6.0" 119 | } 120 | }, 121 | "node_modules/color-convert": { 122 | "version": "1.9.3", 123 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 124 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 125 | "dependencies": { 126 | "color-name": "1.1.3" 127 | } 128 | }, 129 | "node_modules/color-name": { 130 | "version": "1.1.3", 131 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 132 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 133 | }, 134 | "node_modules/color-string": { 135 | "version": "1.9.1", 136 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", 137 | "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 138 | "dependencies": { 139 | "color-name": "^1.0.0", 140 | "simple-swizzle": "^0.2.2" 141 | } 142 | }, 143 | "node_modules/colorspace": { 144 | "version": "1.1.4", 145 | "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", 146 | "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", 147 | "dependencies": { 148 | "color": "^3.1.3", 149 | "text-hex": "1.0.x" 150 | } 151 | }, 152 | "node_modules/combined-stream": { 153 | "version": "1.0.8", 154 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 155 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 156 | "dependencies": { 157 | "delayed-stream": "~1.0.0" 158 | }, 159 | "engines": { 160 | "node": ">= 0.8" 161 | } 162 | }, 163 | "node_modules/dayjs": { 164 | "version": "1.11.10", 165 | "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", 166 | "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" 167 | }, 168 | "node_modules/debug": { 169 | "version": "4.3.4", 170 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 171 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 172 | "dependencies": { 173 | "ms": "2.1.2" 174 | }, 175 | "engines": { 176 | "node": ">=6.0" 177 | }, 178 | "peerDependenciesMeta": { 179 | "supports-color": { 180 | "optional": true 181 | } 182 | } 183 | }, 184 | "node_modules/delayed-stream": { 185 | "version": "1.0.0", 186 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 187 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 188 | "engines": { 189 | "node": ">=0.4.0" 190 | } 191 | }, 192 | "node_modules/enabled": { 193 | "version": "2.0.0", 194 | "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", 195 | "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" 196 | }, 197 | "node_modules/fecha": { 198 | "version": "4.2.3", 199 | "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", 200 | "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" 201 | }, 202 | "node_modules/fn.name": { 203 | "version": "1.1.0", 204 | "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", 205 | "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" 206 | }, 207 | "node_modules/follow-redirects": { 208 | "version": "1.15.6", 209 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", 210 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", 211 | "funding": [ 212 | { 213 | "type": "individual", 214 | "url": "https://github.com/sponsors/RubenVerborgh" 215 | } 216 | ], 217 | "engines": { 218 | "node": ">=4.0" 219 | }, 220 | "peerDependenciesMeta": { 221 | "debug": { 222 | "optional": true 223 | } 224 | } 225 | }, 226 | "node_modules/form-data": { 227 | "version": "4.0.0", 228 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 229 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 230 | "dependencies": { 231 | "asynckit": "^0.4.0", 232 | "combined-stream": "^1.0.8", 233 | "mime-types": "^2.1.12" 234 | }, 235 | "engines": { 236 | "node": ">= 6" 237 | } 238 | }, 239 | "node_modules/inherits": { 240 | "version": "2.0.4", 241 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 242 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 243 | }, 244 | "node_modules/is-arrayish": { 245 | "version": "0.3.2", 246 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 247 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" 248 | }, 249 | "node_modules/is-stream": { 250 | "version": "2.0.1", 251 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 252 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", 253 | "engines": { 254 | "node": ">=8" 255 | }, 256 | "funding": { 257 | "url": "https://github.com/sponsors/sindresorhus" 258 | } 259 | }, 260 | "node_modules/kuler": { 261 | "version": "2.0.0", 262 | "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", 263 | "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" 264 | }, 265 | "node_modules/logform": { 266 | "version": "2.6.0", 267 | "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", 268 | "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", 269 | "dependencies": { 270 | "@colors/colors": "1.6.0", 271 | "@types/triple-beam": "^1.3.2", 272 | "fecha": "^4.2.0", 273 | "ms": "^2.1.1", 274 | "safe-stable-stringify": "^2.3.1", 275 | "triple-beam": "^1.3.0" 276 | }, 277 | "engines": { 278 | "node": ">= 12.0.0" 279 | } 280 | }, 281 | "node_modules/lru-cache": { 282 | "version": "6.0.0", 283 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 284 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 285 | "dependencies": { 286 | "yallist": "^4.0.0" 287 | }, 288 | "engines": { 289 | "node": ">=10" 290 | } 291 | }, 292 | "node_modules/mime-db": { 293 | "version": "1.52.0", 294 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 295 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 296 | "engines": { 297 | "node": ">= 0.6" 298 | } 299 | }, 300 | "node_modules/mime-types": { 301 | "version": "2.1.35", 302 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 303 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 304 | "dependencies": { 305 | "mime-db": "1.52.0" 306 | }, 307 | "engines": { 308 | "node": ">= 0.6" 309 | } 310 | }, 311 | "node_modules/minimatch": { 312 | "version": "5.1.6", 313 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 314 | "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 315 | "dependencies": { 316 | "brace-expansion": "^2.0.1" 317 | }, 318 | "engines": { 319 | "node": ">=10" 320 | } 321 | }, 322 | "node_modules/ms": { 323 | "version": "2.1.2", 324 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 325 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 326 | }, 327 | "node_modules/one-time": { 328 | "version": "1.0.0", 329 | "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", 330 | "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", 331 | "dependencies": { 332 | "fn.name": "1.x.x" 333 | } 334 | }, 335 | "node_modules/proxy-from-env": { 336 | "version": "1.1.0", 337 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 338 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 339 | }, 340 | "node_modules/readable-stream": { 341 | "version": "3.6.2", 342 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 343 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 344 | "dependencies": { 345 | "inherits": "^2.0.3", 346 | "string_decoder": "^1.1.1", 347 | "util-deprecate": "^1.0.1" 348 | }, 349 | "engines": { 350 | "node": ">= 6" 351 | } 352 | }, 353 | "node_modules/safe-buffer": { 354 | "version": "5.2.1", 355 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 356 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 357 | "funding": [ 358 | { 359 | "type": "github", 360 | "url": "https://github.com/sponsors/feross" 361 | }, 362 | { 363 | "type": "patreon", 364 | "url": "https://www.patreon.com/feross" 365 | }, 366 | { 367 | "type": "consulting", 368 | "url": "https://feross.org/support" 369 | } 370 | ] 371 | }, 372 | "node_modules/safe-stable-stringify": { 373 | "version": "2.4.3", 374 | "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", 375 | "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", 376 | "engines": { 377 | "node": ">=10" 378 | } 379 | }, 380 | "node_modules/semver": { 381 | "version": "7.5.4", 382 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 383 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 384 | "dependencies": { 385 | "lru-cache": "^6.0.0" 386 | }, 387 | "bin": { 388 | "semver": "bin/semver.js" 389 | }, 390 | "engines": { 391 | "node": ">=10" 392 | } 393 | }, 394 | "node_modules/simple-git": { 395 | "version": "3.20.0", 396 | "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.20.0.tgz", 397 | "integrity": "sha512-ozK8tl2hvLts8ijTs18iFruE+RoqmC/mqZhjs/+V7gS5W68JpJ3+FCTmLVqmR59MaUQ52MfGQuWsIqfsTbbJ0Q==", 398 | "dependencies": { 399 | "@kwsites/file-exists": "^1.1.1", 400 | "@kwsites/promise-deferred": "^1.1.1", 401 | "debug": "^4.3.4" 402 | }, 403 | "funding": { 404 | "type": "github", 405 | "url": "https://github.com/steveukx/git-js?sponsor=1" 406 | } 407 | }, 408 | "node_modules/simple-swizzle": { 409 | "version": "0.2.2", 410 | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", 411 | "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", 412 | "dependencies": { 413 | "is-arrayish": "^0.3.1" 414 | } 415 | }, 416 | "node_modules/stack-trace": { 417 | "version": "0.0.10", 418 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 419 | "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", 420 | "engines": { 421 | "node": "*" 422 | } 423 | }, 424 | "node_modules/string_decoder": { 425 | "version": "1.3.0", 426 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 427 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 428 | "dependencies": { 429 | "safe-buffer": "~5.2.0" 430 | } 431 | }, 432 | "node_modules/text-hex": { 433 | "version": "1.0.0", 434 | "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", 435 | "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" 436 | }, 437 | "node_modules/triple-beam": { 438 | "version": "1.4.1", 439 | "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", 440 | "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", 441 | "engines": { 442 | "node": ">= 14.0.0" 443 | } 444 | }, 445 | "node_modules/typescript": { 446 | "version": "5.5.4", 447 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", 448 | "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", 449 | "dev": true, 450 | "bin": { 451 | "tsc": "bin/tsc", 452 | "tsserver": "bin/tsserver" 453 | }, 454 | "engines": { 455 | "node": ">=14.17" 456 | } 457 | }, 458 | "node_modules/undici-types": { 459 | "version": "5.26.5", 460 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 461 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 462 | "dev": true 463 | }, 464 | "node_modules/util-deprecate": { 465 | "version": "1.0.2", 466 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 467 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 468 | }, 469 | "node_modules/vscode-jsonrpc": { 470 | "version": "8.2.0", 471 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", 472 | "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", 473 | "engines": { 474 | "node": ">=14.0.0" 475 | } 476 | }, 477 | "node_modules/vscode-languageclient": { 478 | "version": "9.0.1", 479 | "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", 480 | "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", 481 | "dependencies": { 482 | "minimatch": "^5.1.0", 483 | "semver": "^7.3.7", 484 | "vscode-languageserver-protocol": "3.17.5" 485 | }, 486 | "engines": { 487 | "vscode": "^1.82.0" 488 | } 489 | }, 490 | "node_modules/vscode-languageserver-protocol": { 491 | "version": "3.17.5", 492 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", 493 | "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", 494 | "dependencies": { 495 | "vscode-jsonrpc": "8.2.0", 496 | "vscode-languageserver-types": "3.17.5" 497 | } 498 | }, 499 | "node_modules/vscode-languageserver-types": { 500 | "version": "3.17.5", 501 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", 502 | "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" 503 | }, 504 | "node_modules/winston": { 505 | "version": "3.11.0", 506 | "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", 507 | "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==", 508 | "dependencies": { 509 | "@colors/colors": "^1.6.0", 510 | "@dabh/diagnostics": "^2.0.2", 511 | "async": "^3.2.3", 512 | "is-stream": "^2.0.0", 513 | "logform": "^2.4.0", 514 | "one-time": "^1.0.0", 515 | "readable-stream": "^3.4.0", 516 | "safe-stable-stringify": "^2.3.1", 517 | "stack-trace": "0.0.x", 518 | "triple-beam": "^1.3.0", 519 | "winston-transport": "^4.5.0" 520 | }, 521 | "engines": { 522 | "node": ">= 12.0.0" 523 | } 524 | }, 525 | "node_modules/winston-transport": { 526 | "version": "4.6.0", 527 | "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz", 528 | "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==", 529 | "dependencies": { 530 | "logform": "^2.3.2", 531 | "readable-stream": "^3.6.0", 532 | "triple-beam": "^1.3.0" 533 | }, 534 | "engines": { 535 | "node": ">= 12.0.0" 536 | } 537 | }, 538 | "node_modules/yallist": { 539 | "version": "4.0.0", 540 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 541 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 542 | } 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lsp-sample-client", 3 | "description": "VSCode part of a language server", 4 | "author": "Microsoft Corporation", 5 | "license": "MIT", 6 | "version": "0.0.1", 7 | "publisher": "vscode", 8 | "scripts": { 9 | "lint": "npx eslint src/", 10 | "build": "tsc" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/Microsoft/vscode-extension-samples" 15 | }, 16 | "engines": { 17 | "vscode": "^1.85.0" 18 | }, 19 | "dependencies": { 20 | "axios": "^1.7.4", 21 | "dayjs": "^1.11.7", 22 | "simple-git": "^3.16.0", 23 | "triple-beam": "^1.3.0", 24 | "vscode-languageclient": "9.0.1", 25 | "winston": "^3.8.2" 26 | }, 27 | "devDependencies": { 28 | "@types/node": "^18.19.18", 29 | "@types/vscode": "1.85.0", 30 | "typescript": "^5.5.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client/src/addon_manager/commands/disable.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import addonManager from "../services/addonManager.service"; 3 | import { createChildLogger } from "../services/logging.service"; 4 | import { setConfig } from "../../languageserver"; 5 | 6 | type Message = { 7 | data: { 8 | name: string; 9 | }; 10 | }; 11 | 12 | const localLogger = createChildLogger("Disable Addon"); 13 | 14 | export default async (context: vscode.ExtensionContext, message: Message) => { 15 | const addon = addonManager.addons.get(message.data.name); 16 | const workspaceFolders = vscode.workspace.workspaceFolders; 17 | 18 | if (!addon || !workspaceFolders) { 19 | return; 20 | } 21 | 22 | let selectedFolders: vscode.WorkspaceFolder[]; 23 | 24 | if (workspaceFolders && workspaceFolders.length === 1) { 25 | selectedFolders = [workspaceFolders[0]]; 26 | } else { 27 | const folderOptions = await addon.getQuickPickerOptions(true); 28 | const pickResult = await vscode.window.showQuickPick(folderOptions, { 29 | canPickMany: true, 30 | ignoreFocusOut: true, 31 | title: `Disable ${addon.name} in which folders?`, 32 | }); 33 | if (!pickResult) { 34 | localLogger.warn("User did not pick workspace folder"); 35 | await addon.setLock(false); 36 | return; 37 | } 38 | selectedFolders = pickResult.map((selection) => { 39 | return workspaceFolders.find( 40 | (folder) => folder.name === selection.label 41 | ); 42 | }).filter((folder) => !!folder); 43 | } 44 | 45 | for (const folder of selectedFolders) { 46 | await addon.disable(folder); 47 | await setConfig([ 48 | { 49 | action: "set", 50 | key: "Lua.workspace.checkThirdParty", 51 | value: false, 52 | uri: folder.uri, 53 | }, 54 | ]); 55 | } 56 | 57 | return addon.setLock(false); 58 | }; 59 | -------------------------------------------------------------------------------- /client/src/addon_manager/commands/enable.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import addonManager from "../services/addonManager.service"; 3 | import { createChildLogger } from "../services/logging.service"; 4 | import { setConfig } from "../../languageserver"; 5 | import { WebVue } from "../panels/WebVue"; 6 | import { NotificationLevels } from "../types/webvue"; 7 | import { ADDONS_DIRECTORY, getStorageUri } from "../config"; 8 | 9 | type Message = { 10 | data: { 11 | name: string; 12 | }; 13 | }; 14 | 15 | const localLogger = createChildLogger("Enable Addon"); 16 | 17 | export default async (context: vscode.ExtensionContext, message: Message) => { 18 | const addon = addonManager.addons.get(message.data.name); 19 | const workspaceFolders = vscode.workspace.workspaceFolders; 20 | 21 | if (!addon || !workspaceFolders) { 22 | return; 23 | } 24 | 25 | let selectedFolders: vscode.WorkspaceFolder[]; 26 | 27 | if (workspaceFolders && workspaceFolders.length === 1) { 28 | selectedFolders = [workspaceFolders[0]]; 29 | } else { 30 | const folderOptions = await addon.getQuickPickerOptions(false); 31 | 32 | const pickResult = await vscode.window.showQuickPick(folderOptions, { 33 | canPickMany: true, 34 | ignoreFocusOut: true, 35 | title: `Enable ${addon.name} in which folders?`, 36 | }); 37 | if (!pickResult) { 38 | localLogger.warn("User did not pick workspace folder"); 39 | await addon.setLock(false); 40 | return; 41 | } 42 | selectedFolders = pickResult 43 | .map((selection) => { 44 | return workspaceFolders.find( 45 | (folder) => folder.name === selection.label 46 | ); 47 | }) 48 | .filter((folder) => !!folder); 49 | } 50 | 51 | for (const folder of selectedFolders) { 52 | try { 53 | const installLocation = vscode.Uri.joinPath( 54 | getStorageUri(context), 55 | "addonManager", 56 | ADDONS_DIRECTORY 57 | ); 58 | await addon.enable(folder, installLocation); 59 | } catch (e) { 60 | const message = `Failed to enable ${addon.name}!`; 61 | localLogger.error(message, { report: false }); 62 | localLogger.error(String(e), { report: false }); 63 | WebVue.sendNotification({ 64 | level: NotificationLevels.error, 65 | message, 66 | }); 67 | continue; 68 | } 69 | await setConfig([ 70 | { 71 | action: "set", 72 | key: "Lua.workspace.checkThirdParty", 73 | value: false, 74 | uri: folder.uri, 75 | }, 76 | ]); 77 | } 78 | 79 | return addon.setLock(false); 80 | }; 81 | -------------------------------------------------------------------------------- /client/src/addon_manager/commands/getAddons.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { createChildLogger } from "../services/logging.service"; 3 | import addonManager from "../services/addonManager.service"; 4 | import { WebVue } from "../panels/WebVue"; 5 | import { ADDONS_DIRECTORY, getStorageUri } from "../config"; 6 | 7 | const localLogger = createChildLogger("Get Remote Addons"); 8 | 9 | export default async (context: vscode.ExtensionContext) => { 10 | WebVue.setLoadingState(true); 11 | 12 | const installLocation = vscode.Uri.joinPath( 13 | getStorageUri(context), 14 | "addonManager", 15 | ADDONS_DIRECTORY 16 | ); 17 | 18 | if (addonManager.addons.size < 1) { 19 | await addonManager.fetchAddons(installLocation); 20 | } 21 | 22 | WebVue.sendMessage("addonStore", { 23 | property: "total", 24 | value: addonManager.addons.size, 25 | }); 26 | 27 | if (addonManager.addons.size === 0) { 28 | WebVue.setLoadingState(false); 29 | localLogger.verbose("No remote addons found"); 30 | return; 31 | } 32 | 33 | /** Number of addons to load per chunk */ 34 | const CHUNK_SIZE = 30; 35 | 36 | // Get list of addons and sort them alphabetically 37 | const addonList = Array.from(addonManager.addons.values()); 38 | addonList.sort((a, b) => a.displayName.localeCompare(b.displayName)); 39 | 40 | // Send addons to client in chunks 41 | for (let i = 0; i <= addonList.length / CHUNK_SIZE; i++) { 42 | const chunk = addonList.slice(i * CHUNK_SIZE, i * CHUNK_SIZE + CHUNK_SIZE); 43 | const addons = await Promise.all(chunk.map((addon) => addon.toJSON())); 44 | await WebVue.sendMessage("addAddon", { addons }); 45 | } 46 | 47 | WebVue.setLoadingState(false); 48 | }; 49 | -------------------------------------------------------------------------------- /client/src/addon_manager/commands/index.ts: -------------------------------------------------------------------------------- 1 | import enable from "./enable"; 2 | import disable from "./disable"; 3 | import open from "./open"; 4 | import getAddons from "./getAddons"; 5 | import refreshAddons from "./refreshAddons"; 6 | import openLog from "./openLog"; 7 | import update from "./update"; 8 | import uninstall from "./uninstall"; 9 | import setVersion from "./setVersion"; 10 | 11 | export const commands = { 12 | enable, 13 | disable, 14 | open, 15 | getAddons, 16 | refreshAddons, 17 | openLog, 18 | update, 19 | uninstall, 20 | setVersion, 21 | }; 22 | -------------------------------------------------------------------------------- /client/src/addon_manager/commands/open.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { createChildLogger } from "../services/logging.service"; 3 | import { ADDONS_DIRECTORY, getStorageUri } from "../config"; 4 | 5 | const localLogger = createChildLogger("Open Addon"); 6 | 7 | export default async ( 8 | context: vscode.ExtensionContext, 9 | message: { data: { name: string } } 10 | ) => { 11 | const extensionStorageURI = getStorageUri(context); 12 | const uri = vscode.Uri.joinPath( 13 | extensionStorageURI, 14 | "addonManager", 15 | ADDONS_DIRECTORY, 16 | message.data.name 17 | ); 18 | 19 | localLogger.info(`Opening "${message.data.name}" addon in file explorer`); 20 | vscode.env.openExternal(vscode.Uri.file(uri.fsPath)); 21 | }; 22 | -------------------------------------------------------------------------------- /client/src/addon_manager/commands/openLog.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import VSCodeLogFileTransport from "../services/logging/vsCodeLogFileTransport"; 3 | 4 | export default async () => { 5 | vscode.env.openExternal(VSCodeLogFileTransport.currentLogFile); 6 | }; 7 | -------------------------------------------------------------------------------- /client/src/addon_manager/commands/refreshAddons.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import addonManager from "../services/addonManager.service"; 3 | import { ADDONS_DIRECTORY } from "../config"; 4 | import { WebVue } from "../panels/WebVue"; 5 | import { getStorageUri } from "../config" 6 | 7 | export default async (context: vscode.ExtensionContext) => { 8 | WebVue.setLoadingState(true); 9 | 10 | const installLocation = vscode.Uri.joinPath( 11 | getStorageUri(context), 12 | "addonManager", 13 | ADDONS_DIRECTORY 14 | ); 15 | 16 | await addonManager.fetchAddons(installLocation); 17 | 18 | WebVue.setLoadingState(false); 19 | }; 20 | -------------------------------------------------------------------------------- /client/src/addon_manager/commands/setVersion.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { createChildLogger } from "../services/logging.service"; 3 | import addonManager from "../services/addonManager.service"; 4 | import { NotificationLevels } from "../types/webvue"; 5 | import { WebVue } from "../panels/WebVue"; 6 | 7 | const localLogger = createChildLogger("Set Version"); 8 | 9 | export default async ( 10 | context: vscode.ExtensionContext, 11 | message: { data: { name: string; version: string } } 12 | ) => { 13 | const addon = addonManager.addons.get(message.data.name)!; 14 | 15 | try { 16 | if (message.data.version === "Latest") { 17 | await addon.update(); 18 | } else { 19 | await addon.checkout(message.data.version); 20 | } 21 | } catch (e) { 22 | localLogger.error( 23 | `Failed to checkout version ${message.data.version}: ${e}` 24 | ); 25 | WebVue.sendNotification({ 26 | level: NotificationLevels.error, 27 | message: `Failed to checkout version ${message.data.version}`, 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /client/src/addon_manager/commands/uninstall.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import addonManagerService from "../services/addonManager.service"; 3 | 4 | export default async ( 5 | context: vscode.ExtensionContext, 6 | message: { data: { name: string } } 7 | ) => { 8 | const addon = addonManagerService.addons.get(message.data.name); 9 | addon?.uninstall(); 10 | }; 11 | -------------------------------------------------------------------------------- /client/src/addon_manager/commands/update.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import addonManager from "../services/addonManager.service"; 3 | import { git } from "../services/git.service"; 4 | import { DiffResultTextFile } from "simple-git"; 5 | import { WebVue } from "../panels/WebVue"; 6 | import { NotificationLevels } from "../types/webvue"; 7 | import { createChildLogger } from "../services/logging.service"; 8 | 9 | const localLogger = createChildLogger("Update Addon"); 10 | 11 | type Message = { 12 | data: { 13 | name: string; 14 | }; 15 | }; 16 | 17 | export default async (context: vscode.ExtensionContext, message: Message) => { 18 | const addon = addonManager.addons.get(message.data.name); 19 | if (!addon) { 20 | return; 21 | } 22 | try { 23 | await addon.update(); 24 | } catch (e) { 25 | const message = `Failed to update ${addon.name}`; 26 | localLogger.error(message, { report: false }); 27 | localLogger.error(String(e), { report: false }); 28 | WebVue.sendNotification({ 29 | level: NotificationLevels.error, 30 | message, 31 | }); 32 | } 33 | await addon.setLock(false); 34 | 35 | const diff = await git.diffSummary(["HEAD", "origin/main"]); 36 | addon.checkForUpdate(diff.files as DiffResultTextFile[]); 37 | }; 38 | -------------------------------------------------------------------------------- /client/src/addon_manager/config.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | // Development 4 | export const DEVELOPMENT_IFRAME_URL = "http://127.0.0.1:5173"; 5 | 6 | // GitHub Repository Info 7 | export const REPOSITORY = { 8 | PATH: "https://github.com/LuaLS/LLS-Addons.git", 9 | DEFAULT_BRANCH: "main", 10 | } 11 | 12 | export const REPOSITORY_OWNER = "carsakiller"; 13 | export const REPOSITORY_NAME = "LLS-Addons"; 14 | export const REPOSITORY_ISSUES_URL = 15 | "https://github.com/LuaLS/vscode-lua/issues/new?template=bug_report.yml"; 16 | export const ADDONS_DIRECTORY = "addons"; 17 | export const GIT_DOWNLOAD_URL = "https://git-scm.com/downloads"; 18 | 19 | // settings.json file info 20 | export const LIBRARY_SETTING = "Lua.workspace.library"; 21 | 22 | // Addon files 23 | export const PLUGIN_FILENAME = "plugin.lua"; 24 | export const CONFIG_FILENAME = "config.json"; 25 | export const INFO_FILENAME = "info.json"; 26 | 27 | let useGlobal = true 28 | export function getStorageUri(context: vscode.ExtensionContext) { 29 | return useGlobal ? context.globalStorageUri : (context.storageUri ?? context.globalStorageUri) 30 | } 31 | 32 | export function setGlobalStorageUri(use: boolean) { 33 | useGlobal = use 34 | } 35 | -------------------------------------------------------------------------------- /client/src/addon_manager/models/addon.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { createChildLogger } from "../services/logging.service"; 3 | import { CONFIG_FILENAME, INFO_FILENAME, LIBRARY_SETTING } from "../config"; 4 | import { AddonConfig, AddonInfo } from "../types/addon"; 5 | import { WebVue } from "../panels/WebVue"; 6 | import { 7 | applyAddonSettings, 8 | getLibraryPaths, 9 | revokeAddonSettings, 10 | } from "../services/settings.service"; 11 | import { git } from "../services/git.service"; 12 | import filesystem from "../services/filesystem.service"; 13 | import { DiffResultTextFile } from "simple-git"; 14 | import { getConfig, setConfig } from "../../languageserver"; 15 | 16 | const localLogger = createChildLogger("Addon"); 17 | 18 | export class Addon { 19 | readonly name: string; 20 | readonly uri: vscode.Uri; 21 | 22 | #displayName?: string; 23 | /** Whether or not this addon is currently processing an operation. */ 24 | #processing?: boolean; 25 | /** The workspace folders that this addon is enabled in. */ 26 | #enabled: boolean[]; 27 | /** Whether or not this addon has an update available from git. */ 28 | #hasUpdate?: boolean; 29 | /** Whether or not this addon is installed */ 30 | #installed: boolean; 31 | 32 | constructor(name: string, path: vscode.Uri) { 33 | this.name = name; 34 | this.uri = path; 35 | 36 | this.#enabled = []; 37 | this.#hasUpdate = false; 38 | this.#installed = false; 39 | } 40 | 41 | public get displayName() { 42 | return this.#displayName ?? this.name; 43 | } 44 | 45 | /** Fetch addon info from `info.json` */ 46 | public async fetchInfo() { 47 | const infoFilePath = vscode.Uri.joinPath(this.uri, INFO_FILENAME); 48 | const modulePath = vscode.Uri.joinPath(this.uri, "module"); 49 | 50 | const rawInfo = await filesystem.readFile(infoFilePath); 51 | const info = JSON.parse(rawInfo) as AddonInfo; 52 | 53 | this.#displayName = info.name; 54 | 55 | const moduleGit = git.cwd({ path: modulePath.fsPath, root: false }); 56 | 57 | let currentVersion = null; 58 | let tags: string[] = []; 59 | 60 | await this.getEnabled(); 61 | 62 | if (this.#installed) { 63 | await git.fetch(["origin", "--prune", "--prune-tags"]); 64 | tags = ( 65 | await moduleGit.tags([ 66 | "--sort=-taggerdate", 67 | "--merged", 68 | `origin/${await this.getDefaultBranch()}`, 69 | ]) 70 | ).all; 71 | 72 | const currentTag = await moduleGit 73 | .raw(["describe", "--tags", "--exact-match"]) 74 | .catch((err) => { 75 | return null; 76 | }); 77 | const commitsBehindLatest = await moduleGit.raw([ 78 | "rev-list", 79 | `HEAD..origin/${await this.getDefaultBranch()}`, 80 | "--count", 81 | ]); 82 | 83 | if (Number(commitsBehindLatest) < 1) { 84 | currentVersion = "Latest"; 85 | } else if (currentTag != "") { 86 | currentVersion = currentTag; 87 | } else { 88 | currentVersion = await moduleGit 89 | .revparse(["--short", "HEAD"]) 90 | .catch((err) => { 91 | localLogger.warn( 92 | `Failed to get current hash for ${this.name}: ${err}` 93 | ); 94 | return null; 95 | }); 96 | } 97 | } 98 | 99 | return { 100 | name: info.name, 101 | description: info.description, 102 | size: info.size, 103 | hasPlugin: info.hasPlugin, 104 | tags: tags, 105 | version: currentVersion, 106 | }; 107 | } 108 | 109 | /** Get the `config.json` for this addon. */ 110 | public async getConfigurationFile() { 111 | const configURI = vscode.Uri.joinPath( 112 | this.uri, 113 | "module", 114 | CONFIG_FILENAME 115 | ); 116 | 117 | try { 118 | const rawConfig = await filesystem.readFile(configURI); 119 | const config = JSON.parse(rawConfig); 120 | return config as AddonConfig; 121 | } catch (e) { 122 | localLogger.error( 123 | `Failed to read config.json file for ${this.name} (${e})` 124 | ); 125 | throw e; 126 | } 127 | } 128 | 129 | /** Update this addon using git. */ 130 | public async update() { 131 | return git 132 | .submoduleUpdate([this.uri.fsPath]) 133 | .then((message) => localLogger.debug(message)); 134 | } 135 | 136 | public async getDefaultBranch() { 137 | // Get branch from .gitmodules if specified 138 | const targetBranch = await git.raw( 139 | "config", 140 | "-f", 141 | ".gitmodules", 142 | "--get", 143 | `submodule.addons/${this.name}/module.branch` 144 | ); 145 | if (targetBranch) { 146 | return targetBranch; 147 | } 148 | 149 | // Fetch default branch from remote 150 | const modulePath = vscode.Uri.joinPath(this.uri, "module"); 151 | const result = (await git 152 | .cwd({ path: modulePath.fsPath, root: false }) 153 | .remote(["show", "origin"])) as string; 154 | const match = result.match(/HEAD branch: (\w+)/); 155 | 156 | return match![1]; 157 | } 158 | 159 | public async pull() { 160 | const modulePath = vscode.Uri.joinPath(this.uri, "module"); 161 | 162 | return await git.cwd({ path: modulePath.fsPath, root: false }).pull(); 163 | } 164 | 165 | public async checkout(obj: string) { 166 | const modulePath = vscode.Uri.joinPath(this.uri, "module"); 167 | return git 168 | .cwd({ path: modulePath.fsPath, root: false }) 169 | .checkout([obj]); 170 | } 171 | 172 | /** Check whether this addon is enabled, given an array of enabled library paths. 173 | * @param libraryPaths An array of paths from the `Lua.workspace.library` setting. 174 | */ 175 | public checkIfEnabled(libraryPaths: string[]) { 176 | const regex = new RegExp(`${this.name}\/module\/library`, "g"); 177 | 178 | const index = libraryPaths.findIndex((path) => regex.test(path)); 179 | return index !== -1; 180 | } 181 | 182 | /** Get the enabled state for this addon in all opened workspace folders */ 183 | public async getEnabled() { 184 | const folders = await getLibraryPaths(); 185 | 186 | // Check all workspace folders for a path that matches this addon 187 | const folderStates = folders.map((entry) => { 188 | return { 189 | folder: entry.folder, 190 | enabled: this.checkIfEnabled(entry.paths), 191 | }; 192 | }); 193 | 194 | folderStates.forEach( 195 | (entry) => (this.#enabled[entry.folder.index] = entry.enabled) 196 | ); 197 | 198 | const moduleURI = vscode.Uri.joinPath(this.uri, "module"); 199 | 200 | const exists = await filesystem.exists(moduleURI); 201 | const empty = await filesystem.empty(moduleURI); 202 | this.#installed = exists && !empty; 203 | 204 | return folderStates; 205 | } 206 | 207 | public async enable( 208 | folder: vscode.WorkspaceFolder, 209 | installLocation: vscode.Uri 210 | ) { 211 | const librarySetting = ((await getConfig( 212 | LIBRARY_SETTING, 213 | folder.uri 214 | )) ?? []) as string[]; 215 | 216 | const enabled = await this.checkIfEnabled(librarySetting); 217 | if (enabled) { 218 | localLogger.warn(`${this.name} is already enabled`); 219 | this.#enabled[folder.index] = true; 220 | return; 221 | } 222 | 223 | // Init submodule 224 | try { 225 | await git.submoduleInit([this.uri.fsPath]); 226 | localLogger.debug("Initialized submodule"); 227 | } catch (e) { 228 | localLogger.warn(`Unable to initialize submodule for ${this.name}`); 229 | localLogger.warn(e); 230 | throw e; 231 | } 232 | 233 | try { 234 | await git.submoduleUpdate([this.uri.fsPath]); 235 | localLogger.debug("Submodule up to date"); 236 | } catch (e) { 237 | localLogger.warn(`Unable to update submodule for ${this.name}`); 238 | localLogger.warn(e); 239 | throw e; 240 | } 241 | 242 | // Apply addon settings 243 | const libraryPath = vscode.Uri.joinPath( 244 | this.uri, 245 | "module", 246 | "library" 247 | ).path.replace(installLocation.path, "${addons}"); 248 | 249 | const configValues = await this.getConfigurationFile(); 250 | 251 | try { 252 | await setConfig([ 253 | { 254 | action: "add", 255 | key: LIBRARY_SETTING, 256 | value: libraryPath, 257 | uri: folder.uri, 258 | }, 259 | ]); 260 | if (configValues.settings) { 261 | await applyAddonSettings(folder, configValues.settings); 262 | localLogger.info(`Applied addon settings for ${this.name}`); 263 | } 264 | } catch (e) { 265 | localLogger.warn(`Failed to apply settings of "${this.name}"`); 266 | localLogger.warn(e); 267 | return; 268 | } 269 | 270 | this.#enabled[folder.index] = true; 271 | localLogger.info(`Enabled "${this.name}"`); 272 | } 273 | 274 | public async disable(folder: vscode.WorkspaceFolder, silent = false) { 275 | const librarySetting = ((await getConfig( 276 | LIBRARY_SETTING, 277 | folder.uri 278 | )) ?? []) as string[]; 279 | 280 | const regex = new RegExp( 281 | `addons}?[/\\\\]+${this.name}[/\\\\]+module[/\\\\]+library`, 282 | "g" 283 | ); 284 | const index = librarySetting.findIndex((path) => regex.test(path)); 285 | 286 | if (index === -1) { 287 | if (!silent) localLogger.warn(`"${this.name}" is already disabled`); 288 | this.#enabled[folder.index] = false; 289 | return; 290 | } 291 | 292 | // Remove this addon from the library list 293 | librarySetting.splice(index, 1); 294 | const result = await setConfig([ 295 | { 296 | action: "set", 297 | key: LIBRARY_SETTING, 298 | value: librarySetting, 299 | uri: folder.uri, 300 | }, 301 | ]); 302 | if (!result) { 303 | localLogger.error( 304 | `Failed to update ${LIBRARY_SETTING} when disabling ${this.name}` 305 | ); 306 | return; 307 | } 308 | 309 | // Remove addon settings if installed 310 | if (this.#installed) { 311 | const configValues = await this.getConfigurationFile(); 312 | try { 313 | if (configValues.settings) 314 | await revokeAddonSettings(folder, configValues.settings); 315 | } catch (e) { 316 | localLogger.error( 317 | `Failed to revoke settings of "${this.name}"` 318 | ); 319 | return; 320 | } 321 | } 322 | 323 | this.#enabled[folder.index] = false; 324 | localLogger.info(`Disabled "${this.name}"`); 325 | } 326 | 327 | public async uninstall() { 328 | for (const folder of vscode.workspace.workspaceFolders ?? []) { 329 | await this.disable(folder, true); 330 | } 331 | const files = 332 | (await filesystem.readDirectory( 333 | vscode.Uri.joinPath(this.uri, "module"), 334 | { depth: 1 } 335 | )) ?? []; 336 | files.map((f) => { 337 | return filesystem.deleteFile(f.uri, { 338 | recursive: true, 339 | useTrash: false, 340 | }); 341 | }); 342 | await Promise.all(files); 343 | localLogger.info(`Uninstalled ${this.name}`); 344 | this.#installed = false; 345 | this.setLock(false); 346 | } 347 | 348 | /** Convert this addon to an object ready for sending to WebVue. */ 349 | public async toJSON() { 350 | await this.getEnabled(); 351 | 352 | const { name, description, size, hasPlugin, tags, version } = 353 | await this.fetchInfo(); 354 | const enabled = this.#enabled; 355 | const installTimestamp = (await git.log()).latest?.date; 356 | const hasUpdate = this.#hasUpdate; 357 | 358 | return { 359 | name: this.name, 360 | displayName: name, 361 | description, 362 | enabled, 363 | hasPlugin, 364 | installTimestamp, 365 | size, 366 | hasUpdate, 367 | processing: this.#processing, 368 | installed: this.#installed, 369 | tags, 370 | version, 371 | }; 372 | } 373 | 374 | public checkForUpdate(modified: DiffResultTextFile[]) { 375 | this.#hasUpdate = false; 376 | if ( 377 | modified.findIndex((modifiedItem) => 378 | modifiedItem.file.includes(this.name) 379 | ) !== -1 380 | ) { 381 | localLogger.info(`Found update for "${this.name}"`); 382 | this.#hasUpdate = true; 383 | } 384 | return this.#hasUpdate; 385 | } 386 | 387 | /** Get a list of options for a quick picker that lists the workspace 388 | * folders that the addon is enabled/disabled in. 389 | * @param enabledState The state the addon must be in in a folder to be included. 390 | * `true` will only return the folders that the addon is **enabled** in. 391 | * `false` will only return the folders that the addon is **disabled** in 392 | */ 393 | public async getQuickPickerOptions(enabledState: boolean) { 394 | return (await this.getEnabled()) 395 | .filter((entry) => entry.enabled === enabledState) 396 | .map((entry) => { 397 | return { 398 | label: entry.folder.name, 399 | detail: entry.folder.uri.path, 400 | }; 401 | }); 402 | } 403 | 404 | public async setLock(state: boolean) { 405 | this.#processing = state; 406 | return this.sendToWebVue(); 407 | } 408 | 409 | /** Send this addon to WebVue. */ 410 | public async sendToWebVue() { 411 | WebVue.sendMessage("addAddon", { addons: await this.toJSON() }); 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /client/src/addon_manager/panels/WebVue.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import { createChildLogger } from "../services/logging.service"; 4 | import { commands } from "../commands"; 5 | import { Notification, WebVueMessage } from "../types/webvue"; 6 | import { DEVELOPMENT_IFRAME_URL } from "../config"; 7 | 8 | const localLogger = createChildLogger("WebVue"); 9 | const commandLogger = createChildLogger("Command"); 10 | 11 | export class WebVue { 12 | public static currentPanel: WebVue | undefined; 13 | private readonly _panel: vscode.WebviewPanel; 14 | private readonly _extensionUri: vscode.Uri; 15 | private _disposables: vscode.Disposable[] = []; 16 | 17 | private constructor( 18 | context: vscode.ExtensionContext, 19 | panel: vscode.WebviewPanel 20 | ) { 21 | const extensionUri = context.extensionUri; 22 | 23 | this._panel = panel; 24 | this._extensionUri = extensionUri; 25 | this._panel.iconPath = { 26 | dark: vscode.Uri.joinPath(extensionUri, "images", "logo.png"), 27 | light: vscode.Uri.joinPath(extensionUri, "images", "logo.png"), 28 | }; 29 | this._disposables.push( 30 | this._panel.onDidDispose(this.dispose, null, this._disposables), 31 | this._setWebviewMessageListener(this._panel.webview, context) 32 | ); 33 | this._panel.webview.html = this._getWebViewContent( 34 | this._panel.webview, 35 | context 36 | ); 37 | } 38 | 39 | /** Convert a standard file uri to a uri usable by this webview. */ 40 | private toWebviewUri(pathList: string[]) { 41 | return this._panel.webview.asWebviewUri( 42 | vscode.Uri.joinPath(this._extensionUri, ...pathList) 43 | ); 44 | } 45 | 46 | /** Send a message to the webview */ 47 | public static sendMessage( 48 | command: string, 49 | data: { [index: string]: unknown } | unknown 50 | ) { 51 | WebVue.currentPanel?._panel.webview.postMessage({ command, data }); 52 | } 53 | 54 | public static sendNotification(message: Notification) { 55 | WebVue.sendMessage("notify", message); 56 | } 57 | 58 | /** Set the loading state of a store in the webview */ 59 | public static setLoadingState(loading: boolean) { 60 | WebVue.sendMessage("addonStore", { 61 | property: "loading", 62 | value: loading, 63 | }); 64 | } 65 | 66 | /** Reveal or create a new panel in VS Code */ 67 | public static render(context: vscode.ExtensionContext) { 68 | const extensionUri = context.extensionUri; 69 | 70 | if (WebVue.currentPanel) { 71 | WebVue.currentPanel._panel.reveal(vscode.ViewColumn.One); 72 | } else { 73 | const panel = vscode.window.createWebviewPanel( 74 | "lua-addon_manager", 75 | "Lua Addon Manager", 76 | vscode.ViewColumn.Active, 77 | { 78 | enableScripts: true, 79 | enableForms: false, 80 | localResourceRoots: [extensionUri], 81 | } 82 | ); 83 | 84 | WebVue.currentPanel = new WebVue(context, panel); 85 | } 86 | 87 | const workspaceOpen = 88 | vscode.workspace.workspaceFolders !== undefined && 89 | vscode.workspace.workspaceFolders.length > 0; 90 | const clientVersion = context.extension.packageJSON.version; 91 | 92 | WebVue.sendMessage("appStore", { 93 | property: "workspaceState", 94 | value: workspaceOpen, 95 | }); 96 | WebVue.sendMessage("appStore", { 97 | property: "clientVersion", 98 | value: clientVersion, 99 | }); 100 | localLogger.debug(`Workspace Open: ${workspaceOpen}`); 101 | } 102 | 103 | /** Dispose of panel to clean up resources when it is closed */ 104 | public dispose() { 105 | WebVue.currentPanel = undefined; 106 | 107 | this._panel?.dispose(); 108 | 109 | while (this._disposables.length) { 110 | const disposable = this._disposables.pop(); 111 | if (disposable) { 112 | disposable.dispose(); 113 | } 114 | } 115 | } 116 | 117 | /** Get the HTML content of the webview */ 118 | private _getWebViewContent( 119 | webview: vscode.Webview, 120 | context: vscode.ExtensionContext 121 | ) { 122 | if (context.extensionMode !== vscode.ExtensionMode.Production) { 123 | return ` 124 | 125 | 126 |
127 | 128 | 129 |