├── .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];
--------------------------------------------------------------------------------