├── .devcontainer └── devcontainer.json ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── textmate_syntax_related.md └── workflows │ ├── build.yml │ ├── test.yml │ ├── vsce_package.yml │ ├── vsce_publish.yml │ └── vsce_publish_pre.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── COMMON-LISP-symbols.csv └── funcs-and-macros-with-sideeffects.csv ├── declaratives ├── commonlisp_snippets.json └── language-configuration.json ├── images ├── commonlisp_file_icon.svg ├── doc │ ├── data_flow.svg │ ├── dependency_graph.svg │ └── layers.png ├── icon.png ├── snippets.gif ├── syntax_dark_plus.png └── syntax_light_plus.png ├── package-lock.json ├── package.json ├── src └── web │ ├── builders │ ├── DocSymbolInfo.ts │ ├── builders_util.ts │ ├── call_hierarchy_builder │ │ ├── CallHrchyInfo.ts │ │ └── call_hierarchy_builder.ts │ ├── comp_item_builder │ │ ├── OriSymbolsCompItem.ts │ │ ├── UserSymbolsCompItem.ts │ │ ├── cl_kind.ts │ │ └── comp_item_ori_builder.ts │ ├── doc_symbol_builder │ │ └── doc_symbol_builder.ts │ ├── loop_keywords.ts │ └── semantic_tokens_builder │ │ ├── semantic_tokens_builder.ts │ │ ├── token_util.ts │ │ └── update_util.ts │ ├── cl_data │ ├── cl_doc.json │ ├── cl_kind.json │ ├── cl_non_alphabetic.json │ └── cl_non_alphabetic_doc.json │ ├── collect_info │ ├── SymbolInfo.ts │ ├── collect_from_text │ │ ├── ScanDocRes.ts │ │ ├── lambda_list.ts │ │ ├── loop.ts │ │ ├── no_code.ts │ │ ├── non_var.ts │ │ ├── non_var_util.ts │ │ └── var.ts │ └── collect_util.ts │ ├── common │ ├── algorithm.ts │ ├── cl_util.ts │ └── enum.ts │ ├── doc │ └── get_doc.ts │ ├── entry │ ├── TraceableDisposables.ts │ ├── WorkspaceConfig.ts │ └── init.ts │ ├── extension.ts │ └── provider_interface │ ├── StructuredInfo.ts │ ├── TriggerEvent.ts │ ├── provider_util.ts │ ├── providers │ ├── call_hierarchy_provider.ts │ ├── comp_item_provider.ts │ ├── def_provider.ts │ ├── doc_symbol_provider.ts │ ├── hover_provider.ts │ ├── reference_provider.ts │ └── semantic_tokens_provider.ts │ └── structured_info.ts ├── syntaxes ├── cl_codeblock.tmLanguage.json ├── cl_codeblock.yaml ├── commonlisp.tmLanguage.json ├── commonlisp.yaml ├── fixtures │ ├── baselines │ │ ├── demo.record.txt │ │ └── fstr.record.txt │ └── cases │ │ ├── demo.lsp │ │ └── fstr.lsp └── scripts │ ├── build_grammar.mjs │ ├── gen_record.mjs │ ├── gen_record.mts │ ├── test_grammar.mjs │ ├── test_util.mjs │ └── tsconfig.json ├── tsconfig.json └── webpack.config.js /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Node.js", 3 | "image": "mcr.microsoft.com/devcontainers/base:ubuntu", 4 | "features": { 5 | "ghcr.io/devcontainers/features/node:1": { 6 | "version": "20" 7 | } 8 | }, 9 | "postCreateCommand": "npm install", 10 | "customizations": { 11 | "vscode": { 12 | "extensions": ["dbaeumer.vscode-eslint"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | If you have time, please [extension-bisect](https://code.visualstudio.com/blogs/2021/02/16/extension-bisect) first to identify which extension causes the bug. 12 | 13 | Does this issue occur when all the other extensions are disabled?: Yes/No 14 | - VS Code Version: *You can find it by* [*how-do-i-find-the-version*](https://code.visualstudio.com/docs/supporting/FAQ#_how-do-i-find-the-version) 15 | - Desktop Electron Version or Browser version: *You can find it by* [*how-do-i-find-the-version*](https://code.visualstudio.com/docs/supporting/FAQ#_how-do-i-find-the-version) 16 | - OS Version: *which OS and its version* 17 | 18 | **Describe the bug** 19 | A clear and concise description of what the bug or potential improvement is. 20 | 21 | **Steps to Reproduce** 22 | Steps to reproduce the behavior: 23 | 1. Go to '...' 24 | 2. Click on '....' 25 | 26 | **Expected behavior** 27 | A clear and concise description of what you expected to happen. 28 | 29 | **Screenshots** 30 | If applicable, add screenshots to help explain your problem. 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/textmate_syntax_related.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Textmate-syntax related 3 | about: Bug or improvement of Textmate-syntax 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | Please help confirm if it is a purely Textmate grammar issue: 12 | 1. set `"commonLisp.StaticAnalysis.enabled": false` in this extension's built-in settings (under `Common Lisp` tab) or set `"editor.semanticHighlighting.enabled": false` in VS Code's settings. 13 | 2. reload VS Code. 14 | 3. check if you still see the bug and describe your findings. 15 | 16 | - VS Code Version: *You can find it by* [*how-do-i-find-the-version*](https://code.visualstudio.com/docs/supporting/FAQ#_how-do-i-find-the-version) 17 | - Desktop Electron Version or Browser version: *You can find it by* [*how-do-i-find-the-version*](https://code.visualstudio.com/docs/supporting/FAQ#_how-do-i-find-the-version) 18 | - OS Version: *which OS and its version* 19 | 20 | **Describe the bug** 21 | A clear and concise description of what the bug or potential improvement is. 22 | 23 | **Code** 24 | ```lsp 25 | ;; Please include a code snippet that demonstrates the issue 26 | 27 | ``` 28 | 29 | **Current Behavior Screenshot** 30 | 31 | 32 | **Expected Behavior Screenshot** 33 | 34 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | # Controls when the workflow will run 4 | on: 5 | push: 6 | branches: 7 | - master 8 | 9 | pull_request: 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | inputs: 14 | tags: 15 | description: "Tag Name" 16 | required: false 17 | 18 | workflow_call: 19 | 20 | jobs: 21 | build: 22 | name: build 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: actions/setup-node@v4 27 | with: 28 | node-version: 20 29 | - name: Install dependencies 30 | run: npm ci 31 | 32 | - name: tsc compile 33 | run: npm run tscc 34 | 35 | - name: tsc compile with declarationMap 36 | run: npm run tsccd 37 | 38 | - name: Code Linting 39 | run: npm run lint 40 | 41 | - name: Build Grammar from yaml to json 42 | run: npm run bg 43 | 44 | - name: build with webpack 45 | run: npm run webpackp 46 | 47 | - name: clean webpackp 48 | run: rm -rf ./dist 49 | 50 | - name: build with esbuild 51 | run: npm run esbuildp 52 | 53 | test: 54 | uses: ./.github/workflows/test.yml 55 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | #push: 5 | # branches: 6 | # - master 7 | 8 | #pull_request: 9 | 10 | workflow_dispatch: 11 | inputs: 12 | tags: 13 | description: "Tag Name" 14 | required: false 15 | 16 | workflow_call: 17 | 18 | jobs: 19 | test: 20 | name: test 21 | #strategy: 22 | # matrix: 23 | # os: [macos-latest, ubuntu-latest, windows-latest] 24 | #runs-on: ${{ matrix.os }} 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: actions/setup-node@v4 29 | with: 30 | node-version: 20 31 | - name: Install dependencies 32 | run: npm ci 33 | 34 | - name: Build Grammar from yaml to json 35 | run: npm run bg 36 | 37 | - name: Test Grammar 38 | run: npm run testg 39 | 40 | #- name: Headless Test for the Web Extension 41 | # run: xvfb-run -a npm test 42 | -------------------------------------------------------------------------------- /.github/workflows/vsce_package.yml: -------------------------------------------------------------------------------- 1 | name: vsce Package 2 | 3 | # Controls when the workflow will run 4 | on: 5 | push: 6 | branches: 7 | - master 8 | 9 | pull_request: 10 | 11 | release: 12 | types: [created] 13 | 14 | # Allows you to run this workflow manually from the Actions tab 15 | workflow_dispatch: 16 | inputs: 17 | tags: 18 | description: "Tag Name" 19 | required: false 20 | 21 | workflow_call: 22 | 23 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 24 | jobs: 25 | # This workflow contains a single job called "package" 26 | package: 27 | name: package 28 | runs-on: ubuntu-latest 29 | # Steps represent a sequence of tasks that will be executed as part of the job 30 | steps: 31 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 32 | - uses: actions/checkout@v4 33 | - uses: actions/setup-node@v4 34 | with: 35 | node-version: 20 36 | - name: Install dependencies 37 | run: npm ci 38 | 39 | #- name: Build Grammar from yaml to json 40 | # run: npm run bg 41 | 42 | - name: Package web extension 43 | run: npm run vscode:prepublish 44 | 45 | - name: Install vsce 46 | run: npm i -g @vscode/vsce 47 | 48 | - name: run vsce package 49 | run: npm run package 50 | 51 | - name: Archive vsix production 52 | uses: actions/upload-artifact@v4 53 | with: 54 | name: vsix production 55 | path: ./*.vsix 56 | if-no-files-found: error 57 | 58 | - name: print tag 59 | env: 60 | TAGS: ${{ github.event.inputs.tags }} 61 | run: | 62 | echo "[$TAGS] build completed." 63 | -------------------------------------------------------------------------------- /.github/workflows/vsce_publish.yml: -------------------------------------------------------------------------------- 1 | name: vsce Publish 2 | 3 | # Controls when the workflow will run 4 | on: 5 | # Allows you to run this workflow manually from the Actions tab 6 | workflow_dispatch: 7 | inputs: 8 | tags: 9 | description: "Tag Name" 10 | required: true 11 | 12 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 13 | jobs: 14 | test: 15 | uses: ./.github/workflows/test.yml 16 | 17 | publish: 18 | name: publish 19 | #needs: test 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: actions/setup-node@v4 24 | with: 25 | node-version: 20 26 | - name: Install dependencies 27 | run: npm ci 28 | 29 | - name: Package web extension 30 | run: npm run vscode:prepublish 31 | 32 | - name: Install vsce 33 | run: npm i -g @vscode/vsce 34 | 35 | - name: run vsce publish 36 | run: npm run publish 37 | env: 38 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 39 | 40 | - name: print tag 41 | env: 42 | TAGS: ${{ github.event.inputs.tags }} 43 | run: | 44 | echo "[$TAGS] publish completed." 45 | -------------------------------------------------------------------------------- /.github/workflows/vsce_publish_pre.yml: -------------------------------------------------------------------------------- 1 | name: vsce Publish Pre-release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tags: 7 | description: "Tag Name" 8 | required: true 9 | 10 | jobs: 11 | test: 12 | uses: ./.github/workflows/test.yml 13 | 14 | publish_pre: 15 | name: publish pre-release 16 | #needs: test 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: 20 23 | - name: Install dependencies 24 | run: npm ci 25 | 26 | - name: Package web extension 27 | run: npm run vscode:prepublish 28 | 29 | - name: Install vsce 30 | run: npm i -g @vscode/vsce 31 | 32 | - name: run vsce publish with pre-release flag 33 | run: npm run publish-pre 34 | env: 35 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 36 | 37 | - name: print tag 38 | env: 39 | TAGS: ${{ github.event.inputs.tags }} 40 | run: | 41 | echo "[$TAGS] publish with pre-release completed." 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test-web/ 5 | *.vsix 6 | *.zip 7 | *.log 8 | .parcel-cache 9 | syntaxes/fixtures/generated/* -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://code.visualstudio.com/docs/editor/extension-marketplace#_workspace-recommended-extensions 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | // If breakpoint is unbound randomly, just open devtools in Chrome 10 | "name": "Run Web Extension", 11 | "type": "extensionHost", 12 | "debugWebWorkerHost": true, 13 | "request": "launch", 14 | "args": [ 15 | // "--profile-temp", // clean environment 16 | "--extensionDevelopmentPath=${workspaceFolder}", 17 | "--extensionDevelopmentKind=web" 18 | ], 19 | "sourceMaps": true, 20 | "resolveSourceMapLocations": [ 21 | "${workspaceFolder}/**", 22 | "!**/node_modules/**" 23 | ], 24 | "outFiles": ["${workspaceFolder}/dist/web/**/*.js"], 25 | //"preLaunchTask": "npm: esbuildc" 26 | }, 27 | { 28 | "name": "Test with VS Code", 29 | "type": "extensionHost", 30 | "debugWebWorkerHost": true, 31 | "request": "launch", 32 | "args": [ 33 | //"${workspaceFolder}/src/test", 34 | //"--disable-extensions", 35 | "--extensionDevelopmentPath=${workspaceFolder}", 36 | "--extensionDevelopmentKind=web", 37 | "--extensionTestsPath=${workspaceFolder}/src/test/index.node.js" 38 | ], 39 | "sourceMaps": true, 40 | // https://github.com/microsoft/vscode/issues/102042#issuecomment-656402933 41 | "resolveSourceMapLocations": [ 42 | "${workspaceFolder}/**", 43 | "!**/node_modules/**" 44 | ], 45 | "outFiles": ["${workspaceFolder}/dist/web/**/*.js"], 46 | //"preLaunchTask": "npm: esbuildc" 47 | }, 48 | { 49 | "name": "Test in Node", 50 | "request": "launch", 51 | "outputCapture": "std", 52 | "type": "node", 53 | "cwd": "${workspaceFolder}", 54 | "runtimeExecutable": "npm", 55 | "runtimeArgs": ["run", "test"] 56 | }, 57 | { 58 | "name": "Test in terminal", 59 | "command": "npm run test", 60 | "request": "launch", 61 | "type": "node-terminal" 62 | }, 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | 12 | "editor.insertSpaces": true, 13 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "esbuildc", 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | }, 13 | "problemMatcher": [ 14 | "$ts-webpack", 15 | "$tslint-webpack" 16 | ] 17 | }, 18 | { 19 | "type": "npm", 20 | "script": "esbuildw", 21 | "group": "build", 22 | "isBackground": true, 23 | "problemMatcher": [ 24 | "$ts-webpack-watch", 25 | "$tslint-webpack-watch" 26 | ] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | */** 4 | 5 | # Whitelist 6 | !package.json 7 | 8 | !dist/web/extension.js 9 | 10 | !images/commonlisp_file_icon.svg 11 | !images/icon.png 12 | 13 | !syntaxes/commonlisp.tmLanguage.json 14 | !syntaxes/cl_codeblock.tmLanguage.json 15 | 16 | !declaratives/language-configuration.json 17 | !declaratives/commonlisp_snippets.json 18 | 19 | !README.md 20 | !CHANGELOG.md 21 | !LICENSE 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.2.10] - 2024-02-16 4 | ### Changed 5 | - engineering version only 6 | 7 | ## [1.2.9] - 2023-11-19 8 | ### Added 9 | - can optionally de-color `quote` parts 10 | 11 | ## [1.2.8] - 2023-10-04 12 | ### Changed 13 | - engineering version only 14 | 15 | ## [1.2.7] - 2023-08-25 16 | ### Changed 17 | - optimize the packaging size of this extension 18 | 19 | ## [1.2.6] - 2023-08-08 20 | ### Changed 21 | - `README.md` update README 22 | ## [1.2.5] - 2023-07-25 23 | ### Added 24 | - add hover provider for user-defined doc 25 | 26 | ### Changed 27 | - refactor the folder layers 28 | 29 | ## [1.2.4] - 2023-06-27 30 | ### Added 31 | - polish LOOP keywords highlighting 32 | 33 | ## [1.2.3] - 2023-06-27 34 | ### Fixed 35 | - fix non-alphabetic auto-completion 36 | 37 | ## [1.2.2] - 2023-06-26 38 | ### Changed 39 | - `README.md` update README 40 | 41 | ## [1.2.1] - 2023-06-26 42 | ### Added 43 | - add LOOP keywords highlighting 44 | 45 | ### Fixed 46 | - `comp_item_provider.ts` fix duplicated autocomplete items 47 | - fix some minor bugs, no major feature changes 48 | 49 | ### Changed 50 | - `package.json` vscode compatibility is upgraded to 1.63 for pre-release feature 51 | - refactor the trigger mechanism of the semantic analysis. 52 | The response time should be reduced, but more frequent requests may take 53 | more cpu time. 54 | 55 | ### Deprecated 56 | - old `pair_parser.ts` 57 | 58 | ### Removed 59 | - In `WorkspaceConfig.ts`, `debounceTimeout` and `throttleTimeout` are removed 60 | 61 | ## [1.1.4] - 2022-06-29 62 | ### Changed 63 | - `README.md` update README 64 | 65 | ## [1.1.3] - 2022-06-29 66 | ### Fixed 67 | - `README.md` fix some typos 68 | 69 | ## [1.1.2] - 2022-06-28 70 | ### Added 71 | - `src/web` become a web extension 72 | - `syntaxes/commonlisp.yaml` improve syntax highlighting 73 | - `syntaxes/cl_codeblock.yaml` for highlighting code block in Markdown 74 | 75 | ## [0.2.2] - 2022-05-30 76 | ### Fixed 77 | - `syntaxes/commonlisp.yaml` fixed syntax highlighting 78 | 79 | ## [0.2.1] - 2022-05-24 80 | ### Added 81 | - `syntaxes/commonlisp.yaml` added highlighting for formatted strings 82 | - `syntaxes/commonlisp.tmLanguage.json` is automatically built from `syntaxes/commonlisp.yaml` 83 | 84 | ### Changed 85 | - `syntaxes/commonlisp.yaml` more accurate highlighting of packages and literal symbols 86 | - `snippets/commonlisp_snippets.json` more snippets 87 | 88 | ### Deprecated 89 | - `syntaxes/commonlisp.tmLanguage` is achieved, and `syntaxes/commonlisp.yaml` will be actively maintained 90 | 91 | ## [0.1.4] - 2022-03-05 92 | ### Added 93 | - `commonlisp_file_icon.svg` file for common lisp file icon 94 | 95 | ## [0.1.3] - 2021-12-09 96 | ### Changed 97 | - `commonlisp.tmLanguage` fix variables highlighting 98 | 99 | ## [0.1.2] - 2021-12-01 100 | ### Changed 101 | - `commonlisp.tmLanguage` changed highlighting colors and fixed color errors 102 | 103 | ## [0.0.2] - 2020-06-26 104 | ### Added 105 | - `commonlisp.tmLanguage` file syntax highlighting 106 | - `commonlisp.json` file for snippets -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Qingpeng Li 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Common Lisp language support for VS Code 2 | This VS Code extension supports Syntax Highlighting, Snippets, Completion, Hover, Definition, References, Document Symbol, Call Hierarchy, and Semantic Tokens for Common Lisp. 3 | 4 | ## Features 5 | 6 | ### Syntax Highlighting 7 | 8 | 9 | 10 | 11 | ### Snippets 12 | 13 | 14 | ## Usage and Recommendation 15 | Beginner's Guide: [Overview](https://code.visualstudio.com/docs/languages/overview). 16 | 17 | ### Quick Guide 18 | 19 | File Types: `lisp`, `lsp`, `l`, `cl`, `asd`, `asdf`, and you can add more by yourself: [adding-a-file-extension-to-a-language](https://code.visualstudio.com/docs/languages/overview#_adding-a-file-extension-to-a-language). 20 | 21 | |Kind of Symbols |Color (Dark+)|Color (Light+)| 22 | |-|-|-| 23 | |Macro, Declaration | Blue | Dark Blue | 24 | |Special Operator | Purple | Purple | 25 | |Accessor, Functions, Standard Generic Function | Yellow | Khaki | 26 | |Class, System Class, Type, Condition Type | Green | Dark Green | 27 | |Keyword Package Symbol, Local Variable | Sky Blue | Navy Blue | 28 | |Constant Variable | Light Blue | Blue | 29 | |Special Variable | Red | Brown | 30 | 31 | Snippets support: `defun`, `if`, `cond`, `let`, `let*`, `lambda`, etc. 32 | 33 | For huge files, in some rare cases, semantic highlighting might lose synchronization when switching files or some large change happens quickly. You only need to type any character in the file to recover. 34 | 35 | ### Preference 36 | The language identifier (id) is `commonlisp` . 37 | 38 | If you need to customize your setting only for Common Lisp files, in `settings.json`, please add something like 39 | ```json 40 | "[commonlisp]": { 41 | "editor.bracketPairColorization.enabled": false 42 | } 43 | ``` 44 | 45 | Bracket pair colorization: 46 | - This is enabled [by default](https://code.visualstudio.com/updates/v1_67#_bracket-pair-colorization-enabled-by-default) in VS Code. Bracket pair colorization can be disabled by setting `"editor.bracketPairColorization.enabled": false`. 47 | (Thanks to the past contributions of [Bracket Pair Colorizer 2](https://marketplace.visualstudio.com/items?itemName=CoenraadS.bracket-pair-colorizer-2) ) 48 | 49 | Hover tooltip: 50 | - If you find this disturbing, you can disable it in `Editor> Hover` or set a larger delay in `Editor> Hover:Delay`. 51 | 52 | Quick suggestions: 53 | - If you need suggestions while in an active snippet, you can disable `Editor> Suggest:Snippets Prevent Quick Suggestions`. 54 | - If you need `Snippets` to be on the top of suggestions, you can set `"editor.snippetSuggestions": "top"`. 55 | 56 | Semantic highlighting: 57 | - Semantic highlighting can be disabled by setting `"editor.semanticHighlighting.enabled": false`. 58 | 59 | Also, there are some built-in settings of this extension that can customize more **advanced preferences**, 60 | for example, which language feature provider should be used, which token range should be excluded, and how to deal with the backquote part. See [wiki](https://github.com/qingpeng9802/vscode-common-lisp/wiki/Configuration). 61 | 62 | > Please note that the static analysis currently implemented is **experimental** and may be incomplete and contain errors, so the result is not compiler-level. 63 | If you need to disable all [Programmatic Language Features](https://code.visualstudio.com/api/language-extensions/programmatic-language-features), that is, only use TextMate-based syntax highlighting, you can set `"commonLisp.StaticAnalysis.enabled": false` in this extension's built-in settings (under `Common Lisp` tab). 64 | 65 | ## Design 66 | 67 | ### Syntax Highlighting 68 | Because of the functional features of Common Lisp, we use the intuition of Common Lisp to design syntax highlighting instead of the intuition of non-functional language to design syntax highlighting. That is, we strictly follow the CL-ANSI 1.4.4.14 to classify the 978 external symbols in COMMON-LISP package. 69 | 70 | We processed [Common Lisp HyperSpec](https://www.lispworks.com/documentation/HyperSpec/Front/) to get the kind of each symbol. The result is in `./assets/COMMON-LISP-symbols.csv`, and please feel free to reuse the result :) 71 | 72 | We assign different colors to different kinds of symbols, and the assignment rule can be found in the start comment of `./syntaxes/commonlisp.yaml`. This file includes comments (related info in `CL-ANSI`) for all rules. 73 | 74 | > Please use VS Code 1.72.0 or later for the best performance and profile consistency. 75 | 76 | ### Static Analysis 77 | Currently, we use a very simple hand-written parser and combine it with regex to parse the code. Thus, the accuracy, precision, and performance are not good enough. However, we have no plans to complicate the parser further since it is like rebuilding a new wheel (new parser) using TypeScript. 78 | 79 | Since this extension is designed as a [Web Extension](https://code.visualstudio.com/api/extension-guides/web-extensions), we are considering using [node-tree-sitter](https://github.com/tree-sitter/node-tree-sitter) as the parser in the future. However, we have no plan to update the parser recently since we are still evaluating its impact on the architecture of VS Code language service (see [Anycode](https://github.com/microsoft/vscode-anycode)). 80 | 81 | ### Learn More 82 | See [Developer Guide](https://github.com/qingpeng9802/vscode-common-lisp/blob/master/CONTRIBUTING.md). 83 | 84 | ## Acknowledgment 85 | [CL-ANSI Standard Draft](https://franz.com/support/documentation/cl-ansi-standard-draft-w-sidebar.pdf), 86 | [Common Lisp HyperSpec](https://www.lispworks.com/documentation/HyperSpec/Front/), 87 | [vscode-scheme](https://github.com/sjhuangx/vscode-scheme), 88 | [Scheme.tmLanguage](https://github.com/egrachev/sublime-scheme/blob/master/Scheme.tmLanguage), 89 | [Lisp.tmLanguage](https://github.com/bradrobertson/sublime-packages/blob/master/Lisp/Lisp.tmLanguage), 90 | [regex101](https://regex101.com/), 91 | OSS license from [structure101](https://structure101.com/) 92 | 93 | ### Image Credits 94 | The `icon.png` is from [Common-Lisp.net](https://common-lisp.net/) and resized. 95 | The `commonlisp_file_icon.svg` is extracted from the common lisp icon and colored with the purple in Conrad Barski's [Logo](https://www.lisperati.com/logo.html). 96 | `icon.png` and `commonlisp_file_icon.svg` are used under [Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/). 97 | The code segment in `Syntax Highlighting` is from [SBCL Repository](https://github.com/sbcl/sbcl). 98 | -------------------------------------------------------------------------------- /assets/funcs-and-macros-with-sideeffects.csv: -------------------------------------------------------------------------------- 1 | abort,T 2 | break,T 3 | case,T 4 | ccase,T 5 | check-type,T 6 | clear-input,T 7 | close,T 8 | clrhash,T 9 | continue,T 10 | decf,T 11 | define-method-combination,T 12 | define-modify-macro,T 13 | defparameter,T 14 | defvar,T 15 | delete,T 16 | delete-duplicates,T 17 | delete-if,T 18 | delete-if-not,T 19 | describe,T 20 | ecase,T 21 | error,T 22 | export,T 23 | file-position,T 24 | fill,T 25 | fresh-line,T 26 | gensym,T 27 | gentemp,T 28 | get-dispatch-macro-character,T 29 | get-macro-character,T 30 | get-output-stream-string,T 31 | import,T 32 | in-package,T 33 | incf,T 34 | inspect,T 35 | intersection,T 36 | invalid-method-error,T 37 | invoke-debugger,T 38 | invoke-restart,T 39 | invoke-restart-interactively,T 40 | ldiff,T 41 | loop-finish,T 42 | makunbound,T 43 | maphash,T 44 | method-combination-error,T 45 | muffle-warning,T 46 | nconc,T 47 | nintersection,T 48 | nreconc,T 49 | nreverse,T 50 | nset-difference,T 51 | nset-exclusive-or,T 52 | nstring-capitalize,T 53 | nstring-downcase,T 54 | nstring-upcase,T 55 | nsublis,T 56 | nsubst,T 57 | nsubst-if,T 58 | nsubst-if-not,T 59 | nsubstitute,T 60 | nsubstitute-if,T 61 | nsubstitute-if-not,T 62 | nunion,T 63 | otherwise,T 64 | pop,T 65 | pprint-fill,T 66 | pprint-linear,T 67 | pprint-newline,T 68 | pprint-pop,T 69 | pprint-tabular,T 70 | provide,T 71 | psetq,T 72 | push,T 73 | pushnew,T 74 | random,T 75 | read-byte,T 76 | read-sequence,T 77 | reinitialize-instance,T 78 | remf,T 79 | remhash,T 80 | remove,T 81 | remove-duplicates,T 82 | remove-if,T 83 | remove-if-not,T 84 | remprop,T 85 | replace,T 86 | require,T 87 | revappend,T 88 | reverse,T 89 | room,T 90 | rplaca,T 91 | rplacd,T 92 | set,T 93 | set-difference,T 94 | set-dispatch-macro-character,T 95 | set-exclusive-or,T 96 | set-macro-character,T 97 | set-syntax-from-char,T 98 | shadow,T 99 | shadowing-import,T 100 | signal,T 101 | sleep,T 102 | store-value,T 103 | string-capitalize,T 104 | string-downcase,T 105 | string-upcase,T 106 | sublis,T 107 | subst,T 108 | subst-if,T 109 | subst-if-not,T 110 | substitute,T 111 | substitute-if,T 112 | substitute-if-not,T 113 | tailp,T 114 | terpri,T 115 | trace,T 116 | unexport,T 117 | unintern,T 118 | union,T 119 | untrace,T 120 | unuse-package,T 121 | use-package,T 122 | use-value,T 123 | vector-pop,T 124 | warn,T 125 | with-input-from-string,T 126 | with-open-file,T 127 | with-open-stream,T 128 | with-output-to-string,T 129 | write-byte,T 130 | write-char,T 131 | write-sequence,T 132 | y-or-n-p,T 133 | yes-or-no-p,T 134 | -------------------------------------------------------------------------------- /declaratives/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": ";", 5 | // symbols used for start and end a block comment. Remove this entry if your language does not support block comments 6 | "blockComment": [ "#|", "|#" ] 7 | }, 8 | // symbols used as brackets 9 | "brackets": [ 10 | [ "{", "}" ], 11 | [ "[", "]" ], 12 | [ "(", ")" ] 13 | ], 14 | // symbols that are auto closed when typing 15 | "autoClosingPairs": [ 16 | [ "{", "}" ], 17 | [ "[", "]" ], 18 | [ "(", ")" ], 19 | [ "\"", "\"" ], 20 | [ "#|", " |#" ] 21 | ], 22 | // symbols that can be used to surround a selection 23 | "surroundingPairs": [ 24 | [ "{", "}" ], 25 | [ "[", "]" ], 26 | [ "(", ")" ], 27 | [ "\"", "\"" ], 28 | ], 29 | // wordPattern defines what's considered as a word in the programming language. 30 | // Code suggestion features will use this setting to determine word boundaries if wordPattern is set. 31 | // Note this setting won't affect word-related editor commands, 32 | // which are controlled by the editor setting editor.wordSeparators. 33 | "wordPattern": "([#:A-Za-z0-9\\+\\-\\*\\/\\@\\$\\%\\^\\&\\_\\=\\<\\>\\~\\!\\?\\[\\]\\{\\}\\.]+)" 34 | } 35 | -------------------------------------------------------------------------------- /images/commonlisp_file_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/doc/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qingpeng9802/vscode-common-lisp/9d8f44cb6dc76bdec3f5aac913831cac060d2ef2/images/doc/layers.png -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qingpeng9802/vscode-common-lisp/9d8f44cb6dc76bdec3f5aac913831cac060d2ef2/images/icon.png -------------------------------------------------------------------------------- /images/snippets.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qingpeng9802/vscode-common-lisp/9d8f44cb6dc76bdec3f5aac913831cac060d2ef2/images/snippets.gif -------------------------------------------------------------------------------- /images/syntax_dark_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qingpeng9802/vscode-common-lisp/9d8f44cb6dc76bdec3f5aac913831cac060d2ef2/images/syntax_dark_plus.png -------------------------------------------------------------------------------- /images/syntax_light_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qingpeng9802/vscode-common-lisp/9d8f44cb6dc76bdec3f5aac913831cac060d2ef2/images/syntax_light_plus.png -------------------------------------------------------------------------------- /src/web/builders/DocSymbolInfo.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode'; 2 | 3 | import type { SymbolInfo } from '../collect_info/SymbolInfo'; 4 | import type { ScanDocRes } from '../collect_info/collect_from_text/ScanDocRes'; 5 | import { collectLoopVar } from '../collect_info/collect_from_text/loop'; 6 | import { scanDoc } from '../collect_info/collect_from_text/no_code'; 7 | import { collectGlobalDef, collectLocalDef } from '../collect_info/collect_from_text/non_var'; 8 | import { collectKeywordSingleVar, collectKeywordVars } from '../collect_info/collect_from_text/var'; 9 | import { bisectRight } from '../common/algorithm'; 10 | 11 | class DocSymbolInfo { 12 | public readonly document: vscode.TextDocument; 13 | public readonly docRes: ScanDocRes; 14 | 15 | public readonly globalDef: Map; 16 | public readonly globalNamedLambda: Map; 17 | 18 | public readonly localDef: Map; 19 | public readonly localNamedLambda: Map; 20 | 21 | public readonly localAnonLambda: Map; 22 | public readonly localAnonSingle: Map; 23 | public readonly localAnonLoop: Map; 24 | 25 | public readonly loopBlocks: [number, number][]; 26 | public readonly stepFormArr: [number, number][]; 27 | 28 | //public readonly globalNames: Set; 29 | //public readonly allLocalNames: Set; 30 | private _allNames: Set | undefined = undefined; 31 | private _allLocal: Map | undefined = undefined; 32 | 33 | constructor( 34 | document: vscode.TextDocument, buildingConfig: Map 35 | ) { 36 | this.document = document; 37 | const text = document.getText(); 38 | 39 | this.docRes = scanDoc(text); 40 | const excludedRanges = this.docRes.getExcludedRangesForStaticAnalysis(buildingConfig); 41 | 42 | [this.globalDef, this.globalNamedLambda] = collectGlobalDef(document, this.docRes, excludedRanges); 43 | [this.localDef, this.localNamedLambda] = collectLocalDef(document, this.docRes, excludedRanges); 44 | 45 | [this.localAnonLambda, this.stepFormArr] = collectKeywordVars(document, this.docRes, excludedRanges); 46 | this.localAnonSingle = collectKeywordSingleVar(document, this.docRes, excludedRanges); 47 | [this.localAnonLoop, this.loopBlocks] = collectLoopVar(document, this.docRes, excludedRanges); 48 | 49 | // sort to tolerance for multiple definition 50 | for (const info of this.globalDef.values()) { 51 | info.sort((a, b) => { 52 | return a.startPos.isBeforeOrEqual(b.startPos) ? -1 : 1; 53 | }); 54 | } 55 | } 56 | 57 | get allNames() { 58 | if (this._allNames === undefined) { 59 | // for semantic color 60 | this._allNames = new Set(); 61 | for (const ks of [this.globalDef.keys(), this.allLocal.keys()]) { 62 | for (const k of ks) { 63 | this._allNames.add(k); 64 | } 65 | } 66 | } 67 | return this._allNames; 68 | } 69 | 70 | get allLocal() { 71 | if (this._allLocal === undefined) { 72 | this._allLocal = new Map(); 73 | const dicts = [ 74 | this.globalNamedLambda, this.localDef, this.localNamedLambda, 75 | this.localAnonLambda, this.localAnonSingle, this.localAnonLoop 76 | ]; 77 | for (const d of dicts) { 78 | for (const [k, info] of d) { 79 | if (!this._allLocal.has(k)) { 80 | this._allLocal.set(k, []); 81 | } 82 | this._allLocal.get(k)!.push(...info); 83 | } 84 | } 85 | 86 | // make sure the semantic color in order (innermost last) 87 | for (const info of this._allLocal.values()) { 88 | info.sort((a, b) => { 89 | return a.startPos.isBeforeOrEqual(b.startPos) ? -1 : 1; 90 | }); 91 | } 92 | } 93 | return this._allLocal; 94 | } 95 | 96 | private findInnermost(symbols: SymbolInfo[], range: [number, number], position: number): SymbolInfo | undefined { 97 | let farthest: SymbolInfo | undefined = undefined; 98 | 99 | for (const symbol of symbols) { 100 | if (symbol.scope === undefined) { 101 | continue; 102 | } 103 | 104 | // if the finding range is the symbol itself, return it 105 | const [symbolStart, symbolEnd] = symbol.numRange; 106 | if (symbolStart === range[0] && symbolEnd === range[1]) { 107 | return symbol; 108 | } 109 | 110 | if (symbol.scope[0] <= position && position <= symbol.scope[1]) { 111 | if (farthest === undefined || (farthest.scope !== undefined && symbol.scope[0] > farthest.scope[0])) { 112 | farthest = symbol; 113 | } 114 | } 115 | 116 | } 117 | return farthest; 118 | } 119 | 120 | private isInStepForm(numPosition: number, shadow: SymbolInfo[]) { 121 | // select candidate shadow 122 | shadow = shadow.filter(item => item.containerName === 'do'); 123 | if (shadow.length === 0) { 124 | return undefined; 125 | } 126 | 127 | // filter by the smallest range for quick elimination 128 | const idx = bisectRight(this.stepFormArr, numPosition, item => item[0]); 129 | if (idx === -1 || idx === 0 || numPosition >= this.stepFormArr[idx - 1][1]) { 130 | return undefined; 131 | } 132 | 133 | // now, can confirm position is in step form, waive scope 134 | // check extended scope [numRange[1], scope[0]] 135 | shadow = shadow.filter(item => item.scope !== undefined); 136 | const idxDo = bisectRight(shadow, numPosition, item => item.numRange[1]); 137 | if (idxDo === -1 || idxDo === 0 || numPosition >= shadow[idxDo - 1].scope![0]) { 138 | return undefined; 139 | } 140 | return shadow[idxDo - 1]; 141 | } 142 | 143 | // return [selected symbol, shadowed symbols] 144 | // if global is selected, return shadowed symbols (position===undefined) 145 | // if local is selected, return empty shadowed symbols (position!==undefined) 146 | public getSymbolWithShadowByRange( 147 | word: string, 148 | range: [number, number] | vscode.Range, 149 | positionFlag: vscode.Position | undefined 150 | ): [SymbolInfo | undefined, SymbolInfo[]] { 151 | const localShadow = this.allLocal.get(word); 152 | const shadow = (localShadow !== undefined) ? localShadow : []; 153 | 154 | if (positionFlag === undefined) { 155 | // only global definition 156 | const res = this.globalDef.get(word); 157 | return (res !== undefined && res.length !== 0) ? 158 | [res.at(-1), shadow] : [undefined, shadow]; 159 | 160 | } else if (shadow === undefined || shadow.length === 0) { 161 | // no shadow means that we cannot find word in local definition, then 162 | // just find word in the upstream which is global definition 163 | const res = this.globalDef.get(word); 164 | return (res !== undefined && res.length !== 0) ? 165 | [res.at(-1), []] : [undefined, []]; 166 | 167 | } else { 168 | // we got some shadow 169 | // we try to find the inner most scope from the position by shrinking the scope, then 170 | // return the definition of that inner most scope 171 | const numPosition = this.document.offsetAt(positionFlag); 172 | if (!Array.isArray(range)) { 173 | range = [this.document.offsetAt(range.start), this.document.offsetAt(range.end)]; 174 | } 175 | const innermost = this.findInnermost(shadow, range, numPosition); 176 | if (innermost !== undefined) { 177 | return [innermost, []]; 178 | } 179 | 180 | // check if it is `do`'s step form 181 | const stepForm = this.isInStepForm(numPosition, shadow); 182 | if (stepForm !== undefined) { 183 | return [stepForm, []]; 184 | } 185 | 186 | // we cannot find valid scope by the position, 187 | // which means that the word is actually in global. 188 | // Therefore, we back global definition. 189 | const res = this.globalDef.get(word); 190 | return (res !== undefined && res.length !== 0) ? 191 | [res.at(-1), shadow] : [undefined, shadow]; 192 | } 193 | } 194 | 195 | 196 | } 197 | 198 | export { DocSymbolInfo }; 199 | -------------------------------------------------------------------------------- /src/web/builders/builders_util.ts: -------------------------------------------------------------------------------- 1 | import type { SymbolInfo } from '../collect_info/SymbolInfo'; 2 | import { bisectLeft, bisectRight } from '../common/algorithm'; 3 | 4 | import type { DocSymbolInfo } from './DocSymbolInfo'; 5 | 6 | function findMatchPairAfterP( 7 | absIndex: number, pair: [number, number][], validUpper: number | undefined = undefined 8 | ): number { 9 | const idx = bisectLeft(pair, absIndex, item => item[0]); 10 | if (idx === -1 || idx === 0) { 11 | return -1; 12 | } 13 | 14 | const res = pair[idx - 1][1]; 15 | // validUpper is not including 16 | if (res < absIndex || (validUpper !== undefined && validUpper <= res)) { 17 | return -1; 18 | } 19 | return res + 1; 20 | } 21 | 22 | function isShadowed(currRange: [number, number], shadow: SymbolInfo[]): boolean { 23 | for (const s of shadow) { 24 | if ( 25 | // s.scope contains currRange 26 | (s.scope !== undefined && s.scope[0] <= currRange[0] && currRange[1] <= s.scope[1]) || 27 | // intersects with definition 28 | (s.numRange[0] <= currRange[1] && currRange[0] <= s.numRange[1]) 29 | ) { 30 | return true; 31 | } 32 | 33 | } 34 | return false; 35 | } 36 | 37 | function getScopedSameNameWordsExcludeItself( 38 | symbolinfo: SymbolInfo, 39 | needColorDict: Map, 40 | currDocSymbolInfo: DocSymbolInfo, 41 | ): [number, number][] { 42 | const sameNameWords = needColorDict.get(symbolinfo.name); 43 | if (sameNameWords === undefined || sameNameWords.length === 0) { 44 | return []; 45 | } 46 | 47 | if (symbolinfo.scope !== undefined) { 48 | // local, use scope to narrow the set of words 49 | const [scopeStart, scopeEnd] = symbolinfo.scope; 50 | const selectFirst = (item: [number, number]) => item[0]; 51 | 52 | const idxStart = bisectRight(sameNameWords, scopeStart, selectFirst); 53 | const idxEnd = bisectRight(sameNameWords, scopeEnd, selectFirst); 54 | const scopedSameNameWords = sameNameWords.slice(idxStart, idxEnd); 55 | 56 | if (symbolinfo.containerName === 'do') { 57 | const stepFormArr = currDocSymbolInfo.stepFormArr; 58 | // check extended scope [numRange[1], scope[0]] 59 | const idxStart = bisectRight(sameNameWords, symbolinfo.numRange[1], selectFirst); 60 | const idxEnd = bisectRight(sameNameWords, scopeStart, selectFirst); 61 | scopedSameNameWords.push( 62 | ...sameNameWords.slice(idxStart, idxEnd).filter(wordRange => { 63 | const idx = bisectRight(stepFormArr, wordRange[0], selectFirst); 64 | return (idx !== -1 && idx !== 0 && wordRange[1] <= stepFormArr[idx - 1][1]); 65 | }) 66 | ); 67 | } 68 | 69 | return scopedSameNameWords; 70 | } else { 71 | // global, exclude same name def 72 | const defs = currDocSymbolInfo.globalDef.get(symbolinfo.name); 73 | if (defs === undefined) { 74 | return []; 75 | } 76 | const sameNameDefStartSet = new Set( 77 | defs.map(item => item.numRange[0]) 78 | ); 79 | const filtered = sameNameWords.filter(item => !sameNameDefStartSet.has(item[0])); 80 | return filtered; 81 | } 82 | } 83 | 84 | export { 85 | findMatchPairAfterP, 86 | isShadowed, 87 | getScopedSameNameWordsExcludeItself 88 | }; 89 | -------------------------------------------------------------------------------- /src/web/builders/call_hierarchy_builder/CallHrchyInfo.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode'; 2 | 3 | class CallHrchyInfo { 4 | // {name, {stringify, item}} 5 | public readonly callHrchyItems: Map> = 6 | new Map>(); 7 | 8 | // {name, {stringify, item}} 9 | public readonly incomingCall: Map> = 10 | new Map>(); 11 | 12 | // {name, {stringify, item}} 13 | public readonly outgoingCall: Map> = 14 | new Map>(); 15 | 16 | constructor() { 17 | } 18 | } 19 | 20 | export { CallHrchyInfo }; 21 | -------------------------------------------------------------------------------- /src/web/builders/call_hierarchy_builder/call_hierarchy_builder.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import type { SymbolInfo } from '../../collect_info/SymbolInfo'; 4 | import { isRangeIntExcludedRanges, bisectRight } from '../../common/algorithm'; 5 | import type { DocSymbolInfo } from '../DocSymbolInfo'; 6 | import { isShadowed } from '../builders_util'; 7 | 8 | import { CallHrchyInfo } from './CallHrchyInfo'; 9 | 10 | // @sideEffect ic: vscode.CallHierarchyIncomingCall 11 | // @sideEffect og: vscode.CallHierarchyOutgoingCall 12 | function buildEdge( 13 | icD: Map, 14 | stringifyCallerKey: string | undefined, fromItem: vscode.CallHierarchyItem | undefined, 15 | ogD: Map, 16 | stringifyIsCalledKey: string | undefined, toItem: vscode.CallHierarchyItem | undefined, 17 | callAppearRange: vscode.Range[] 18 | ) { 19 | if (fromItem !== undefined && stringifyCallerKey !== undefined) { 20 | icD.set(stringifyCallerKey, new vscode.CallHierarchyIncomingCall(fromItem, callAppearRange)); 21 | } 22 | if (toItem !== undefined && stringifyIsCalledKey !== undefined) { 23 | ogD.set(stringifyIsCalledKey, new vscode.CallHierarchyOutgoingCall(toItem, callAppearRange)); 24 | } 25 | return; 26 | } 27 | 28 | function getRealIsCalled( 29 | globalOrderedRange: [string, [number, number]], currDocSymbolInfo: DocSymbolInfo, 30 | ): [string, vscode.Range, SymbolInfo] | undefined { 31 | 32 | const [isCalledName, isCalledRange] = globalOrderedRange; 33 | const [realIsCalled, shadow] = currDocSymbolInfo.getSymbolWithShadowByRange(isCalledName, isCalledRange, undefined); 34 | if (realIsCalled === undefined) { 35 | return undefined; 36 | } 37 | if (shadow !== undefined && shadow.length !== 0 && isShadowed(isCalledRange, shadow)) { 38 | return undefined; 39 | } 40 | if (isRangeIntExcludedRanges(isCalledRange, currDocSymbolInfo.docRes.commentAndStringRange)) { 41 | return undefined; 42 | } 43 | 44 | const callAppearRange = new vscode.Range( 45 | currDocSymbolInfo.document.positionAt(isCalledRange[0]), 46 | currDocSymbolInfo.document.positionAt(isCalledRange[1]), 47 | ); 48 | 49 | return [isCalledName, callAppearRange, realIsCalled]; 50 | } 51 | 52 | function buildEdgeForIsCalledRange( 53 | currCallHierarchyInfo: CallHrchyInfo, realIsCalledRes: [string, vscode.Range, SymbolInfo], 54 | callerName: string, callerSymbolStr: string | undefined, fromItem: vscode.CallHierarchyItem | undefined, 55 | ) { 56 | const [isCalledName, callAppearRange, realIsCalled] = realIsCalledRes; 57 | 58 | fromItem = (fromItem === undefined) ? 59 | // when the file is the caller, the file has no `range`. 60 | // Therefore, make the range circle back, that is, 61 | // let the range points to the isCalled itself 62 | new vscode.CallHierarchyItem( 63 | vscode.SymbolKind.Namespace, callerName, 64 | realIsCalled.uri.path, realIsCalled.uri, 65 | realIsCalled.range, realIsCalled.range 66 | ) : fromItem; 67 | 68 | const realIsCalledStr = realIsCalled.stringify; 69 | const callHrchyItems = currCallHierarchyInfo.callHrchyItems; 70 | if (callHrchyItems.get(isCalledName) === undefined) { 71 | callHrchyItems.set(isCalledName, new Map([[realIsCalledStr, realIsCalled.toCallHierarchyItem()]])); 72 | } 73 | const toItem = callHrchyItems.get(isCalledName)?.get(realIsCalledStr); 74 | 75 | // we are creating two directional edges 76 | // 77 | // IncomingCall OutgoingCall 78 | // 79 | // fromItems 80 | // | 81 | // | 82 | // \|/ 83 | // key: 1. isCalled 2. caller 84 | // | 85 | // | 86 | // \|/ 87 | // toItems 88 | // 89 | // note that 90 | // `fromItems` is constructed from caller and 91 | // `toItems` is constructed from isCalled. 92 | // that is, our dictionary needs two infos to build an edge 93 | const iCMap = currCallHierarchyInfo.incomingCall; 94 | const oGMap = currCallHierarchyInfo.outgoingCall; 95 | if (!iCMap.has(isCalledName)) { 96 | iCMap.set(isCalledName, new Map()); 97 | } 98 | 99 | buildEdge( 100 | iCMap.get(isCalledName)!, realIsCalledStr, fromItem, 101 | oGMap.get(callerName)!, callerSymbolStr, toItem, 102 | [callAppearRange] 103 | ); 104 | } 105 | 106 | function genAllCallHierarchyItemsNonOrphan( 107 | callHrchyInfo: CallHrchyInfo, 108 | currDocSymbolInfo: DocSymbolInfo, globalOrderedRanges: [string, [number, number]][] 109 | ): Set { 110 | // init 111 | const visited: Set = new Set(); 112 | 113 | const callHrchyItems = callHrchyInfo.callHrchyItems; 114 | const oGMap = callHrchyInfo.outgoingCall; 115 | 116 | const infos = Array.from(currDocSymbolInfo.globalDef.values()).flat().sort((a, b) => { 117 | return a.numRange[0] - b.numRange[0]; 118 | }); 119 | 120 | // iterate all globalDef as caller 121 | // for example a() {b, c}, `a` as the caller 122 | for (const info of infos) { 123 | const callerName = info.name; 124 | const callerSymbolStr = info.stringify; 125 | 126 | if (callHrchyItems.get(callerName) === undefined) { 127 | callHrchyItems.set(callerName, new Map([[callerSymbolStr, info.toCallHierarchyItem()]])); 128 | } 129 | const fromItem = callHrchyItems.get(callerName)?.get(callerSymbolStr); 130 | if (fromItem === undefined) { 131 | continue; 132 | } 133 | 134 | // isCalled is in this range 135 | // for example a() {b, c}, `b` and `c` are in the range 136 | const isCalledRange = info.symbolInPRange; 137 | if (isCalledRange === undefined) { 138 | continue; 139 | } 140 | 141 | if (!oGMap.has(callerName)) { 142 | oGMap.set(callerName, new Map()); 143 | } 144 | 145 | const idxStart = bisectRight(globalOrderedRanges, isCalledRange[0], item => item[1][0]); 146 | const idxEnd = bisectRight(globalOrderedRanges, isCalledRange[1], item => item[1][0]); 147 | 148 | // find possible isCalleds ranges 149 | for (let i = idxStart; i < idxEnd; ++i) { 150 | visited.add(i); 151 | 152 | const isCalledRange = globalOrderedRanges[i]; 153 | const realIsCalledRes = getRealIsCalled(isCalledRange, currDocSymbolInfo); 154 | if (realIsCalledRes === undefined) { 155 | continue; 156 | } 157 | buildEdgeForIsCalledRange( 158 | callHrchyInfo, realIsCalledRes, 159 | callerName, callerSymbolStr, fromItem 160 | ); 161 | } 162 | 163 | } 164 | 165 | return visited; 166 | } 167 | 168 | function genAllCallHierarchyItems( 169 | currDocSymbolInfo: DocSymbolInfo, globalOrderedRanges: [string, [number, number]][] 170 | ): CallHrchyInfo { 171 | const callHrchyInfo: CallHrchyInfo = new CallHrchyInfo(); 172 | const visited = genAllCallHierarchyItemsNonOrphan( 173 | callHrchyInfo, currDocSymbolInfo, globalOrderedRanges 174 | ); 175 | 176 | // make the file as definition, that is, 177 | // make the file as caller 178 | const fileName = currDocSymbolInfo.document.uri.path.split('/').pop(); 179 | const callerName = (fileName !== undefined) ? fileName : 'Untitled'; 180 | const oGMap = callHrchyInfo.outgoingCall; 181 | if (!oGMap.has(callerName)) { 182 | oGMap.set(callerName, new Map()); 183 | } 184 | 185 | // find isCalled ranges who is NOT visited. (orphans) 186 | for (let i = 0; i < globalOrderedRanges.length; ++i) { 187 | if (visited.has(i)) { 188 | continue; 189 | } 190 | 191 | const isCalledRange = globalOrderedRanges[i]; 192 | const realIsCalledRes = getRealIsCalled(isCalledRange, currDocSymbolInfo); 193 | if (realIsCalledRes === undefined) { 194 | continue; 195 | } 196 | // only build IncomingCall Edge since the file cannot be called, 197 | // that is, cannot be `toItems` 198 | buildEdgeForIsCalledRange( 199 | callHrchyInfo, realIsCalledRes, 200 | callerName, undefined, undefined 201 | ); 202 | } 203 | return callHrchyInfo; 204 | } 205 | 206 | export { genAllCallHierarchyItems }; 207 | 208 | -------------------------------------------------------------------------------- /src/web/builders/comp_item_builder/OriSymbolsCompItem.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode'; 2 | 3 | class OriSymbolsCompItem { 4 | public readonly oriSymbols: vscode.CompletionItem[]; 5 | 6 | public readonly afterAmpersand: vscode.CompletionItem[]; 7 | public readonly afterAsterisk: vscode.CompletionItem[]; 8 | 9 | public readonly afterColon: vscode.CompletionItem[]; 10 | public readonly afterTilde: vscode.CompletionItem[]; 11 | public readonly afterSharpsign: vscode.CompletionItem[]; 12 | 13 | public readonly loopSymbols: vscode.CompletionItem[]; 14 | 15 | constructor( 16 | oriSymbols: vscode.CompletionItem[], 17 | afterAmpersand: vscode.CompletionItem[], 18 | afterAsterisk: vscode.CompletionItem[], 19 | afterColon: vscode.CompletionItem[], 20 | afterTilde: vscode.CompletionItem[], 21 | afterSharpsign: vscode.CompletionItem[], 22 | loopSymbols: vscode.CompletionItem[] 23 | ) { 24 | 25 | this.oriSymbols = oriSymbols; 26 | this.afterAmpersand = afterAmpersand; 27 | this.afterAsterisk = afterAsterisk; 28 | this.afterColon = afterColon; 29 | this.afterTilde = afterTilde; 30 | this.afterSharpsign = afterSharpsign; 31 | this.loopSymbols = loopSymbols; 32 | } 33 | } 34 | 35 | export { OriSymbolsCompItem }; 36 | -------------------------------------------------------------------------------- /src/web/builders/comp_item_builder/UserSymbolsCompItem.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode'; 2 | 3 | import { bisectRight } from '../../common/algorithm'; 4 | import type { DocSymbolInfo } from '../DocSymbolInfo'; 5 | 6 | class UserSymbolsCompItem { 7 | public readonly globalCItems: vscode.CompletionItem[] = []; 8 | public readonly localScopeCItems: [vscode.CompletionItem, [number, number]][] = []; 9 | 10 | constructor(currDocSymbolInfo: DocSymbolInfo) { 11 | for (const SIs of currDocSymbolInfo.globalDef.values()) { 12 | if (SIs !== undefined && SIs.length !== 0) { 13 | this.globalCItems.push(SIs[0].toCompletionItem()); 14 | } 15 | } 16 | 17 | for (const SIs of currDocSymbolInfo.allLocal.values()) { 18 | for (const SI of SIs) { 19 | if (SI.scope === undefined) { 20 | continue; 21 | } 22 | this.localScopeCItems.push([SI.toCompletionItem(), SI.scope]); 23 | } 24 | } 25 | 26 | this.localScopeCItems.sort((a, b) => a[1][0] - b[1][0]); // sort by scope start 27 | } 28 | 29 | public getUserompletionItems(position: number): vscode.CompletionItem[] { 30 | const res: vscode.CompletionItem[] = this.globalCItems; 31 | 32 | const idx = bisectRight(this.localScopeCItems, position, item => item[1][0]); 33 | for (let i = idx - 1; i >= 0; --i) { 34 | const [compItem, numRange] = this.localScopeCItems[i]; 35 | const [start, end] = numRange; 36 | // contains position 37 | if (start <= position && position <= end) { 38 | res.push(compItem); 39 | } 40 | } 41 | 42 | return res; 43 | } 44 | 45 | } 46 | 47 | export { UserSymbolsCompItem }; 48 | -------------------------------------------------------------------------------- /src/web/builders/comp_item_builder/comp_item_ori_builder.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { 4 | getDocByName, getDocByNameNonAlphabetic, 5 | _non_alphabetic, non_alphabetic_index_str, 6 | loop_keyword_str 7 | } from '../../doc/get_doc'; 8 | import { loopKeywordsCompItemMap, loopKeywordsSet } from '../loop_keywords'; 9 | 10 | import { OriSymbolsCompItem } from './OriSymbolsCompItem'; 11 | import { clOriSymbolsByKind, ClSymbolKind } from './cl_kind'; 12 | 13 | const clKindToVscodeCIKind: Map = 14 | new Map([ 15 | [ClSymbolKind.Accessor, vscode.CompletionItemKind.Method], 16 | [ClSymbolKind.Function, vscode.CompletionItemKind.Function], 17 | [ClSymbolKind.LocalFunction, vscode.CompletionItemKind.Function], 18 | [ClSymbolKind.StandardGenericFunction, vscode.CompletionItemKind.Function], 19 | [ClSymbolKind.Class, vscode.CompletionItemKind.Class], 20 | [ClSymbolKind.SystemClass, vscode.CompletionItemKind.Class], 21 | [ClSymbolKind.ConditionType, vscode.CompletionItemKind.Class], 22 | [ClSymbolKind.ConstantVariable, vscode.CompletionItemKind.Constant], 23 | [ClSymbolKind.Declaration, vscode.CompletionItemKind.Keyword], 24 | [ClSymbolKind.Macro, vscode.CompletionItemKind.Keyword], 25 | [ClSymbolKind.LocalMacro, vscode.CompletionItemKind.Keyword], 26 | [ClSymbolKind.MacroLambdaList, vscode.CompletionItemKind.Keyword], 27 | [ClSymbolKind.OrdinaryLambdaList, vscode.CompletionItemKind.Keyword], 28 | [ClSymbolKind.SpecialForm, vscode.CompletionItemKind.Keyword], 29 | [ClSymbolKind.SpecialOperator, vscode.CompletionItemKind.Keyword], 30 | [ClSymbolKind.Symbol, vscode.CompletionItemKind.Keyword], 31 | [ClSymbolKind.Type, vscode.CompletionItemKind.TypeParameter], 32 | [ClSymbolKind.TypeSpecifier, vscode.CompletionItemKind.Method], 33 | [ClSymbolKind.Variable, vscode.CompletionItemKind.Variable] 34 | ]); 35 | 36 | 37 | function assignKindAndDoc( 38 | symbolsArr: string[], kind: vscode.CompletionItemKind 39 | ): vscode.CompletionItem[] { 40 | const citems: vscode.CompletionItem[] = []; 41 | for (const s of symbolsArr) { 42 | // prevent duplicated items in NonAlphabetic 43 | const prefix = s[0]; 44 | if (prefix === '&' || prefix === '*') { 45 | continue; 46 | } 47 | 48 | const ci = new vscode.CompletionItem(s, kind); 49 | const doc = getDocByName(s.toLowerCase()); 50 | if (doc !== undefined) { 51 | ci.documentation = doc; 52 | } 53 | 54 | citems.push(ci); 55 | } 56 | return citems; 57 | } 58 | 59 | // Currently, VS Code does not support sort of autocompletion items. 60 | // (https://github.com/microsoft/vscode/issues/80444) 61 | // Thus, the order of symbol is not matter here. 62 | function genOriSymbols(): vscode.CompletionItem[] { 63 | const citems: vscode.CompletionItem[] = []; 64 | // integrity check 65 | const completeSymbols: string[] = []; 66 | for (const [k, partSymbols] of clOriSymbolsByKind) { 67 | const kind = clKindToVscodeCIKind.get(k)!; 68 | citems.push(...assignKindAndDoc(partSymbols, kind)); 69 | completeSymbols.push(...partSymbols); 70 | } 71 | if (completeSymbols.length !== 978) { 72 | console.warn(`[Autocompletion] Got ${completeSymbols.length}. \ 73 | Please make sure all 978 commonlisp symbols have been included.`); 74 | } 75 | if (citems.length !== 923) { 76 | // not 978 since we filtered `&` and `*` out 77 | console.warn(`[Autocompletion] Built incomplete kind list (${citems.length}) of symbols`); 78 | } 79 | 80 | return citems; 81 | } 82 | 83 | // Index - Non-Alphabetic 84 | function assignKindAndDocNonAlphabetic( 85 | prefix: string, symbolsArr: string[], kind: vscode.CompletionItemKind 86 | ): vscode.CompletionItem[] { 87 | const citems: vscode.CompletionItem[] = []; 88 | for (const s of symbolsArr) { 89 | const fullKeyword = prefix + s; 90 | const ci = new vscode.CompletionItem(fullKeyword, kind); 91 | 92 | if (prefix === '#' || prefix === '~') { 93 | // we do not get doc for `#` and `~` 94 | ci.documentation = new vscode.MarkdownString(non_alphabetic_index_str); 95 | ci.documentation.isTrusted = true; 96 | ci.documentation.supportHtml = true; 97 | 98 | } else if (prefix === '&' || prefix === '*') { 99 | const doc = getDocByName(fullKeyword.toLowerCase()); 100 | ci.documentation = doc; 101 | 102 | } else if (prefix === ':') { 103 | const doc = getDocByNameNonAlphabetic(s); 104 | ci.documentation = doc; 105 | 106 | } else { } 107 | 108 | citems.push(ci); 109 | } 110 | return citems; 111 | } 112 | 113 | function genNonAlphabeticDict(): Map { 114 | const d: Map = new Map(); 115 | 116 | for (const [k, v] of Object.entries(_non_alphabetic)) { 117 | if (k === '#' || k === '&' || k === '~') { 118 | d.set(k, assignKindAndDocNonAlphabetic(k, v, vscode.CompletionItemKind.Keyword)); 119 | 120 | } else if (k === '*') { 121 | d.set(k, assignKindAndDocNonAlphabetic(k, v, vscode.CompletionItemKind.Variable)); 122 | 123 | } else if (k === ':') { 124 | d.set(k, assignKindAndDocNonAlphabetic(k, v, vscode.CompletionItemKind.Property)); 125 | } else { 126 | console.warn(`[Autocompletion] Unknown non-alphabetic key: ${k}`); 127 | } 128 | } 129 | return d; 130 | } 131 | 132 | function genLoopKeywords() { 133 | // https://www.lispworks.com/documentation/lw51/CLHS/Body/m_loop.htm#loop 134 | // https://lispcookbook.github.io/cl-cookbook/iteration.html 135 | const citems: vscode.CompletionItem[] = []; 136 | for (const k of loopKeywordsSet) { 137 | const item = new vscode.CompletionItem(k, loopKeywordsCompItemMap.get(k)); 138 | 139 | item.detail = 'LOOP keyword'; 140 | 141 | item.documentation = new vscode.MarkdownString(loop_keyword_str); 142 | item.documentation.isTrusted = true; 143 | item.documentation.supportHtml = true; 144 | 145 | citems.push(item); 146 | } 147 | 148 | return citems; 149 | } 150 | 151 | function genAllOriSymbols() { 152 | const oriSymbols: vscode.CompletionItem[] = genOriSymbols(); 153 | 154 | const nonAlphabeticDict: Map = genNonAlphabeticDict(); 155 | const afterAmpersand: vscode.CompletionItem[] = nonAlphabeticDict.get('&')!; 156 | const afterAsterisk: vscode.CompletionItem[] = nonAlphabeticDict.get('*')!; 157 | const afterColon: vscode.CompletionItem[] = nonAlphabeticDict.get(':')!; 158 | const afterTilde: vscode.CompletionItem[] = nonAlphabeticDict.get('~')!; 159 | const afterSharpsign: vscode.CompletionItem[] = nonAlphabeticDict.get('#')!; 160 | 161 | const loopSymbols: vscode.CompletionItem[] = genLoopKeywords(); 162 | 163 | return new OriSymbolsCompItem( 164 | oriSymbols, afterAmpersand, afterAsterisk, afterColon, afterTilde, afterSharpsign, loopSymbols 165 | ); 166 | } 167 | 168 | 169 | export { genAllOriSymbols }; 170 | -------------------------------------------------------------------------------- /src/web/builders/doc_symbol_builder/doc_symbol_builder.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode'; 2 | 3 | import type { SymbolInfo } from '../../collect_info/SymbolInfo'; 4 | import { bisectRight } from '../../common/algorithm'; 5 | import type { DocSymbolInfo } from '../DocSymbolInfo'; 6 | import { findMatchPairAfterP } from '../builders_util'; 7 | 8 | function genAnonContainerNameNum(d: Map, anonContainerName: string): number { 9 | // {anonymous container name, count} 10 | if (d.has(anonContainerName)) { 11 | d.set(anonContainerName, d.get(anonContainerName)! + 1); 12 | } else { 13 | d.set(anonContainerName, 1); 14 | } 15 | return d.get(anonContainerName)!; 16 | } 17 | 18 | // @sideEffect: SymbolInfo.numberedContainerName 19 | function globalContainerToDocumentSymbolDict( 20 | defs: Map, numberedContainerName: string 21 | ): Map { 22 | const res: Map = new Map(); 23 | 24 | for (const [defName, info] of defs) { 25 | for (const item of info) { 26 | res.set(defName, item.toDocumentSymbol(numberedContainerName)); 27 | } 28 | } 29 | 30 | return res; 31 | } 32 | 33 | // @sideEffect: SymbolInfo.numberedContainerName 34 | function noValidContainerToDocumentSymbolDict( 35 | defs: Map, anonContainerNameDict: Map 36 | ): Map { 37 | const res: Map = new Map(); 38 | 39 | for (const [defName, info] of defs) { 40 | for (const item of info) { 41 | const numberedContainerName = (item.containerName !== undefined) ? 42 | `${item.containerName}<${genAnonContainerNameNum(anonContainerNameDict, item.containerName)}>` : 43 | ''; 44 | res.set(defName, item.toDocumentSymbol(numberedContainerName)); 45 | } 46 | 47 | } 48 | 49 | return res; 50 | } 51 | 52 | // @sideEffect: SymbolInfo.numberedContainerName 53 | function noValidContainerToDocumentSymbol( 54 | defs: Map, anonContainerNameDict: Map 55 | ): vscode.DocumentSymbol[] { 56 | const res: vscode.DocumentSymbol[] = []; 57 | 58 | for (const info of defs.values()) { 59 | for (const item of info) { 60 | const numberedContainerName = (item.containerName !== undefined) ? 61 | `${item.containerName}<${genAnonContainerNameNum(anonContainerNameDict, item.containerName)}>` : 62 | ''; 63 | res.push(item.toDocumentSymbol(numberedContainerName)); 64 | } 65 | 66 | } 67 | 68 | return res; 69 | } 70 | 71 | // @sideEffect: SymbolInfo.symbolInPRange 72 | function genSortedTopLevelScopes(pair: [number, number][], symbols: SymbolInfo[]): [string, [number, number]][] { 73 | const res: [string, [number, number]][] = []; 74 | for (const symbol of symbols) { 75 | const endInd = findMatchPairAfterP(symbol.numRange[0], pair); 76 | symbol.symbolInPRange = [symbol.numRange[0], endInd]; 77 | res.push([symbol.name, symbol.symbolInPRange]); 78 | //console.log(symbol.symbolInPRange, symbol.scope); 79 | } 80 | 81 | res.sort((a, b) => a[1][0] - b[1][0]); // sort by range start 82 | return res; 83 | } 84 | 85 | function genDocumentSymbol(currDocSymbolInfo: DocSymbolInfo): vscode.DocumentSymbol[] { 86 | // for anonymous container name 87 | const anonContainerNameDict: Map = new Map(); 88 | 89 | const [globalDefSIDict, localDefSIDict] = 90 | withNamedContainerToDocumentSymbolDict(currDocSymbolInfo, anonContainerNameDict); 91 | 92 | // collect wild vars (vars without valid container) 93 | const localAnonLambdaVarDSs = 94 | noValidContainerToDocumentSymbol(currDocSymbolInfo.localAnonLambda, anonContainerNameDict); 95 | const localAnonSingleVarDSs = 96 | noValidContainerToDocumentSymbol(currDocSymbolInfo.localAnonSingle, anonContainerNameDict); 97 | const localAnonVarDSs = [...localAnonLambdaVarDSs, ...localAnonSingleVarDSs]; 98 | 99 | // try local as parent 100 | const restlocalAnonVarDSs1 = 101 | tryAppendVarsToParentScope( 102 | currDocSymbolInfo, localAnonVarDSs, localDefSIDict, currDocSymbolInfo.localDef 103 | ); 104 | const restlocalAnonVarDSs2 = [...localDefSIDict.values(), ...restlocalAnonVarDSs1]; 105 | 106 | // try global as parent 107 | const restlocalAnonVarDSs3 = 108 | tryAppendVarsToParentScope( 109 | currDocSymbolInfo, restlocalAnonVarDSs2, globalDefSIDict, currDocSymbolInfo.globalDef 110 | ); 111 | 112 | return [...globalDefSIDict.values(), ...restlocalAnonVarDSs3]; 113 | } 114 | 115 | function withNamedContainerToDocumentSymbolDict( 116 | currDocSymbolInfo: DocSymbolInfo, anonContainerNameDict: Map 117 | ) { 118 | // start assgin 119 | // global 120 | const fileName = currDocSymbolInfo.document.uri.path.split('/').pop(); 121 | const globalContainerName = (fileName !== undefined) ? fileName : 'Untitled'; 122 | const globalDefSIDict = globalContainerToDocumentSymbolDict(currDocSymbolInfo.globalDef, globalContainerName); 123 | for (const sInfos of currDocSymbolInfo.globalNamedLambda.values()) { 124 | for (const sInfo of sInfos) { 125 | if (sInfo.containerName === undefined) { 126 | continue; 127 | } 128 | const containerDocumentSymbol = globalDefSIDict.get(sInfo.containerName); 129 | if (containerDocumentSymbol === undefined) { 130 | //console.warn(`cannot find containerDocumentSymbol with global name: ${sInfo.containerName}`); 131 | continue; 132 | } 133 | containerDocumentSymbol.children.push(sInfo.toDocumentSymbol()); 134 | } 135 | } 136 | 137 | // local 138 | const localDefSIDict = noValidContainerToDocumentSymbolDict(currDocSymbolInfo.localDef, anonContainerNameDict); 139 | for (const sInfos of currDocSymbolInfo.localNamedLambda.values()) { 140 | for (const sInfo of sInfos) { 141 | if (sInfo.containerName === undefined) { 142 | continue; 143 | } 144 | const containerDocumentSymbol = localDefSIDict.get(sInfo.containerName); 145 | if (containerDocumentSymbol === undefined) { 146 | //console.warn(`cannot find containerDocumentSymbol with local name: ${sInfo.containerName}`); 147 | continue; 148 | } 149 | containerDocumentSymbol.children.push(sInfo.toDocumentSymbol()); 150 | } 151 | } 152 | 153 | return [globalDefSIDict, localDefSIDict]; 154 | } 155 | 156 | function tryAppendVarsToParentScope( 157 | currDocSymbolInfo: DocSymbolInfo, localAnonVarDSs: vscode.DocumentSymbol[], 158 | localDefSIDict: Map, def: Map 159 | ) { 160 | // try to append wild vars to parent scope from down to top 161 | const restlocalAnonVarDSs: vscode.DocumentSymbol[] = []; 162 | 163 | const topLevelDefs = Array.from(def.values()).flat(); 164 | const topLevelScopes = genSortedTopLevelScopes(currDocSymbolInfo.docRes.pair, topLevelDefs); 165 | 166 | for (const si of localAnonVarDSs) { 167 | const siStart = currDocSymbolInfo.document.offsetAt(si.range.start); 168 | const siEnd = currDocSymbolInfo.document.offsetAt(si.range.end); 169 | 170 | let idx = bisectRight(topLevelScopes, siStart, item => item[1][0]); 171 | while (idx > 0) { 172 | const topLevelScopesLast = topLevelScopes[idx - 1]; 173 | const [lastScopeDefName, lastScope] = topLevelScopesLast; 174 | const [lastScopeStart, lastScopeEnd] = lastScope; 175 | 176 | if (lastScopeStart <= siStart && siEnd <= lastScopeEnd) { 177 | const lastScopeDocumentSymbol = localDefSIDict.get(lastScopeDefName); 178 | if (lastScopeDocumentSymbol === undefined) { 179 | idx--; 180 | continue; 181 | } 182 | 183 | lastScopeDocumentSymbol.children.push(si); 184 | break; 185 | } else { 186 | idx--; 187 | } 188 | } 189 | if (idx === 0) { 190 | restlocalAnonVarDSs.push(si); 191 | } 192 | } 193 | return restlocalAnonVarDSs; 194 | } 195 | 196 | export { genDocumentSymbol }; 197 | -------------------------------------------------------------------------------- /src/web/builders/loop_keywords.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | // https://www.lispworks.com/documentation/lcl50/loop/loop-index.html 4 | // https://www.lispworks.com/documentation/lw51/CLHS/Body/m_loop.htm 5 | // https://lispcookbook.github.io/cl-cookbook/iteration.html 6 | const loopKeywordsTokenMap = new Map([ 7 | // Part 1. follow clauses 8 | // name 9 | ['named', 'keyword'], 10 | // prologue 11 | ['initially', 'keyword'], 12 | // epilogue 13 | ['finally', 'keyword'], 14 | // variable initialization and stepping 15 | ['for', 'keyword'], 16 | ['as', 'keyword'], 17 | ['with', 'keyword'], 18 | ['repeat', 'keyword'], 19 | 20 | // Part 2. follow clauses 21 | // unconditional execution 22 | ['do', 'keyword'], 23 | ['doing', 'keyword'], 24 | ['return', 'keyword'], 25 | //value accumulation 26 | ['collect', 'function'], 27 | ['collecting', 'function'], 28 | ['append', 'function'], 29 | ['appending', 'function'], 30 | ['nconc', 'function'], 31 | ['nconcing', 'function'], 32 | ['count', 'function'], 33 | ['counting', 'function'], 34 | ['sum', 'function'], 35 | ['summing', 'function'], 36 | ['maximize', 'function'], 37 | ['maximizing', 'function'], 38 | ['minimize', 'function'], 39 | ['minimizing', 'function'], 40 | // used by value accumulation above 41 | ['into', 'keyword'], 42 | // macro End-Test Control Clause Keyword 43 | //['loop-finish', 'macro'],//colored 44 | // conditional execution 45 | ['thereis', 'keyword'], 46 | ['always', 'keyword'], 47 | ['never', 'keyword'], 48 | ['if', 'keyword'], 49 | ['when', 'keyword'], 50 | ['unless', 'keyword'], 51 | ['while', 'keyword'], 52 | ['until', 'keyword'], 53 | ['else', 'keyword'], 54 | ['end', 'keyword'], 55 | 56 | // Part 3. NOT follow clauses 57 | // adv. for order 58 | ['then', 'macro'], 59 | // pronoun 60 | ['it', 'macro'], 61 | // preposition 62 | ['=', 'macro'], // override color 63 | //['and', 'macro'], //colored 64 | ['to', 'macro'], 65 | ['upto', 'macro'], 66 | ['downto', 'macro'], 67 | ['below', 'macro'], 68 | ['above', 'macro'], 69 | ['by', 'macro'], 70 | ['from', 'macro'], 71 | ['downfrom', 'macro'], 72 | ['upfrom', 'macro'], 73 | ['in', 'macro'], 74 | ['of', 'macro'], 75 | ['on', 'macro'], 76 | ['across', 'macro'], 77 | // others 78 | ['being', 'macro'], 79 | ['each', 'macro'], 80 | ['the', 'macro'], 81 | ['using', 'macro'], 82 | 83 | // Part 4. NOT follow clauses 84 | // noun 85 | ['hash-key', 'macro'], 86 | ['hash-keys', 'macro'], 87 | ['hash-value', 'macro'], 88 | ['hash-values', 'macro'], 89 | ['present-symbol', 'macro'], 90 | ['present-symbols', 'macro'], 91 | ['external-symbol', 'macro'], 92 | ['external-symbols', 'macro'], 93 | ['symbol', 'macro'], 94 | ['symbols', 'macro'], 95 | 96 | // type 97 | ['nil', 'macro'], 98 | ['t', 'macro'], 99 | ['fixnum', 'type'], 100 | ['float', 'type'], 101 | ['integer', 'type'], 102 | ['number', 'type'], 103 | ['of-type', 'type'], 104 | ]); 105 | 106 | const loopKeywordsCompItemMap = new Map([ 107 | // Part 1. follow clauses 108 | // name 109 | ['named', vscode.CompletionItemKind.Keyword], 110 | // prologue 111 | ['initially', vscode.CompletionItemKind.Keyword], 112 | // epilogue 113 | ['finally', vscode.CompletionItemKind.Keyword], 114 | // variable initialization and stepping 115 | ['for', vscode.CompletionItemKind.Keyword], 116 | ['as', vscode.CompletionItemKind.Keyword], 117 | ['with', vscode.CompletionItemKind.Keyword], 118 | ['repeat', vscode.CompletionItemKind.Keyword], 119 | 120 | // Part 2. follow clauses 121 | // unconditional execution 122 | ['do', vscode.CompletionItemKind.Keyword], 123 | ['doing', vscode.CompletionItemKind.Keyword], 124 | ['return', vscode.CompletionItemKind.Keyword], 125 | //value accumulation 126 | ['collect', vscode.CompletionItemKind.Function], 127 | ['collecting', vscode.CompletionItemKind.Function], 128 | ['append', vscode.CompletionItemKind.Function], 129 | ['appending', vscode.CompletionItemKind.Function], 130 | ['nconc', vscode.CompletionItemKind.Function], 131 | ['nconcing', vscode.CompletionItemKind.Function], 132 | ['count', vscode.CompletionItemKind.Function], 133 | ['counting', vscode.CompletionItemKind.Function], 134 | ['sum', vscode.CompletionItemKind.Function], 135 | ['summing', vscode.CompletionItemKind.Function], 136 | ['maximize', vscode.CompletionItemKind.Function], 137 | ['maximizing', vscode.CompletionItemKind.Function], 138 | ['minimize', vscode.CompletionItemKind.Function], 139 | ['minimizing', vscode.CompletionItemKind.Function], 140 | // used by value accumulation above 141 | ['into', vscode.CompletionItemKind.Keyword], 142 | // macro End-Test Control Clause Keyword 143 | //['loop-finish', vscode.CompletionItemKind.Keyword],//colored 144 | // conditional execution 145 | ['thereis', vscode.CompletionItemKind.Keyword], 146 | ['always', vscode.CompletionItemKind.Keyword], 147 | ['never', vscode.CompletionItemKind.Keyword], 148 | ['if', vscode.CompletionItemKind.Keyword], 149 | ['when', vscode.CompletionItemKind.Keyword], 150 | ['unless', vscode.CompletionItemKind.Keyword], 151 | ['while', vscode.CompletionItemKind.Keyword], 152 | ['until', vscode.CompletionItemKind.Keyword], 153 | ['else', vscode.CompletionItemKind.Keyword], 154 | ['end', vscode.CompletionItemKind.Keyword], 155 | 156 | // Part 3. NOT follow clauses 157 | // adv. for order 158 | ['then', vscode.CompletionItemKind.Keyword], 159 | // pronoun 160 | ['it', vscode.CompletionItemKind.Keyword], 161 | // preposition 162 | ['=', vscode.CompletionItemKind.Keyword], // override color 163 | //['and', vscode.CompletionItemKind.Keyword], //colored 164 | ['to', vscode.CompletionItemKind.Keyword], 165 | ['upto', vscode.CompletionItemKind.Keyword], 166 | ['downto', vscode.CompletionItemKind.Keyword], 167 | ['below', vscode.CompletionItemKind.Keyword], 168 | ['above', vscode.CompletionItemKind.Keyword], 169 | ['by', vscode.CompletionItemKind.Keyword], 170 | ['from', vscode.CompletionItemKind.Keyword], 171 | ['downfrom', vscode.CompletionItemKind.Keyword], 172 | ['upfrom', vscode.CompletionItemKind.Keyword], 173 | ['in', vscode.CompletionItemKind.Keyword], 174 | ['of', vscode.CompletionItemKind.Keyword], 175 | ['on', vscode.CompletionItemKind.Keyword], 176 | ['across', vscode.CompletionItemKind.Keyword], 177 | // others 178 | ['being', vscode.CompletionItemKind.Keyword], 179 | ['each', vscode.CompletionItemKind.Keyword], 180 | ['the', vscode.CompletionItemKind.Keyword], 181 | ['using', vscode.CompletionItemKind.Keyword], 182 | 183 | // Part 4. NOT follow clauses 184 | // noun 185 | ['hash-key', vscode.CompletionItemKind.Keyword], 186 | ['hash-keys', vscode.CompletionItemKind.Keyword], 187 | ['hash-value', vscode.CompletionItemKind.Keyword], 188 | ['hash-values', vscode.CompletionItemKind.Keyword], 189 | ['present-symbol', vscode.CompletionItemKind.Keyword], 190 | ['present-symbols', vscode.CompletionItemKind.Keyword], 191 | ['external-symbol', vscode.CompletionItemKind.Keyword], 192 | ['external-symbols', vscode.CompletionItemKind.Keyword], 193 | ['symbol', vscode.CompletionItemKind.Keyword], 194 | ['symbols', vscode.CompletionItemKind.Keyword], 195 | 196 | // type 197 | ['nil', vscode.CompletionItemKind.Keyword], 198 | ['t', vscode.CompletionItemKind.Keyword], 199 | ['fixnum', vscode.CompletionItemKind.TypeParameter], 200 | ['float', vscode.CompletionItemKind.TypeParameter], 201 | ['integer', vscode.CompletionItemKind.TypeParameter], 202 | ['number', vscode.CompletionItemKind.TypeParameter], 203 | ['of-type', vscode.CompletionItemKind.TypeParameter], 204 | ]); 205 | 206 | const loopKeywordsSet = new Set(loopKeywordsTokenMap.keys()); 207 | 208 | export { loopKeywordsTokenMap, loopKeywordsCompItemMap, loopKeywordsSet }; 209 | -------------------------------------------------------------------------------- /src/web/builders/semantic_tokens_builder/semantic_tokens_builder.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import type { SymbolInfo } from '../../collect_info/SymbolInfo'; 4 | import { isRangeIntExcludedRanges } from '../../common/algorithm'; 5 | import type { DocSymbolInfo } from '../DocSymbolInfo'; 6 | import { getScopedSameNameWordsExcludeItself } from '../builders_util'; 7 | 8 | import { _encodeTokenType, _encodeTokenModifiers, vscodeKindToTokenType } from './token_util'; 9 | import { overrideQuote, updateLoop } from './update_util'; 10 | 11 | function genAllPossibleWord( 12 | currDocSymbolInfo: DocSymbolInfo 13 | ): [Map, [string, [number, number]][]] { 14 | const text = currDocSymbolInfo.docRes.text; 15 | //const t = performance.now(); 16 | 17 | // slow regex 18 | // eslint-disable-next-line max-len 19 | // const reg = /((?<=,)@?([#:A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.]+)|(?<=[^A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.\,])([#:A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.]+))(?=[^A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.])/igmd; 20 | 21 | // not start with colon 22 | const reg = /((?<=[^A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.\,]|,@)([#:A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.]+)|(?<=,)[#:A-Za-z0-9\+\-\*\/\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.][#:A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.]*)(?=[^A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.])/igm; 23 | const matchRes = text.matchAll(reg); 24 | 25 | const needColorDict: Map = new Map(); 26 | const globalOrderedRanges: [string, [number, number]][] = []; 27 | 28 | const allNames = currDocSymbolInfo.allNames; 29 | allNames.forEach(v => { 30 | needColorDict.set(v, []); 31 | }); 32 | const globalDef = new Set(currDocSymbolInfo.globalDef.keys()); 33 | 34 | 35 | for (const r of matchRes) { 36 | const word = r[1].toLowerCase(); 37 | const hasName = needColorDict.get(word); 38 | 39 | if (hasName !== undefined) { 40 | const ind = r.index!; 41 | const rindex = ind; 42 | const numRange: [number, number] = [rindex, rindex + word.length]; 43 | hasName.push(numRange); 44 | 45 | if (globalDef.has(word)) { 46 | globalOrderedRanges.push([word, numRange]); 47 | } 48 | } 49 | } 50 | //console.log(`token: ${performance.now() - t} ms`); 51 | return [needColorDict, globalOrderedRanges]; 52 | } 53 | 54 | function getTokenDict( 55 | currDocSymbolInfo: DocSymbolInfo, 56 | needColorDict: Map, 57 | buildingConfig: Map, 58 | tokensBuilder: vscode.SemanticTokensBuilder 59 | ) { 60 | // config 61 | const excludedRanges: [number, number][] = 62 | currDocSymbolInfo.docRes.getExcludedRangesForDocumentSemanticTokensProvider(buildingConfig); 63 | 64 | // overlap in order! 65 | updateTokenDict(currDocSymbolInfo, excludedRanges, needColorDict, 'local', tokensBuilder); 66 | updateTokenDict(currDocSymbolInfo, excludedRanges, needColorDict, 'global', tokensBuilder); 67 | 68 | updateLoop(currDocSymbolInfo, excludedRanges, tokensBuilder); 69 | 70 | // 1. tried to de-color all single-quoted parts 71 | // vscode semantic highlighting does not support multi-line tokens 72 | // so we split the multi-line tokens into multiple single-line tokens manually. 73 | // This may have a negative impact on performance. 74 | if (buildingConfig.get('commonLisp.DocumentSemanticTokensProvider.NotColorQuoted') === true) { 75 | overrideQuote(currDocSymbolInfo, tokensBuilder); 76 | } 77 | // 78 | // 2. tried to de-color all no-formatted strings 79 | // not sure if it can cover all cases or not 80 | // overrideNotFormattedString(currDocSymbolInfo, tokensBuilder); 81 | 82 | } 83 | 84 | function updateTokenDict( 85 | currDocSymbolInfo: DocSymbolInfo, 86 | excludedRanges: [number, number][], 87 | needColorDict: Map, 88 | updateScope: 'global' | 'local', 89 | tokensBuilder: vscode.SemanticTokensBuilder, 90 | ) { 91 | const isGlobal = updateScope === 'global'; 92 | const d = isGlobal ? currDocSymbolInfo.globalDef : currDocSymbolInfo.allLocal; 93 | 94 | for (const [name, info] of d) { 95 | // this is left to `yaml grammar` style guide 96 | if (name.startsWith(':')) { 97 | continue; 98 | } 99 | // do not override global variables color, this is left to `yaml grammar` file 100 | if (isGlobal && ( 101 | (name.startsWith('+') && name.endsWith('+')) || 102 | (name.startsWith('*') && name.endsWith('*'))) 103 | ) { 104 | continue; 105 | } 106 | 107 | for (const item of info) { 108 | if (isGlobal && item.kind === vscode.SymbolKind.Variable) { 109 | continue; 110 | } 111 | 112 | // color def itself 113 | const startPos = item.startPos; 114 | setParsedToken(tokensBuilder, item, startPos, isGlobal); 115 | // color its scope 116 | const scopedSameNameWords = getScopedSameNameWordsExcludeItself(item, needColorDict, currDocSymbolInfo); 117 | for (const rang of scopedSameNameWords) { 118 | if (isRangeIntExcludedRanges(rang, excludedRanges)) { 119 | continue; 120 | } 121 | 122 | const startPos = currDocSymbolInfo.document.positionAt(rang[0]); 123 | setParsedToken(tokensBuilder, item, startPos); 124 | } 125 | 126 | } 127 | } 128 | 129 | } 130 | 131 | // dependency injection tokensBuilder 132 | function setParsedToken( 133 | tokensBuilder: vscode.SemanticTokensBuilder, 134 | item: SymbolInfo, 135 | startPos: vscode.Position, 136 | isGlobal = true 137 | ) { 138 | const nameStr = item.name; 139 | const packagePrefixIndScope = nameStr.indexOf(':'); 140 | let len = nameStr.length; 141 | if (packagePrefixIndScope !== -1) { 142 | // `::` 143 | const lastPackagePrefixIndScope = nameStr.lastIndexOf(':'); 144 | if (lastPackagePrefixIndScope + 1 < nameStr.length) { 145 | const subItemName = nameStr.substring(lastPackagePrefixIndScope + 1); 146 | if ( 147 | isGlobal && 148 | (subItemName.startsWith('+') && subItemName.endsWith('+')) || 149 | (subItemName.startsWith('*') && subItemName.endsWith('*'))) { 150 | return; 151 | } 152 | } 153 | startPos = startPos.translate(0, packagePrefixIndScope); 154 | len = len - packagePrefixIndScope; 155 | } 156 | 157 | const tokenMapRes = vscodeKindToTokenType.get(item.kind)!; 158 | let tokenType = ''; 159 | let tokenModifiers: string[] = []; 160 | if (Array.isArray(tokenMapRes)) { 161 | tokenType = tokenMapRes[0]; 162 | tokenModifiers = tokenMapRes[1]; 163 | } else { 164 | tokenType = tokenMapRes; 165 | tokenModifiers = []; 166 | } 167 | const encodedTT = _encodeTokenType(tokenType); 168 | const encodedTMs = _encodeTokenModifiers(tokenModifiers); 169 | 170 | tokensBuilder.push( 171 | startPos.line, startPos.character, len, 172 | encodedTT, encodedTMs 173 | ); 174 | //const key = `${item.name}|${startPos.line},${startPos.character},${item.name.length}`; 175 | //console.log(key); 176 | } 177 | 178 | function buildSemanticTokens( 179 | currDocSymbolInfo: DocSymbolInfo, needColorDict: Map, buildingConfig: Map 180 | ): vscode.SemanticTokens { 181 | const tokensBuilder = new vscode.SemanticTokensBuilder(); 182 | 183 | getTokenDict(currDocSymbolInfo, needColorDict, buildingConfig, tokensBuilder); 184 | 185 | return tokensBuilder.build(); 186 | } 187 | 188 | export { buildSemanticTokens, genAllPossibleWord }; 189 | -------------------------------------------------------------------------------- /src/web/builders/semantic_tokens_builder/token_util.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | const tokenTypes = new Map(); 4 | const tokenModifiers = new Map(); 5 | 6 | const legend = getLegend(); 7 | 8 | function getLegend() { 9 | const tokenTypesLegend: string[] = [ 10 | 'namespace', 11 | 'class', 12 | 'enum', 13 | 'interface', 14 | 'struct', 15 | 'typeParameter', 16 | 'type', 17 | 'parameter', 18 | 'variable', 19 | 'property', 20 | 'enumMember', 21 | 'decorator', 22 | 'event', 23 | 'function', 24 | 'method', 25 | 'macro', 26 | 'label', 27 | 'comment', 28 | 'string', 29 | 'keyword', 30 | 'number', 31 | 'regexp', 32 | 'operator', 33 | '' // placeholder for unknown 34 | ]; 35 | 36 | const tokenModifiersLegend: string[] = [ 37 | 'declaration', 38 | 'definition', 39 | 'readonly', 40 | 'static', 41 | 'deprecated', 42 | 'abstract', 43 | 'async', 44 | 'modification', 45 | 'documentation', 46 | 'defaultLibrary', 47 | ]; 48 | 49 | tokenTypesLegend.forEach((tokenType, index) => tokenTypes.set(tokenType, index)); 50 | tokenModifiersLegend.forEach((tokenModifier, index) => tokenModifiers.set(tokenModifier, index)); 51 | 52 | return new vscode.SemanticTokensLegend(tokenTypesLegend, tokenModifiersLegend); 53 | } 54 | 55 | const vscodeKindToTokenType: Map = 56 | new Map([ 57 | // no direct mapping 58 | [vscode.SymbolKind.File, 'variable'], 59 | // no direct mapping 60 | [vscode.SymbolKind.Module, 'namespace'], 61 | [vscode.SymbolKind.Namespace, 'namespace'], 62 | // no direct mapping 63 | [vscode.SymbolKind.Package, 'type'], 64 | [vscode.SymbolKind.Class, 'class'], 65 | [vscode.SymbolKind.Method, 'method'], 66 | [vscode.SymbolKind.Property, 'property'], 67 | [vscode.SymbolKind.Field, ''], 68 | [vscode.SymbolKind.Constructor, ''], 69 | [vscode.SymbolKind.Enum, 'enum'], 70 | [vscode.SymbolKind.Interface, 'interface'], 71 | [vscode.SymbolKind.Function, 'function'], 72 | [vscode.SymbolKind.Variable, 'variable'], 73 | [vscode.SymbolKind.Constant, ['variable', ['readonly']]], 74 | 75 | [vscode.SymbolKind.String, 'string'], 76 | [vscode.SymbolKind.Number, 'number'], 77 | // no direct mapping 78 | [vscode.SymbolKind.Boolean, ''], 79 | // no direct mapping 80 | [vscode.SymbolKind.Array, ''], 81 | // no direct mapping 82 | [vscode.SymbolKind.Object, ''], 83 | // no direct mapping 84 | [vscode.SymbolKind.Key, ''], 85 | // no direct mapping 86 | [vscode.SymbolKind.Null, ''], 87 | [vscode.SymbolKind.EnumMember, 'enumMember'], 88 | [vscode.SymbolKind.Struct, 'struct'], 89 | [vscode.SymbolKind.Event, 'event'], 90 | [vscode.SymbolKind.Operator, 'operator'], 91 | [vscode.SymbolKind.TypeParameter, 'typeParameter'] 92 | ]); 93 | 94 | // https://github.com/microsoft/vscode-extension-samples/blob/main/semantic-tokens-sample/src/extension.ts#L45-L65 95 | function _encodeTokenType(tokenType: string): number { 96 | if (tokenTypes.has(tokenType)) { 97 | return tokenTypes.get(tokenType)!; 98 | } else if (tokenType === 'notInLegend') { 99 | return tokenTypes.size + 2; 100 | } 101 | return 0; 102 | } 103 | 104 | function _encodeTokenModifiers(strTokenModifiers: string[]): number { 105 | let result = 0; 106 | for (const tokenModifier of strTokenModifiers) { 107 | if (tokenModifiers.has(tokenModifier)) { 108 | result = result | (1 << tokenModifiers.get(tokenModifier)!); 109 | } else if (tokenModifier === 'notInLegend') { 110 | result = result | (1 << tokenModifiers.size + 2); 111 | } 112 | } 113 | return result; 114 | } 115 | 116 | export { 117 | legend, vscodeKindToTokenType, 118 | _encodeTokenType, _encodeTokenModifiers 119 | }; 120 | -------------------------------------------------------------------------------- /src/web/builders/semantic_tokens_builder/update_util.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode'; 2 | 3 | import { excludeRangesFromRanges, isRangeIntExcludedRanges, mergeSortedIntervals } from '../../common/algorithm'; 4 | import type { DocSymbolInfo } from '../DocSymbolInfo'; 5 | import { loopKeywordsSet, loopKeywordsTokenMap } from '../loop_keywords'; 6 | 7 | import { _encodeTokenType, _encodeTokenModifiers } from './token_util'; 8 | 9 | function updateLoop( 10 | currDocSymbolInfo: DocSymbolInfo, 11 | excludedRanges: [number, number][], 12 | tokensBuilder: vscode.SemanticTokensBuilder, 13 | ) { 14 | const document = currDocSymbolInfo.document; 15 | const text = document.getText(); 16 | 17 | for (const blockRange of currDocSymbolInfo.loopBlocks) { 18 | const [start, end] = blockRange; 19 | const currText = text.substring(start, end); 20 | 21 | const reg = /(?<=[^:A-Za-z0-9\-\=])([:A-Za-z0-9\-\=]+)(?=[^:A-Za-z0-9\-\=])/igm; 22 | const matchRes = currText.matchAll(reg); 23 | 24 | for (const r of matchRes) { 25 | const word = r[1].toLowerCase(); 26 | if ( 27 | !loopKeywordsSet.has(word) && 28 | // some people would like use : before the keyword 29 | !(word.includes(':') && loopKeywordsSet.has(word.substring(word.indexOf(':') + 1))) 30 | ) { 31 | continue; 32 | } 33 | 34 | const rindex = r.index! + start; 35 | const numRange: [number, number] = [rindex, rindex + word.length]; 36 | if (isRangeIntExcludedRanges(numRange, excludedRanges)) { 37 | continue; 38 | } 39 | 40 | const tokenMapRes = loopKeywordsTokenMap.get( 41 | word.startsWith(':') ? word.substring(1) : word 42 | ); 43 | if (tokenMapRes === undefined) { 44 | //console.warn(`cannot get tokenMap for name: ${word}`); 45 | continue; 46 | } 47 | let tokenType = ''; 48 | let tokenModifiers: string[] = []; 49 | if (Array.isArray(tokenMapRes)) { 50 | tokenType = tokenMapRes[0]; 51 | tokenModifiers = tokenMapRes[1]; 52 | } else { 53 | tokenType = tokenMapRes; 54 | tokenModifiers = []; 55 | } 56 | 57 | const startPos = document.positionAt(rindex); 58 | const encodedTT = _encodeTokenType(tokenType); 59 | const encodedTMs = _encodeTokenModifiers(tokenModifiers); 60 | tokensBuilder.push( 61 | startPos.line, startPos.character, word.length, 62 | encodedTT, encodedTMs 63 | ); 64 | //const key = `loop ${word}|${startPos.line},${startPos.character},${word.length}`; 65 | //console.log(key); 66 | } 67 | 68 | } 69 | } 70 | 71 | function getlineEndRanges(text: string): [number, number][] { 72 | const reg = /\r\n|\r|\n/gm; 73 | const matchRes = text.matchAll(reg); 74 | const lineEndRanges: [number, number][] = []; 75 | for (const r of matchRes) { 76 | if (r.index !== undefined) { 77 | // +1 to fit into `excludeRangesFromRanges` 78 | lineEndRanges.push([r.index + 1, r.index + r.length]); 79 | } 80 | } 81 | return lineEndRanges; 82 | } 83 | 84 | function overrideQuote( 85 | currDocSymbolInfo: DocSymbolInfo, 86 | tokensBuilder: vscode.SemanticTokensBuilder 87 | ) { 88 | const scanDocRes = currDocSymbolInfo.docRes; 89 | const backquotePairAndSymbol = mergeSortedIntervals( 90 | [...scanDocRes.backquotePairRange, ...scanDocRes.backquoteRange].sort((a, b) => a[0] - b[0])); 91 | const commaPairAndSymbol = mergeSortedIntervals( 92 | [...scanDocRes.commaPairRange, ...scanDocRes.commaRange].sort((a, b) => a[0] - b[0])); 93 | const excludedComma = excludeRangesFromRanges(backquotePairAndSymbol, commaPairAndSymbol); 94 | 95 | 96 | const document = currDocSymbolInfo.document; 97 | const textColorRanges = mergeSortedIntervals([ 98 | ...scanDocRes.quotedPairRange, 99 | ...scanDocRes.quotedRange, 100 | ...excludedComma 101 | ].sort((a, b) => a[0] - b[0])); 102 | 103 | 104 | const lineEndRanges = getlineEndRanges(currDocSymbolInfo.docRes.text); 105 | const breakLineTextColorRanges = excludeRangesFromRanges(textColorRanges, lineEndRanges); 106 | 107 | for (const textRange of breakLineTextColorRanges) { 108 | const [start, end] = textRange; 109 | const startPos = document.positionAt(start); 110 | tokensBuilder.push( 111 | startPos.line, startPos.character, end - start, 112 | _encodeTokenType('operator'), _encodeTokenModifiers([]) 113 | ); 114 | } 115 | } 116 | 117 | function overrideNotFormattedString( 118 | currDocSymbolInfo: DocSymbolInfo, 119 | tokensBuilder: vscode.SemanticTokensBuilder 120 | ) { 121 | const document = currDocSymbolInfo.document; 122 | const text = document.getText(); 123 | const reg = /(?<=^|\s|\()format[\S\s]*?"/igm; 124 | const matchRes = text.matchAll(reg); 125 | const doubleQSet = new Set(); 126 | for (const r of matchRes) { 127 | if (r.index === undefined) { 128 | continue; 129 | } 130 | doubleQSet.add(r.index + r[0].length - 1); 131 | } 132 | 133 | for (const strRange of currDocSymbolInfo.docRes.stringRange) { 134 | const [start, end] = strRange; 135 | if (!doubleQSet.has(start)) { 136 | // actually not formatted str 137 | const startPos = document.positionAt(start); 138 | tokensBuilder.push( 139 | startPos.line, startPos.character, end - start, 140 | _encodeTokenType('string'), _encodeTokenModifiers([]) 141 | ); 142 | } 143 | } 144 | } 145 | 146 | // Due to the restrictions below, we cannot make this feature optional 147 | // https://github.com/microsoft/vscode/issues/580 148 | // https://github.com/microsoft/vscode/issues/68647 149 | function updatePairMatchFirstWord( 150 | currDocSymbolInfo: DocSymbolInfo, 151 | coloredPosMap: Map, 152 | tokensBuilder: vscode.SemanticTokensBuilder, 153 | ) { 154 | const pair = currDocSymbolInfo.docRes.pair; 155 | const text = currDocSymbolInfo.docRes.text; 156 | for (const p of pair) { 157 | const leftP = p[0]; 158 | const rightP = p[1]; 159 | 160 | let textInd = leftP + 1; // index-safe is guaranteed by p[1] 161 | while (textInd < rightP && /\s/.test(text[textInd])) { 162 | textInd += 1; 163 | } 164 | 165 | if (coloredPosMap.has(textInd)) { 166 | const [encodedTT, encodedTMs] = coloredPosMap.get(textInd)!; 167 | const startPosLeftP = currDocSymbolInfo.document.positionAt(leftP); 168 | tokensBuilder.push( 169 | startPosLeftP.line, startPosLeftP.character, 1, 170 | encodedTT, encodedTMs 171 | ); 172 | const startPosRightP = currDocSymbolInfo.document.positionAt(rightP); 173 | tokensBuilder.push( 174 | startPosRightP.line, startPosRightP.character, 1, 175 | encodedTT, encodedTMs 176 | ); 177 | } 178 | 179 | } 180 | 181 | } 182 | 183 | export { 184 | updateLoop, 185 | overrideQuote, 186 | overrideNotFormattedString, 187 | updatePairMatchFirstWord 188 | }; 189 | -------------------------------------------------------------------------------- /src/web/cl_data/cl_non_alphabetic.json: -------------------------------------------------------------------------------- 1 | {"#":["#","'","(","*","+","-",".",":","=","A","B","C","O","P","R","S","X","\\","|"],"&":["allow-other-keys","aux","body","environment","key","optional","rest","whole"],"*":["BREAK-ON-SIGNALS*","break-on-warnings*","COMPILE-FILE-PATHNAME*","COMPILE-FILE-TRUENAME*","COMPILE-PRINT*","COMPILE-VERBOSE*","DEBUG-IO*","DEBUGGER-HOOK*","DEFAULT-PATHNAME-DEFAULTS*","ERROR-OUTPUT*","FEATURES*","features*","GENSYM-COUNTER*","LOAD-PATHNAME*","LOAD-PRINT*","LOAD-TRUENAME*","LOAD-VERBOSE*","MACROEXPAND-HOOK*","MODULES*","PACKAGE*","PRINT-ARRAY*","PRINT-BASE*","PRINT-CASE*","PRINT-CIRCLE*","print-circle*","PRINT-ESCAPE*","PRINT-GENSYM*","PRINT-LENGTH*","PRINT-LEVEL*","PRINT-LINES*","PRINT-MISER-WIDTH*","PRINT-PPRINT-DISPATCH*","PRINT-PRETTY*","PRINT-RADIX*","PRINT-READABLY*","PRINT-RIGHT-MARGIN*","QUERY-IO*","RANDOM-STATE*","READ-BASE*","read-base*","READ-DEFAULT-FLOAT-FORMAT*","READ-EVAL*","read-eval*","READ-SUPPRESS*","READTABLE*","STANDARD-INPUT*","STANDARD-OUTPUT*","TERMINAL-IO*","TRACE-OUTPUT*"],":":["abort","absolute","accessor","adjustable","after","allocation","allow-other-keys","ansi-cl","append","argument-precedence-order","arguments","around","array","back","base","before","block","capitalize","case","class","cltl1","cltl2","common","common-lisp","compile-toplevel","conc-name","constructor","copier","count","create","current","declare","default","default-initargs","defaults","description","device","direction","directory","displaced-index-offset","displaced-to","documentation","downcase","draft-ansi-cl","draft-ansi-cl-2","element-type","end","end1","end2","environment","error","escape","execute","expected-type","export","external","external-format","fill","fill-pointer","format-arguments","from-end","generic-function","generic-function-class","gensym","host","identity","identity-with-one-argument","ieee-floating-point","if-does-not-exist","if-exists","import-from","include","index","inherited","init-form","initarg","initform","initial-contents","initial-element","initial-offset","initial-value","input","installed","instance","interactive","interactive-function","intern","internal","invert","io","junk-allowed","key","lambda-list","length","level","line","line-relative","linear","lines","load-toplevel","local","mandatory","metaclass","method","method-class","method-combination","miser","miser-width","most-specific-first","most-specific-last","name","named","new-version","newest","nicknames","no-error","off","oldest","on","operands","operator","order","output","output-file","override","overwrite","per-line-prefix","pixel-size","pprint-dispatch","pre-line-prefix","predicate","prefix","preserve","preserve-whitespace","pretty","previous","print","print-function","print-object","probe","radix","read-only","readably","reader","rehash-size","rehash-threshold","relative","rename","rename-and-delete","report","report-function","required","right-margin","section","section-relative","shadow","shadowing-import-from","size","slot-names","start","start1","start2","stream","suffix","supersede","test","test-function","test-not","type","unspecific","up","upcase","use","verbose","version","wild","wild-inferiors","wild-inferors","writer","x3j13"],"~":["$","%","&","(",")","*","/",";","<",">","?","A","B","C","D","E","F","G","I","O","P","R","S","T","W","X","[","]","^","_","{","|","}","~"]} 2 | -------------------------------------------------------------------------------- /src/web/collect_info/SymbolInfo.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | const vscodeCIKindToVscodeCIKind: Map = 4 | new Map([ 5 | [vscode.SymbolKind.File, vscode.CompletionItemKind.File], 6 | [vscode.SymbolKind.Module, vscode.CompletionItemKind.Module], 7 | // no direct mapping 8 | [vscode.SymbolKind.Namespace, vscode.CompletionItemKind.Module], 9 | // no direct mapping 10 | [vscode.SymbolKind.Package, vscode.CompletionItemKind.Module], 11 | [vscode.SymbolKind.Class, vscode.CompletionItemKind.Class], 12 | [vscode.SymbolKind.Method, vscode.CompletionItemKind.Method], 13 | [vscode.SymbolKind.Property, vscode.CompletionItemKind.Property], 14 | [vscode.SymbolKind.Field, vscode.CompletionItemKind.Field], 15 | [vscode.SymbolKind.Constructor, vscode.CompletionItemKind.Constructor], 16 | [vscode.SymbolKind.Enum, vscode.CompletionItemKind.Enum], 17 | [vscode.SymbolKind.Interface, vscode.CompletionItemKind.Interface], 18 | [vscode.SymbolKind.Function, vscode.CompletionItemKind.Function], 19 | [vscode.SymbolKind.Variable, vscode.CompletionItemKind.Variable], 20 | [vscode.SymbolKind.Constant, vscode.CompletionItemKind.Constant], 21 | // no direct mapping 22 | [vscode.SymbolKind.String, vscode.CompletionItemKind.Variable], 23 | // no direct mapping 24 | [vscode.SymbolKind.Number, vscode.CompletionItemKind.Variable], 25 | // no direct mapping 26 | [vscode.SymbolKind.Boolean, vscode.CompletionItemKind.Variable], 27 | // no direct mapping 28 | [vscode.SymbolKind.Array, vscode.CompletionItemKind.Variable], 29 | // no direct mapping 30 | [vscode.SymbolKind.Object, vscode.CompletionItemKind.Variable], 31 | // no direct mapping 32 | [vscode.SymbolKind.Key, vscode.CompletionItemKind.Variable], 33 | // no direct mapping 34 | [vscode.SymbolKind.Null, vscode.CompletionItemKind.Keyword], 35 | [vscode.SymbolKind.EnumMember, vscode.CompletionItemKind.EnumMember], 36 | [vscode.SymbolKind.Struct, vscode.CompletionItemKind.Struct], 37 | [vscode.SymbolKind.Event, vscode.CompletionItemKind.Event], 38 | [vscode.SymbolKind.Operator, vscode.CompletionItemKind.Operator], 39 | [vscode.SymbolKind.TypeParameter, vscode.CompletionItemKind.TypeParameter], 40 | 41 | ]); 42 | 43 | class SymbolInfo { 44 | private readonly document: vscode.TextDocument; 45 | public readonly uri: vscode.Uri; 46 | 47 | public readonly name: string; 48 | public readonly containerName: string | undefined; 49 | // if has scope, this symbol is valid only if the symbol is in scope 50 | public readonly scope: [number, number] | undefined; 51 | public readonly kind: vscode.SymbolKind; 52 | public readonly numRange: [number, number]; 53 | public readonly docStr: string | undefined; 54 | 55 | private _startPos: vscode.Position | undefined = undefined; 56 | private _range: vscode.Range | undefined = undefined; 57 | private _loc: vscode.Location | undefined = undefined; 58 | private _stringify: string | undefined = undefined; 59 | 60 | private numberedContainerName: string | undefined = undefined; 61 | public symbolInPRange: [number, number] | undefined = undefined; 62 | 63 | constructor( 64 | document: vscode.TextDocument, 65 | name: string, 66 | containerName: string | undefined, 67 | scope: [number, number] | undefined, 68 | kind: vscode.SymbolKind, 69 | numRange: [number, number], 70 | docStr: string | undefined = undefined 71 | ) { 72 | 73 | this.document = document; 74 | this.uri = document.uri; 75 | 76 | this.name = name; 77 | this.containerName = containerName; 78 | this.scope = scope; 79 | this.kind = kind; 80 | this.numRange = numRange; 81 | this.docStr = docStr; 82 | } 83 | 84 | get startPos() { 85 | if (this._startPos === undefined) { 86 | this._startPos = this.document.positionAt(this.numRange[0]); 87 | } 88 | return this._startPos; 89 | } 90 | 91 | get range() { 92 | if (this._range === undefined) { 93 | this._range = new vscode.Range( 94 | this.startPos, 95 | this.document.positionAt(this.numRange[1]), 96 | ); 97 | } 98 | return this._range; 99 | } 100 | 101 | get loc() { 102 | if (this._loc === undefined) { 103 | this._loc = new vscode.Location(this.uri, this.range); 104 | } 105 | return this._loc; 106 | } 107 | 108 | get stringify() { 109 | if (this._stringify === undefined) { 110 | this._stringify = `${this.name}|${this.uri.path}|${this.range.start.line},${this.range.start.character},${this.range.end.line},${this.range.end.character}`; 111 | } 112 | return this._stringify; 113 | } 114 | 115 | public toCompletionItem(): vscode.CompletionItem { 116 | const citemLabel: vscode.CompletionItemLabel = { 117 | label: this.name, 118 | // in doc_symbol_builder.ts, numberedContainerName will not be assigned if no need 119 | description: (this.numberedContainerName !== undefined) ? 120 | this.numberedContainerName : this.containerName, 121 | }; 122 | 123 | return new vscode.CompletionItem(citemLabel, vscodeCIKindToVscodeCIKind.get(this.kind)); 124 | } 125 | 126 | public toCallHierarchyItem(): vscode.CallHierarchyItem { 127 | const detail = 128 | (this.numberedContainerName !== undefined) ? 129 | this.numberedContainerName : 130 | ((this.containerName !== undefined) ? this.containerName : ''); 131 | 132 | return new vscode.CallHierarchyItem( 133 | this.kind, 134 | this.name, 135 | detail, 136 | this.uri, 137 | this.range, 138 | this.range 139 | ); 140 | } 141 | 142 | public toDocumentSymbol( 143 | numberedContainerName: string | undefined = undefined 144 | ): vscode.DocumentSymbol { 145 | if (numberedContainerName !== undefined) { 146 | this.numberedContainerName = numberedContainerName; 147 | return new vscode.DocumentSymbol( 148 | this.name, 149 | numberedContainerName, 150 | this.kind, this.range, this.range 151 | ); 152 | } 153 | 154 | if (this.containerName === undefined) { 155 | // console.warn('`this.containerName` cannot be undefined'); 156 | } 157 | return new vscode.DocumentSymbol( 158 | this.name, 159 | this.containerName!, 160 | this.kind, this.range, this.range 161 | ); 162 | } 163 | 164 | } 165 | 166 | export { SymbolInfo }; 167 | -------------------------------------------------------------------------------- /src/web/collect_info/collect_from_text/lambda_list.ts: -------------------------------------------------------------------------------- 1 | import { clValidSymbolSingleCharColonSet } from '../../common/cl_util'; 2 | import { findMatchPairExactP, isSpace } from '../collect_util'; 3 | 4 | import type { ScanDocRes } from './ScanDocRes'; 5 | 6 | const suppliedPParameterSet = new Set(['&optional', '&key']); 7 | 8 | function processRecList( 9 | varsStr: string, baseInd: number, scanDocRes: ScanDocRes, 10 | keywordStatus: string, allowDestructuring = false 11 | ): Map { 12 | let res: Map = new Map(); 13 | 14 | let currName = ''; 15 | let currStart = -1; 16 | let i = 1; 17 | 18 | // console.log(`processRecList: ${varsStr}`); 19 | 20 | while (i < varsStr.length) { 21 | //console.log(`${i}: ${varsStr[i]}|${currName}|${currStart}`); 22 | 23 | if (varsStr[i] === ';') { 24 | while (i < varsStr.length && varsStr[i] !== '\n') { 25 | //console.log(`LC| ${index}: ${doc[index]}`); 26 | ++i; 27 | } 28 | 29 | } else if (varsStr[i] === '|' && varsStr[i - 1] === '#') { 30 | while (i < varsStr.length && (varsStr[i] !== '#' || varsStr[i - 1] !== '|')) { 31 | //console.log(`BC| ${index}: ${doc[index]}`); 32 | ++i; 33 | } 34 | 35 | } else if (varsStr[i] === '#') { 36 | const idx = skipReaderMarcro(i, baseInd, varsStr, scanDocRes.pairMap); 37 | if (idx < 0) { 38 | return res; 39 | } 40 | i = idx; 41 | 42 | } else if (clValidSymbolSingleCharColonSet.has(varsStr[i])) { 43 | // `var` 44 | if (currStart === -1) { 45 | currStart = i; 46 | } 47 | 48 | currName += varsStr[i]; 49 | ++i; 50 | 51 | } else if (varsStr[i] === '(') { 52 | // `( var` 53 | const idx = findMatchPairExactP(baseInd + i, scanDocRes.pairMap); 54 | if (idx === -1) { 55 | return res; 56 | } 57 | const newInd = idx - baseInd; 58 | if (varsStr[i - 1] === '`' || varsStr[i - 1] === '\'') { 59 | // skip pair 60 | i = newInd; 61 | continue; 62 | } 63 | 64 | if (!allowDestructuring || keywordStatus !== '') { 65 | const needPParameter = suppliedPParameterSet.has(keywordStatus); 66 | justEatOneVar(i + 1, baseInd, varsStr, newInd, scanDocRes, res, needPParameter); // i+1 skip current `(` 67 | } else { 68 | //console.log(`Rec...ing|${keywordStatus} at ${i}, ${baseInd + i}`); 69 | const workingStr = varsStr.substring(i, newInd); 70 | const addRes = processRecList(workingStr, baseInd + i, scanDocRes, '', allowDestructuring); 71 | res = new Map([...res, ...addRes]); 72 | } 73 | i = newInd; 74 | 75 | } else if (isSpace(varsStr[i]) || varsStr[i] === ')') { 76 | // cannot find match anymore 77 | const endState = endName(currName, currStart, baseInd, res); 78 | if (endState === -1) { 79 | keywordStatus = currName.toLowerCase(); 80 | } 81 | 82 | currStart = -1; 83 | currName = ''; 84 | ++i; 85 | 86 | } else { 87 | ++i; 88 | } 89 | } 90 | // console.log(res); 91 | return res; 92 | } 93 | 94 | function justEatOneVar( 95 | i: number, baseInd: number, varsStr: string, newInd: number, scanDocRes: ScanDocRes, 96 | res: Map, needPParameter = false 97 | ): boolean { 98 | let varName = ''; 99 | let varStart = -1; 100 | 101 | // (var [init-form [supplied-p-parameter]]) total 3 items 102 | let countItem = 0; 103 | 104 | while (i < newInd) { 105 | if (varsStr[i] === ';') { 106 | while (i < newInd && varsStr[i] !== '\n') { 107 | ++i; 108 | } 109 | 110 | } else if (varsStr[i] === '|' && varsStr[i - 1] === '#') { 111 | while (i < newInd && (varsStr[i] !== '#' || varsStr[i - 1] !== '|')) { 112 | ++i; 113 | } 114 | 115 | } else if (clValidSymbolSingleCharColonSet.has(varsStr[i])) { 116 | if (varStart === -1) { 117 | varStart = i; 118 | } 119 | varName += varsStr[i]; 120 | ++i; 121 | 122 | } else if (needPParameter && countItem === 1 && varsStr[i] === '(') { 123 | const idx = findMatchPairExactP(baseInd + i, scanDocRes.pairMap); 124 | if (idx === -1) { 125 | return false; 126 | } 127 | const newInd = idx - baseInd; 128 | i = newInd; 129 | countItem = 2; 130 | 131 | } else if (isSpace(varsStr[i]) || varsStr[i] === ')') { 132 | // cannot find match anymore 133 | if (needPParameter) { 134 | if (countItem === 0) { 135 | if (endName(varName, varStart, baseInd, res) === 1) { 136 | countItem = 1; 137 | } 138 | } else if (countItem === 1) { 139 | if (endName(varName, varStart, baseInd, res, true) === 1) { 140 | countItem = 2; 141 | } 142 | } else if (countItem === 2) { 143 | if (endName(varName, varStart, baseInd, res) === 1) { 144 | return true; 145 | } 146 | } 147 | 148 | } else { 149 | if (endName(varName, varStart, baseInd, res) === 1) { 150 | return true; 151 | } 152 | } 153 | 154 | varStart = -1; 155 | varName = ''; 156 | ++i; 157 | } else { 158 | ++i; 159 | } 160 | 161 | } 162 | return false; 163 | } 164 | 165 | function skipReaderMarcro(i: number, baseInd: number, varsStr: string, pairMap: Map): number { 166 | //skip reader macro #+ & #- 167 | if (i + 1 < varsStr.length && (varsStr[i + 1] === '+' || varsStr[i + 1] === '-')) { 168 | if (i + 2 < varsStr.length) { 169 | 170 | if (varsStr[i + 2] === '(' && i + 3 < varsStr.length) { 171 | // skip pair 172 | const newInd = findMatchPairExactP(baseInd + i + 2, pairMap); 173 | return newInd - baseInd; 174 | } else { 175 | // skip str 176 | while (i < varsStr.length && !isSpace(varsStr[i]) && varsStr[i] !== ')') { 177 | ++i; 178 | } 179 | return i; 180 | } 181 | 182 | } else { 183 | return -1; 184 | } 185 | } 186 | 187 | ++i; 188 | return i; 189 | } 190 | 191 | function endName( 192 | currName: string, currStart: number, baseInd: number, res: Map, drop = false 193 | ): number { 194 | // just no dup reset 195 | if (currName && currStart !== -1 && currName !== '.' && !currName.includes(':')) { 196 | if (currName.startsWith('&')) { 197 | return -1; 198 | } else { 199 | //console.log(`endStr|${currName}: ${baseInd + currStart} -> ${baseInd + currStart + currName.length}`); 200 | if (!drop) { 201 | res.set(currName, [baseInd + currStart, baseInd + currStart + currName.length]); 202 | } 203 | return 1; 204 | } 205 | 206 | } 207 | return 0; 208 | } 209 | 210 | // `varStr` must start from and contain first '(' 211 | // `leftPOffset` is the absolute index of whole text when i == 0 [ind at '('] 212 | function processVars( 213 | leftPOffset: number, scanDocRes: ScanDocRes, validUpper: number, allowDestructuring: boolean, 214 | ): [Map, number] | undefined { 215 | //const res: Map = new Map(); 216 | 217 | const varsStrStart = leftPOffset; 218 | const varsStrEnd: number = findMatchPairExactP(leftPOffset, scanDocRes.pairMap, validUpper); 219 | if (varsStrEnd === -1) { 220 | return undefined; 221 | } 222 | const varsStr = scanDocRes.text.substring(varsStrStart, varsStrEnd); 223 | 224 | return [processRecList(varsStr, leftPOffset, scanDocRes, '', allowDestructuring), varsStrEnd]; 225 | } 226 | 227 | export { processVars }; 228 | -------------------------------------------------------------------------------- /src/web/collect_info/collect_from_text/loop.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { isRangeIntExcludedRanges } from '../../common/algorithm'; 4 | import { clValidSymbolSingleCharColonSet } from '../../common/cl_util'; 5 | import { SymbolInfo } from '../SymbolInfo'; 6 | import { addToDictArr, getValidGroupRes, findMatchPairExactP, isSpace } from '../collect_util'; 7 | 8 | import type { ScanDocRes } from './ScanDocRes'; 9 | import { processVars } from './lambda_list'; 10 | 11 | const loopStartClauseKeywordSet = new Set([ 12 | 'named', 13 | 'initially', 14 | 'finally', 15 | 'for', 16 | 'as', 17 | 'with', 18 | 'repeat', 19 | 'do', 20 | 'doing', 21 | 'return', 22 | 'collect', 23 | 'collecting', 24 | 'append', 25 | 'appending', 26 | 'nconc', 27 | 'nconcing', 28 | 'count', 29 | 'counting', 30 | 'sum', 31 | 'summing', 32 | 'maximize', 33 | 'maximizing', 34 | 'minimize', 35 | 'minimizing', 36 | 'into', 37 | 'loop-finish', 38 | 'thereis', 39 | 'always', 40 | 'never', 41 | 'if', 42 | 'when', 43 | 'unless', 44 | 'while', 45 | 'until', 46 | 'else', 47 | 'end', 48 | ]); 49 | 50 | function findFollowingAnd(baseInd: number, varsStr: string, scanDocRes: ScanDocRes): [number, number][] { 51 | let varName = ''; 52 | let varStart = -1; 53 | 54 | const andPosition: [number, number][] = []; 55 | let seeAnd = false; 56 | let startSingleVar = -1; 57 | 58 | let i = 0; 59 | const upper = varsStr.length; 60 | while (i < upper) { 61 | if (varsStr[i] === ';') { 62 | while (i < upper && varsStr[i] !== '\n') { 63 | ++i; 64 | } 65 | 66 | } else if (varsStr[i] === '|' && varsStr[i - 1] === '#') { 67 | while (i < upper && (varsStr[i] !== '#' || varsStr[i - 1] !== '|')) { 68 | ++i; 69 | } 70 | 71 | } else if (clValidSymbolSingleCharColonSet.has(varsStr[i])) { 72 | if (seeAnd) { 73 | startSingleVar = i; 74 | } 75 | 76 | if (varStart === -1) { 77 | varStart = i; 78 | } 79 | varName += varsStr[i]; 80 | ++i; 81 | 82 | } else if (varsStr[i] === '(') { 83 | const idx = findMatchPairExactP(baseInd + i, scanDocRes.pairMap); 84 | if (idx === -1) { 85 | return andPosition; 86 | } 87 | const newInd = idx - baseInd; 88 | if (seeAnd) { 89 | andPosition.push([i, newInd]); 90 | seeAnd = false; 91 | } 92 | i = newInd; 93 | 94 | } else if (isSpace(varsStr[i]) || varsStr[i] === ')') { 95 | // cannot find match anymore 96 | if (varName && varStart !== -1 && 97 | varName !== '.' && 98 | !varName.includes(':') && 99 | !varName.startsWith('&') 100 | ) { 101 | if (loopStartClauseKeywordSet.has(varName)) { 102 | return andPosition; 103 | } 104 | if (seeAnd && startSingleVar !== -1) { 105 | andPosition.push([startSingleVar, startSingleVar + varName.length]); 106 | startSingleVar = -1; 107 | seeAnd = false; 108 | } 109 | if (varName === 'and') { 110 | seeAnd = true; 111 | } 112 | } 113 | 114 | varStart = -1; 115 | varName = ''; 116 | ++i; 117 | } else { 118 | ++i; 119 | } 120 | } 121 | return andPosition; 122 | } 123 | 124 | function collectLoopVar( 125 | document: vscode.TextDocument, scanDocRes: ScanDocRes, excludedRange: [number, number][] 126 | ): [Map, [number, number][]] { 127 | // Practical Common Lisp 22. LOOP for Black Belts https://gigamonkeys.com/book/loop-for-black-belts.html 128 | // Common Lisp the Language, 2nd Edition 26.3.2. Kinds of Loop Clauses https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node240.html 129 | 130 | const text = scanDocRes.text; 131 | const defLocalNames: Map = new Map(); 132 | const loopBlocks: [number, number][] = []; 133 | 134 | // commonlisp.yaml macro 135 | const matchRes = text.matchAll(/(?<=^|\s|\(|,@|,\.|,)(\()(\s*)(loop)(\s)/igmd); 136 | for (const r of matchRes) { 137 | if (r.indices === undefined) { 138 | continue; 139 | } 140 | 141 | // working in currtext 142 | const loopStart = r.indices[1][0]; 143 | const closedParenthese = findMatchPairExactP(loopStart, scanDocRes.pairMap); 144 | if (closedParenthese === -1) { 145 | continue; 146 | } 147 | loopBlocks.push([loopStart, closedParenthese]); 148 | 149 | const subStart = r.indices[4][0]; 150 | const subText = text.substring(subStart, closedParenthese); 151 | const subMatchRes = subText.matchAll(/(?<=^|\s|\(|,@|,\.|,):?(for|as|with|into|named)\s+(([#:A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.]+)\s|(\())/igmd); 152 | for (const subR of subMatchRes) { 153 | if (subR.indices === undefined) { 154 | continue; 155 | } 156 | 157 | const defLocalName = getValidGroupRes(subR, [3, 4]); 158 | if (defLocalName === undefined) { 159 | continue; 160 | } 161 | 162 | const containerName = subR[1].toLowerCase(); 163 | if (defLocalName === '(') { 164 | const varsStart = subR.indices[4][0] + subStart; 165 | multiVars(defLocalNames, document, scanDocRes, excludedRange, varsStart, containerName, closedParenthese); 166 | } else { 167 | const nameRangeInd: [number, number] = [subR.indices[3][0] + subStart, subR.indices[3][1] + subStart]; 168 | singleVar(defLocalNames, defLocalName, document, excludedRange, nameRangeInd, containerName, closedParenthese); 169 | } 170 | 171 | // process `and` after current declaration 172 | const afterKeyword = subR.indices[1][1]; 173 | const baseInd = subStart + afterKeyword; 174 | const afterKeywordText = subText.substring(afterKeyword); 175 | const followingAnd = findFollowingAnd(baseInd, afterKeywordText, scanDocRes); 176 | 177 | for (const rang of followingAnd) { 178 | if (afterKeywordText[rang[0]] === '(') { 179 | const varsStart = rang[0] + baseInd; 180 | multiVars(defLocalNames, document, scanDocRes, excludedRange, varsStart, containerName, closedParenthese); 181 | } else { 182 | const nameRangeInd: [number, number] = [rang[0] + baseInd, rang[1] + baseInd]; 183 | const name = afterKeywordText.substring(...rang).toLowerCase(); 184 | singleVar(defLocalNames, name, document, excludedRange, nameRangeInd, containerName, closedParenthese); 185 | } 186 | } 187 | 188 | } 189 | 190 | } 191 | return [defLocalNames, loopBlocks]; 192 | } 193 | 194 | function singleVar( 195 | defLocalNames: Map, 196 | defLocalName: string, document: vscode.TextDocument, excludedRange: [number, number][], 197 | nameRangeInd: [number, number], containerName: string, closedParenthese: number 198 | ) { 199 | if (isRangeIntExcludedRanges(nameRangeInd, excludedRange)) { 200 | return; 201 | } 202 | 203 | const lexicalScope: [number, number] = [nameRangeInd[1], closedParenthese]; 204 | 205 | addToDictArr(defLocalNames, defLocalName, new SymbolInfo( 206 | document, defLocalName, containerName, lexicalScope, 207 | vscode.SymbolKind.Variable, nameRangeInd 208 | )); 209 | } 210 | 211 | function multiVars( 212 | defLocalNames: Map, 213 | document: vscode.TextDocument, scanDocRes: ScanDocRes, excludedRange: [number, number][], 214 | varsStart: number, containerName: string, closedParenthese: number, 215 | ) { 216 | const varsRes = processVars(varsStart, scanDocRes, closedParenthese, true); 217 | if (varsRes === undefined) { 218 | return; 219 | } 220 | const [vars, varsStrEnd] = varsRes; 221 | 222 | for (const [nn, rang] of vars) { 223 | const nnLower = nn.toLowerCase(); 224 | if (nnLower === 'nil') { 225 | continue; 226 | } 227 | if (isRangeIntExcludedRanges(rang, excludedRange)) { 228 | continue; 229 | } 230 | 231 | const secondLexicalScope: [number, number] = [varsStrEnd, closedParenthese]; 232 | 233 | addToDictArr(defLocalNames, nnLower, new SymbolInfo( 234 | document, nnLower, containerName, secondLexicalScope, 235 | vscode.SymbolKind.Variable, rang 236 | )); 237 | } 238 | } 239 | 240 | export { collectLoopVar }; 241 | -------------------------------------------------------------------------------- /src/web/collect_info/collect_from_text/var.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { isRangeIntExcludedRanges } from '../../common/algorithm'; 4 | import { clValidSymbolSingleCharColonSet } from '../../common/cl_util'; 5 | import { SymbolInfo } from '../SymbolInfo'; 6 | import { 7 | findMatchPairExactP, addToDictArr, getValidGroupRes, isSpace 8 | } from '../collect_util'; 9 | 10 | import type { ScanDocRes } from './ScanDocRes'; 11 | import { processVars } from './lambda_list'; 12 | 13 | const seqBindSet = new Set(['let*', 'do*', 'prog*', 'lambda', 'destructuring-bind']); 14 | 15 | function getStepFormArr(vars: Map, varsStr: string, baseInd: number, scanDocRes: ScanDocRes) { 16 | // {name, validScope} 17 | const res: [number, number][] = []; 18 | 19 | for (const rang of vars.values()) { 20 | const end = rang[1] - baseInd; 21 | const stepForm = getStepFormRange(end, baseInd, varsStr, scanDocRes); 22 | if (stepForm !== undefined) { 23 | res.push(stepForm); 24 | } 25 | } 26 | 27 | return res; 28 | } 29 | 30 | function getStepFormRange( 31 | i: number, baseInd: number, varsStr: string, scanDocRes: ScanDocRes, 32 | ): [number, number] | undefined { 33 | 34 | let varName = ''; 35 | let varStart = -1; 36 | 37 | // (var [init-form [step-form]]) total 2 items 38 | // ^ from here 39 | let countItem = 0; 40 | const upper = varsStr.length; 41 | while (i < upper) { 42 | if (varsStr[i] === ';') { 43 | while (i < upper && varsStr[i] !== '\n') { 44 | ++i; 45 | } 46 | 47 | } else if (varsStr[i] === '|' && varsStr[i - 1] === '#') { 48 | while (i < upper && (varsStr[i] !== '#' || varsStr[i - 1] !== '|')) { 49 | ++i; 50 | } 51 | 52 | } else if (clValidSymbolSingleCharColonSet.has(varsStr[i])) { 53 | if (varStart === -1) { 54 | varStart = i; 55 | } 56 | varName += varsStr[i]; 57 | ++i; 58 | 59 | } else if (varsStr[i] === '(') { 60 | if (countItem === 0) { 61 | const idx = findMatchPairExactP(baseInd + i, scanDocRes.pairMap); 62 | if (idx === -1) { 63 | return undefined; 64 | } 65 | const newInd = idx - baseInd; 66 | i = newInd; 67 | countItem = 1; 68 | } else if (countItem === 1) { 69 | const idx = findMatchPairExactP(baseInd + i, scanDocRes.pairMap); 70 | if (idx === -1) { 71 | return undefined; 72 | } else { 73 | return [baseInd + i, idx]; 74 | } 75 | } else { 76 | return undefined; 77 | } 78 | 79 | } else if (isSpace(varsStr[i]) || varsStr[i] === ')') { 80 | // cannot find match anymore 81 | if (countItem === 0) { 82 | if (varName && varStart !== -1 && 83 | varName !== '.' && 84 | !varName.includes(':') && 85 | !varName.startsWith('&') 86 | ) { 87 | countItem = 1; 88 | } 89 | } else { 90 | return undefined; 91 | } 92 | 93 | varStart = -1; 94 | varName = ''; 95 | ++i; 96 | } else { 97 | ++i; 98 | } 99 | } 100 | return undefined; 101 | } 102 | 103 | 104 | function collectKeywordVars( 105 | document: vscode.TextDocument, scanDocRes: ScanDocRes, excludedRange: [number, number][] 106 | ): [Map, [number, number][]] { 107 | const text = scanDocRes.text; 108 | const defLocalNames: Map = new Map(); 109 | const stepForm: [number, number][] = []; 110 | 111 | // commonlisp.yaml special-operator | macro 112 | // `progv` allows binding one or more dynamic variables, is not implemented 113 | const matchRes1 = text.matchAll(/(?<=#'|^|\s|\(|,@|,\.|,)(\()(\s*)(lambda|let\*|let|symbol-macrolet|do\*|do|prog\*|prog|multiple-value-bind|destructuring-bind)\s+?(\()/igmd); 114 | 115 | for (const r of matchRes1) { 116 | if (r.indices === undefined) { 117 | continue; 118 | } 119 | 120 | // working in currtext 121 | const closedParenthese = findMatchPairExactP(r.indices[1][0], scanDocRes.pairMap); 122 | if (closedParenthese === -1) { 123 | continue; 124 | } 125 | 126 | const leftPInd = r.indices[4][0]; 127 | 128 | // get vars list, start with `(` 129 | const currK = r[3].toLowerCase(); 130 | const varsRes = processVars(leftPInd, scanDocRes, closedParenthese, (currK === 'destructuring-bind')); 131 | if (varsRes === undefined) { 132 | continue; 133 | } 134 | 135 | const [vars, varsStrEnd] = varsRes; 136 | 137 | if (currK === 'do') { 138 | // https://www.lispworks.com/documentation/lw60/CLHS/Body/m_do_do.htm step-form 139 | // only for `do` since `do*` is sequencial binding 140 | stepForm.push(...getStepFormArr(vars, text.substring(leftPInd, varsStrEnd), leftPInd, scanDocRes)); 141 | } 142 | 143 | if (seqBindSet.has(currK)) { 144 | // get lexical scope 145 | // only for let* | do* | prog* | lambda | destructuring-bind, sequencial binding 146 | for (const [nn, rang] of vars) { 147 | const nnLower = nn.toLowerCase(); 148 | if (isRangeIntExcludedRanges(rang, excludedRange)) { 149 | continue; 150 | } 151 | 152 | const lexicalScope: [number, number] = [rang[1], closedParenthese]; 153 | 154 | addToDictArr(defLocalNames, nnLower, new SymbolInfo( 155 | document, nnLower, currK, lexicalScope, 156 | vscode.SymbolKind.Variable, rang 157 | )); 158 | } 159 | } else { 160 | // get lexical scope 161 | // lexical scope valid after definition, that is, after next '()' pair 162 | const startValidPos = findMatchPairExactP(leftPInd, scanDocRes.pairMap, closedParenthese); 163 | if (startValidPos === -1) { 164 | continue; 165 | } 166 | 167 | const lexicalScope: [number, number] = [startValidPos, closedParenthese]; 168 | 169 | for (const [nn, rang] of vars) { 170 | const nnLower = nn.toLowerCase(); 171 | if (isRangeIntExcludedRanges(rang, excludedRange)) { 172 | continue; 173 | } 174 | 175 | addToDictArr(defLocalNames, nnLower, new SymbolInfo( 176 | document, nnLower, currK, lexicalScope, 177 | vscode.SymbolKind.Variable, rang 178 | )); 179 | } 180 | } 181 | 182 | } 183 | //console.log(defLocalNames); 184 | return [defLocalNames, stepForm]; 185 | } 186 | 187 | function collectKeywordSingleVar( 188 | document: vscode.TextDocument, scanDocRes: ScanDocRes, excludedRange: [number, number][] 189 | ): Map { 190 | const text = scanDocRes.text; 191 | const defLocalNames: Map = new Map(); 192 | 193 | // commonlisp.yaml macro 194 | const matchRes2 = text.matchAll(/(?<=#'|^|\s|\(|,@|,\.|,)(\()(\s*)(do-symbols|do-external-symbols|do-all-symbols|dolist|dotimes|pprint-logical-block|with-input-from-string|with-open-file|with-open-stream|with-output-to-string|with-package-iterator|with-hash-table-iterator)\s+?(\()\s*?([#:A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.]+)(\s|\)|\()/igmd); 195 | 196 | for (const r of matchRes2) { 197 | if (r.indices === undefined) { 198 | continue; 199 | } 200 | 201 | const defLocalName = getValidGroupRes(r, [5]); 202 | if (defLocalName === undefined) { 203 | continue; 204 | } 205 | const defLocalNameLower = defLocalName.toLowerCase(); 206 | 207 | const nameRangeInd = r.indices[5]; 208 | if (isRangeIntExcludedRanges(nameRangeInd, excludedRange)) { 209 | continue; 210 | } 211 | 212 | // console.log(`${defLocalName}: Range`); 213 | 214 | // working in currtext 215 | const closedParenthese = findMatchPairExactP(r.indices[1][0], scanDocRes.pairMap); 216 | if (closedParenthese === -1) { 217 | continue; 218 | } 219 | 220 | const startValidPos = findMatchPairExactP(r.indices[4][0], scanDocRes.pairMap, closedParenthese); 221 | if (startValidPos === -1) { 222 | continue; 223 | } 224 | // console.log(`${defLocalName}: ${startValidPos} -> ${closedParenthese}`); 225 | const lexicalScope: [number, number] = [startValidPos, closedParenthese]; 226 | const currK = r[3].toLowerCase(); 227 | 228 | addToDictArr(defLocalNames, defLocalNameLower, new SymbolInfo( 229 | document, defLocalNameLower, currK, lexicalScope, 230 | vscode.SymbolKind.Variable, nameRangeInd 231 | )); 232 | 233 | } 234 | return defLocalNames; 235 | } 236 | 237 | 238 | export { collectKeywordVars, collectKeywordSingleVar }; 239 | -------------------------------------------------------------------------------- /src/web/collect_info/collect_util.ts: -------------------------------------------------------------------------------- 1 | const space = new Set([' ', '\f', '\n', '\r', '\t', '\v']); 2 | const isSpace = (c: string) => space.has(c); 3 | 4 | function getValidGroupInd(indices: [number, number][], nameGroup: number[]): [number, number] | undefined { 5 | for (const g of nameGroup) { 6 | if (indices[g] !== undefined) { 7 | return indices[g]; 8 | } 9 | } 10 | return undefined; 11 | } 12 | 13 | function getValidGroupRes(r: RegExpMatchArray, nameGroup: number[]): string | undefined { 14 | for (const g of nameGroup) { 15 | if (r[g] !== undefined) { 16 | return r[g]; 17 | } 18 | } 19 | return undefined; 20 | /* 21 | let defName: string | undefined = undefined; 22 | 23 | for (const g of nameGroup) { 24 | if (r[g] !== undefined) { 25 | defName = r[g]; 26 | break; 27 | } 28 | } 29 | 30 | // exclude KEYWORD package symbols 31 | if (defName === undefined) { 32 | return undefined; 33 | } 34 | 35 | 36 | //if (isStringClValidSymbol(defName) === undefined) { 37 | // return undefined; 38 | //} 39 | 40 | return defName; 41 | */ 42 | } 43 | 44 | function addToDictArr(dict: Map, k: string, item: any) { 45 | dict.get(k); 46 | if (!dict.has(k)) { 47 | dict.set(k, []); 48 | } 49 | dict.get(k)!.push(item); 50 | } 51 | 52 | function findMatchPairExactP( 53 | absIndex: number, pairMap: Map, validUpper: number | undefined = undefined 54 | ): number { 55 | const idx = pairMap.get(absIndex); 56 | if (idx === undefined) { 57 | return -1; 58 | } 59 | // validUpper is not including 60 | if (idx < absIndex || (validUpper !== undefined && validUpper <= idx)) { 61 | return -1; 62 | } 63 | return idx + 1; 64 | } 65 | 66 | export { 67 | getValidGroupRes, getValidGroupInd, 68 | addToDictArr, 69 | findMatchPairExactP, 70 | isSpace, 71 | }; 72 | -------------------------------------------------------------------------------- /src/web/common/algorithm.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode'; 2 | 3 | function excludeRangesFromRanges(oriRanges: [number, number][], excludeRanges: [number, number][]): [number, number][] { 4 | const res: [number, number][] = []; 5 | for (const currRange of oriRanges) { 6 | const [start, end] = currRange; 7 | 8 | const idxStart = bisectRight(excludeRanges, start, item => item[0]); 9 | const idxEnd = bisectRight(excludeRanges, end, item => item[1]); 10 | 11 | if (idxStart === idxEnd) { 12 | res.push([start, end]); 13 | continue; 14 | } 15 | 16 | let newStart = start; 17 | for (let i = idxStart; i < idxEnd; ++i) { 18 | const [exStart, exEnd] = excludeRanges[i]; 19 | res.push([newStart, exStart]); 20 | newStart = exEnd; 21 | } 22 | res.push([newStart, end]); 23 | } 24 | 25 | return res; 26 | } 27 | 28 | 29 | function mergeSortedIntervals(intervals: [number, number][]): [number, number][] { 30 | const merged: [number, number][] = []; 31 | for (const interval of intervals) { 32 | const [intervalStart, intervalEnd] = interval; 33 | const mergedLast = merged.at(-1)!; 34 | if (merged.length === 0 || mergedLast[1] < intervalStart) { 35 | merged.push(interval); 36 | } else { 37 | mergedLast[1] = Math.max(mergedLast[1], intervalEnd); 38 | } 39 | } 40 | return merged; 41 | } 42 | 43 | function mergeSortedMXArrList(sortedMXArrLis: [number, number][][]) { 44 | const res = sortedMXArrLis.reduce( 45 | (previousValue, currentValue) => mergeSortedMXArr(previousValue, currentValue), 46 | [] 47 | ); 48 | return res; 49 | } 50 | 51 | // MX: mutually exclusive 52 | function mergeSortedMXArr(arr1: [number, number][], arr2: [number, number][]): [number, number][] { 53 | if (arr1.length === 0) { 54 | return arr2; 55 | } else if (arr2.length === 0) { 56 | return arr1; 57 | } 58 | 59 | const m = arr1.length; 60 | const n = arr2.length; 61 | const total = m + n; 62 | // use packed arr instead of holey arr https://v8.dev/blog/elements-kinds 63 | const res: [number, number][] = []; 64 | 65 | for (let i = 0, p1 = 0, p2 = 0; i < total; ++i) { 66 | // run out arr2 || arr1 is smaller 67 | if (p2 >= n || (p1 < m && arr1[p1][0] < arr2[p2][0])) { 68 | res.push(arr1[p1]); 69 | ++p1; 70 | } else { 71 | res.push(arr2[p2]); 72 | ++p2; 73 | } 74 | } 75 | 76 | return res; 77 | } 78 | 79 | function bisectRight(arr: any[], x: number | vscode.Position, key?: (item: any) => number | vscode.Position): number { 80 | let mid = -1; 81 | let lo = 0; 82 | let hi = arr.length; 83 | 84 | if (key !== undefined) { 85 | if (typeof x === 'number') { 86 | while (lo < hi) { 87 | mid = Math.floor((lo + hi) / 2); 88 | if (x < (key(arr[mid]) as number)) { 89 | hi = mid; 90 | } else { 91 | lo = mid + 1; 92 | } 93 | } 94 | } else { 95 | while (lo < hi) { 96 | mid = Math.floor((lo + hi) / 2); 97 | if (x.isBefore(key(arr[mid]) as vscode.Position)) { 98 | hi = mid; 99 | } else { 100 | lo = mid + 1; 101 | } 102 | } 103 | } 104 | 105 | } else { 106 | while (lo < hi) { 107 | mid = Math.floor((lo + hi) / 2); 108 | if (x < arr[mid]) { 109 | hi = mid; 110 | } else { 111 | lo = mid + 1; 112 | } 113 | } 114 | } 115 | return lo; 116 | } 117 | 118 | function bisectLeft(arr: any[], x: number | vscode.Position, key?: (item: any) => number | vscode.Position): number { 119 | let mid = -1; 120 | let lo = 0; 121 | let hi = arr.length; 122 | 123 | if (key !== undefined) { 124 | if (typeof x === 'number') { 125 | while (lo < hi) { 126 | mid = Math.floor((lo + hi) / 2); 127 | if (x <= (key(arr[mid]) as number)) { 128 | hi = mid; 129 | } else { 130 | lo = mid + 1; 131 | } 132 | } 133 | } else { 134 | while (lo < hi) { 135 | mid = Math.floor((lo + hi) / 2); 136 | if (x.isBeforeOrEqual(key(arr[mid]) as vscode.Position)) { 137 | hi = mid; 138 | } else { 139 | lo = mid + 1; 140 | } 141 | } 142 | } 143 | 144 | } else { 145 | while (lo < hi) { 146 | mid = Math.floor((lo + hi) / 2); 147 | if (x <= arr[mid]) { 148 | hi = mid; 149 | } else { 150 | lo = mid + 1; 151 | } 152 | } 153 | } 154 | return lo; 155 | } 156 | 157 | // leave for example only 158 | function sortRangeInPlaceEntry(d: [any, [number, number]][]) { 159 | d.sort( 160 | (a, b) => { 161 | return a[1][0] - b[1][0]; 162 | }); 163 | } 164 | function sortRangeInPlace(arr: vscode.Range[]) { 165 | arr.sort( 166 | (a, b) => { 167 | return a.start.isBeforeOrEqual(b.start) ? -1 : 1; 168 | }); 169 | } 170 | 171 | // we do not need to sort excludedRanges before search since we add those ranges in order 172 | function isRangeIntExcludedRanges(r: [number, number], excludedRange: [number, number][]): boolean { 173 | if (excludedRange.length === 0) { 174 | return false; 175 | } 176 | 177 | const rStart: number = r[0]; 178 | const idx = bisectRight(excludedRange, rStart, item => item[0]); 179 | 180 | if (idx > 0 && excludedRange[idx - 1][0] <= rStart && rStart < excludedRange[idx - 1][1]) { 181 | return true; 182 | } 183 | return false; 184 | } 185 | 186 | function isRangeIntExcludedRange(r: vscode.Range, excludedRange: vscode.Range[]): boolean { 187 | if (excludedRange.length === 0) { 188 | return false; 189 | } 190 | 191 | const rStart: vscode.Position = r.start; 192 | const idx = bisectRight(excludedRange, rStart, item => item.start); 193 | if (idx > 0 && excludedRange[idx - 1].contains(rStart)) { 194 | return true; 195 | } 196 | return false; 197 | } 198 | 199 | export { 200 | isRangeIntExcludedRanges, 201 | excludeRangesFromRanges, 202 | mergeSortedIntervals, 203 | mergeSortedMXArrList, 204 | mergeSortedMXArr, 205 | bisectRight, 206 | bisectLeft 207 | }; 208 | -------------------------------------------------------------------------------- /src/web/common/cl_util.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode'; 2 | 3 | const CL_MODE: vscode.DocumentSelector = 'commonlisp'; 4 | 5 | // string, only for common lisp original symbols 6 | const clOriSymbolChars = /[A-Za-z12\+\-\*\/\&\=\<\>]+/igm; 7 | 8 | // single char, only for common lisp original symbols 9 | const clValidSymbolSingleCharSet: Set = new Set([ 10 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 11 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 12 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 13 | '+', '-', '*', '/', '@', '$', '%', '^', '&', '_', '=', '<', '>', '~', '.', '!', '?', '[', ']', '{', '}']); 14 | const clValidSymbolSingleCharColonSet: Set = new Set([ 15 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 16 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 17 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 18 | '+', '-', '*', '/', '@', '$', '%', '^', '&', '_', '=', '<', '>', '~', '.', '!', '?', '[', ']', '{', '}', ':']); 19 | const clValidSymbolSingleCharColonSharpSet: Set = new Set([ 20 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 21 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 22 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 23 | '+', '-', '*', '/', '@', '$', '%', '^', '&', '_', '=', '<', '>', '~', '.', '!', '?', '[', ']', '{', '}', ':', '#']); 24 | const clValidSymbolSingleChar = /[A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.]/i; 25 | 26 | function isStringClValidSymbol(str: string): boolean { 27 | for (const c of str) { 28 | if (!clValidSymbolSingleCharSet.has(c)) { 29 | return false; 30 | } 31 | } 32 | return true; 33 | } 34 | 35 | // CL-ANSI 2.1.4 Character Syntax Types 36 | const clValidSymbolChars = /[A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.]+/igm; 37 | 38 | // start with `:` (non-alphabetic) 39 | const clValidStartWithColon = /:?[A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.]+/igm; 40 | 41 | // start with `:` (non-alphabetic) 42 | const clValidWithColonSharp = /[#:A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.]+/igm; 43 | 44 | function escapeRegExp(str: string) { 45 | return str.replace(/[.*+?^${}()|[\]\\\/\-]/g, '\\$&'); // $& means the whole matched string 46 | } 47 | 48 | export { 49 | CL_MODE, 50 | 51 | // clOriSymbolChars, 52 | // clValidSymbolSingleChar, 53 | 54 | //clValidSymbolSingleCharSet, 55 | //isStringClValidSymbol, 56 | //escapeRegExp, 57 | 58 | clValidSymbolSingleCharColonSet, 59 | //clValidSymbolSingleCharColonSharpSet, 60 | //clValidSymbolChars, 61 | //clValidStartWithColon, 62 | clValidWithColonSharp 63 | }; 64 | -------------------------------------------------------------------------------- /src/web/common/enum.ts: -------------------------------------------------------------------------------- 1 | const enum ExcludeRanges { 2 | CommentString = 'comment and string', 3 | Comment = 'comment', 4 | String = 'string', 5 | None = 'none' 6 | } 7 | 8 | const enum SingleQuoteAndBackQuoteHighlight { 9 | SQ = 'single quote', 10 | SQAndBQC = "single quote and backquote's comma only", 11 | SQAndBQAll = "single quote and backquote's all", 12 | BQC = "backquote's comma only", 13 | BQAll = "backquote's all", 14 | None = 'none' 15 | } 16 | 17 | const enum SingleQuoteAndBackQuoteExcludedRanges { 18 | SQ = 'single quote', 19 | SQBQButComma = 'single quote and backquote, but comma is saved', 20 | SQAndBQ = "single quote and backquote's all", 21 | BQButComma = 'backquote, but comma is saved', 22 | BQ = "backquote's all", 23 | None = 'none' 24 | } 25 | 26 | const enum UpdateOption { 27 | getDocSymbolInfo = 'getDocSymbolInfo', 28 | genUserSymbolsCompItem = 'genUserSymbolsCompItem', 29 | genDocumentSymbol = 'genDocumentSymbol', 30 | genAllPossibleWord = 'genAllPossibleWord', 31 | buildSemanticTokens = 'buildSemanticTokens', 32 | genAllCallHierarchyItems = 'genAllCallHierarchyItems', 33 | } 34 | 35 | const enum TriggerProvider { 36 | provideCompletionItems = 'provideCompletionItems', 37 | prepareCallHierarchy = 'prepareCallHierarchy', 38 | provideDefinition = 'provideDefinition', 39 | provideDocumentSymbols = 'provideDocumentSymbols', 40 | provideReferences = 'provideReferences', 41 | provideDocumentSemanticTokens = 'provideDocumentSemanticTokens', 42 | provideHoverUser = 'provideHoverUser', 43 | } 44 | 45 | export { 46 | ExcludeRanges, 47 | SingleQuoteAndBackQuoteHighlight, 48 | SingleQuoteAndBackQuoteExcludedRanges, 49 | UpdateOption, 50 | TriggerProvider 51 | }; 52 | -------------------------------------------------------------------------------- /src/web/doc/get_doc.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import _doc from '../cl_data/cl_doc.json'; 4 | import _kind from '../cl_data/cl_kind.json'; 5 | import _non_alphabetic from '../cl_data/cl_non_alphabetic.json'; 6 | import _doc_non_alphabetic from '../cl_data/cl_non_alphabetic_doc.json'; 7 | 8 | const non_alphabetic_index_str = `\n\n[[Docs]](https://www.lispworks.com/documentation/lw50/CLHS/Front/X_Mast_9.htm)`; 9 | const loop_keyword_str = `\n\n[[Docs]](https://www.lispworks.com/documentation/lw51/CLHS/Body/m_loop.htm#loop)`; 10 | 11 | const kindKeys = new Set(Object.keys(_kind)); 12 | 13 | function _getDocByName(symbolName: string, _docJson: Record): vscode.MarkdownString | undefined { 14 | const docByName = _docJson[symbolName]; 15 | // console.log(docByName); 16 | if (docByName === undefined) { 17 | // console.log(`[GetDoc] Missing documentation for ${symbolName}`); 18 | return undefined; 19 | } 20 | 21 | const res = new vscode.MarkdownString(docByName); 22 | res.isTrusted = true; 23 | res.supportHtml = true; 24 | return res; 25 | } 26 | 27 | function getDocByName(symbolName: string): vscode.MarkdownString | undefined { 28 | return _getDocByName(symbolName, _doc); 29 | } 30 | 31 | function getDocByNameNonAlphabetic(symbolName: string): vscode.MarkdownString | undefined { 32 | if (symbolName.startsWith(':')) { 33 | symbolName = symbolName.substring(1); 34 | } 35 | 36 | const res = _getDocByName(symbolName, _doc_non_alphabetic); 37 | res?.appendMarkdown(non_alphabetic_index_str); 38 | return res; 39 | } 40 | 41 | function getDoc(word: string) { 42 | const isOriSymbol = kindKeys.has(word); 43 | if (isOriSymbol) { 44 | return getDocByName(word); 45 | } else if (word.startsWith(':')) { 46 | return getDocByNameNonAlphabetic(word); 47 | } else { 48 | return undefined; 49 | } 50 | } 51 | 52 | export { 53 | getDoc, 54 | getDocByName, getDocByNameNonAlphabetic, 55 | non_alphabetic_index_str, _non_alphabetic, 56 | loop_keyword_str 57 | }; 58 | -------------------------------------------------------------------------------- /src/web/entry/TraceableDisposables.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode'; 2 | 3 | import { registerCallHierarchyProvider } from '../provider_interface/providers/call_hierarchy_provider'; 4 | import { registerCompletionItemProviders } from '../provider_interface/providers/comp_item_provider'; 5 | import { registerDefinitionProvider } from '../provider_interface/providers/def_provider'; 6 | import { registerDocumentSymbolProvider } from '../provider_interface/providers/doc_symbol_provider'; 7 | import { registerHoverProviders } from '../provider_interface/providers/hover_provider'; 8 | import { registerReferenceProvider } from '../provider_interface/providers/reference_provider'; 9 | import { registerSemanticProvider } from '../provider_interface/providers/semantic_tokens_provider'; 10 | 11 | class TraceableDisposables { 12 | public readonly disposables: Map = 13 | new Map([ 14 | ['eventOnDidChangeTD', undefined], 15 | ['eventOnDidChangeATE', undefined], 16 | 17 | ['userCompletionItemProvider', undefined], 18 | ['oriCompletionItemProvider', undefined], 19 | ['loopCompletionItemProvider', undefined], 20 | 21 | ['ampersandCompletionItemProvider', undefined], 22 | ['asteriskCompletionItemProvider', undefined], 23 | ['colonCompletionItemProvider', undefined], 24 | 25 | ['tildeCompletionItemProvider', undefined], 26 | ['sharpsignCompletionItemProvider', undefined], 27 | 28 | ['oriHoverProvider', undefined], 29 | ['userHoverProvider', undefined], 30 | ['definitionProvider', undefined], 31 | ['documentSymbolProvider', undefined], 32 | ['referenceProvider', undefined], 33 | ['documentSemanticTokensProvider', undefined], 34 | ['callHierarchyProvider', undefined], 35 | ]); 36 | 37 | private static readonly cfgMapDisposable: Map = new Map([ 38 | ['commonLisp.providers.CompletionItemProviders.user.enabled', 'userCompletionItemProvider'], 39 | ['commonLisp.providers.CompletionItemProviders.original.enabled', 'oriCompletionItemProvider'], 40 | ['commonLisp.providers.CompletionItemProviders.loop.enabled', 'loopCompletionItemProvider'], 41 | 42 | ['commonLisp.providers.CompletionItemProviders.ampersand.enabled', 'ampersandCompletionItemProvider'], 43 | ['commonLisp.providers.CompletionItemProviders.asterisk.enabled', 'asteriskCompletionItemProvider'], 44 | ['commonLisp.providers.CompletionItemProviders.colon.enabled', 'colonCompletionItemProvider'], 45 | 46 | ['commonLisp.providers.CompletionItemProviders.tilde.enabled', 'tildeCompletionItemProvider'], 47 | ['commonLisp.providers.CompletionItemProviders.sharpsign.enabled', 'sharpsignCompletionItemProvider'], 48 | 49 | ['commonLisp.providers.HoverProviders.original.enabled', 'oriHoverProvider'], 50 | ['commonLisp.providers.HoverProviders.user.enabled', 'userHoverProvider'], 51 | ['commonLisp.providers.DefinitionProvider.enabled', 'definitionProvider'], 52 | ['commonLisp.providers.DocumentSymbolProvider.enabled', 'documentSymbolProvider'], 53 | ['commonLisp.providers.ReferenceProvider.enabled', 'referenceProvider'], 54 | ['commonLisp.providers.DocumentSemanticTokensProvider.enabled', 'documentSemanticTokensProvider'], 55 | ['commonLisp.providers.CallHierarchyProvider.enabled', 'callHierarchyProvider'], 56 | ]); 57 | 58 | constructor() { 59 | 60 | } 61 | 62 | public disposeAll() { 63 | for (const [k, dis] of this.disposables) { 64 | if (dis !== undefined) { 65 | dis.dispose(); 66 | this.disposables.set(k, undefined); 67 | } 68 | } 69 | } 70 | 71 | private disposeProviderByName(disposableName: string) { 72 | if (!this.disposables.has(disposableName)) { 73 | return; 74 | } 75 | 76 | if (this.disposables.get(disposableName) !== undefined) { 77 | //console.log('disp', disposableName); 78 | this.disposables.get(disposableName)?.dispose(); 79 | this.disposables.set(disposableName, undefined); 80 | } else { 81 | return; 82 | } 83 | } 84 | 85 | 86 | private setProviderByName(disposableName: string, contextSubcriptions: vscode.Disposable[]) { 87 | if (!this.disposables.has(disposableName)) { 88 | return; 89 | } 90 | 91 | if (this.disposables.get(disposableName) !== undefined) { 92 | return; 93 | } 94 | //console.log('setting ' + disposableName); 95 | const provider = this.registerProviderByName(disposableName); 96 | 97 | if (provider === undefined) { 98 | return; 99 | } 100 | this.disposables.set(disposableName, provider); 101 | contextSubcriptions.push(provider); 102 | } 103 | 104 | private registerProviderByName(disposableName: string) { 105 | switch (disposableName) { 106 | case 'userCompletionItemProvider': 107 | return registerCompletionItemProviders('user'); 108 | 109 | case 'oriCompletionItemProvider': 110 | return registerCompletionItemProviders('ori'); 111 | 112 | case 'loopCompletionItemProvider': 113 | return registerCompletionItemProviders('loop'); 114 | 115 | case 'ampersandCompletionItemProvider': 116 | return registerCompletionItemProviders('ampersand'); 117 | 118 | case 'asteriskCompletionItemProvider': 119 | return registerCompletionItemProviders('asterisk'); 120 | 121 | case 'colonCompletionItemProvider': 122 | return registerCompletionItemProviders('colon'); 123 | 124 | case 'tildeCompletionItemProvider': 125 | return registerCompletionItemProviders('tilde'); 126 | 127 | case 'sharpsignCompletionItemProvider': 128 | return registerCompletionItemProviders('sharpsign'); 129 | 130 | case 'oriHoverProvider': 131 | return registerHoverProviders('ori'); 132 | 133 | case 'userHoverProvider': 134 | return registerHoverProviders('user'); 135 | 136 | case 'definitionProvider': 137 | return registerDefinitionProvider(); 138 | 139 | case 'documentSymbolProvider': 140 | return registerDocumentSymbolProvider(); 141 | 142 | case 'referenceProvider': 143 | return registerReferenceProvider(); 144 | 145 | case 'documentSemanticTokensProvider': 146 | return registerSemanticProvider(); 147 | 148 | case 'callHierarchyProvider': 149 | return registerCallHierarchyProvider(); 150 | 151 | default: 152 | return undefined; 153 | } 154 | } 155 | 156 | public updateDisposables( 157 | contextSubcriptions: vscode.Disposable[], 158 | workspaceConfigKey: string, 159 | newTraceableDisposablesVal: any 160 | ) { 161 | const providerKeys = new Set(TraceableDisposables.cfgMapDisposable.keys()); 162 | 163 | let disposableName: string | undefined = ''; 164 | if (workspaceConfigKey === 'editor.semanticHighlighting.enabled') { 165 | disposableName = 'documentSemanticTokensProvider'; 166 | } else if (providerKeys.has(workspaceConfigKey)) { 167 | disposableName = TraceableDisposables.cfgMapDisposable.get(workspaceConfigKey); 168 | } 169 | 170 | if (disposableName === undefined) { 171 | return; 172 | } 173 | 174 | if (newTraceableDisposablesVal === false) { 175 | this.disposeProviderByName(disposableName); 176 | } else { 177 | this.setProviderByName(disposableName, contextSubcriptions); 178 | } 179 | } 180 | } 181 | 182 | export { TraceableDisposables }; 183 | -------------------------------------------------------------------------------- /src/web/entry/WorkspaceConfig.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { ExcludeRanges, SingleQuoteAndBackQuoteHighlight, SingleQuoteAndBackQuoteExcludedRanges } from '../common/enum'; 4 | import type { StructuredInfo } from '../provider_interface/StructuredInfo'; 5 | 6 | import type { TraceableDisposables } from './TraceableDisposables'; 7 | 8 | class WorkspaceConfig { 9 | private static readonly CL_ID = 'commonlisp'; 10 | 11 | private readonly config: Map = new Map([ 12 | ['commonLisp.StaticAnalysis.enabled', true], 13 | 14 | ['editor.semanticHighlighting.enabled', undefined], 15 | 16 | ['commonLisp.StaticAnalysis.SingleQuoteAndBackQuote.ExcludedRanges', 17 | SingleQuoteAndBackQuoteExcludedRanges.BQButComma], 18 | ['commonLisp.DocumentSemanticTokensProvider.ExcludedRanges', ExcludeRanges.CommentString], 19 | ['commonLisp.DocumentSemanticTokensProvider.SingleQuoteAndBackQuote.Highlight', 20 | SingleQuoteAndBackQuoteHighlight.SQAndBQC], 21 | ['commonLisp.ReferenceProvider.ExcludedRanges', ExcludeRanges.CommentString], 22 | ['commonLisp.ReferenceProvider.BackQuoteFilter.enabled', true], 23 | ['commonLisp.DefinitionProvider.ExcludedRanges', ExcludeRanges.None], 24 | ['commonLisp.DefinitionProvider.BackQuoteFilter.enabled', true], 25 | 26 | ['commonLisp.DocumentSemanticTokensProvider.NotColorQuoted', false], 27 | 28 | ['commonLisp.providers.CompletionItemProviders.user.enabled', true], 29 | ['commonLisp.providers.CompletionItemProviders.original.enabled', true], 30 | ['commonLisp.providers.CompletionItemProviders.loop.enabled', true], 31 | 32 | ['commonLisp.providers.CompletionItemProviders.ampersand.enabled', true], 33 | ['commonLisp.providers.CompletionItemProviders.asterisk.enabled', true], 34 | ['commonLisp.providers.CompletionItemProviders.colon.enabled', true], 35 | 36 | ['commonLisp.providers.CompletionItemProviders.tilde.enabled', false], 37 | ['commonLisp.providers.CompletionItemProviders.sharpsign.enabled', false], 38 | 39 | ['commonLisp.providers.HoverProviders.original.enabled', true], 40 | ['commonLisp.providers.HoverProviders.user.enabled', true], 41 | ['commonLisp.providers.DefinitionProvider.enabled', true], 42 | ['commonLisp.providers.DocumentSymbolProvider.enabled', true], 43 | ['commonLisp.providers.ReferenceProvider.enabled', true], 44 | ['commonLisp.providers.DocumentSemanticTokensProvider.enabled', true], 45 | ['commonLisp.providers.CallHierarchyProvider.enabled', true], 46 | ]); 47 | 48 | public initConfig(contextSubcriptions: vscode.Disposable[], traceableDisposables: TraceableDisposables) { 49 | const config = vscode.workspace.getConfiguration(); 50 | const langEntry: any = config.get(`[${WorkspaceConfig.CL_ID}]`); 51 | for (const k of this.config.keys()) { 52 | const newVal = config.get(k); 53 | const newConfigVal = langEntry && (langEntry[k] !== undefined) ? langEntry[k] : newVal; 54 | 55 | this.config.set(k, newConfigVal); 56 | traceableDisposables.updateDisposables(contextSubcriptions, k, newConfigVal); 57 | } 58 | //console.log(workspaceConfig); 59 | } 60 | 61 | public updateConfig( 62 | contextSubcriptions: vscode.Disposable[], 63 | traceableDisposables: TraceableDisposables, 64 | e: vscode.ConfigurationChangeEvent 65 | ) { 66 | const config = vscode.workspace.getConfiguration(); 67 | const langEntry: any = config.get(`[${WorkspaceConfig.CL_ID}]`); 68 | for (const k of this.config.keys()) { 69 | const newVal = e.affectsConfiguration(k) ? config.get(k) : undefined; 70 | const newConfigVal = ( 71 | e.affectsConfiguration(k, { languageId: WorkspaceConfig.CL_ID }) && 72 | langEntry && (langEntry[k] !== undefined) 73 | ) ? 74 | langEntry[k] : newVal; 75 | 76 | 77 | if (newConfigVal !== undefined) { 78 | //console.log(`update workspace config: ${k} = ${newConfigVal as string}`); 79 | this.config.set(k, newConfigVal); 80 | traceableDisposables.updateDisposables(contextSubcriptions, k, newConfigVal); 81 | } 82 | } 83 | 84 | } 85 | 86 | public syncBuildingConfigWithConfig(structuredInfo: StructuredInfo) { 87 | // copy the configs that are needed for building later 88 | // so `workspaceConfig` is decoupled from the building process 89 | // that is, maintaining Unidirectional Data Flow here 90 | for (const k of structuredInfo.buildingConfig.keys()) { 91 | structuredInfo.buildingConfig.set(k, this.config.get(k)); 92 | } 93 | } 94 | } 95 | 96 | export { WorkspaceConfig }; 97 | -------------------------------------------------------------------------------- /src/web/entry/init.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { CL_MODE } from '../common/cl_util'; 4 | import type { StructuredInfo } from '../provider_interface/StructuredInfo'; 5 | 6 | import { TraceableDisposables } from './TraceableDisposables'; 7 | import { WorkspaceConfig } from './WorkspaceConfig'; 8 | 9 | const traceableDisposables: TraceableDisposables = new TraceableDisposables(); 10 | const workspaceConfig: WorkspaceConfig = new WorkspaceConfig(); 11 | let activeEditor = vscode.window.activeTextEditor; 12 | 13 | function init( 14 | contextSubcriptions: vscode.Disposable[], 15 | workspaceConfig: WorkspaceConfig, 16 | traceableDisposables: TraceableDisposables, 17 | structuredInfo: StructuredInfo 18 | ) { 19 | const config = vscode.workspace.getConfiguration(); 20 | if (config.get('commonLisp.StaticAnalysis.enabled')) { 21 | workspaceConfig.initConfig(contextSubcriptions, traceableDisposables); 22 | setDirtyHookForStructuredInfo(contextSubcriptions, traceableDisposables, structuredInfo); 23 | 24 | workspaceConfig.syncBuildingConfigWithConfig(structuredInfo); 25 | } 26 | 27 | vscode.workspace.onDidChangeConfiguration((e) => { 28 | if (e.affectsConfiguration('commonLisp.StaticAnalysis.enabled')) { 29 | const config = vscode.workspace.getConfiguration(); 30 | if (!config.get('commonLisp.StaticAnalysis.enabled')) { 31 | traceableDisposables.disposeAll(); 32 | return; 33 | } 34 | 35 | workspaceConfig.initConfig(contextSubcriptions, traceableDisposables); 36 | setDirtyHookForStructuredInfo(contextSubcriptions, traceableDisposables, structuredInfo); 37 | } else { 38 | workspaceConfig.updateConfig(contextSubcriptions, traceableDisposables, e); 39 | } 40 | 41 | workspaceConfig.syncBuildingConfigWithConfig(structuredInfo); 42 | }, contextSubcriptions); 43 | } 44 | 45 | function setDirtyHookForStructuredInfo( 46 | contextSubcriptions: vscode.Disposable[], 47 | traceableDisposables: TraceableDisposables, 48 | structuredInfo: StructuredInfo 49 | ) { 50 | // For text update mechanism 51 | // See https://github.com/microsoft/vscode-extension-samples/tree/a4f2ebf7ddfd44fb610bcbb080e97c7ce9a0ef44/decorator-sample 52 | // and https://github.com/microsoft/vscode-extension-samples/blob/62e7e59776c41e4495665d3a084621ea61a026e9/tree-view-sample/src/jsonOutline.ts 53 | // 54 | // According to https://github.com/microsoft/vscode/issues/49975#issuecomment-389817172 55 | // the document change event is emitted before any language actions (IntelliSense, outline, etc.). 56 | // Therefore, it is safe to use `onDidChangeTextDocument` and `onDidChangeActiveTextEditor` event 57 | // to mark dirty on the StructuredInfo. 58 | if (traceableDisposables.disposables.get('eventOnDidChangeTD') === undefined) { 59 | traceableDisposables.disposables.set('eventOnDidChangeTD', vscode.workspace.onDidChangeTextDocument(event => { 60 | if (event.contentChanges.length !== 0 && activeEditor !== undefined && event.document === activeEditor.document && 61 | event.document.languageId === CL_MODE) { 62 | structuredInfo.setDirty(true); 63 | //console.log('dirty it by TD'); 64 | } 65 | }, null, contextSubcriptions)); 66 | } 67 | 68 | if (traceableDisposables.disposables.get('eventOnDidChangeATE') === undefined) { 69 | traceableDisposables.disposables.set('eventOnDidChangeATE', vscode.window.onDidChangeActiveTextEditor(editor => { 70 | if (editor === undefined) { 71 | return; 72 | } 73 | activeEditor = editor; 74 | if (editor !== undefined && editor.document.languageId === CL_MODE) { 75 | structuredInfo.setDirty(true); 76 | //console.log('dirty it by ATE'); 77 | } 78 | }, null, contextSubcriptions)); 79 | } 80 | } 81 | 82 | 83 | export { init, workspaceConfig, traceableDisposables }; 84 | 85 | -------------------------------------------------------------------------------- /src/web/extension.ts: -------------------------------------------------------------------------------- 1 | // The module 'vscode' contains the VS Code extensibility API 2 | // Import the module and reference it with the alias vscode in your code below 3 | import type * as vscode from 'vscode'; 4 | 5 | import { init, workspaceConfig, traceableDisposables } from './entry/init'; 6 | import { structuredInfo } from './provider_interface/structured_info'; 7 | 8 | // this method is called when your extension is activated 9 | // your extension is activated the very first time the command is executed 10 | export function activate(context: vscode.ExtensionContext) { 11 | init(context.subscriptions, workspaceConfig, traceableDisposables, structuredInfo); 12 | } 13 | 14 | // this method is called when your extension is deactivated 15 | export function deactivate() { } 16 | -------------------------------------------------------------------------------- /src/web/provider_interface/StructuredInfo.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode'; 2 | 3 | import { DocSymbolInfo } from '../builders/DocSymbolInfo'; 4 | import type { CallHrchyInfo } from '../builders/call_hierarchy_builder/CallHrchyInfo'; 5 | import { genAllCallHierarchyItems } from '../builders/call_hierarchy_builder/call_hierarchy_builder'; 6 | import { UserSymbolsCompItem } from '../builders/comp_item_builder/UserSymbolsCompItem'; 7 | import { genDocumentSymbol } from '../builders/doc_symbol_builder/doc_symbol_builder'; 8 | import { buildSemanticTokens, genAllPossibleWord } from '../builders/semantic_tokens_builder/semantic_tokens_builder'; 9 | import { 10 | TriggerProvider, ExcludeRanges, 11 | SingleQuoteAndBackQuoteExcludedRanges, SingleQuoteAndBackQuoteHighlight, UpdateOption 12 | } from '../common/enum'; 13 | 14 | import type { TriggerEvent } from './TriggerEvent'; 15 | 16 | class StructuredInfo { 17 | public currDocSymbolInfo: DocSymbolInfo | undefined = undefined; 18 | public currDocumentSymbol: vscode.DocumentSymbol[] | undefined = undefined; 19 | public currUserSymbolsCompItem: UserSymbolsCompItem | undefined = undefined; 20 | public currSemanticTokens: vscode.SemanticTokens | undefined = undefined; 21 | public currCallHierarchyInfo: CallHrchyInfo | undefined = undefined; 22 | 23 | // optimization: use [number, number] to avoid `positionAt` overhead 24 | public needColorDict: Map | undefined = undefined; 25 | public globalOrderedRanges: [string, [number, number]][] | undefined = undefined; 26 | 27 | // building process config passed down from workspace config 28 | public readonly buildingConfig: Map = new Map([ 29 | ['commonLisp.DocumentSemanticTokensProvider.SingleQuoteAndBackQuote.Highlight', 30 | SingleQuoteAndBackQuoteHighlight.SQAndBQC], 31 | ['commonLisp.StaticAnalysis.SingleQuoteAndBackQuote.ExcludedRanges', 32 | SingleQuoteAndBackQuoteExcludedRanges.BQButComma], 33 | ['commonLisp.ReferenceProvider.BackQuoteFilter.enabled', true], 34 | ['commonLisp.DefinitionProvider.BackQuoteFilter.enabled', true], 35 | 36 | ['commonLisp.DefinitionProvider.ExcludedRanges', ExcludeRanges.None], 37 | ['commonLisp.ReferenceProvider.ExcludedRanges', ExcludeRanges.CommentString], 38 | ['commonLisp.DocumentSemanticTokensProvider.ExcludedRanges', ExcludeRanges.CommentString], 39 | 40 | ['commonLisp.DocumentSemanticTokensProvider.NotColorQuoted', false], 41 | ]); 42 | 43 | // dirty flag indicates that the document has been changed, 44 | // and curr info needs to be updated. 45 | private readonly dirty: Map = new Map([ 46 | [UpdateOption.getDocSymbolInfo, true], 47 | [UpdateOption.genUserSymbolsCompItem, true], 48 | [UpdateOption.genDocumentSymbol, true], 49 | [UpdateOption.genAllPossibleWord, true], 50 | [UpdateOption.buildSemanticTokens, true], 51 | [UpdateOption.genAllCallHierarchyItems, true], 52 | ]); 53 | 54 | private static readonly actionUsedByProviders = new Map>([ 55 | [UpdateOption.getDocSymbolInfo, new Set([ 56 | TriggerProvider.provideCompletionItems, TriggerProvider.prepareCallHierarchy, 57 | TriggerProvider.provideDefinition, TriggerProvider.provideDocumentSymbols, 58 | TriggerProvider.provideReferences, TriggerProvider.provideDocumentSemanticTokens, 59 | TriggerProvider.provideHoverUser 60 | ])], 61 | 62 | [UpdateOption.genUserSymbolsCompItem, new Set([TriggerProvider.provideCompletionItems])], 63 | 64 | [UpdateOption.genDocumentSymbol, new Set([ 65 | TriggerProvider.provideCompletionItems, TriggerProvider.prepareCallHierarchy, 66 | TriggerProvider.provideDocumentSymbols 67 | ])], 68 | 69 | [UpdateOption.genAllPossibleWord, new Set([ 70 | TriggerProvider.provideReferences, TriggerProvider.provideDocumentSemanticTokens, 71 | TriggerProvider.prepareCallHierarchy 72 | ])], 73 | 74 | [UpdateOption.buildSemanticTokens, new Set([TriggerProvider.provideDocumentSemanticTokens])], 75 | [UpdateOption.genAllCallHierarchyItems, new Set([TriggerProvider.prepareCallHierarchy])] 76 | ]); 77 | 78 | constructor() { 79 | 80 | } 81 | 82 | public setDirty(value: boolean, keySet: Set | undefined = undefined) { 83 | const keys = (keySet === undefined) ? this.dirty.keys() : keySet; 84 | for (const k of keys) { 85 | this.dirty.set(k, value); 86 | } 87 | } 88 | 89 | private getNeedUpdateByTriggerProvider(triggerProvider: TriggerProvider): UpdateOption[] { 90 | const needUpdateArr: UpdateOption[] = []; 91 | for (const [k, v] of StructuredInfo.actionUsedByProviders) { 92 | if (v.has(triggerProvider)) { 93 | needUpdateArr.push(k); 94 | } 95 | } 96 | return needUpdateArr; 97 | } 98 | 99 | public updateInfoByDoc(doc: vscode.TextDocument, triggerEvent: TriggerEvent) { 100 | //const t = performance.now(); 101 | const triggerProvider: TriggerProvider = triggerEvent.triggerProvider; 102 | const needUpdateArr = this.getNeedUpdateByTriggerProvider(triggerProvider); 103 | 104 | //const needUpdateSetCheckDirty = new Set(needUpdateArr); 105 | // comment this part for profile 106 | const needUpdateSetCheckDirty = new Set( 107 | needUpdateArr.filter(ele => this.dirty.get(ele)) 108 | ); 109 | 110 | // order matters here! 111 | if (needUpdateSetCheckDirty.has(UpdateOption.getDocSymbolInfo)) { 112 | this.currDocSymbolInfo = new DocSymbolInfo(doc, this.buildingConfig); 113 | } 114 | 115 | if (this.currDocSymbolInfo === undefined) { 116 | console.warn(`[StructuredInfo.updateResultByDoc] this.currDocSymbolInfo===undefined`); 117 | return; 118 | } 119 | 120 | if (needUpdateSetCheckDirty.has(UpdateOption.genUserSymbolsCompItem)) { 121 | this.currUserSymbolsCompItem = new UserSymbolsCompItem(this.currDocSymbolInfo); 122 | } 123 | 124 | if (needUpdateSetCheckDirty.has(UpdateOption.genDocumentSymbol)) { 125 | this.currDocumentSymbol = genDocumentSymbol(this.currDocSymbolInfo); 126 | } 127 | 128 | if (needUpdateSetCheckDirty.has(UpdateOption.genAllPossibleWord)) { 129 | [this.needColorDict, this.globalOrderedRanges] = genAllPossibleWord(this.currDocSymbolInfo); 130 | } 131 | 132 | if (needUpdateSetCheckDirty.has(UpdateOption.buildSemanticTokens)) { 133 | if (this.needColorDict === undefined) { 134 | console.warn(`[StructuredInfo.updateResultByDoc] this.needColorDict===undefined`); 135 | return; 136 | } 137 | this.currSemanticTokens = buildSemanticTokens(this.currDocSymbolInfo, this.needColorDict, this.buildingConfig); 138 | } 139 | 140 | if (needUpdateSetCheckDirty.has(UpdateOption.genAllCallHierarchyItems)) { 141 | if (this.globalOrderedRanges === undefined) { 142 | console.warn(`[StructuredInfo.updateResultByDoc] this.globalOrderedRanges===undefined`); 143 | return; 144 | } 145 | this.currCallHierarchyInfo = genAllCallHierarchyItems(this.currDocSymbolInfo, this.globalOrderedRanges); 146 | } 147 | 148 | this.setDirty(false, needUpdateSetCheckDirty); 149 | 150 | //console.log(`finish: ${performance.now() - t}ms`, needUpdateSetCheckDirty); 151 | } 152 | } 153 | 154 | export { StructuredInfo }; 155 | -------------------------------------------------------------------------------- /src/web/provider_interface/TriggerEvent.ts: -------------------------------------------------------------------------------- 1 | import type { TriggerProvider } from '../common/enum'; 2 | 3 | class TriggerEvent { 4 | public readonly triggerProvider: TriggerProvider; 5 | 6 | constructor(triggerProvider: TriggerProvider) { 7 | this.triggerProvider = triggerProvider; 8 | } 9 | 10 | } 11 | 12 | export { TriggerEvent }; 13 | -------------------------------------------------------------------------------- /src/web/provider_interface/provider_util.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { clValidWithColonSharp } from '../common/cl_util'; 4 | 5 | // no using lexical scope 6 | // Common Lisp the Language, 2nd Edition 7 | // 7.1. Reference https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node78.html#SECTION001111000000000000000 8 | function isQuote(document: vscode.TextDocument, position: vscode.Position): vscode.Range | undefined { 9 | const parentheseRange = document.getWordRangeAtPosition(position, /(?<=^|\s|\(|,@|,\.|,)\s*?quote\s*?[A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.]+?\s*?(?=(\s|\(|\)))/igm); 10 | const quoteSymbolRange = document.getWordRangeAtPosition(position, /(?<=^|\s|\(|,@|,\.|,)'[A-Za-z0-9\+\-\*\/\@\$\%\^\&\_\=\<\>\~\!\?\[\]\{\}\.]+?(?=(\s|\(|\)))/igm); 11 | return (parentheseRange !== undefined) ? parentheseRange : quoteSymbolRange; 12 | } 13 | 14 | function getCLWordRangeAtPosition( 15 | document: vscode.TextDocument, position: vscode.Position): vscode.Range | undefined { 16 | const range = document.getWordRangeAtPosition(position, clValidWithColonSharp); 17 | if (range === undefined) { 18 | return undefined; 19 | } 20 | const word = document.getText(range); 21 | 22 | // https://www.lispworks.com/documentation/lw60/CLHS/Body/02_df.htm 23 | // ,@ 24 | if (word.startsWith('@')) { 25 | const prevc = document.getText(new vscode.Range(range.start.translate(0, -1), range.start)); 26 | if (prevc === ',') { 27 | return range.with(range.start.translate(0, 1)); 28 | } 29 | } 30 | 31 | return range; 32 | } 33 | 34 | export { 35 | isQuote, 36 | getCLWordRangeAtPosition 37 | }; 38 | -------------------------------------------------------------------------------- /src/web/provider_interface/providers/call_hierarchy_provider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import type { DocSymbolInfo } from '../../builders/DocSymbolInfo'; 4 | import type { CallHrchyInfo } from '../../builders/call_hierarchy_builder/CallHrchyInfo'; 5 | import { CL_MODE } from '../../common/cl_util'; 6 | import { TriggerProvider } from '../../common/enum'; 7 | import { TriggerEvent } from '../TriggerEvent'; 8 | import { getCLWordRangeAtPosition } from '../provider_util'; 9 | import { structuredInfo } from '../structured_info'; 10 | 11 | function getCallHierarchyCallsByCallHierarchyItem( 12 | item: vscode.CallHierarchyItem, callHierarchyCallsDict: Map> 13 | ): vscode.CallHierarchyOutgoingCall[]; 14 | function getCallHierarchyCallsByCallHierarchyItem( 15 | item: vscode.CallHierarchyItem, callHierarchyCallsDict: Map> 16 | ): vscode.CallHierarchyIncomingCall[]; 17 | function getCallHierarchyCallsByCallHierarchyItem( 18 | item: vscode.CallHierarchyItem, 19 | callHierarchyCallsDict: Map> 20 | ): (vscode.CallHierarchyIncomingCall | vscode.CallHierarchyOutgoingCall)[] { 21 | 22 | const callHierarchyItemStr = `${item.name}|${item.uri.path}|${item.range.start.line},${item.range.start.character},${item.range.end.line},${item.range.end.character}`; 23 | const callHierarchyCallDict = callHierarchyCallsDict.get(item.name); 24 | if (callHierarchyCallDict === undefined) { 25 | return []; 26 | } 27 | const res = callHierarchyCallDict.get(callHierarchyItemStr); 28 | return (res !== undefined) ? [res] : []; 29 | } 30 | 31 | function getCallHrchyItems( 32 | currDocSymbolInfo: DocSymbolInfo, range: vscode.Range, currCallHierarchyInfo: CallHrchyInfo 33 | ): vscode.CallHierarchyItem | vscode.CallHierarchyItem[] { 34 | 35 | const word = currDocSymbolInfo.document.getText(range); 36 | const queryStr = `${word}|${currDocSymbolInfo.document.uri.path}|${range.start.line},${range.start.character},${range.end.line},${range.end.character}`; 37 | 38 | const itemD = currCallHierarchyInfo.callHrchyItems.get(word); 39 | if (itemD === undefined) { 40 | return []; 41 | } 42 | const res = itemD.get(queryStr); 43 | return (res !== undefined) ? res : []; 44 | } 45 | 46 | function registerCallHierarchyProvider() { 47 | 48 | const callHierarchyProvider = vscode.languages.registerCallHierarchyProvider( 49 | CL_MODE, 50 | { 51 | prepareCallHierarchy(document, position, token) { 52 | const range = getCLWordRangeAtPosition(document, position); 53 | if (range === undefined) { 54 | return undefined; 55 | } 56 | 57 | structuredInfo.updateInfoByDoc(document, new TriggerEvent(TriggerProvider.prepareCallHierarchy)); 58 | if (structuredInfo.currDocSymbolInfo === undefined || structuredInfo.currCallHierarchyInfo === undefined) { 59 | return undefined; 60 | } 61 | 62 | const res = getCallHrchyItems(structuredInfo.currDocSymbolInfo, range, structuredInfo.currCallHierarchyInfo); 63 | return res; 64 | }, 65 | 66 | provideCallHierarchyIncomingCalls(item, token) { 67 | if (structuredInfo.currCallHierarchyInfo === undefined) { 68 | return undefined; 69 | } 70 | 71 | const res = getCallHierarchyCallsByCallHierarchyItem(item, structuredInfo.currCallHierarchyInfo.incomingCall); 72 | return res; 73 | }, 74 | provideCallHierarchyOutgoingCalls(item, token) { 75 | if (structuredInfo.currCallHierarchyInfo === undefined) { 76 | return undefined; 77 | } 78 | 79 | const res = getCallHierarchyCallsByCallHierarchyItem(item, structuredInfo.currCallHierarchyInfo.outgoingCall); 80 | return res; 81 | } 82 | } 83 | ); 84 | 85 | return callHierarchyProvider; 86 | } 87 | 88 | export { registerCallHierarchyProvider }; 89 | -------------------------------------------------------------------------------- /src/web/provider_interface/providers/comp_item_provider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { genAllOriSymbols } from '../../builders/comp_item_builder/comp_item_ori_builder'; 4 | import { bisectRight } from '../../common/algorithm'; 5 | import { CL_MODE } from '../../common/cl_util'; 6 | import { TriggerProvider } from '../../common/enum'; 7 | import { TriggerEvent } from '../TriggerEvent'; 8 | import { getCLWordRangeAtPosition } from '../provider_util'; 9 | import { structuredInfo } from '../structured_info'; 10 | 11 | // only need once 12 | const oriSymbolsCompItem = genAllOriSymbols(); 13 | 14 | // default completion item's word range does not include the '-' character, so we need to reset it 15 | // @sideEffect: compItems:vscode.CompletionItem[] 16 | function resetCompItemWordRange(range: vscode.Range, compItems: vscode.CompletionItem[]) { 17 | compItems.forEach(item => { 18 | item.range = range; 19 | }); 20 | } 21 | 22 | function registerCompletionItemProviders(providerName: string): vscode.Disposable | undefined { 23 | switch (providerName) { 24 | case 'user': 25 | return vscode.languages.registerCompletionItemProvider( 26 | CL_MODE, 27 | { 28 | provideCompletionItems(document, position, token, context) { 29 | const range = getCLWordRangeAtPosition(document, position); 30 | if (range === undefined) { 31 | return undefined; 32 | } 33 | 34 | structuredInfo.updateInfoByDoc(document, new TriggerEvent(TriggerProvider.provideCompletionItems)); 35 | if (structuredInfo.currUserSymbolsCompItem === undefined) { 36 | return undefined; 37 | } 38 | 39 | const numPosition = document.offsetAt(position); 40 | const userSymbols = structuredInfo.currUserSymbolsCompItem.getUserompletionItems(numPosition); 41 | 42 | resetCompItemWordRange(range, userSymbols); 43 | 44 | return userSymbols; 45 | } 46 | }); 47 | case 'loop': 48 | return vscode.languages.registerCompletionItemProvider( 49 | CL_MODE, 50 | { 51 | provideCompletionItems(document, position, token, context) { 52 | const range = getCLWordRangeAtPosition(document, position); 53 | if (range === undefined) { 54 | return undefined; 55 | } 56 | structuredInfo.updateInfoByDoc(document, new TriggerEvent(TriggerProvider.provideCompletionItems)); 57 | if ( 58 | structuredInfo.currUserSymbolsCompItem === undefined || 59 | structuredInfo.currDocSymbolInfo === undefined 60 | ) { 61 | return undefined; 62 | } 63 | 64 | const numPosition = document.offsetAt(position); 65 | const loopBlocks = structuredInfo.currDocSymbolInfo.loopBlocks; 66 | const idx = bisectRight(loopBlocks, numPosition, item => item[0]); 67 | if (idx === -1 || idx === 0) { 68 | return undefined; 69 | } 70 | if (loopBlocks[idx - 1][1] < numPosition) { 71 | return undefined; 72 | } 73 | 74 | const loopSymbols = oriSymbolsCompItem.loopSymbols; 75 | resetCompItemWordRange(range, loopSymbols); 76 | 77 | return loopSymbols; 78 | } 79 | }); 80 | case 'ori': 81 | return vscode.languages.registerCompletionItemProvider( 82 | CL_MODE, 83 | { 84 | provideCompletionItems(document, position, token, context) { 85 | const range = getCLWordRangeAtPosition(document, position); 86 | if (range === undefined) { 87 | return undefined; 88 | } 89 | resetCompItemWordRange(range, oriSymbolsCompItem.oriSymbols); 90 | 91 | return oriSymbolsCompItem.oriSymbols; 92 | } 93 | }); 94 | case 'ampersand': 95 | return vscode.languages.registerCompletionItemProvider( 96 | CL_MODE, 97 | { 98 | provideCompletionItems(document, position, token, context) { 99 | const range = getCLWordRangeAtPosition(document, position); 100 | if (range === undefined) { 101 | return undefined; 102 | } 103 | resetCompItemWordRange(range, oriSymbolsCompItem.afterAmpersand); 104 | 105 | return oriSymbolsCompItem.afterAmpersand; 106 | } 107 | }, '&'); 108 | case 'asterisk': 109 | return vscode.languages.registerCompletionItemProvider( 110 | CL_MODE, 111 | { 112 | provideCompletionItems(document, position, token, context) { 113 | const range = getCLWordRangeAtPosition(document, position); 114 | if (range === undefined) { 115 | return undefined; 116 | } 117 | resetCompItemWordRange(range, oriSymbolsCompItem.afterAsterisk); 118 | 119 | return oriSymbolsCompItem.afterAsterisk; 120 | } 121 | }, '*'); 122 | case 'colon': 123 | return vscode.languages.registerCompletionItemProvider( 124 | CL_MODE, 125 | { 126 | provideCompletionItems(document, position, token, context) { 127 | const range = getCLWordRangeAtPosition(document, position); 128 | if (range === undefined) { 129 | return undefined; 130 | } 131 | resetCompItemWordRange(range, oriSymbolsCompItem.afterColon); 132 | 133 | return oriSymbolsCompItem.afterColon; 134 | } 135 | }, ':'); 136 | case 'tilde': 137 | return vscode.languages.registerCompletionItemProvider( 138 | CL_MODE, 139 | { 140 | provideCompletionItems(document, position, token, context) { 141 | const range = getCLWordRangeAtPosition(document, position); 142 | if (range === undefined) { 143 | return undefined; 144 | } 145 | resetCompItemWordRange(range, oriSymbolsCompItem.afterTilde); 146 | 147 | return oriSymbolsCompItem.afterTilde; 148 | } 149 | }, '~'); 150 | case 'sharpsign': 151 | return vscode.languages.registerCompletionItemProvider( 152 | CL_MODE, 153 | { 154 | provideCompletionItems(document, position, token, context) { 155 | const range = getCLWordRangeAtPosition(document, position); 156 | if (range === undefined) { 157 | return undefined; 158 | } 159 | resetCompItemWordRange(range, oriSymbolsCompItem.afterSharpsign); 160 | 161 | return oriSymbolsCompItem.afterSharpsign; 162 | } 163 | }, '#'); 164 | default: 165 | return undefined; 166 | } 167 | } 168 | 169 | export { registerCompletionItemProviders }; 170 | -------------------------------------------------------------------------------- /src/web/provider_interface/providers/def_provider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import type { DocSymbolInfo } from '../../builders/DocSymbolInfo'; 4 | import { isRangeIntExcludedRanges } from '../../common/algorithm'; 5 | import { CL_MODE } from '../../common/cl_util'; 6 | import { TriggerProvider } from '../../common/enum'; 7 | import { TriggerEvent } from '../TriggerEvent'; 8 | import { isQuote, getCLWordRangeAtPosition } from '../provider_util'; 9 | import { structuredInfo } from '../structured_info'; 10 | 11 | function registerDefinitionProvider() { 12 | const definitionProvider = vscode.languages.registerDefinitionProvider( 13 | CL_MODE, 14 | { 15 | provideDefinition(document, position, token) { 16 | const range = getCLWordRangeAtPosition(document, position); 17 | if (range === undefined) { 18 | return undefined; 19 | } 20 | 21 | structuredInfo.updateInfoByDoc(document, new TriggerEvent(TriggerProvider.provideDefinition)); 22 | if (structuredInfo.currDocSymbolInfo === undefined) { 23 | return undefined; 24 | } 25 | 26 | const positionFlag = (isQuote(document, position) !== undefined) ? undefined : position; 27 | return getSymbolLocByRange( 28 | structuredInfo.currDocSymbolInfo, 29 | range, 30 | positionFlag, 31 | structuredInfo.buildingConfig 32 | ); 33 | } 34 | } 35 | ); 36 | 37 | return definitionProvider; 38 | 39 | } 40 | 41 | // Common Lisp the Language, 2nd Edition 42 | // 5.2.1. Named Functions https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node63.html#SECTION00921000000000000000 43 | // set `position` to `undefined`, will only process global definition 44 | function getSymbolLocByRange( 45 | currDocSymbolInfo: DocSymbolInfo, range: vscode.Range, positionFlag: vscode.Position | undefined, 46 | buildingConfig: Map 47 | ): vscode.Location | undefined { 48 | 49 | // config 50 | const excludedRanges = 51 | currDocSymbolInfo.docRes.getExcludedRangesForDefReferenceProvider(buildingConfig, 'DefinitionProvider'); 52 | const doc = currDocSymbolInfo.document; 53 | const numRange: [number, number] = [doc.offsetAt(range.start), doc.offsetAt(range.end)]; 54 | if (isRangeIntExcludedRanges(numRange, excludedRanges)) { 55 | return undefined; 56 | } 57 | const word = doc.getText(range); 58 | if (!word) { 59 | return undefined; 60 | } 61 | const [symbolSelected, shadow] = currDocSymbolInfo.getSymbolWithShadowByRange( 62 | word.toLowerCase(), range, positionFlag 63 | ); 64 | return symbolSelected?.loc; 65 | } 66 | 67 | export { registerDefinitionProvider }; 68 | -------------------------------------------------------------------------------- /src/web/provider_interface/providers/doc_symbol_provider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { CL_MODE } from '../../common/cl_util'; 4 | import { TriggerProvider } from '../../common/enum'; 5 | import { TriggerEvent } from '../TriggerEvent'; 6 | import { structuredInfo } from '../structured_info'; 7 | 8 | function registerDocumentSymbolProvider() { 9 | const documentSymbolProvider = vscode.languages.registerDocumentSymbolProvider( 10 | CL_MODE, 11 | { 12 | provideDocumentSymbols(document, token) { 13 | structuredInfo.updateInfoByDoc(document, new TriggerEvent(TriggerProvider.provideDocumentSymbols)); 14 | 15 | return structuredInfo.currDocumentSymbol; 16 | } 17 | } 18 | ); 19 | 20 | return documentSymbolProvider; 21 | } 22 | 23 | export { registerDocumentSymbolProvider }; 24 | -------------------------------------------------------------------------------- /src/web/provider_interface/providers/hover_provider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import type { DocSymbolInfo } from '../../builders/DocSymbolInfo'; 4 | import { isRangeIntExcludedRanges } from '../../common/algorithm'; 5 | import { CL_MODE } from '../../common/cl_util'; 6 | import { TriggerProvider } from '../../common/enum'; 7 | import { getDoc } from '../../doc/get_doc'; 8 | import { TriggerEvent } from '../TriggerEvent'; 9 | import { isQuote, getCLWordRangeAtPosition } from '../provider_util'; 10 | import { structuredInfo } from '../structured_info'; 11 | 12 | // Example: https://github.com/NativeScript/nativescript-vscode-extension/blob/474c22c3dea9f9a145dc2cccf01b05e38850de90/src/services/language-services/hover/widget-hover.ts 13 | function registerHoverProviders(providerName: string) { 14 | switch (providerName) { 15 | case 'ori': 16 | return vscode.languages.registerHoverProvider( 17 | CL_MODE, 18 | { 19 | provideHover(document, position, token) { 20 | const range = getCLWordRangeAtPosition(document, position); 21 | if (range === undefined) { 22 | return undefined; 23 | } 24 | 25 | const word = document.getText(range); 26 | //console.log(word); 27 | if (!word) { 28 | return undefined; 29 | } 30 | 31 | const tooltip = getDoc(word.toLowerCase()); 32 | return (tooltip !== undefined) ? new vscode.Hover(tooltip) : undefined; 33 | }, 34 | } 35 | ); 36 | case 'user': 37 | return vscode.languages.registerHoverProvider( 38 | CL_MODE, 39 | { 40 | provideHover(document, position, token) { 41 | const range = getCLWordRangeAtPosition(document, position); 42 | if (range === undefined) { 43 | return undefined; 44 | } 45 | 46 | structuredInfo.updateInfoByDoc(document, new TriggerEvent(TriggerProvider.provideHoverUser)); 47 | if (structuredInfo.currDocSymbolInfo === undefined) { 48 | return undefined; 49 | } 50 | 51 | const positionFlag = (isQuote(document, position) !== undefined) ? undefined : position; 52 | const docStr = getSymbolDocStrByRange( 53 | structuredInfo.currDocSymbolInfo, 54 | range, 55 | positionFlag, 56 | structuredInfo.buildingConfig 57 | ); 58 | 59 | if (docStr === undefined) { 60 | return undefined; 61 | } 62 | 63 | const tooltip = new vscode.MarkdownString(docStr); 64 | tooltip.isTrusted = true; 65 | tooltip.supportHtml = true; 66 | return new vscode.Hover(tooltip); 67 | }, 68 | } 69 | ); 70 | default: 71 | return undefined; 72 | } 73 | } 74 | 75 | function getSymbolDocStrByRange( 76 | currDocSymbolInfo: DocSymbolInfo, range: vscode.Range, positionFlag: vscode.Position | undefined, 77 | buildingConfig: Map 78 | ): string | undefined { 79 | 80 | // config 81 | const excludedRanges = 82 | currDocSymbolInfo.docRes.getExcludedRangesForDefReferenceProvider(buildingConfig, 'DefinitionProvider'); 83 | const doc = currDocSymbolInfo.document; 84 | const numRange: [number, number] = [doc.offsetAt(range.start), doc.offsetAt(range.end)]; 85 | if (isRangeIntExcludedRanges(numRange, excludedRanges)) { 86 | return undefined; 87 | } 88 | const word = doc.getText(range); 89 | if (!word) { 90 | return undefined; 91 | } 92 | const [symbolSelected, shadow] = currDocSymbolInfo.getSymbolWithShadowByRange( 93 | word.toLowerCase(), range, positionFlag 94 | ); 95 | return symbolSelected?.docStr; 96 | } 97 | 98 | export { registerHoverProviders }; 99 | -------------------------------------------------------------------------------- /src/web/provider_interface/providers/reference_provider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import type { DocSymbolInfo } from '../../builders/DocSymbolInfo'; 4 | import { isShadowed, getScopedSameNameWordsExcludeItself } from '../../builders/builders_util'; 5 | import { isRangeIntExcludedRanges } from '../../common/algorithm'; 6 | import { CL_MODE } from '../../common/cl_util'; 7 | import { TriggerProvider } from '../../common/enum'; 8 | import { TriggerEvent } from '../TriggerEvent'; 9 | import { isQuote, getCLWordRangeAtPosition } from '../provider_util'; 10 | import { structuredInfo } from '../structured_info'; 11 | 12 | function registerReferenceProvider() { 13 | const referenceProvider = vscode.languages.registerReferenceProvider( 14 | CL_MODE, 15 | { 16 | provideReferences(document, position, context, token) { 17 | const range = getCLWordRangeAtPosition(document, position); 18 | if (range === undefined) { 19 | return undefined; 20 | } 21 | 22 | structuredInfo.updateInfoByDoc(document, new TriggerEvent(TriggerProvider.provideReferences)); 23 | if (structuredInfo.currDocSymbolInfo === undefined) { 24 | return undefined; 25 | } 26 | 27 | const positionFlag = (isQuote(document, position) !== undefined) ? undefined : position; 28 | 29 | return getReferenceByWord( 30 | structuredInfo.currDocSymbolInfo, 31 | range, 32 | positionFlag, 33 | structuredInfo.buildingConfig, 34 | structuredInfo.needColorDict, 35 | true 36 | ); 37 | } 38 | } 39 | ); 40 | 41 | return referenceProvider; 42 | } 43 | 44 | // Design options: include? definition, include? comment, include? string 45 | // See https://github.com/microsoft/vscode/issues/74237 46 | function getReferenceByWord( 47 | currDocSymbolInfo: DocSymbolInfo, range: vscode.Range, positionFlag: vscode.Position | undefined, 48 | buildingConfig: Map, needColorDict: Map | undefined, 49 | includeDefinition: boolean 50 | ): vscode.Location[] { 51 | 52 | // config 53 | const excludedRanges = 54 | currDocSymbolInfo.docRes.getExcludedRangesForDefReferenceProvider(buildingConfig, 'ReferenceProvider'); 55 | const doc = currDocSymbolInfo.document; 56 | const numRange: [number, number] = [doc.offsetAt(range.start), doc.offsetAt(range.end)]; 57 | if (isRangeIntExcludedRanges(numRange, excludedRanges)) { 58 | return []; 59 | } 60 | const word = doc.getText(range); 61 | if (!word) { 62 | return []; 63 | } 64 | const [symbolSelected, shadow] = currDocSymbolInfo.getSymbolWithShadowByRange( 65 | word.toLowerCase(), range, positionFlag 66 | ); 67 | if (symbolSelected === undefined) { 68 | return []; 69 | } 70 | 71 | if (needColorDict === undefined) { 72 | return []; 73 | } 74 | 75 | const scopedSameNameWords = getScopedSameNameWordsExcludeItself(symbolSelected, needColorDict, currDocSymbolInfo); 76 | const res: vscode.Location[] = []; 77 | // loop cache 78 | const isSymbolSelectedLengthLargerThanOne = (symbolSelected.name.length > 1); 79 | const isShaowValid = shadow !== undefined && shadow.length !== 0; 80 | const uri = doc.uri; 81 | for (const wordRange of scopedSameNameWords) { 82 | if (isRangeIntExcludedRanges(wordRange, excludedRanges) 83 | ) { 84 | continue; 85 | } 86 | 87 | const [wordStart, wordEnd] = wordRange; 88 | 89 | if (positionFlag !== undefined) { 90 | // lexcial scope is enabled, exclude global vars (with quote) from lexical scope 91 | if ( 92 | isSymbolSelectedLengthLargerThanOne && 93 | (isQuote(doc, doc.positionAt(wordStart)) !== undefined)) { 94 | continue; 95 | } 96 | } 97 | 98 | // shadowing is enabled, exclude local vars from global scope 99 | if (isShaowValid && isShadowed(wordRange, shadow)) { 100 | continue; 101 | } 102 | 103 | // console.log(`${document.offsetAt(range.start)} -> ${document.offsetAt(range.end)}`); 104 | res.push(new vscode.Location( 105 | uri, 106 | new vscode.Range(doc.positionAt(wordStart), doc.positionAt(wordEnd)) 107 | )); 108 | 109 | } 110 | 111 | if (includeDefinition) { 112 | res.push(symbolSelected.loc); 113 | } 114 | 115 | return res; 116 | } 117 | 118 | export { registerReferenceProvider }; 119 | -------------------------------------------------------------------------------- /src/web/provider_interface/providers/semantic_tokens_provider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { legend } from '../../builders/semantic_tokens_builder/token_util'; 4 | import { CL_MODE } from '../../common/cl_util'; 5 | import { TriggerProvider } from '../../common/enum'; 6 | import { TriggerEvent } from '../TriggerEvent'; 7 | import { structuredInfo } from '../structured_info'; 8 | 9 | function registerSemanticProvider() { 10 | const semanticProvider = vscode.languages.registerDocumentSemanticTokensProvider( 11 | CL_MODE, 12 | { 13 | provideDocumentSemanticTokens(document, token) { 14 | structuredInfo.updateInfoByDoc(document, new TriggerEvent(TriggerProvider.provideDocumentSemanticTokens)); 15 | 16 | return structuredInfo.currSemanticTokens; 17 | } 18 | }, 19 | legend 20 | ); 21 | 22 | return semanticProvider; 23 | } 24 | 25 | export { registerSemanticProvider }; 26 | -------------------------------------------------------------------------------- /src/web/provider_interface/structured_info.ts: -------------------------------------------------------------------------------- 1 | import { StructuredInfo } from './StructuredInfo'; 2 | 3 | const structuredInfo: StructuredInfo = new StructuredInfo(); 4 | 5 | export { structuredInfo }; 6 | -------------------------------------------------------------------------------- /syntaxes/cl_codeblock.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [], 3 | "injectionSelector": "L:text.html.markdown", 4 | "patterns": [ 5 | { 6 | "include": "#commonlisp-code-block" 7 | } 8 | ], 9 | "repository": { 10 | "commonlisp-code-block": { 11 | "begin": "(^|\\G)(\\s*)(\\`{3,}|~{3,})\\s*(?i:(commonlisp|cl|lsp)(\\s+[^`~]*)?$)", 12 | "name": "markup.fenced_code.block.markdown", 13 | "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", 14 | "beginCaptures": { 15 | "3": { 16 | "name": "punctuation.definition.markdown" 17 | }, 18 | "4": { 19 | "name": "fenced_code.block.language.markdown" 20 | }, 21 | "5": { 22 | "name": "fenced_code.block.language.attributes.markdown" 23 | } 24 | }, 25 | "endCaptures": { 26 | "3": { 27 | "name": "punctuation.definition.markdown" 28 | } 29 | }, 30 | "patterns": [ 31 | { 32 | "begin": "(^|\\G)(\\s*)(.*)", 33 | "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", 34 | "contentName": "meta.embedded.block.commonlisp", 35 | "patterns": [ 36 | { 37 | "include": "source.commonlisp" 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | }, 44 | "scopeName": "markdown.commonlisp.codeblock" 45 | } -------------------------------------------------------------------------------- /syntaxes/cl_codeblock.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Ported from https://github.com/mjbvz/vscode-fenced-code-block-grammar-injection-example 3 | # Under MIT License 4 | 5 | fileTypes: [] 6 | injectionSelector: L:text.html.markdown 7 | patterns: 8 | - include: "#commonlisp-code-block" 9 | 10 | repository: 11 | commonlisp-code-block: 12 | begin: "(^|\\G)(\\s*)(\\`{3,}|~{3,})\\s*(?i:(commonlisp|cl|lsp)(\\s+[^`~]*)?$)" 13 | name: markup.fenced_code.block.markdown 14 | end: "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$" 15 | beginCaptures: 16 | '3': 17 | name: punctuation.definition.markdown 18 | '4': 19 | name: fenced_code.block.language.markdown 20 | '5': 21 | name: fenced_code.block.language.attributes.markdown 22 | endCaptures: 23 | '3': 24 | name: punctuation.definition.markdown 25 | patterns: 26 | - begin: "(^|\\G)(\\s*)(.*)" 27 | while: "(^|\\G)(?!\\s*([`~]{3,})\\s*$)" 28 | contentName: meta.embedded.block.commonlisp 29 | patterns: 30 | - include: source.commonlisp 31 | 32 | scopeName: markdown.commonlisp.codeblock 33 | 34 | ... 35 | -------------------------------------------------------------------------------- /syntaxes/fixtures/cases/demo.lsp: -------------------------------------------------------------------------------- 1 | ;; demo code 2 | (with-test (:name :aprof-instance :skipped-on (not :immobile-space)) 3 | (let (seen-this seen-that) 4 | (dolist (line (split-string 5 | (with-output-to-string (s) 6 | (sb-aprof:aprof-run #'make-structs :stream s)) 7 | #\newline)) 8 | (when (search "THIS-STRUCT" line) (setq seen-this t)) 9 | (when (search "THAT-STRUCT" line) (setq seen-that t))) 10 | (assert (and seen-this seen-that)))) 11 | 12 | (defun my-list (&rest x) 13 | (declare (optimize sb-c::instrument-consing)) 14 | x) 15 | (compile 'my-list) 16 | 17 | #+nil 18 | (let ((l (sb-impl::%hash-table-alist sb-c::*checkgen-used-types*))) 19 | (format t "~&Types needed by checkgen: ('+' = has internal error number)~%") 20 | (setq l (sort l #'> :key #'cadr)) 21 | (loop for (type-spec . (count . interr-p)) in l 22 | do (format t "~:[ ~;+~] ~5D ~S~%" interr-p count type-spec)) 23 | (format t "~&Error numbers not used by checkgen:~%") 24 | (loop for (spec symbol) across sb-c:+backend-internal-errors+ 25 | when (and (not (stringp spec)) 26 | (not (gethash spec sb-c::*checkgen-used-types*))) 27 | do (format t " ~S~%" spec))) 28 | 29 | -------------------------------------------------------------------------------- /syntaxes/fixtures/cases/fstr.lsp: -------------------------------------------------------------------------------- 1 | (defun print-xapping (xapping stream depth) 2 | (declare (ignore depth)) 3 | (format stream 4 | ;; Are you ready for this one? 5 | "~:[{~;[~]~:{~S~:[->~S~;~*~]~:^ ~}~:[~; ~]~ 6 | ~{~S->~^ ~}~:[~; ~]~[~*~;->~S~;->~*~]~:[}~;]~]" 7 | ;; Is that clear? 8 | (xectorp xapping) 9 | (do ((vp (xectorp xapping)) 10 | (sp (finite-part-is-xetp xapping)) 11 | (d (xapping-domain xapping) (cdr d)) 12 | (r (xapping-range xapping) (cdr r)) 13 | (z '() (cons (list (if vp (car r) (car d)) 14 | (or vp sp) 15 | (car r)) 16 | z))) 17 | ((null d) (reverse z))) 18 | (and (xapping-domain xapping) 19 | (or (xapping-exceptions xapping) 20 | (xapping-infinite xapping))) 21 | (xapping-exceptions xapping) 22 | (and (xapping-exceptions xapping) 23 | (xapping-infinite xapping)) 24 | (ecase (xapping-infinite xapping) 25 | ((nil) 0) 26 | (:constant 1) 27 | (:universal 2)) 28 | (xapping-default xapping) 29 | (xectorp xapping))) 30 | 31 | (defun f (n) (format nil "~@(~R~) error~:P detected." n)) -------------------------------------------------------------------------------- /syntaxes/scripts/build_grammar.mjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { promises as fsPromises } from 'fs'; 4 | 5 | import { load } from 'js-yaml'; 6 | 7 | const syntaxes_root = 'syntaxes/'; 8 | const INPUT_GRAM_PATH = `${syntaxes_root}commonlisp.yaml`; 9 | const OUTPUT_GRAM_PATH = `${syntaxes_root}commonlisp.tmLanguage.json`; 10 | const INPUT_INJMD_GRAM_PATH = `${syntaxes_root}cl_codeblock.yaml`; 11 | const OUTPUT_INJMD_GRAM_PATH = `${syntaxes_root}cl_codeblock.tmLanguage.json`; 12 | 13 | /** 14 | * @param {string} inputFilePath 15 | * @param {string} outputFilePath 16 | * @return {Promise} 17 | */ 18 | async function buildGrammar(inputFilePath, outputFilePath) { 19 | const inputFile = await fsPromises.readFile(inputFilePath, { encoding: 'utf8' }); 20 | const jsonDoc = load(inputFile); 21 | await fsPromises.writeFile(outputFilePath, JSON.stringify(jsonDoc, null, 2)); 22 | } 23 | 24 | await buildGrammar(INPUT_GRAM_PATH, OUTPUT_GRAM_PATH); 25 | await buildGrammar(INPUT_INJMD_GRAM_PATH, OUTPUT_INJMD_GRAM_PATH); 26 | 27 | export { buildGrammar }; 28 | -------------------------------------------------------------------------------- /syntaxes/scripts/gen_record.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) Microsoft Corporation 3 | All rights reserved. 4 | 5 | MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | // Ported from https://github.com/microsoft/TypeScript-TmLanguage/blob/master/tests/build.ts 26 | import { promises as fsPromises } from 'fs'; 27 | import * as path from 'path'; 28 | import oniguruma from 'vscode-oniguruma'; 29 | import vt from 'vscode-textmate'; 30 | const FUSED_MODE = true; 31 | // Part 1: config grammar names 32 | /** `scopeName` in the grammar file */ 33 | var GrammarScopeName; 34 | (function (GrammarScopeName) { 35 | GrammarScopeName["lisp"] = "source.commonlisp"; 36 | })(GrammarScopeName || (GrammarScopeName = {})); 37 | /** file name of the grammar file */ 38 | const grammarFileNames = new Map([ 39 | [GrammarScopeName.lisp, 'commonlisp.tmLanguage.json'] 40 | ]); 41 | const syntaxes_root = './syntaxes/'; 42 | /** get the path of the grammar file */ 43 | const getGrammarPath = (scopeName) => path.join(syntaxes_root, grammarFileNames.get(scopeName)); 44 | // Part 2: get vscode-textmate registry 45 | /** get vscode-textmate registry */ 46 | async function getRegistery() { 47 | // load Oniguruma lib 48 | const onigPath = path.resolve('./node_modules/vscode-oniguruma/release/onig.wasm'); 49 | const wasmBin = (await fsPromises.readFile(onigPath)).buffer; 50 | const vscodeOnigurumaLib = oniguruma.loadWASM(wasmBin).then(() => { 51 | return { 52 | createOnigScanner(patterns) { return new oniguruma.OnigScanner(patterns); }, 53 | createOnigString(s) { return new oniguruma.OnigString(s); } 54 | }; 55 | }); 56 | return new vt.Registry({ 57 | onigLib: vscodeOnigurumaLib, 58 | loadGrammar: async function (scopeName) { 59 | const path = getGrammarPath(scopeName); 60 | if (!path) { 61 | return null; 62 | } 63 | const content = await fsPromises.readFile(path, { encoding: 'utf-8' }); 64 | const rawGrammar = vt.parseRawGrammar(content, path); 65 | return rawGrammar; 66 | } 67 | }); 68 | } 69 | /** get tokens and iterate the ruleStack */ 70 | function tokenizeLine(mainGrammar, line) { 71 | const lineTokens = mainGrammar.grammar.tokenizeLine(line, mainGrammar.ruleStack); 72 | mainGrammar.ruleStack = lineTokens.ruleStack; 73 | return lineTokens.tokens; 74 | } 75 | /** generate the record */ 76 | function writeTokenLine(token, outputLines, prevScope) { 77 | const startingSpaces = ' '.repeat(token.startIndex + 1); 78 | const locatingString = '^'.repeat(token.endIndex - token.startIndex); 79 | const hasInvalidTokenScopeExtension = (token) => token.scopes.some(scope => !scope.endsWith('.commonlisp')); 80 | const hasInvalidScopeExtension = hasInvalidTokenScopeExtension(token) ? 'has_INCORRECT_SCOPE_EXTENSION' : ''; 81 | const scope = `sc ${token.scopes.slice(1).join(' ')}${hasInvalidScopeExtension}`; // replace `source.commonlisp` with `sc` 82 | // fuse the indicators while getting the same scope 83 | if (FUSED_MODE && scope === prevScope) { 84 | outputLines[outputLines.length - 2] += '^'; 85 | return scope; 86 | } 87 | // add indicator 88 | outputLines.push(startingSpaces + locatingString); 89 | // add scope name 90 | outputLines.push(startingSpaces + scope); 91 | return scope; 92 | } 93 | /** iterate the lines of the text and produce the record */ 94 | function generateScopesWorker(mainGrammar, oriLineArr) { 95 | const cleanCodeLines = []; 96 | const recordLines = []; 97 | for (const oriLine of oriLineArr) { 98 | // console.log(`\nTokenizing line: ${oriLine}`); 99 | cleanCodeLines.push(oriLine); 100 | recordLines.push(`>${oriLine}`); 101 | let prevScope = ''; 102 | const mainLineTokens = tokenizeLine(mainGrammar, oriLine); 103 | for (const token of mainLineTokens) { 104 | // Notice that `\n` is added to every token so lastIndex+1. 105 | // https://github.com/microsoft/vscode-textmate/issues/15#issuecomment-227128870 106 | // you may VSCODE_TEXTMATE_DEBUG=true 107 | //console.log( 108 | // ` - token [${token.startIndex} -> ${token.endIndex}] `.padEnd(22, ' ') + 109 | // `|${oriLine.substring(token.startIndex, token.endIndex)}|\n` + 110 | // `${' '.repeat(22)}${token.scopes.join(', ')}` 111 | //); 112 | prevScope = writeTokenLine(token, recordLines, prevScope); 113 | } 114 | } 115 | const result = `original file\n` + 116 | `-----------------------------------\n` + 117 | `${cleanCodeLines.join('\n')}` + 118 | `\n` + 119 | `-----------------------------------\n` + 120 | `\n` + 121 | `Grammar: ${grammarFileNames.get(mainGrammar.scopeName)}\n` + 122 | `-----------------------------------\n` + 123 | `${recordLines.join('\n')}`; 124 | //console.log(result); 125 | return result; 126 | } 127 | /** API for turning string into record. 128 | * pass vt.IGrammer to avoid loading grammar again. 129 | */ 130 | function generateScopes(text, grammar) { 131 | //const text = await fsPromises.readFile('syntaxes/fixtures/cases/demo.lsp', { encoding: 'utf-8' }); 132 | const oriLineArr = text.split(/\r\n|\r|\n/); 133 | const initGrammar = (scopeName) => { 134 | //const grammar = await (await getRegistery()).loadGrammar(scopeName); 135 | return { 136 | scopeName: scopeName, 137 | grammar: grammar, 138 | ruleStack: vt.INITIAL 139 | }; 140 | }; 141 | return generateScopesWorker(initGrammar(GrammarScopeName.lisp), oriLineArr); 142 | } 143 | export { GrammarScopeName, generateScopes, getRegistery, }; 144 | -------------------------------------------------------------------------------- /syntaxes/scripts/gen_record.mts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) Microsoft Corporation 3 | All rights reserved. 4 | 5 | MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | // Ported from https://github.com/microsoft/TypeScript-TmLanguage/blob/master/tests/build.ts 26 | 27 | import { promises as fsPromises } from 'fs'; 28 | import * as path from 'path'; 29 | 30 | import oniguruma from 'vscode-oniguruma'; 31 | import vt from 'vscode-textmate'; 32 | 33 | const FUSED_MODE = true; 34 | 35 | // Part 1: config grammar names 36 | 37 | /** `scopeName` in the grammar file */ 38 | enum GrammarScopeName { 39 | lisp = 'source.commonlisp', 40 | } 41 | /** file name of the grammar file */ 42 | const grammarFileNames: Map = new Map([ 43 | [GrammarScopeName.lisp, 'commonlisp.tmLanguage.json'] 44 | ]); 45 | const syntaxes_root = './syntaxes/'; 46 | /** get the path of the grammar file */ 47 | const getGrammarPath = (scopeName: GrammarScopeName) => 48 | path.join(syntaxes_root, grammarFileNames.get(scopeName)!); 49 | 50 | // Part 2: get vscode-textmate registry 51 | 52 | /** get vscode-textmate registry */ 53 | async function getRegistery() { 54 | // load Oniguruma lib 55 | const onigPath = path.resolve('./node_modules/vscode-oniguruma/release/onig.wasm'); 56 | const wasmBin = (await fsPromises.readFile(onigPath)).buffer; 57 | const vscodeOnigurumaLib: Promise = 58 | oniguruma.loadWASM(wasmBin).then(() => { 59 | return { 60 | createOnigScanner(patterns) { return new oniguruma.OnigScanner(patterns); }, 61 | createOnigString(s) { return new oniguruma.OnigString(s); } 62 | }; 63 | }); 64 | 65 | return new vt.Registry({ 66 | onigLib: vscodeOnigurumaLib, 67 | loadGrammar: async function (scopeName: GrammarScopeName) { 68 | const path = getGrammarPath(scopeName); 69 | if (!path) { 70 | return null; 71 | } 72 | const content = await fsPromises.readFile(path, { encoding: 'utf-8' }); 73 | const rawGrammar = vt.parseRawGrammar(content, path); 74 | return rawGrammar; 75 | } 76 | }); 77 | } 78 | 79 | // Part 3: generate scopes 80 | 81 | /** the struct for iterating in vscode-textmate */ 82 | interface Grammar { 83 | scopeName: GrammarScopeName; 84 | grammar: vt.IGrammar; 85 | ruleStack: vt.StateStack; 86 | } 87 | 88 | /** get tokens and iterate the ruleStack */ 89 | function tokenizeLine(mainGrammar: Grammar, line: string) { 90 | const lineTokens = mainGrammar.grammar.tokenizeLine(line, mainGrammar.ruleStack); 91 | mainGrammar.ruleStack = lineTokens.ruleStack; 92 | return lineTokens.tokens; 93 | } 94 | 95 | /** generate the record */ 96 | function writeTokenLine(token: vt.IToken, outputLines: string[], prevScope: string): string { 97 | const startingSpaces = ' '.repeat(token.startIndex + 1); 98 | const locatingString = '^'.repeat(token.endIndex - token.startIndex); 99 | 100 | const hasInvalidTokenScopeExtension = (token: vt.IToken) => 101 | token.scopes.some(scope => !scope.endsWith('.commonlisp')); 102 | const hasInvalidScopeExtension = 103 | hasInvalidTokenScopeExtension(token) ? 'has_INCORRECT_SCOPE_EXTENSION' : ''; 104 | 105 | const scope = `sc ${token.scopes.slice(1,).join(' ')}${hasInvalidScopeExtension}`; // replace `source.commonlisp` with `sc` 106 | 107 | // fuse the indicators while getting the same scope 108 | if (FUSED_MODE && scope === prevScope) { 109 | outputLines[outputLines.length - 2] += '^'; 110 | return scope; 111 | } 112 | 113 | // add indicator 114 | outputLines.push(startingSpaces + locatingString); 115 | // add scope name 116 | outputLines.push(startingSpaces + scope); 117 | 118 | return scope; 119 | } 120 | 121 | /** iterate the lines of the text and produce the record */ 122 | function generateScopesWorker(mainGrammar: Grammar, oriLineArr: string[]): string { 123 | const cleanCodeLines: string[] = []; 124 | const recordLines: string[] = []; 125 | 126 | for (const oriLine of oriLineArr) { 127 | // console.log(`\nTokenizing line: ${oriLine}`); 128 | cleanCodeLines.push(oriLine); 129 | recordLines.push(`>${oriLine}`); 130 | 131 | let prevScope = ''; 132 | const mainLineTokens = tokenizeLine(mainGrammar, oriLine); 133 | for (const token of mainLineTokens) { 134 | // Notice that `\n` is added to every token so lastIndex+1. 135 | // https://github.com/microsoft/vscode-textmate/issues/15#issuecomment-227128870 136 | // you may VSCODE_TEXTMATE_DEBUG=true 137 | //console.log( 138 | // ` - token [${token.startIndex} -> ${token.endIndex}] `.padEnd(22, ' ') + 139 | // `|${oriLine.substring(token.startIndex, token.endIndex)}|\n` + 140 | // `${' '.repeat(22)}${token.scopes.join(', ')}` 141 | //); 142 | prevScope = writeTokenLine(token, recordLines, prevScope); 143 | } 144 | } 145 | 146 | const result = 147 | `original file\n` + 148 | `-----------------------------------\n` + 149 | `${cleanCodeLines.join('\n')}` + 150 | `\n` + 151 | `-----------------------------------\n` + 152 | `\n` + 153 | `Grammar: ${grammarFileNames.get(mainGrammar.scopeName)!}\n` + 154 | `-----------------------------------\n` + 155 | `${recordLines.join('\n')}`; 156 | //console.log(result); 157 | return result; 158 | } 159 | 160 | /** API for turning string into record. 161 | * pass vt.IGrammer to avoid loading grammar again. 162 | */ 163 | function generateScopes(text: string, grammar: vt.IGrammar) { 164 | //const text = await fsPromises.readFile('syntaxes/fixtures/cases/demo.lsp', { encoding: 'utf-8' }); 165 | 166 | const oriLineArr = text.split(/\r\n|\r|\n/); 167 | 168 | const initGrammar = (scopeName: GrammarScopeName) => { 169 | //const grammar = await (await getRegistery()).loadGrammar(scopeName); 170 | return { 171 | scopeName: scopeName, 172 | grammar: grammar, 173 | ruleStack: vt.INITIAL 174 | }; 175 | }; 176 | 177 | return generateScopesWorker( 178 | initGrammar(GrammarScopeName.lisp), 179 | oriLineArr 180 | ); 181 | } 182 | 183 | export { 184 | GrammarScopeName, 185 | generateScopes, 186 | getRegistery, 187 | }; 188 | -------------------------------------------------------------------------------- /syntaxes/scripts/test_grammar.mjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { promises as fsPromises } from 'fs'; 4 | import assert from 'node:assert'; 5 | import { describe, it } from 'node:test'; 6 | import * as path from 'path'; 7 | 8 | import { 9 | ensureCleanGeneratedFolder, 10 | getGrammar, 11 | checkFileExists, 12 | generateAndWrite, 13 | baselineFolder, 14 | casesFolder 15 | } from './test_util.mjs'; 16 | 17 | /** 18 | * @param {string} recordName 19 | * @param {string} generatedText 20 | * @return {Promise} 21 | */ 22 | async function assertMatchBaseline(recordName, generatedText) { 23 | const baselineFile = path.join(baselineFolder, recordName); 24 | 25 | if (await checkFileExists(baselineFile)) { 26 | const baselineText = await fsPromises.readFile(baselineFile, { encoding: 'utf8' }); 27 | 28 | assert.strictEqual(generatedText, baselineText, `Expected [${recordName}]'s baseline to match.`); 29 | } else { 30 | assert(false, 'Baseline not found.'); 31 | } 32 | } 33 | 34 | /** 35 | * @param {string[]} cases 36 | * @param {IGrammar} grammar 37 | * @return {void} 38 | */ 39 | function testIfMatchOn(cases, grammar) { 40 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 41 | describe(`generating and comparing records`, { concurrency: cases.length }, 42 | () => { 43 | for (const c of cases) { 44 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 45 | it(`[${c}] record matching`, async () => { 46 | const [recordName, generatedText] = await generateAndWrite(c, grammar); 47 | await assertMatchBaseline(recordName, generatedText); 48 | }); 49 | } 50 | } 51 | ); 52 | } 53 | 54 | /** 55 | * @param {string[]} cases 56 | * @return {Promise} 57 | */ 58 | async function generateRecordsFor(cases = []) { 59 | const allCases = await fsPromises.readdir(casesFolder); 60 | let needCases = []; 61 | 62 | if (cases.length !== 0) { 63 | const allCasesSet = new Set(allCases); 64 | // {name: fullNameBase} 65 | const allCaseNamesMap = new Map(); 66 | allCases.forEach(c => { 67 | allCaseNamesMap.set(path.parse(c).name, c); 68 | }); 69 | 70 | for (const c of cases) { 71 | if (allCasesSet.has(c)) { 72 | needCases.push(c); 73 | } else if (allCaseNamesMap.has(c)) { 74 | needCases.push(allCaseNamesMap.get(c)); 75 | } 76 | } 77 | } else { 78 | needCases = allCases; 79 | } 80 | 81 | await ensureCleanGeneratedFolder(); 82 | testIfMatchOn(needCases, await getGrammar()); 83 | } 84 | 85 | await generateRecordsFor(process.argv.slice(2)); 86 | 87 | export { generateRecordsFor }; 88 | -------------------------------------------------------------------------------- /syntaxes/scripts/test_util.mjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { promises as fsPromises } from 'fs'; 4 | import * as path from 'path'; 5 | 6 | import { generateScopes, getRegistery, GrammarScopeName } from './gen_record.mjs'; 7 | 8 | const syntaxes_root = 'syntaxes/'; 9 | const generatedFolder = `${syntaxes_root}fixtures/generated`; 10 | const baselineFolder = `${syntaxes_root}fixtures/baselines`; 11 | const casesFolder = `${syntaxes_root}fixtures/cases`; 12 | 13 | /** 14 | * @param {string} file 15 | * @return {Promise} 16 | */ 17 | async function checkFileExists(file) { 18 | try { 19 | await fsPromises.access(file, fsPromises.constants.F_OK); 20 | return true; 21 | } catch { 22 | return false; 23 | } 24 | } 25 | 26 | /** 27 | * @return {Promise} 28 | */ 29 | async function ensureCleanGeneratedFolder() { 30 | if (await checkFileExists(generatedFolder)) { 31 | for (const f of await fsPromises.readdir(generatedFolder)) { 32 | await fsPromises.unlink(path.join(generatedFolder, f)); 33 | } 34 | await fsPromises.rmdir(generatedFolder); 35 | } 36 | await fsPromises.mkdir(generatedFolder); 37 | } 38 | 39 | /** 40 | * @param {string} c 41 | * @param {IGrammar} grammar 42 | * @return {Promise<[string, string]>} 43 | */ 44 | async function generateAndWrite(c, grammar) { 45 | const caseText = await fsPromises.readFile( 46 | path.join(casesFolder, c), 47 | { encoding: 'utf8' } 48 | ); 49 | 50 | const generatedText = generateScopes(caseText, grammar); 51 | const caseName = path.parse(c); 52 | const recordName = `${caseName.name}.record.txt`; 53 | await fsPromises.writeFile( 54 | path.join(generatedFolder, recordName), 55 | generatedText 56 | ); // write generated text 57 | 58 | return [recordName, generatedText]; 59 | } 60 | 61 | /** 62 | * @return {Promise} 63 | */ 64 | async function getGrammar() { 65 | const grammar = await (await getRegistery()).loadGrammar(GrammarScopeName.lisp); 66 | if (grammar === null) { 67 | throw new TypeError('the loading result of grammar is null, expected vt.IGrammar'); 68 | } 69 | return grammar; 70 | } 71 | 72 | export { 73 | ensureCleanGeneratedFolder, 74 | getGrammar, 75 | checkFileExists, 76 | generateAndWrite, 77 | generatedFolder, 78 | baselineFolder, 79 | casesFolder 80 | }; 81 | -------------------------------------------------------------------------------- /syntaxes/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "pretty": true, 4 | "lib": [ 5 | "es2022", 6 | "WebWorker" 7 | ], 8 | "target": "es2022", 9 | "module": "NodeNext", 10 | "moduleResolution": "NodeNext", 11 | "isolatedModules": true, 12 | "verbatimModuleSyntax": true, 13 | "resolveJsonModule": true, 14 | "esModuleInterop": true, 15 | 16 | //"declaration": true, // extra type check 17 | //"declarationMap": true, 18 | //"sourceMap": true, 19 | 20 | "strict": true, 21 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 22 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 23 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 24 | "noImplicitOverride": true, 25 | "noPropertyAccessFromIndexSignature": true, 26 | 27 | "allowUnreachableCode": false, 28 | "allowUnusedLabels": false, 29 | "newLine": "lf", 30 | 31 | //"allowJs": true, 32 | } 33 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "src", 4 | "outDir": "dist", 5 | 6 | "pretty": true, 7 | "lib": [ 8 | "es2022", 9 | "WebWorker" 10 | ], 11 | "target": "es2022", 12 | "module": "commonjs", 13 | //"moduleResolution": "bundler", 14 | "isolatedModules": true, 15 | //"verbatimModuleSyntax": true, 16 | "resolveJsonModule": true, 17 | "esModuleInterop": true, 18 | 19 | //"declaration": true, // extra type check 20 | //"declarationMap": true, 21 | "sourceMap": true, 22 | 23 | "strict": true, 24 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 25 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 26 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 27 | "noImplicitOverride": true, 28 | "noPropertyAccessFromIndexSignature": true, 29 | 30 | "allowUnreachableCode": false, 31 | "allowUnusedLabels": false, 32 | "newLine": "lf", 33 | 34 | // "allowJs": true, 35 | }, 36 | "include": [ 37 | "src" 38 | ], 39 | "exclude": [ 40 | "node_modules" 41 | ] 42 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | //@ts-check 7 | 'use strict'; 8 | 9 | //@ts-check 10 | /** @typedef {import('webpack').Configuration} WebpackConfig **/ 11 | 12 | const path = require('path'); 13 | const webpack = require('webpack'); 14 | 15 | /** @type WebpackConfig */ 16 | const webExtensionConfig = { 17 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') 18 | target: 'webworker', // extensions run in a webworker context 19 | entry: { 20 | 'extension': './src/web/extension.ts', 21 | }, 22 | output: { 23 | filename: '[name].js', 24 | path: path.join(__dirname, './dist/web'), 25 | libraryTarget: 'commonjs2', 26 | devtoolModuleFilenameTemplate: '../../[resource-path]', 27 | clean: true, 28 | }, 29 | resolve: { 30 | mainFields: ['browser', 'module', 'main'], // look for `browser` entry point in imported node modules 31 | extensions: ['.ts', '.js'], // support ts-files and js-files 32 | alias: { 33 | // provides alternate implementation for node module and source files 34 | }, 35 | fallback: { 36 | // Webpack 5 no longer polyfills Node.js core modules automatically. 37 | // see https://webpack.js.org/configuration/resolve/#resolvefallback 38 | // for the list of Node.js core module polyfills. 39 | 'assert': require.resolve('assert') 40 | } 41 | }, 42 | module: { 43 | rules: [{ 44 | test: /\.ts$/, 45 | include: path.join(__dirname, './src'), 46 | exclude: /node_modules/, 47 | use: [{ 48 | loader: 'ts-loader' 49 | }] 50 | }] 51 | }, 52 | plugins: [ 53 | new webpack.optimize.LimitChunkCountPlugin({ 54 | maxChunks: 1 // disable chunks by default since web extensions must be a single bundle 55 | }), 56 | new webpack.ProvidePlugin({ 57 | process: 'process/browser', // provide a shim for the global `process` variable 58 | }), 59 | ], 60 | externals: { 61 | 'vscode': 'commonjs vscode', // ignored because it doesn't exist 62 | }, 63 | performance: { 64 | hints: false 65 | }, 66 | devtool: 'nosources-source-map', // create a source map that points to the original source file 67 | infrastructureLogging: { 68 | level: "log", // enables logging required for problem matchers 69 | }, 70 | watchOptions: { 71 | ignored: /node_modules/ 72 | }, 73 | }; 74 | 75 | module.exports = [webExtensionConfig]; --------------------------------------------------------------------------------