├── .editorconfig ├── .git-blame-ignore-revs ├── .github ├── dependabot.yml └── workflows │ ├── changelog.yml │ ├── dependency-review.yml │ ├── main.yml │ ├── nix.yml │ ├── opam-dependency-submission.yml │ └── update-flake-lock.yml ├── .gitignore ├── .ocamlformat ├── .ocamlformat-ignore ├── .vscode-test.js ├── .vscode ├── launch.json └── tasks.json ├── .yarnrc.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── assets ├── arrow-circle-up-dark.svg ├── arrow-circle-up-light.svg ├── book-open-dark.svg ├── book-open-light.svg ├── chat-dark.svg ├── chat-light.svg ├── collection-dark.svg ├── collection-light.svg ├── dependency-dark-selected.svg ├── dependency-dark.svg ├── dependency-light-selected.svg ├── dependency-light.svg ├── discord-dark.svg ├── discord-light.svg ├── document-search-dark.svg ├── document-search-light.svg ├── github-dark.svg ├── github-light.svg ├── logo.png ├── logo.svg ├── number-dark.svg ├── number-light.svg ├── plus-dark.svg ├── plus-light.svg ├── refresh-dark.svg ├── refresh-light.svg ├── switch-impl-intf.dark.svg ├── switch-impl-intf.light.svg ├── terminal-dark.svg ├── terminal-light.svg ├── x-dark.svg └── x-light.svg ├── astexplorer ├── LICENCE.txt ├── css │ ├── highlight.css │ └── style.css ├── index.html ├── package.json └── src │ ├── Interaction │ ├── BrowserInteractor.js │ ├── Interactor.js │ ├── InteractorFactory.js │ └── VsCodeInteractorFactory.js │ ├── components │ ├── ASTOutput.js │ └── visualization │ │ ├── SelectedNodeContext.js │ │ ├── Tree.js │ │ ├── css │ │ └── tree.css │ │ ├── focusNodes.js │ │ ├── index.js │ │ └── tree │ │ ├── CompactArrayView.js │ │ ├── CompactObjectView.js │ │ └── Element.js │ ├── containers │ └── ASTOutputContainer.js │ ├── core │ ├── ParseResult.js │ └── TreeAdapter.js │ ├── index.js │ ├── parserMiddleware.js │ ├── parsers │ ├── refmt-ml.js │ └── utils │ │ ├── SettingsRenderer.js │ │ └── defaultParserInterface.js │ ├── utils │ ├── classnames.js │ ├── debounce.js │ ├── logger.js │ ├── pubsub.js │ └── stringify.js │ └── vscode.js ├── biome.json ├── doc ├── code_lens.png ├── commands.png ├── pick_sandbox.png └── trace_verbose.png ├── dune ├── dune-project ├── flake.lock ├── flake.nix ├── languages ├── META.json ├── dune.json ├── menhir.json ├── oasis.json ├── ocaml.json ├── ocamlbuild.json ├── ocamlformat.json ├── ocamllex.json ├── opam-install.json ├── opam.json └── reason.json ├── package.json ├── snippets ├── dune-project.json ├── dune.json ├── ocaml.json └── ocamllex.json ├── src-bindings ├── interop │ ├── dune │ ├── interop.ml │ └── interop.mli ├── node │ ├── dune │ ├── node.ml │ ├── node.mli │ └── node_stub.js ├── polka │ ├── dune │ ├── polka.ml │ ├── polka.mli │ └── polka_stub.js ├── vscode │ ├── dune │ ├── vscode.ml │ ├── vscode.mli │ └── vscode_stub.js └── vscode_languageclient │ ├── dune │ ├── vscode_languageclient.ml │ ├── vscode_languageclient.mli │ └── vscode_languageclient_stub.js ├── src ├── ast_editor.ml ├── ast_editor.mli ├── ast_editor_state.ml ├── ast_editor_state.mli ├── cm_editor.ml ├── cm_editor.mli ├── cmd.ml ├── cmd.mli ├── custom_requests.ml ├── custom_requests.mli ├── documentation_server.ml ├── documentation_server.mli ├── dune ├── dune_formatter.ml ├── dune_formatter.mli ├── dune_task_provider.ml ├── dune_task_provider.mli ├── earlybird.ml ├── earlybird.mli ├── esy.ml ├── esy.mli ├── extension_commands.ml ├── extension_commands.mli ├── extension_consts.ml ├── extension_instance.ml ├── extension_instance.mli ├── import.ml ├── ocaml_lsp.ml ├── ocaml_lsp.mli ├── ocaml_windows.ml ├── ocaml_windows.mli ├── odig.ml ├── odig.mli ├── opam.ml ├── opam.mli ├── output.ml ├── output.mli ├── path.ml ├── path.mli ├── platform.ml ├── platform.mli ├── ppx_tools │ ├── dumpast.ml │ ├── dumpast.mli │ ├── dune │ ├── ppx_tools.ml │ ├── ppx_tools.mli │ ├── traverse_ast.ml │ └── traverse_ast2.ml ├── repl.ml ├── repl.mli ├── sandbox.ml ├── sandbox.mli ├── settings.ml ├── settings.mli ├── switch_impl_intf.ml ├── switch_impl_intf.mli ├── terminal_sandbox.ml ├── terminal_sandbox.mli ├── treeview_commands.ml ├── treeview_commands.mli ├── treeview_help.ml ├── treeview_help.mli ├── treeview_sandbox.ml ├── treeview_sandbox.mli ├── treeview_switches.ml ├── treeview_switches.mli ├── type_selection.ml ├── vscode_ocaml_platform.ml └── vscode_ocaml_platform.mli ├── syntaxes ├── META.json ├── atd.json ├── cram.json ├── dune-project.json ├── dune-workspace.json ├── dune.json ├── menhir-action.json ├── menhir.json ├── merlin.json ├── mlx.json ├── oasis.json ├── ocaml-markdown-codeblock.json ├── ocaml.interface.json ├── ocaml.json ├── ocamlbuild.json ├── ocamldoc.json ├── ocamlformat.json ├── ocamllex.json ├── opam-install.json ├── opam.json ├── reason-markdown-codeblock.json └── reason.json ├── tests ├── fixtures │ ├── dune │ └── sample-opam │ │ ├── foo.ml │ │ ├── main.ml │ │ └── sample-opam.opam └── suite │ ├── basic │ └── problems.test.js │ └── opam │ └── languageId.test.js ├── tsconfig.json ├── vscode-interop.opam ├── vscode-node.opam ├── vscode-ocaml-platform.opam ├── vscode.opam └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [Makefile] 12 | indent_style = tab 13 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # To understand why we need this file, see https://www.moxio.com/blog/43/ignoring-bulk-change-commits-with-git-blame 2 | 3 | # add ocamlformat config `wrap-fun-args=false` 4 | 5 | ad2ebf3a357c17ac184bcb0c43540cb2a9a614d2 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | labels: 8 | - dependencies 9 | - no changelog 10 | - package-ecosystem: npm 11 | directory: / 12 | schedule: 13 | interval: daily 14 | labels: 15 | - dependencies 16 | - no changelog 17 | groups: 18 | react: 19 | patterns: 20 | - "react" 21 | - "react-dom" 22 | ignore: 23 | - dependency-name: "@types/vscode" 24 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | name: Changelog check 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | types: [opened, synchronize, reopened, labeled, unlabeled] 7 | 8 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency 9 | # Concurrent workflows are grouped by the PR or branch that triggered them 10 | # (github.ref) and the name of the workflow (github.workflow). The 11 | # 'cancel-in-progress' option then make sure that only one workflow is running 12 | # at a time. This doesn't prevent new jobs from running, rather it cancels 13 | # already running jobs before scheduling new jobs. 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name == 'pull_request' || github.sha }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | Changelog-Entry-Check: 20 | name: Check Changelog Action 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: tarides/changelog-check-action@v3 24 | with: 25 | changelog: CHANGELOG.md 26 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | name: Dependency Review 2 | 3 | on: pull_request 4 | 5 | permissions: read-all 6 | 7 | jobs: 8 | dependency-review: 9 | name: Dependency Review 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout tree 13 | uses: actions/checkout@v4 14 | - name: Dependency Review 15 | uses: actions/dependency-review-action@v4 16 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Builds, tests & co 2 | 3 | on: 4 | push: 5 | pull_request: 6 | release: 7 | types: 8 | - published 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | build-and-test: 14 | permissions: 15 | attestations: write 16 | id-token: write 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout tree 20 | uses: actions/checkout@v4 21 | 22 | - name: Set-up OCaml 23 | uses: ocaml/setup-ocaml@v3 24 | with: 25 | ocaml-compiler: 5 26 | 27 | - name: Set-up Node.js 28 | uses: actions/setup-node@v4 29 | with: 30 | node-version: 24 31 | check-latest: true 32 | 33 | - run: corepack enable 34 | 35 | - name: Install dependencies 36 | run: | 37 | yarn install --immutable 38 | opam install . --deps-only --with-test 39 | 40 | - name: Run biome checks 41 | run: yarn lint 42 | 43 | - name: Build and package extension 44 | run: opam exec -- make pkg 45 | 46 | - name: Generate artifact attestation 47 | if: github.event_name == 'release' 48 | uses: actions/attest-build-provenance@v2 49 | with: 50 | subject-path: ocaml-platform.vsix 51 | 52 | - name: Upload artifact 53 | uses: actions/upload-artifact@v4 54 | with: 55 | name: ocaml-platform-${{ github.sha }} 56 | path: ocaml-platform.vsix 57 | 58 | - name: Test extension 59 | run: xvfb-run -a yarn test 60 | 61 | lint-opam: 62 | runs-on: ubuntu-latest 63 | steps: 64 | - name: Checkout tree 65 | uses: actions/checkout@v4 66 | - name: Set-up OCaml 67 | uses: ocaml/setup-ocaml@v3 68 | with: 69 | ocaml-compiler: 5 70 | - uses: ocaml/setup-ocaml/lint-opam@v3 71 | 72 | lint-fmt: 73 | runs-on: ubuntu-latest 74 | steps: 75 | - name: Checkout tree 76 | uses: actions/checkout@v4 77 | - name: Set-up OCaml 78 | uses: ocaml/setup-ocaml@v3 79 | with: 80 | ocaml-compiler: 5 81 | - uses: ocaml/setup-ocaml/lint-fmt@v3 82 | 83 | release: 84 | if: github.event_name == 'release' 85 | needs: 86 | - build-and-test 87 | - lint-opam 88 | - lint-fmt 89 | permissions: 90 | contents: write 91 | runs-on: ubuntu-latest 92 | steps: 93 | - name: Checkout tree 94 | uses: actions/checkout@v4 95 | 96 | - name: Set-up Node.js 97 | uses: actions/setup-node@v4 98 | with: 99 | node-version: 24 100 | check-latest: true 101 | 102 | - run: corepack enable 103 | 104 | - name: Install dependencies 105 | run: yarn install --immutable 106 | 107 | - name: Download artifact 108 | uses: actions/download-artifact@v4 109 | with: 110 | name: ocaml-platform-${{ github.sha }} 111 | 112 | - name: Publish extension to VSCode Marketplace 113 | continue-on-error: true 114 | run: yarn deploy:vsce 115 | env: 116 | VSCE_PAT: ${{ secrets.VSCODE_MARKETPLACE_TOKEN }} 117 | 118 | - name: Publish extension to Open VSX Registry 119 | continue-on-error: true 120 | run: yarn deploy:ovsx --pat "$OVSX_PAT" 121 | env: 122 | OVSX_PAT: ${{ secrets.OPEN_VSX_TOKEN }} 123 | 124 | - name: Upload artifact to GitHub release 125 | continue-on-error: true 126 | uses: softprops/action-gh-release@v2 127 | with: 128 | files: ocaml-platform.vsix 129 | -------------------------------------------------------------------------------- /.github/workflows/nix.yml: -------------------------------------------------------------------------------- 1 | name: Nix 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | permissions: read-all 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | with: 16 | submodules: true 17 | - uses: cachix/install-nix-action@v31 18 | with: 19 | nix_path: nixpkgs=channel:nixos-unstable 20 | - run: nix develop -c make nix-tests 21 | -------------------------------------------------------------------------------- /.github/workflows/opam-dependency-submission.yml: -------------------------------------------------------------------------------- 1 | name: Opam Dependency Submission 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | concurrency: 9 | group: opam-dependency-submission 10 | cancel-in-progress: true 11 | 12 | permissions: read-all 13 | 14 | jobs: 15 | opam-dependency-submission: 16 | permissions: 17 | contents: write 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout tree 21 | uses: actions/checkout@v4 22 | - name: Set-up OCaml 23 | uses: ocaml/setup-ocaml@v3 24 | with: 25 | ocaml-compiler: 5 26 | - uses: ocaml/setup-ocaml/analysis@v3 27 | -------------------------------------------------------------------------------- /.github/workflows/update-flake-lock.yml: -------------------------------------------------------------------------------- 1 | name: Update Nix Flake Lock 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: 0 0 * * 0 7 | 8 | jobs: 9 | lockfile: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v4 14 | - uses: cachix/install-nix-action@v31 15 | with: 16 | nix_path: nixpkgs=channel:nixos-unstable 17 | - uses: DeterminateSystems/update-flake-lock@v25 18 | with: 19 | pr-labels: | 20 | dependencies 21 | no changelog 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ocamlbuild working directory 2 | _build/ 3 | 4 | # ocamlbuild targets 5 | *.byte 6 | *.native 7 | 8 | # Merlin configuring file for Vim and Emacs 9 | .merlin 10 | 11 | # Dune generated files 12 | *.install 13 | 14 | # Local OPAM switch 15 | _opam/ 16 | 17 | # npm stuff 18 | .yarn/ 19 | node_modules/ 20 | 21 | # VSCode settings 22 | .vscode/settings.json 23 | 24 | # VSCode build artifacts 25 | dist/ 26 | .vscode-test/ 27 | *.vsix 28 | 29 | # DS_Store 30 | .DS_Store 31 | 32 | # build cache 33 | .parcel-cache/ 34 | -------------------------------------------------------------------------------- /.ocamlformat: -------------------------------------------------------------------------------- 1 | # upgrading the version, don't forget to update ocamlformat version in `dune-project` 2 | version=0.27.0 3 | profile=janestreet 4 | parse-docstrings=true 5 | -------------------------------------------------------------------------------- /.ocamlformat-ignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode-test/ 3 | -------------------------------------------------------------------------------- /.vscode-test.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require("@vscode/test-cli"); 2 | 3 | module.exports = defineConfig({ 4 | files: "tests/suite/**/*.test.js", 5 | }); 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "runtimeExecutable": "${execPath}", 9 | "outFiles": [ 10 | "${workspaceRoot}/_build/default/src/vscode_ocaml_platform.bc.js" 11 | ], 12 | "args": [ 13 | "--disable-extensions", 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ] 16 | }, 17 | { 18 | "name": "Extension (no disable-extensions)", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "runtimeExecutable": "${execPath}", 22 | "outFiles": [ 23 | "${workspaceRoot}/_build/default/src/vscode_ocaml_platform.bc.js" 24 | ], 25 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "shell", 6 | "command": "dune build src/vscode_ocaml_platform.bc.js -w", 7 | "label": "dune build src/vscode_ocaml_platform.bc.js -w", 8 | "problemMatcher": "$ocamlc", 9 | "presentation": { 10 | "reveal": "never" 11 | }, 12 | "group": { 13 | "kind": "build", 14 | "isDefault": true 15 | } 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Setup your development environment 4 | 5 | You need Opam, you can install it by following 6 | [Opam's documentation](https://opam.ocaml.org/doc/Install.html). 7 | 8 | With Opam installed, you can install the dependencies in a new local switch 9 | with: 10 | 11 | ```bash 12 | make switch 13 | ``` 14 | 15 | Or globally, with: 16 | 17 | ```bash 18 | make deps 19 | ``` 20 | 21 | Then, build the project with: 22 | 23 | ```bash 24 | make build 25 | ``` 26 | 27 | ### Running the extension 28 | 29 | After building the project, you can run the extension from VSCode by going in 30 | the "Run and Debug" tab (`Shift + Cmd + D`) and running the `Extension` task. 31 | This will open a new VSCode window with only the `vscode-ocaml` extension 32 | activated. 33 | 34 | ### Running Tests 35 | 36 | You can run the test compiled with: 37 | 38 | ```bash 39 | make test 40 | ``` 41 | 42 | This will run both the OCaml tests and the JavaScript ones. 43 | 44 | ### Format code 45 | 46 | To format the code, you can run: 47 | 48 | ``` 49 | make fmt 50 | ``` 51 | 52 | This will format the OCaml source code with `ocamlformat` and the JavaScript 53 | source code with `prettier`. 54 | 55 | ## Submitting a PR 56 | 57 | To submit a PR make sure you create a new branch, add the code and commit it. 58 | 59 | ``` 60 | git checkout -b my-bug-fix 61 | git add fixed-file.ml 62 | git commit -m "fix a bug" 63 | git push -u origin my-bug-fix 64 | ``` 65 | 66 | From here you can then open a PR from GitHub. Before committing your code it is 67 | very useful to: 68 | 69 | - Format the code: this should be as simple as `make fmt` 70 | - Make sure it builds: running `make build`, this is also very important if you 71 | add data to the repository as it will "crunch" the data into the static OCaml 72 | modules (more information below) 73 | - Run the tests: this will check that all the data is correctly formatted and 74 | can be invoked with `make test` 75 | 76 | ### Changelog 77 | 78 | User-visible changes should come with an entry in the changelog under the 79 | appropriate part of the unreleased section. PR that doesn't provide an entry 80 | will fail CI check. This behavior can be overridden by using the "no changelog" 81 | label, which is used for changes that are not user-visible. 82 | 83 | ## Repository Structure 84 | 85 | The following snippet describes VSCode OCaml's repository structure. 86 | 87 | ```text 88 | . 89 | │ 90 | ├── assets/ 91 | | Static assets included in the packaged extension. 92 | │ 93 | ├── astexplorer/ 94 | | Vendored version of `astexplorer` used to provide the AST explorer for OCaml source code. 95 | │ 96 | ├── doc/ 97 | | Assets and files used for documentation. 98 | │ 99 | ├── languages/ 100 | | Definitions of the languages supported in the extension. 101 | │ 102 | ├── src/ 103 | | Source for VSCode OCaml extension. 104 | │ 105 | ├── src-bindings/ 106 | | Source for VSCode OCaml's bindings to JavaScript libraries. 107 | │ 108 | ├── syntaxes/ 109 | | Definitions of the syntaxes supported in the extension. 110 | │ 111 | ├── test/ 112 | | Unit tests and integration tests for VSCode OCaml. 113 | | 114 | ├── dune 115 | ├── dune-project 116 | | Dune file used to mark the root of the project and define project-wide parameters. 117 | | For the documentation of the syntax, see https://dune.readthedocs.io/en/stable/dune-files.html#dune-project 118 | | 119 | ├── Makefile 120 | | Make file containing common development command. 121 | │ 122 | ├── package-lock.json 123 | ├── package.json 124 | | Package file for NPM packages. This is used to defined the JavaScript dependencies of the project. 125 | │ 126 | ├── README.md 127 | | The documentation included in the extension's overview on the VSCode Marketplace. 128 | │ 129 | ├── vscode-ocaml.opam 130 | └── vscode.opam 131 | Opam package definitions. 132 | To know more about creating and publishing opam packages, see https://opam.ocaml.org/doc/Packaging.html. 133 | ``` 134 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2019 OCaml Labs 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := all 2 | 3 | .PHONY: all 4 | all: 5 | dune build 6 | 7 | .PHONY: npm-deps 8 | npm-deps: 9 | yarn install --immutable 10 | 11 | .PHONY: deps 12 | deps: 13 | $(MAKE) npm-deps 14 | opam install --deps-only --with-test --with-doc --with-dev-setup --yes . 15 | 16 | .PHONY: create_switch 17 | create_switch: 18 | opam switch create . 5.3.0 --no-install 19 | 20 | .PHONY: switch 21 | switch: create_switch deps 22 | 23 | .PHONY: build 24 | build: # https://github.com/ewanharris/vscode-versions 25 | dune build src/vscode_ocaml_platform.bc.js --profile=release 26 | yarn workspace astexplorer build 27 | yarn esbuild _build/default/src/vscode_ocaml_platform.bc.js \ 28 | --bundle \ 29 | --external:vscode \ 30 | --minify \ 31 | --outdir=dist \ 32 | --packages=bundle \ 33 | --platform=node \ 34 | --target=node22 \ 35 | --analyze 36 | 37 | .PHONY: test 38 | test: 39 | dune build @runtest 40 | yarn test 41 | 42 | .PHONY: clean 43 | clean: 44 | dune clean 45 | $(RM) -r astexplorer/dist dist 46 | 47 | .PHONY: doc 48 | doc: 49 | dune build @doc 50 | 51 | .PHONY: fmt 52 | fmt: 53 | dune build --auto-promote @fmt 54 | yarn biome format --write 55 | 56 | .PHONY: watch 57 | watch: 58 | dune build @all -w --terminal-persistence=clear-on-rebuild 59 | 60 | .PHONY: pkg 61 | pkg: clean build 62 | yarn package 63 | 64 | .PHONY: install 65 | install: pkg 66 | code --force --install-extension ocaml-platform.vsix 67 | 68 | .PHONY: 69 | nix-tests: 70 | dune build @runtest @all 71 | -------------------------------------------------------------------------------- /assets/arrow-circle-up-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/arrow-circle-up-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/book-open-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/book-open-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/chat-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/chat-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/collection-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/collection-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/dependency-dark-selected.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/dependency-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/dependency-light-selected.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/dependency-light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/discord-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/discord-light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/document-search-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/document-search-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/github-dark.svg: -------------------------------------------------------------------------------- 1 | 3 | GitHub icon 4 | 5 | -------------------------------------------------------------------------------- /assets/github-light.svg: -------------------------------------------------------------------------------- 1 | GitHub icon -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocamllabs/vscode-ocaml-platform/f32e73f1c6169159a439acf1f7ceffe807f43729/assets/logo.png -------------------------------------------------------------------------------- /assets/number-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/number-light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/plus-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/plus-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/refresh-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/refresh-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/switch-impl-intf.dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | background 5 | 6 | 7 | 8 | 9 | 10 | 11 | Layer 1 12 | ml 13 | mli 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/switch-impl-intf.light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | background 5 | 6 | 7 | 8 | 9 | 10 | 11 | Layer 1 12 | ml 13 | mli 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/terminal-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/terminal-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/x-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/x-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /astexplorer/LICENCE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014-2021 Felix Kling and astexplorer contributors https://astexplorer.net 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the “Software”), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | 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. -------------------------------------------------------------------------------- /astexplorer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AST explorer 7 | 10 | 11 | 12 |
13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /astexplorer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astexplorer", 3 | "private": true, 4 | "sideEffects": [ 5 | "./src/index.js" 6 | ], 7 | "type": "module", 8 | "scripts": { 9 | "build": "parcel build --no-cache index.html", 10 | "dev": "parcel watch --no-cache index.html" 11 | }, 12 | "dependencies": { 13 | "font-awesome": "4.7.0", 14 | "json-stringify-safe": "5.0.1", 15 | "process": "0.11.10", 16 | "prop-types": "15.8.1", 17 | "react": "19.1.0", 18 | "react-dom": "19.1.0", 19 | "react-redux": "9.2.0" 20 | }, 21 | "devDependencies": { 22 | "parcel": "2.15.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /astexplorer/src/Interaction/BrowserInteractor.js: -------------------------------------------------------------------------------- 1 | import Interactor from "./Interactor"; 2 | 3 | Interactor.showInformationMessage = (text) => alert(text); 4 | Interactor.getDirectoryInfo = (callback) => 5 | callback( 6 | "Directory: C:\\somewhere\\somedir\\ \n SomeFile.sql, SomeOtherFile.sql", 7 | ); 8 | 9 | export default Interactor; 10 | -------------------------------------------------------------------------------- /astexplorer/src/Interaction/Interactor.js: -------------------------------------------------------------------------------- 1 | const Interactor = { 2 | showInformationMessage: (text) => 3 | console.log(`showInformationMessage ${text}`), 4 | getDirectoryInfo: (callback) => console.log(`getDirectoryInfo ${callback}`), 5 | }; 6 | 7 | export default Interactor; 8 | -------------------------------------------------------------------------------- /astexplorer/src/Interaction/InteractorFactory.js: -------------------------------------------------------------------------------- 1 | import BrowserInteractor from "./BrowserInteractor"; 2 | import VsCodeInteractorFactory from "./VsCodeInteractorFactory"; 3 | 4 | function tryAcquireVsCodeApi() { 5 | try { 6 | return acquireVsCodeApi(); 7 | } catch { 8 | // In this case we are not in VsCode context 9 | return null; 10 | } 11 | } 12 | 13 | function create() { 14 | const vsCodeApi = tryAcquireVsCodeApi(); 15 | 16 | if (vsCodeApi === null) { 17 | return BrowserInteractor; 18 | } else { 19 | return VsCodeInteractorFactory.createFromVsCodeApi(vsCodeApi); 20 | } 21 | } 22 | 23 | const InteractorFactory = { 24 | create: create, 25 | }; 26 | 27 | export default InteractorFactory; 28 | -------------------------------------------------------------------------------- /astexplorer/src/Interaction/VsCodeInteractorFactory.js: -------------------------------------------------------------------------------- 1 | import Interactor from "./Interactor"; 2 | 3 | const VsCodeStateChangeCallbacks = { 4 | getDirectoryInfo: (directoryInfo) => {}, 5 | }; 6 | 7 | const VsCodeStateChangeBuffer = { 8 | directoryInfo: "", 9 | }; 10 | 11 | window.addEventListener("message", (event) => { 12 | const message = event.data; 13 | 14 | switch (message.command) { 15 | case "getDirectoryInfo": 16 | VsCodeStateChangeBuffer.directoryInfo += message.directoryInfo; 17 | VsCodeStateChangeCallbacks.getDirectoryInfo( 18 | VsCodeStateChangeBuffer.directoryInfo, 19 | ); 20 | break; 21 | } 22 | }); 23 | 24 | function createFromVsCodeApi(vscode) { 25 | Interactor.showInformationMessage = (text) => 26 | vscode.postMessage({ 27 | command: "showInformationMessage", 28 | text: text, 29 | }); 30 | 31 | Interactor.getDirectoryInfo = (callback) => { 32 | VsCodeStateChangeCallbacks.getDirectoryInfo = callback; 33 | VsCodeStateChangeBuffer.directoryInfo = ""; 34 | vscode.postMessage({ command: "getDirectoryInfo" }); 35 | }; 36 | 37 | return Interactor; 38 | } 39 | 40 | const VsCodeInteractorFactory = { 41 | createFromVsCodeApi: (vscode) => createFromVsCodeApi(vscode), 42 | }; 43 | 44 | export default VsCodeInteractorFactory; 45 | -------------------------------------------------------------------------------- /astexplorer/src/components/ASTOutput.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react"; 3 | import cx from "../utils/classnames.js"; 4 | import { vscode } from "../vscode"; 5 | import visualizations from "./visualization"; 6 | 7 | const { useState } = React; 8 | 9 | function formatTime(time) { 10 | if (!time) { 11 | return null; 12 | } 13 | if (time < 1000) { 14 | return `${time}ms`; 15 | } 16 | return `${(time / 1000).toFixed(2)}s`; 17 | } 18 | 19 | export default function ASTOutput({ 20 | parseResult = {}, 21 | ppParseResult = {}, 22 | position = null, 23 | error = null, 24 | }) { 25 | const [selectedOutput, setSelectedOutput] = useState(0); 26 | const { ast = null } = parseResult; 27 | let output; 28 | output = ( 29 | 30 | {React.createElement(visualizations[0], { 31 | parseResult: selectedOutput == 0 ? parseResult : ppParseResult, 32 | position, 33 | selectedOutput, 34 | error, 35 | })} 36 | 37 | ); 38 | 39 | let buttons = visualizations.map((_, index) => ( 40 | 55 | )); 56 | 57 | return ( 58 |
59 |
60 | {buttons} 61 | {/* {formatTime(parseResult.time)} 62 | */}{" "} 63 |
64 | {output} 65 |
66 | ); 67 | } 68 | 69 | ASTOutput.propTypes = { 70 | parseResult: PropTypes.object, 71 | position: PropTypes.number, 72 | }; 73 | 74 | class ErrorBoundary extends React.Component { 75 | constructor(props) { 76 | super(props); 77 | this.state = { error: null, errorInfo: null }; 78 | } 79 | 80 | componentDidCatch(error, errorInfo) { 81 | this.setState({ 82 | error: error, 83 | errorInfo: errorInfo, 84 | }); 85 | } 86 | 87 | render() { 88 | /* if (this.state.errorInfo) { 89 | return

{this.state.error.toString()}

; 90 | } */ 91 | return this.props.children; 92 | } 93 | } 94 | 95 | ErrorBoundary.propTypes = { 96 | children: PropTypes.node, 97 | }; 98 | -------------------------------------------------------------------------------- /astexplorer/src/components/visualization/SelectedNodeContext.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const SelectedNodeContext = React.createContext(); 4 | 5 | function useSelectedNode() { 6 | const context = React.useContext(SelectedNodeContext); 7 | if (!context) { 8 | throw new Error( 9 | "useSelectedNode must be used within a SelectedNodeContext", 10 | ); 11 | } 12 | return context; 13 | } 14 | 15 | let unselectCallback; 16 | 17 | function setSelectedNode(node, cb) { 18 | if (unselectCallback) { 19 | unselectCallback(); 20 | } 21 | if (node) { 22 | global.$node = node; 23 | unselectCallback = cb; 24 | } else { 25 | unselectCallback = null; 26 | delete global.$node; 27 | } 28 | } 29 | 30 | function SelectedNodeProvider(props) { 31 | return ; 32 | } 33 | 34 | export { SelectedNodeProvider, useSelectedNode }; 35 | -------------------------------------------------------------------------------- /astexplorer/src/components/visualization/Tree.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react"; 3 | import { treeAdapterFromParseResult } from "../../core/TreeAdapter.js"; 4 | import { logEvent } from "../../utils/logger"; 5 | import { publish } from "../../utils/pubsub.js"; 6 | import { vscode } from "../../vscode"; 7 | import focusNodes from "./focusNodes.js"; 8 | import { SelectedNodeProvider } from "./SelectedNodeContext.js"; 9 | import Element from "./tree/Element"; 10 | 11 | import "./css/tree.css"; 12 | 13 | const { useReducer, useMemo, useRef, useLayoutEffect } = React; 14 | 15 | const STORAGE_KEY = "tree_settings"; 16 | 17 | function initSettings() { 18 | const storedSettings = global.localStorage.getItem(STORAGE_KEY); 19 | return storedSettings 20 | ? JSON.parse(storedSettings) 21 | : { 22 | autofocus: true, 23 | hideEmptyKeys: false, 24 | hideLocationData: false, 25 | hideTypeKeys: true, 26 | }; 27 | } 28 | 29 | function reducer(state, element) { 30 | const newState = { ...state, [element.name]: element.checked }; 31 | 32 | global.localStorage.setItem(STORAGE_KEY, JSON.stringify(newState)); 33 | logEvent( 34 | "tree_view_settings", 35 | element.checked ? "enabled" : "disabled", 36 | element.name, 37 | ); 38 | 39 | return newState; 40 | } 41 | 42 | function makeCheckbox(name, settings, updateSettings) { 43 | return ( 44 | updateSettings(event.target)} 49 | /> 50 | ); 51 | } 52 | 53 | export default function Tree({ parseResult, position, error, selectedOutput }) { 54 | if (error) { 55 | return

{error.toString()}

; 56 | } 57 | const [settings, updateSettings] = useReducer(reducer, null, initSettings); 58 | const treeAdapter = useMemo( 59 | () => treeAdapterFromParseResult(parseResult, settings), 60 | [parseResult.treeAdapter, settings], 61 | ); 62 | const rootElement = useRef(); 63 | 64 | focusNodes("init"); 65 | useLayoutEffect(() => { 66 | focusNodes("focus", rootElement); 67 | }); 68 | 69 | return ( 70 |
71 |
72 | {treeAdapter.getConfigurableFilters().map((filter) => 73 | filter.key != "hideTypeKeys" 74 | ? 75 | 79 | ​ 80 | 81 | : "", 82 | )} 83 | {selectedOutput == 1 84 | ? 94 | : ""} 95 |
96 | 112 |
113 | ); 114 | } 115 | 116 | Tree.propTypes = { 117 | parseResult: PropTypes.object, 118 | position: PropTypes.number, 119 | }; 120 | -------------------------------------------------------------------------------- /astexplorer/src/components/visualization/css/tree.css: -------------------------------------------------------------------------------- 1 | .tree-visualization { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .tree-visualization > .toolbar { 7 | padding: 5px; 8 | flex-shrink: 0; 9 | } 10 | 11 | .tree-visualization > .toolbar label { 12 | cursor: pointer; 13 | margin-right: 5px; 14 | -webkit-touch-callout: none; 15 | user-select: none; 16 | white-space: nowrap; 17 | } 18 | 19 | .tree-visualization ul { 20 | margin: 0; 21 | padding-left: 20px; 22 | overflow: auto; 23 | } 24 | 25 | .tree-visualization > ul { 26 | cursor: default; 27 | box-sizing: border-box; 28 | font-family: sans-serif; 29 | -webkit-touch-callout: none; 30 | user-select: none; 31 | flex: 1; 32 | } 33 | 34 | .tree-visualization .value-body { 35 | min-width: 300px; 36 | width: fit-content; 37 | } 38 | -------------------------------------------------------------------------------- /astexplorer/src/components/visualization/focusNodes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This may be a horrible way to do it, but this function is called from React 3 | * components to "collect" all elements that represent AST nodes that are 4 | * currently "focused", i.e. the position of the caret corresponds with this 5 | * node. 6 | * Since a node can appear multiple times in the parser output, multiple elements 7 | * can be highlighted. The question is: which element should we scroll to? 8 | * My current answer: The one that is closest to the vertical center of the 9 | * view. 10 | * React components cannot solve this themselves since they don't have knowledge 11 | * over other elements. 12 | * So this function works as follows: 13 | * - At render, the tree root initializes a new set of nodes. 14 | * - Whenever a child node is rendered and "in focus", it adds a ref to the 15 | * list of elements. 16 | * - After render, the tree root triggers the focus logic. The element that is 17 | * closest to the center is scrolled into the view. 18 | */ 19 | let nodes; 20 | 21 | export default function (message, arg) { 22 | switch (message) { 23 | case "init": 24 | nodes = new Set(); 25 | break; 26 | case "add": 27 | nodes.add(arg); 28 | break; 29 | case "focus": { 30 | const root = arg.current; 31 | const size = nodes.size; 32 | try { 33 | if (size === 1) { 34 | nodes.values().next().value.current.scrollIntoView(); 35 | } else if (size > 1) { 36 | const rootRect = root.getBoundingClientRect(); 37 | const center = (rootRect.y + rootRect.height) / 2 + rootRect.y; 38 | const closest = Array.from(nodes).reduce((closest, element) => { 39 | if (!element.current) { 40 | return closest; 41 | } 42 | const elementRect = element.current.getBoundingClientRect(); 43 | const distance = elementRect.y - center; 44 | const minDistance = Math.min( 45 | Math.abs(distance), 46 | Math.abs(distance + elementRect.height), 47 | ); 48 | 49 | if (!closest || closest[1] > minDistance) { 50 | return [element.current, minDistance]; 51 | } 52 | return closest; 53 | }, null); 54 | if (closest) { 55 | closest[0].scrollIntoView(); 56 | } 57 | } 58 | } catch (e) { 59 | // eslint-disable-next-line no-console 60 | console.error("Unable to scroll node into view:", e.message); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /astexplorer/src/components/visualization/index.js: -------------------------------------------------------------------------------- 1 | import Tree from "./Tree"; 2 | 3 | export default [Tree, Tree]; 4 | -------------------------------------------------------------------------------- /astexplorer/src/components/visualization/tree/CompactArrayView.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react"; 3 | 4 | export default class CompactArrayView extends React.Component { 5 | shouldComponentUpdate(nextProps) { 6 | return nextProps.array.length !== this.props.array.length; 7 | } 8 | 9 | render() { 10 | let { array } = this.props; 11 | let count = array.length; 12 | 13 | if (count === 0) { 14 | return {"[ ]"}; 15 | } else { 16 | return ( 17 | 18 | {"["} 19 | 20 | {count + " element" + (count > 1 ? "s" : "")} 21 | 22 | {"]"} 23 | 24 | ); 25 | } 26 | } 27 | } 28 | 29 | CompactArrayView.propTypes = { 30 | /** 31 | * The array of elements to represent. 32 | */ 33 | array: PropTypes.oneOfType([ 34 | PropTypes.array, 35 | PropTypes.shape({ length: PropTypes.number }), 36 | ]).isRequired, 37 | onClick: PropTypes.func, 38 | }; 39 | -------------------------------------------------------------------------------- /astexplorer/src/components/visualization/tree/CompactObjectView.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react"; 3 | 4 | export default function CompactObjectView({ keys, onClick }) { 5 | if (keys.length === 0) { 6 | return {""}; 7 | } else { 8 | if (keys.length > 5) { 9 | keys = keys.slice(0, 5).concat([`... +${keys.length - 5}`]); 10 | } 11 | return ( 12 | 13 | {"{"} 14 | 15 | {keys.join(", ")} 16 | 17 | {"}"} 18 | 19 | ); 20 | } 21 | } 22 | 23 | CompactObjectView.propTypes = { 24 | keys: PropTypes.arrayOf(PropTypes.string).isRequired, 25 | onClick: PropTypes.func, 26 | }; 27 | -------------------------------------------------------------------------------- /astexplorer/src/containers/ASTOutputContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from "react-redux"; 2 | import ASTOutput from "../components/ASTOutput"; 3 | //import * as selectors from '../store/selectors'; 4 | import * as astresult from "./test.json"; 5 | 6 | function mapStateToProps(_state) { 7 | return { 8 | parseResult: astresult, 9 | position: 1, 10 | }; 11 | } 12 | 13 | export default connect(mapStateToProps)(ASTOutput); 14 | -------------------------------------------------------------------------------- /astexplorer/src/core/ParseResult.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Describes the result of a parse process. Only exists here for documentation 3 | * purposes. 4 | */ 5 | // eslint-disable-next-line no-unused-vars 6 | const ParseResult = { 7 | /** 8 | * The generated AST 9 | */ 10 | ast: "any", 11 | 12 | /** 13 | * An error object, if parsing resulted in an error 14 | */ 15 | error: "Object", 16 | 17 | /** 18 | * How long it took to generate the AST 19 | */ 20 | time: "number", 21 | 22 | treeAdapter: { 23 | /** 24 | * The type of the adapter to use, as defined in TreeAdapters.js 25 | */ 26 | type: "string", 27 | /** 28 | * Override the default options with these values 29 | */ 30 | options: "Object", 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /astexplorer/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import ASTOutput from "./components/ASTOutput"; 4 | import InteractorFactory from "./Interaction/InteractorFactory"; 5 | import getTreeAdapter from "./parserMiddleware"; 6 | import newParser from "./parsers/refmt-ml"; 7 | 8 | const Interactor = InteractorFactory.create(); 9 | 10 | class Index extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { treeAdapter: getTreeAdapter(newParser) }; 14 | this.state = { 15 | astResult: { 16 | ast: null, 17 | error: null, 18 | time: 120, 19 | treeAdapter: this.state.treeAdapter, 20 | }, 21 | position: -2, 22 | ppAstResult: { 23 | ast: null, 24 | error: null, 25 | time: 120, 26 | treeAdapter: this.state.treeAdapter, 27 | }, 28 | }; 29 | window.addEventListener("contextmenu", (event) => { 30 | event.preventDefault(); 31 | const xPos = event.pageX + "px"; 32 | const yPos = event.pageY + "px"; 33 | console.log(xPos); /* TODO rightclick action */ 34 | console.log(yPos); 35 | }); 36 | window.addEventListener("message", (event) => { 37 | switch (event.data.type) { 38 | case "ast": 39 | this.setState({ 40 | astResult: { 41 | ast: event.data.value, 42 | error: null, 43 | time: 0, 44 | treeAdapter: getTreeAdapter(newParser), 45 | }, 46 | }); 47 | this.setState({ 48 | error: null, 49 | }); 50 | break; 51 | case "pp_ast": 52 | this.setState({ 53 | ppAstResult: { 54 | ast: event.data.value, 55 | error: null, 56 | time: 0, 57 | treeAdapter: getTreeAdapter(newParser), 58 | }, 59 | }); 60 | this.setState({ 61 | error: null, 62 | }); 63 | break; 64 | case "focus": 65 | this.setState({ position: event.data.value }); 66 | break; 67 | case "error": 68 | this.setState({ error: event.data.value }); 69 | break; 70 | } 71 | }); 72 | } 73 | 74 | updateFilesToDisplay() { 75 | Interactor.getDirectoryInfo((directoryInfo) => { 76 | this.setState({ directoryInfo: directoryInfo }); 77 | }); 78 | } 79 | 80 | render() { 81 | return ( 82 | <> 83 |
84 | 90 | {this.message} 91 |
92 | 93 | ); 94 | } 95 | } 96 | 97 | const root = ReactDOM.createRoot(document.getElementById("index")); 98 | 99 | root.render(); 100 | -------------------------------------------------------------------------------- /astexplorer/src/parserMiddleware.js: -------------------------------------------------------------------------------- 1 | import { 2 | emptyKeysFilter, 3 | ignoreKeysFilter, 4 | locationInformationFilter, 5 | typeKeysFilter, 6 | } from "./core/TreeAdapter.js"; 7 | 8 | export default function getTreeAdapter(newParser) { 9 | return { 10 | type: "default", 11 | options: { 12 | openByDefault: (newParser.opensByDefault || (() => false)).bind( 13 | newParser, 14 | ), 15 | nodeToRange: newParser.nodeToRange.bind(newParser), 16 | nodeToName: newParser.getNodeName.bind(newParser), 17 | walkNode: newParser.forEachProperty.bind(newParser), 18 | filters: [ 19 | ignoreKeysFilter(newParser._ignoredProperties), 20 | emptyKeysFilter(), 21 | locationInformationFilter(newParser.locationProps), 22 | typeKeysFilter(newParser.typeProps), 23 | ], 24 | locationProps: newParser.locationProps, 25 | }, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /astexplorer/src/parsers/refmt-ml.js: -------------------------------------------------------------------------------- 1 | import defaultParserInterface from "./utils/defaultParserInterface"; 2 | 3 | const ID = "ocaml"; 4 | const locKeys = [ 5 | "loc", 6 | "pcd_loc", 7 | "pcf_loc", 8 | "pci_loc", 9 | "pcl_loc", 10 | "pctf_loc", 11 | "pcty_loc", 12 | "pexp_loc", 13 | "pexp_loc_stack", 14 | "pincl_loc", 15 | "pld_loc", 16 | "pmb_loc", 17 | "pmd_loc", 18 | "pms_loc", 19 | "pmod_loc", 20 | "pmtd_loc", 21 | "pmty_loc", 22 | "popen_loc", 23 | "ppat_loc", 24 | "ppat_loc_stack", 25 | "psig_loc", 26 | "pstr_loc", 27 | "ptyp_loc", 28 | "ptyp_loc_stack", 29 | "ptype_loc", 30 | "pval_loc", 31 | "pvb_loc", 32 | "pbop_loc", 33 | "ptyext_loc", 34 | "pext_loc", 35 | "ptyexn_loc", 36 | "pdir_loc", 37 | "pdira_loc", 38 | ]; 39 | 40 | export default { 41 | ...defaultParserInterface, 42 | 43 | id: ID, 44 | displayName: ID, 45 | version: "0.1", 46 | homepage: `https://google.com`, 47 | locationProps: new Set(locKeys), 48 | 49 | loadParser(callback) { 50 | //require(['astexplorer-refmt'], callback); 51 | }, 52 | 53 | parse(parser, code) { 54 | return parser.parseOcaml(code); 55 | }, 56 | 57 | getNodeName(node) { 58 | return node.type; 59 | }, 60 | 61 | nodeToRange(node) { 62 | const locKey = locKeys.find((key) => 63 | Object.prototype.hasOwnProperty.call(node, key), 64 | ); 65 | if (locKey) { 66 | const range = 67 | node[locKey].origin_loc_start != undefined 68 | ? [ 69 | node[locKey].loc_start.pos_cnum, 70 | node[locKey].loc_end.pos_cnum, 71 | node[locKey].origin_loc_start.pos_cnum, 72 | node[locKey].origin_loc_end.pos_cnum, 73 | ] 74 | : [node[locKey].loc_start.pos_cnum, node[locKey].loc_end.pos_cnum]; 75 | return range; 76 | } 77 | }, 78 | }; 79 | -------------------------------------------------------------------------------- /astexplorer/src/parsers/utils/SettingsRenderer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react"; 3 | 4 | const identity = (v) => v; 5 | 6 | function valuesFromArray(settings) { 7 | return settings.reduce( 8 | (obj, name) => ((obj[name] = settings.indexOf(name) > -1), obj), 9 | {}, 10 | ); 11 | } 12 | 13 | function getValuesFromSettings(settings) { 14 | if (Array.isArray(settings)) { 15 | return valuesFromArray(settings); 16 | } 17 | return settings; 18 | } 19 | 20 | function defaultUpdater(settings, name, value) { 21 | return { ...settings, [name]: value }; 22 | } 23 | 24 | function arrayUpdater(settings, name, value) { 25 | settings = new Set(settings); 26 | if (value) { 27 | settings.add(name); 28 | } else { 29 | settings.delete(name); 30 | } 31 | return Array.from(settings); 32 | } 33 | 34 | function getUpdateStrategy(settings) { 35 | if (Array.isArray(settings)) { 36 | return arrayUpdater; 37 | } 38 | return defaultUpdater; 39 | } 40 | 41 | export default function SettingsRenderer(props) { 42 | const { settingsConfiguration, parserSettings, onChange } = props; 43 | const { 44 | title, 45 | fields, 46 | required = new Set(), 47 | update = getUpdateStrategy(parserSettings), 48 | } = settingsConfiguration; 49 | const values = (settingsConfiguration.values || getValuesFromSettings)( 50 | parserSettings, 51 | ); 52 | 53 | return ( 54 |
55 | {title ?

{title}

: null} 56 |
    57 | {fields.map((setting) => { 58 | if (typeof setting === "string") { 59 | return ( 60 |
  • 61 | 73 |
  • 74 | ); 75 | } else if (Array.isArray(setting)) { 76 | const [fieldName, options, converter = identity] = setting; 77 | return ( 78 |
  • 79 | 106 |
  • 107 | ); 108 | } else if (setting && typeof setting === "object") { 109 | return ( 110 | 115 | onChange({ ...parserSettings, [setting.key]: settings }) 116 | } 117 | /> 118 | ); 119 | } 120 | })} 121 |
122 |
123 | ); 124 | } 125 | 126 | SettingsRenderer.propTypes = { 127 | settingsConfiguration: PropTypes.object.isRequired, 128 | parserSettings: PropTypes.oneOfType([PropTypes.object, PropTypes.array]) 129 | .isRequired, 130 | onChange: PropTypes.func.isRequired, 131 | }; 132 | -------------------------------------------------------------------------------- /astexplorer/src/utils/classnames.js: -------------------------------------------------------------------------------- 1 | export default function cx(...configs) { 2 | return configs 3 | .map((config) => 4 | typeof config === "string" 5 | ? config 6 | : Object.keys(config) 7 | .filter((k) => config[k]) 8 | .join(" "), 9 | ) 10 | .join(" "); 11 | } 12 | -------------------------------------------------------------------------------- /astexplorer/src/utils/debounce.js: -------------------------------------------------------------------------------- 1 | export default function debounce(f, timeout = 100) { 2 | let timer; 3 | let lastArgs; 4 | let lastThis; 5 | 6 | return function (...args) { 7 | lastThis = this; 8 | lastArgs = args; 9 | if (timer) { 10 | return; 11 | } 12 | timer = setTimeout(() => { 13 | timer = null; 14 | f.apply(lastThis, lastArgs); 15 | }, timeout); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /astexplorer/src/utils/logger.js: -------------------------------------------------------------------------------- 1 | export function logEvent(category, action, label) { 2 | global.ga("send", "event", category, action, label); 3 | } 4 | 5 | export function logError(message, fatal) { 6 | global.ga("send", "exception", { exDescription: message, exFatal: fatal }); 7 | } 8 | -------------------------------------------------------------------------------- /astexplorer/src/utils/pubsub.js: -------------------------------------------------------------------------------- 1 | const subscribers = {}; 2 | 3 | export function subscribe(topic, handler) { 4 | let handlers = subscribers[topic]; 5 | if (!handlers) { 6 | handlers = subscribers[topic] = []; 7 | } 8 | if (handlers.indexOf(handler) === -1) { 9 | handlers.push(handler); 10 | } 11 | 12 | return () => handlers.splice(handlers.indexOf(handler), 1); 13 | } 14 | 15 | export function publish(topic, data) { 16 | /* if (subscribers[topic]) { 17 | setTimeout(function callSubscribers() { 18 | if (subscribers[topic]) { 19 | const handlers = subscribers[topic]; 20 | for (var i = 0; i < handlers.length; i++) { 21 | handlers[i](data); 22 | } 23 | } 24 | }, 0); 25 | } */ 26 | } 27 | 28 | export function clear(unsubscribers) { 29 | unsubscribers.forEach(call); 30 | } 31 | 32 | function call(f) { 33 | return f(); 34 | } 35 | -------------------------------------------------------------------------------- /astexplorer/src/utils/stringify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts a JS value to a sensible string representation. 3 | */ 4 | export default function stringify(value) { 5 | switch (typeof value) { 6 | case "function": 7 | return value.toString().match(/function[^(]*\([^)]*\)/)[0]; 8 | case "object": 9 | return value ? JSON.stringify(value, stringify) : "null"; 10 | case "undefined": 11 | return "undefined"; 12 | case "number": 13 | case "bigint": 14 | return Number.isNaN(value) ? "NaN" : String(value); 15 | default: 16 | return JSON.stringify(value); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /astexplorer/src/vscode.js: -------------------------------------------------------------------------------- 1 | export const vscode = acquireVsCodeApi(); 2 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/@biomejs/biome/configuration_schema.json", 3 | "vcs": { 4 | "enabled": true, 5 | "clientKind": "git", 6 | "useIgnoreFile": true, 7 | "defaultBranch": "master" 8 | }, 9 | "linter": { 10 | "enabled": true, 11 | "includes": ["**", "!astexplorer/**"], 12 | "domains": { 13 | "project": "recommended" 14 | }, 15 | "rules": { 16 | "recommended": true 17 | } 18 | }, 19 | "formatter": { 20 | "enabled": true, 21 | "useEditorconfig": true 22 | }, 23 | "assist": { 24 | "enabled": true, 25 | "actions": { 26 | "recommended": true 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /doc/code_lens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocamllabs/vscode-ocaml-platform/f32e73f1c6169159a439acf1f7ceffe807f43729/doc/code_lens.png -------------------------------------------------------------------------------- /doc/commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocamllabs/vscode-ocaml-platform/f32e73f1c6169159a439acf1f7ceffe807f43729/doc/commands.png -------------------------------------------------------------------------------- /doc/pick_sandbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocamllabs/vscode-ocaml-platform/f32e73f1c6169159a439acf1f7ceffe807f43729/doc/pick_sandbox.png -------------------------------------------------------------------------------- /doc/trace_verbose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocamllabs/vscode-ocaml-platform/f32e73f1c6169159a439acf1f7ceffe807f43729/doc/trace_verbose.png -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (dirs :standard \ astexplorer node_modules tests) 2 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.19) 2 | 3 | (name vscode-ocaml-platform) 4 | 5 | (source 6 | (github ocamllabs/vscode-ocaml-platform)) 7 | 8 | (license ISC) 9 | 10 | (maintainers "Rudi Grinberg ") 11 | 12 | (authors "Rudi Grinberg " 13 | "Sora Morimoto " "Max Lantas " 14 | "Manas " "Rusty Key " 15 | "Louis Roché " 16 | "Ulugbek Abdullaev " 17 | "Javier Chávarri ") 18 | 19 | (generate_opam_files true) 20 | 21 | (package 22 | (name vscode-ocaml-platform) 23 | (synopsis "Visual Studio Code extension for OCaml") 24 | (allow_empty) 25 | (depends 26 | (vscode 27 | (= :version)) 28 | (ocaml 29 | (>= 5.3)) 30 | (js_of_ocaml 31 | (>= 6.0)) 32 | (gen_js_api 33 | (= 1.1.4)) 34 | (base 35 | (>= v0.17)) 36 | (promise_jsoo 37 | (>= 0.4.3)) 38 | (jsonoo 39 | (>= 0.3)) 40 | (ocaml-version 41 | (>= 4.0)) 42 | (ppxlib 43 | (>= 0.35)) 44 | (opam-file-format 45 | (>= 2.1.6)) 46 | (ocamlformat 47 | (and 48 | (= 0.27.0) 49 | :with-dev-setup)) 50 | (ocaml-lsp-server :with-dev-setup))) 51 | 52 | (package 53 | (name vscode-interop) 54 | (synopsis 55 | "Interop library compatible with the vscode and vscode-node bindings") 56 | (depends 57 | (ocaml 58 | (>= 4.08)) 59 | (js_of_ocaml 60 | (>= 6.0)) 61 | (gen_js_api 62 | (= 1.1.4)) 63 | (ocamlformat 64 | (and 65 | (= 0.27.0) 66 | :with-dev-setup)) 67 | (ocaml-lsp-server :with-dev-setup))) 68 | 69 | (package 70 | (name vscode) 71 | (synopsis "Visual Studio Code Bindings") 72 | (depends 73 | (ocaml 74 | (>= 4.08)) 75 | (js_of_ocaml 76 | (>= 6.0)) 77 | (gen_js_api 78 | (= 1.1.4)) 79 | (promise_jsoo 80 | (>= 0.4.3)) 81 | (jsonoo 82 | (>= 0.3)) 83 | (vscode-interop 84 | (= :version)) 85 | (ocamlformat 86 | (and 87 | (= 0.27.0) 88 | :with-dev-setup)) 89 | (ocaml-lsp-server :with-dev-setup))) 90 | 91 | (package 92 | (name vscode-node) 93 | (synopsis 94 | "Bindings for Node.js that are used by the Visual Studio Code extension") 95 | (depends 96 | (ocaml 97 | (>= 4.08)) 98 | (js_of_ocaml 99 | (>= 6.0)) 100 | (gen_js_api 101 | (= 1.1.4)) 102 | (promise_jsoo 103 | (>= 0.4.3)) 104 | (jsonoo 105 | (>= 0.3)) 106 | (vscode-interop 107 | (= :version)) 108 | (ocamlformat 109 | (and 110 | (= 0.27.0) 111 | :with-dev-setup)) 112 | (ocaml-lsp-server :with-dev-setup))) 113 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "jsonoo": { 22 | "flake": false, 23 | "locked": { 24 | "lastModified": 1709356063, 25 | "narHash": "sha256-BElpGAAIiC6Y7TY7yTW60Er5YJVwbn179+vdOZB4jNY=", 26 | "owner": "mnxn", 27 | "repo": "jsonoo", 28 | "rev": "39c88c9299ab68d41daa4fb17b885a048e09b8f1", 29 | "type": "github" 30 | }, 31 | "original": { 32 | "owner": "mnxn", 33 | "repo": "jsonoo", 34 | "type": "github" 35 | } 36 | }, 37 | "nix-filter": { 38 | "locked": { 39 | "lastModified": 1731533336, 40 | "narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=", 41 | "owner": "numtide", 42 | "repo": "nix-filter", 43 | "rev": "f7653272fd234696ae94229839a99b73c9ab7de0", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "numtide", 48 | "repo": "nix-filter", 49 | "type": "github" 50 | } 51 | }, 52 | "nixpkgs": { 53 | "locked": { 54 | "lastModified": 1748735090, 55 | "narHash": "sha256-XV5rcZZumE0q6IvqH+jnSPc9hJqICAdDFMBr5CHEW1w=", 56 | "owner": "nixos", 57 | "repo": "nixpkgs", 58 | "rev": "d32aab510c78db1cf9bd7ecbe453799421e3d530", 59 | "type": "github" 60 | }, 61 | "original": { 62 | "owner": "nixos", 63 | "repo": "nixpkgs", 64 | "type": "github" 65 | } 66 | }, 67 | "root": { 68 | "inputs": { 69 | "flake-utils": "flake-utils", 70 | "jsonoo": "jsonoo", 71 | "nix-filter": "nix-filter", 72 | "nixpkgs": "nixpkgs" 73 | } 74 | }, 75 | "systems": { 76 | "locked": { 77 | "lastModified": 1681028828, 78 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 79 | "owner": "nix-systems", 80 | "repo": "default", 81 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 82 | "type": "github" 83 | }, 84 | "original": { 85 | "owner": "nix-systems", 86 | "repo": "default", 87 | "type": "github" 88 | } 89 | } 90 | }, 91 | "root": "root", 92 | "version": 7 93 | } 94 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "vscode-ocaml-platform Nix Flake"; 3 | 4 | inputs.nix-filter.url = "github:numtide/nix-filter"; 5 | inputs.flake-utils.url = "github:numtide/flake-utils"; 6 | inputs.nixpkgs.url = "github:nixos/nixpkgs"; 7 | inputs.jsonoo.url = "github:mnxn/jsonoo"; 8 | inputs.jsonoo.flake = false; 9 | 10 | outputs = { self, nixpkgs, flake-utils, nix-filter, ... }@inputs: 11 | flake-utils.lib.eachDefaultSystem (system: 12 | let 13 | pkgs = import nixpkgs { 14 | inherit system; 15 | config = { allowUnfree = true; }; 16 | }; 17 | inherit (pkgs.ocamlPackages) buildDunePackage; 18 | jsonoo = buildDunePackage { 19 | pname = "jsonoo"; 20 | version = "n/a"; 21 | src = inputs.jsonoo; 22 | nativeBuildInputs = [ pkgs.ocamlPackages.gen_js_api ]; 23 | propagatedBuildInputs = 24 | (with pkgs.ocamlPackages; [ 25 | gen_js_api 26 | js_of_ocaml 27 | js_of_ocaml-ppx 28 | ]); 29 | duneVersion = "3"; 30 | }; 31 | in 32 | rec { 33 | packages = rec { 34 | default = vscode-ocaml-platform; 35 | vscode = buildDunePackage { 36 | pname = "vscode"; 37 | version = "n/a"; 38 | src = ./.; 39 | duneVersion = "3"; 40 | propagatedBuildInputs = with pkgs.ocamlPackages; [ 41 | gen_js_api 42 | js_of_ocaml 43 | jsonoo 44 | promise_jsoo 45 | ]; 46 | checkInputs = with pkgs.ocamlPackages; [ ]; 47 | doCheck = true; 48 | }; 49 | vscode-ocaml-platform = buildDunePackage { 50 | pname = "vscode-ocaml-platform"; 51 | inherit (packages.vscode) src version; 52 | propagatedBuildInputs = [ packages.vscode jsonoo ] ++ 53 | (with pkgs.ocamlPackages; [ 54 | base 55 | gen_js_api 56 | js_of_ocaml 57 | ocaml-version 58 | opam-file-format 59 | ppxlib 60 | promise_jsoo 61 | ]); 62 | duneVersion = "3"; 63 | }; 64 | }; 65 | devShells.default = pkgs.mkShell { 66 | inputsFrom = pkgs.lib.attrValues packages; 67 | buildInputs = with pkgs.ocamlPackages; [ 68 | jsonoo 69 | ocaml-lsp 70 | pkgs.ocamlformat_0_27_0 71 | pkgs.vscode 72 | pkgs.yarn-berry 73 | ]; 74 | shellHook = '' 75 | make npm-deps 76 | ''; 77 | }; 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /languages/META.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "#" 4 | }, 5 | "brackets": [["(", ")"]], 6 | "autoClosingPairs": [ 7 | { "open": "(", "close": ")" }, 8 | { "open": "\"", "close": "\"", "notIn": ["string"] } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /languages/dune.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": ";" 4 | }, 5 | "brackets": [["{", "}"], ["[", "]"], ["(", ")"]], 6 | "autoClosingPairs": [ 7 | { "open": "{", "close": "}" }, 8 | { "open": "[", "close": "]" }, 9 | { "open": "(", "close": ")" }, 10 | { "open": "\"", "close": "\"", "notIn": ["string", "comment"] } 11 | ], 12 | "surroundingPairs": [ 13 | { "open": "[", "close": "]" }, 14 | { "open": "(", "close": ")" }, 15 | { "open": "\"", "close": "\"" } 16 | ], 17 | "wordPattern": { 18 | "pattern": "[A-Za-z_'`][A-Za-z_'.0-9]*" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /languages/menhir.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//", 4 | "blockComment": ["/*", "*/"] 5 | }, 6 | "brackets": [["{", "}"], ["[", "]"], ["(", ")"], ["<", ">"], ["%{", "%}"]], 7 | "autoClosingPairs": [ 8 | ["{", "}"], 9 | ["[", "]"], 10 | ["(", ")"], 11 | ["<", ">"], 12 | ["\"", "\""], 13 | ["'", "'"], 14 | ["%{", "%}"] 15 | ], 16 | "surroundingPairs": [["(", ")"], ["<", ">"], ["\"", "\""]] 17 | } 18 | -------------------------------------------------------------------------------- /languages/oasis.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "#" 4 | }, 5 | "autoClosingPairs": [{ "open": "\"", "close": "\"", "notIn": ["string"] }] 6 | } 7 | -------------------------------------------------------------------------------- /languages/ocaml.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoClosingPairs": [ 3 | { "open": "{", "close": "}" }, 4 | { "open": "[", "close": "]" }, 5 | { "open": "(", "close": ")" }, 6 | { "open": "\"", "close": "\"", "notIn": ["string"] }, 7 | { "open": "(*", "close": "*", "notIn": ["string"] }, 8 | { "open": "[|", "close": "|", "notIn": ["string"] } 9 | ], 10 | "brackets": [["{", "}"], ["[", "]"], ["(", ")"], ["[|", "|]"]], 11 | "comments": { 12 | "blockComment": ["(*", "*)"] 13 | }, 14 | "surroundingPairs": [ 15 | ["{", "}"], 16 | ["[", "]"], 17 | ["(", ")"], 18 | ["'", "'"], 19 | ["\"", "\""] 20 | ], 21 | "indentationRules": { 22 | "decreaseIndentPattern": "^\\s*(end|done|with|in|else)\\b|^\\s*;;", 23 | "increaseIndentPattern": "\\b(begin|object)\\s*$|\\b(let|and)([$&*+\\-/=>@^|<][!?$&*+\\-/=>@^|%:]*)? [a-zA-Z0-9'_-]+( [^ ]+)* =\\s*$|method!?[ \\t]+.*=[ \\t]*$|->[ \\t]*$|\\b(for|while)(_lwt)?[ \\t]+.*[ \\t]+do[ \\t]*$|(\\btry(_lwt)?$|\\bif\\s+.*\\sthen$|\\belse|[:=]\\s*sig|=\\s*struct)\\s*$", 24 | "indentNextLinePattern": "(?!\\bif.*then.*(else.*|(;|[ \\t]in)[ \\t]*$))\\bif|\\bthen[ \\t]*$|\\belse[ \\t]*$$" 25 | }, 26 | "onEnterRules": [ 27 | { 28 | "beforeText": "\\(\\*\\*", 29 | "afterText": "\\*\\)", 30 | "action": { 31 | "indent": "none", 32 | "appendText": " " 33 | } 34 | }, 35 | { 36 | "beforeText": "\\(\\*", 37 | "afterText": "\\*\\)", 38 | "action": { 39 | "indent": "none", 40 | "appendText": " " 41 | } 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /languages/ocamlbuild.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "#" 4 | }, 5 | "brackets": [["(", ")"], ["{", "}"], ["<", ">"]], 6 | "autoClosingPairs": [ 7 | { "open": "(", "close": ")" }, 8 | { "open": "{", "close": "}" }, 9 | { "open": "<", "close": ">" }, 10 | { "open": "\"", "close": "\"", "notIn": ["string"] } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /languages/ocamlformat.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "#" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /languages/ocamllex.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "blockComment": ["(*", "*)"] 4 | }, 5 | "brackets": [["{", "}"], ["[", "]"], ["(", ")"]], 6 | "autoClosingPairs": [ 7 | ["{", "}"], 8 | ["[", "]"], 9 | ["(", ")"], 10 | ["\"", "\""], 11 | ["'", "'"] 12 | ], 13 | "surroundingPairs": [["(", ")"], ["\"", "\""]] 14 | } 15 | -------------------------------------------------------------------------------- /languages/opam-install.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "#" 4 | }, 5 | "brackets": [["{", "}"], ["[", "]"]], 6 | "autoClosingPairs": [["{", "}"], ["[", "]"], ["\"", "\""]], 7 | "surroundingPairs": [["{", "}"], ["[", "]"], ["\"", "\""]] 8 | } 9 | -------------------------------------------------------------------------------- /languages/opam.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "#" 4 | }, 5 | "brackets": [["{", "}"], ["[", "]"]], 6 | "autoClosingPairs": [ 7 | ["{", "}"], 8 | ["[", "]"], 9 | ["\"", "\""], 10 | ["'", "'"], 11 | ["\"\"\"", "\"\"\""] 12 | ], 13 | "surroundingPairs": [ 14 | ["{", "}"], 15 | ["[", "]"], 16 | ["(", ")"], 17 | ["\"", "\""], 18 | ["'", "'"], 19 | ["\"\"\"", "\"\"\""] 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /languages/reason.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoClosingPairs": [ 3 | { "open": "{", "close": "}" }, 4 | { "open": "[", "close": "]" }, 5 | { "open": "(", "close": ")" }, 6 | { "open": "\"", "close": "\"", "notIn": ["string"] }, 7 | { "open": "/**", "close": " */", "notIn": ["string"] } 8 | ], 9 | "brackets": [["{", "}"], ["[", "]"], ["(", ")"]], 10 | "comments": { 11 | "lineComment": "//", 12 | "blockComment": ["/*", "*/"] 13 | }, 14 | "surroundingPairs": [ 15 | ["{", "}"], 16 | ["[", "]"], 17 | ["(", ")"], 18 | ["'", "'"], 19 | ["\"", "\""] 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /snippets/dune-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang": { 3 | "prefix": "lang", 4 | "body": ["(lang $1 $2)"] 5 | }, 6 | "name": { 7 | "prefix": "name", 8 | "body": ["(name $1)"] 9 | }, 10 | "using": { 11 | "prefix": "using", 12 | "body": ["(using $1 $2)"] 13 | }, 14 | "info": { 15 | "prefix": "info", 16 | "body": [ 17 | "(license $1)\n(authors \"$2\")\n(maintainers \"$3\")\n(source (${4|uri,github|} $5))\n(bug_reports \"$6\")\n(homepage \"$7\")\n(documentation \"$8\")" 18 | ] 19 | }, 20 | "package": { 21 | "prefix": "package", 22 | "body": [ 23 | "(package\n (name $1)\n (synopsis \"$2\")\n (description \"$3\")\n (depends $4))" 24 | ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /snippets/dune.json: -------------------------------------------------------------------------------- 1 | { 2 | "alias": { 3 | "prefix": "alias", 4 | "body": ["(alias\n (name $1)\n (deps $2))"] 5 | }, 6 | "env": { 7 | "prefix": "env", 8 | "body": ["(env\n ($1 $2))"] 9 | }, 10 | "executable": { 11 | "prefix": "executable", 12 | "body": ["(executable\n (name $1)\n (public_name $2)\n (libraries $3))"] 13 | }, 14 | "executables": { 15 | "prefix": "executables", 16 | "body": ["(executables\n (names $1)\n (public_names $2)\n (libraries $3))"] 17 | }, 18 | "install": { 19 | "prefix": "install", 20 | "body": ["(install\n (section $1)\n (files ($2.exe as $3)))"] 21 | }, 22 | "library": { 23 | "prefix": "library", 24 | "body": ["(library\n (name $1)\n (public_name $2)\n (libraries $3))"] 25 | }, 26 | "rule": { 27 | "prefix": "rule", 28 | "body": ["(rule\n (alias $1)\n (deps $2)\n (targets $3)\n (action $4))"] 29 | }, 30 | "test": { 31 | "prefix": "test", 32 | "body": ["(test\n (name $1)\n (libraries $2))"] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /snippets/ocaml.json: -------------------------------------------------------------------------------- 1 | { 2 | "lambda": { 3 | "prefix": "fun", 4 | "body": ["fun (${1:pattern}) -> ${2:${1:pattern}}"] 5 | }, 6 | "let .. in": { 7 | "prefix": "let", 8 | "body": ["let ${1:pattern} = ${2:()} in$0"] 9 | }, 10 | "function": { 11 | "prefix": "func", 12 | "body": ["function\n| $0"] 13 | }, 14 | "variant pattern": { 15 | "prefix": "|", 16 | "body": ["| ${1:_} -> $0"] 17 | }, 18 | "let (toplevel)": { 19 | "prefix": "let", 20 | "body": ["let ${1:pattern} = $0"] 21 | }, 22 | "val": { 23 | "prefix": "val", 24 | "body": ["val ${1:name} : $0"] 25 | }, 26 | "sig": { 27 | "prefix": "sig", 28 | "body": ["sig\n ${1}\nend$0"] 29 | }, 30 | "struct": { 31 | "prefix": "struct", 32 | "body": ["struct\n ${1}\nend$0"] 33 | }, 34 | "module": { 35 | "prefix": "module", 36 | "body": ["module ${1:M} = struct\n ${2}\nend$0"] 37 | }, 38 | "module signature": { 39 | "prefix": "module", 40 | "body": ["module ${1:M} : sig\n ${2}\nend$0"] 41 | }, 42 | "module type": { 43 | "prefix": "module", 44 | "body": ["module type ${1:M} = sig\n ${2}\nend$0"] 45 | }, 46 | "match": { 47 | "prefix": "match", 48 | "body": ["match ${1:scrutinee} with\n| ${2:pattern} -> ${3:${2:pattern}}"] 49 | }, 50 | "type declaration": { 51 | "prefix": "type", 52 | "body": ["type ${1:name} = $0"] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /snippets/ocamllex.json: -------------------------------------------------------------------------------- 1 | { 2 | "rule": { 3 | "prefix": "rule", 4 | "body": ["rule ${1:pattern} = parse", "| $0"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src-bindings/interop/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (public_name vscode-interop) 3 | (name interop) 4 | (libraries js_of_ocaml gen_js_api) 5 | (preprocess 6 | (pps gen_js_api.ppx)) 7 | (modes byte)) 8 | -------------------------------------------------------------------------------- /src-bindings/interop/interop.ml: -------------------------------------------------------------------------------- 1 | let iter_set obj field f = function 2 | | Some value -> Ojs.set_prop_ascii obj field (f value) 3 | | None -> () 4 | ;; 5 | 6 | let undefined = Ojs.variable "undefined" 7 | 8 | type 'a or_undefined = 'a option 9 | 10 | let or_undefined_of_js ml_of_js js_val = 11 | if js_val != undefined && js_val != Ojs.null then Some (ml_of_js js_val) else None 12 | ;; 13 | 14 | let or_undefined_to_js ml_to_js = function 15 | | Some ml_val -> ml_to_js ml_val 16 | | None -> undefined 17 | ;; 18 | 19 | type 'a maybe_list = 'a list 20 | 21 | let maybe_list_of_js ml_of_js js_val = 22 | if js_val != undefined && js_val != Ojs.null then Ojs.list_of_js ml_of_js js_val else [] 23 | ;; 24 | 25 | let maybe_list_to_js ml_to_js = function 26 | | [] -> undefined 27 | | xs -> Ojs.list_to_js ml_to_js xs 28 | ;; 29 | 30 | module Regexp = struct 31 | type t = Js_of_ocaml.Regexp.regexp 32 | 33 | let t_of_js : Ojs.t -> Js_of_ocaml.Regexp.regexp = Stdlib.Obj.magic 34 | let t_to_js : Js_of_ocaml.Regexp.regexp -> Ojs.t = Stdlib.Obj.magic 35 | 36 | include 37 | [%js: 38 | val replace 39 | : string 40 | -> regexp:t 41 | -> replacer:(string -> (Ojs.t list[@js.variadic]) -> string) 42 | -> string 43 | [@@js.call]] 44 | 45 | type replacer = 46 | matched:string -> captures:string list -> offset:int -> string:string -> string 47 | 48 | let replace s ~regexp ~replacer = 49 | let rec separate acc = function 50 | | offset :: string :: _ when Ojs.type_of offset = "number" -> 51 | List.rev acc, [%js.to: int] offset, [%js.to: string] string 52 | | capture :: args -> separate ([%js.to: string] capture :: acc) args 53 | | _ -> assert false 54 | (* replacer arguments will always be terminated with a numeric offset and 55 | final string *) 56 | in 57 | let js_replacer matched args = 58 | let captures, offset, string = separate [] args in 59 | replacer ~matched ~captures ~offset ~string 60 | in 61 | replace s ~regexp ~replacer:js_replacer 62 | ;; 63 | end 64 | 65 | module Dict = struct 66 | module StringMap = Map.Make (String) 67 | 68 | let t_of_js value_of_js js_obj = 69 | let ml_map = ref StringMap.empty in 70 | let iter key = 71 | let value = value_of_js (Ojs.get_prop_ascii js_obj key) in 72 | ml_map := StringMap.add key value !ml_map 73 | in 74 | Ojs.iter_properties js_obj iter; 75 | !ml_map 76 | ;; 77 | 78 | let t_to_js value_to_js ml_map = 79 | let to_js (k, v) = k, value_to_js v in 80 | StringMap.to_seq ml_map |> Seq.map to_js |> Array.of_seq |> Ojs.obj 81 | ;; 82 | 83 | let of_alist alist = StringMap.of_seq (List.to_seq alist) 84 | 85 | include StringMap 86 | end 87 | 88 | module Js = struct 89 | type 'a t = (module Ojs.T with type t = 'a) 90 | 91 | module type Generic = sig 92 | type 'a t 93 | 94 | val t_to_js : ('a -> Ojs.t) -> 'a t -> Ojs.t 95 | val t_of_js : (Ojs.t -> 'a) -> Ojs.t -> 'a t 96 | end 97 | 98 | module Unit = struct 99 | type t = unit [@@js] 100 | end 101 | 102 | module Result (Ok : Ojs.T) (Error : Ojs.T) = struct 103 | type t = (Ok.t, Error.t) result 104 | 105 | type js_result = 106 | { case : string 107 | ; ok : Ojs.t 108 | ; error : Ojs.t 109 | } 110 | [@@js] 111 | 112 | let t_of_js js_val = 113 | match js_result_of_js js_val with 114 | | { case = "ok"; ok; _ } -> Ok ([%js.to: Ok.t] ok) 115 | | { case = "error"; error; _ } -> Error ([%js.to: Error.t] error) 116 | | _ -> assert false 117 | ;; 118 | 119 | let t_to_js = function 120 | | Ok ok -> 121 | let ok = [%js.of: Ok.t] ok in 122 | js_result_to_js { case = "ok"; ok; error = undefined } 123 | | Error error -> 124 | let error = [%js.of: Error.t] error in 125 | js_result_to_js { case = "error"; error; ok = undefined } 126 | ;; 127 | end 128 | 129 | module Or_undefined (T : Ojs.T) = struct 130 | type t = T.t or_undefined [@@js] 131 | end 132 | 133 | module Dict (T : Ojs.T) = struct 134 | type t = T.t Dict.t [@@js] 135 | end 136 | end 137 | 138 | module Interface = struct 139 | module Make () = struct 140 | type t = Ojs.t [@@js] 141 | end 142 | 143 | module Extend (Super : Ojs.T) () = struct 144 | type t = Super.t [@@js] 145 | end 146 | 147 | module Generic (Super : Ojs.T) () = struct 148 | type 'a t = Super.t [@@js] 149 | end 150 | end 151 | 152 | module Class = Interface 153 | -------------------------------------------------------------------------------- /src-bindings/interop/interop.mli: -------------------------------------------------------------------------------- 1 | val iter_set : Ojs.t -> string -> ('a -> Ojs.t) -> 'a option -> unit 2 | 3 | type 'a or_undefined = 'a option 4 | 5 | val or_undefined_of_js : (Ojs.t -> 'a) -> Ojs.t -> 'a option 6 | val or_undefined_to_js : ('a -> Ojs.t) -> 'a option -> Ojs.t 7 | 8 | type 'a maybe_list = 'a list 9 | 10 | val maybe_list_of_js : (Ojs.t -> 'a) -> Ojs.t -> 'a list 11 | val maybe_list_to_js : ('a -> Ojs.t) -> 'a list -> Ojs.t 12 | 13 | module Regexp : sig 14 | type t = Js_of_ocaml.Regexp.regexp 15 | 16 | val t_of_js : Ojs.t -> Js_of_ocaml.Regexp.regexp 17 | val t_to_js : Js_of_ocaml.Regexp.regexp -> Ojs.t 18 | 19 | type replacer = 20 | matched:string -> captures:string list -> offset:int -> string:string -> string 21 | 22 | val replace : string -> regexp:t -> replacer:replacer -> string 23 | end 24 | 25 | module Dict : sig 26 | include Map.S with type key = string 27 | 28 | val t_of_js : (Ojs.t -> 'a) -> Ojs.t -> 'a t 29 | val t_to_js : ('a -> Ojs.t) -> 'a t -> Ojs.t 30 | val of_alist : (string * 'a) list -> 'a t 31 | end 32 | 33 | module Js : sig 34 | type 'a t = (module Ojs.T with type t = 'a) 35 | 36 | module type Generic = sig 37 | type 'a t 38 | 39 | val t_to_js : ('a -> Ojs.t) -> 'a t -> Ojs.t 40 | val t_of_js : (Ojs.t -> 'a) -> Ojs.t -> 'a t 41 | end 42 | 43 | module Unit : Ojs.T with type t = unit 44 | module Result (Ok : Ojs.T) (Error : Ojs.T) : Ojs.T with type t = (Ok.t, Error.t) result 45 | module Or_undefined (T : Ojs.T) : Ojs.T with type t = T.t or_undefined 46 | module Dict (T : Ojs.T) : Ojs.T with type t = T.t Dict.t 47 | end 48 | 49 | module Interface : sig 50 | module Make () : Ojs.T with type t = private Ojs.t 51 | module Extend (Super : Ojs.T) () : Ojs.T with type t = private Super.t 52 | module Generic (Super : Ojs.T) () : Js.Generic with type 'a t = private Super.t 53 | end 54 | 55 | module Class = Interface 56 | -------------------------------------------------------------------------------- /src-bindings/node/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name node) 3 | (public_name vscode-node) 4 | (preprocess 5 | (pps gen_js_api.ppx)) 6 | (js_of_ocaml 7 | (javascript_files node_stub.js)) 8 | (libraries vscode-interop promise_jsoo jsonoo gen_js_api js_of_ocaml) 9 | (modes byte)) 10 | -------------------------------------------------------------------------------- /src-bindings/node/node.mli: -------------------------------------------------------------------------------- 1 | val __filename : unit -> string 2 | val __dirname : unit -> string 3 | 4 | module Timeout : sig 5 | include Ojs.T 6 | 7 | val hasRef : t -> bool 8 | val ref : t -> t 9 | val refresh : t -> t 10 | val unref : t -> t 11 | end 12 | 13 | val setInterval : (unit -> unit) -> int -> Timeout.t 14 | val setTimeout : (unit -> unit) -> int -> Timeout.t 15 | val clearTimeout : Timeout.t -> unit 16 | 17 | module Process : sig 18 | val cwd : unit -> string 19 | val platform : string 20 | val arch : string 21 | 22 | module Env : sig 23 | val get : string -> string option 24 | val set : string -> string -> unit 25 | val env : unit -> string Interop.Dict.t 26 | end 27 | end 28 | 29 | module JsError : sig 30 | include Ojs.T with type t = Promise.error 31 | 32 | val message : t -> string 33 | end 34 | 35 | module Buffer : sig 36 | include Ojs.T 37 | 38 | val toString : t -> string 39 | val from : string -> t 40 | val concat : t list -> t 41 | val append : t ref -> t -> unit 42 | 43 | val write 44 | : t 45 | -> string:string 46 | -> ?offset:int 47 | -> ?length:int 48 | -> ?encoding:string 49 | -> unit 50 | -> unit 51 | end 52 | 53 | module Stream : sig 54 | module Readable : sig 55 | include Ojs.T 56 | 57 | type chunk = 58 | [ `String of string 59 | | `Buffer of Buffer.t 60 | ] 61 | 62 | val on 63 | : t 64 | -> [ `Close of unit -> unit 65 | | `Data of chunk:chunk -> unit 66 | | `End of unit -> unit 67 | | `Error of err:JsError.t -> unit 68 | | `Pause of unit -> unit 69 | | `Readable of unit -> unit 70 | | `Resume of unit -> unit 71 | ] 72 | -> unit 73 | end 74 | 75 | module Writable : sig 76 | include Ojs.T 77 | 78 | val on 79 | : t 80 | -> [ `Close of unit -> unit 81 | | `Drain of unit -> unit 82 | | `Error of err:JsError.t -> unit 83 | | `Finish of unit -> unit 84 | | `Pipe of src:t -> unit 85 | | `Unpipe of src:t -> unit 86 | ] 87 | -> unit 88 | 89 | val write : t -> string -> unit 90 | val end_ : t -> unit 91 | end 92 | end 93 | 94 | module Path : sig 95 | val delimiter : char 96 | val sep : char 97 | val basename : string -> string 98 | val dirname : string -> string 99 | val extname : string -> string 100 | val isAbsolute : string -> bool 101 | val join : string list -> string 102 | end 103 | 104 | module Os : sig 105 | val homedir : unit -> string 106 | end 107 | 108 | module Fs : sig 109 | val readDir : string -> (string list, string) result Promise.t 110 | val readFile : string -> string Promise.t 111 | val exists : string -> bool Promise.t 112 | end 113 | 114 | module Net : sig 115 | module Socket : sig 116 | include Ojs.T 117 | 118 | val make : unit -> t 119 | val isPaused : t -> bool 120 | val destroy : t -> unit 121 | val connect : t -> port:int -> host:string -> t 122 | val setTimeout : t -> int -> t 123 | 124 | val on 125 | : t 126 | -> [ `Connect of unit -> unit 127 | | `Timeout of unit -> unit 128 | | `Error of err:JsError.t -> unit 129 | ] 130 | -> unit 131 | end 132 | end 133 | 134 | module ChildProcess : sig 135 | module Options : sig 136 | include Ojs.T 137 | 138 | val create : ?cwd:string -> ?env:string Interop.Dict.t -> unit -> t 139 | end 140 | 141 | type return = 142 | { exitCode : int 143 | ; stdout : string 144 | ; stderr : string 145 | } 146 | 147 | type event = 148 | | Spawned 149 | | Stdout of string 150 | | Stderr of string 151 | | Closed 152 | | ProcessError of JsError.t 153 | 154 | val exec 155 | : ?logger:(event -> unit) 156 | -> ?stdin:string 157 | -> ?options:Options.t 158 | -> string 159 | -> return Promise.t 160 | 161 | val spawn 162 | : ?logger:(event -> unit) 163 | -> ?stdin:string 164 | -> ?options:Options.t 165 | -> string 166 | -> string array 167 | -> return Promise.t 168 | end 169 | -------------------------------------------------------------------------------- /src-bindings/node/node_stub.js: -------------------------------------------------------------------------------- 1 | globalThis.child_process = require("node:child_process"); 2 | globalThis.constants = require("node:constants"); 3 | globalThis.fs = require("node:fs/promises"); 4 | globalThis.net = require("node:net"); 5 | globalThis.os = require("node:os"); 6 | globalThis.path = require("node:path"); 7 | -------------------------------------------------------------------------------- /src-bindings/polka/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name polka) 3 | (preprocess 4 | (pps gen_js_api.ppx)) 5 | (js_of_ocaml 6 | (javascript_files polka_stub.js)) 7 | (libraries vscode-interop promise_jsoo jsonoo js_of_ocaml gen_js_api node) 8 | (modes byte)) 9 | -------------------------------------------------------------------------------- /src-bindings/polka/polka.ml: -------------------------------------------------------------------------------- 1 | open Interop 2 | 3 | module Server = struct 4 | include Interface.Make () 5 | 6 | module Address = struct 7 | include Interface.Make () 8 | include [%js: val port : t -> int [@@js.get]] 9 | include [%js: val address : t -> string [@@js.get]] 10 | end 11 | 12 | include 13 | [%js: 14 | val close : t -> ?callback:(Node.JsError.t or_undefined -> unit) -> unit -> t 15 | [@@js.call "close"] 16 | 17 | val address : t -> Address.t or_undefined [@@js.call] 18 | val on : t -> string -> Ojs.t -> unit [@@js.call]] 19 | 20 | let on t = function 21 | | `Close f -> on t "close" @@ [%js.of: unit -> unit] f 22 | | `Error f -> on t "error" @@ [%js.of: err:Node.JsError.t -> unit] f 23 | ;; 24 | end 25 | 26 | type polka = Ojs.t [@@js] 27 | 28 | module Middleware = struct 29 | module Request = struct 30 | include Interface.Make () 31 | end 32 | 33 | module Response = struct 34 | include Interface.Make () 35 | end 36 | 37 | type t = request:Request.t -> response:Response.t -> next:(unit -> polka) -> polka 38 | [@@js] 39 | end 40 | 41 | include 42 | [%js: 43 | val create : unit -> polka [@@js.global "polka"] 44 | 45 | val listen_ 46 | : polka 47 | -> int 48 | -> ?hostname:string 49 | -> ?callback:(unit -> unit) 50 | -> unit 51 | -> polka 52 | [@@js.call "listen"] 53 | 54 | val get_ : polka -> string -> (unit -> unit) -> polka [@@js.call "get"] 55 | val use_ : polka -> (Middleware.t list[@js.variadic]) -> polka [@@js.call "use"] 56 | val server : polka -> Server.t [@@js.get]] 57 | 58 | let get path callback t = get_ t path callback 59 | let listen port ?hostname ?callback t = listen_ t port ?hostname ?callback 60 | let use middlewares t = use_ t middlewares 61 | 62 | module Sirv = struct 63 | module Options = struct 64 | include Interface.Make () 65 | include [%js: val create : dev:bool -> t [@@js.builder]] 66 | end 67 | 68 | include 69 | [%js: 70 | val serve : string -> ?options:Options.t -> unit -> (Middleware.t[@js.dummy]) 71 | [@@js.global "sirv"]] 72 | end 73 | -------------------------------------------------------------------------------- /src-bindings/polka/polka.mli: -------------------------------------------------------------------------------- 1 | open Interop 2 | 3 | module Server : sig 4 | module Address : sig 5 | include Ojs.T 6 | 7 | val port : t -> int 8 | val address : t -> string 9 | end 10 | 11 | include Ojs.T 12 | 13 | val close : t -> ?callback:(Node.JsError.t or_undefined -> unit) -> unit -> t 14 | val address : t -> Address.t or_undefined 15 | (* TODO: the return type can also be a string in case the server uses a pipe 16 | or Unix Domain Socket, but we don't handle that case *) 17 | 18 | val on : t -> [ `Close of unit -> unit | `Error of err:Node.JsError.t -> unit ] -> unit 19 | end 20 | 21 | type polka 22 | 23 | module Middleware : sig 24 | module Request : sig 25 | include Ojs.T 26 | end 27 | 28 | module Response : sig 29 | include Ojs.T 30 | end 31 | 32 | type t = request:Request.t -> response:Response.t -> next:(unit -> polka) -> polka 33 | 34 | include Ojs.T with type t := t 35 | end 36 | 37 | val create : unit -> polka 38 | val listen : int -> ?hostname:string -> ?callback:(unit -> unit) -> polka -> unit -> polka 39 | val get : string -> (unit -> unit) -> polka -> polka 40 | val use : Middleware.t list -> polka -> polka 41 | val server : polka -> Server.t 42 | 43 | module Sirv : sig 44 | module Options : sig 45 | include Ojs.T 46 | 47 | val create : dev:bool -> t 48 | end 49 | 50 | val serve : string -> ?options:Options.t -> unit -> Middleware.t 51 | end 52 | -------------------------------------------------------------------------------- /src-bindings/polka/polka_stub.js: -------------------------------------------------------------------------------- 1 | globalThis.polka = require("polka"); 2 | globalThis.sirv = require("sirv"); 3 | -------------------------------------------------------------------------------- /src-bindings/vscode/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (public_name vscode) 3 | (preprocess 4 | (pps gen_js_api.ppx)) 5 | (js_of_ocaml 6 | (javascript_files vscode_stub.js)) 7 | (libraries vscode-interop promise_jsoo jsonoo js_of_ocaml gen_js_api) 8 | (modes byte)) 9 | -------------------------------------------------------------------------------- /src-bindings/vscode/vscode_stub.js: -------------------------------------------------------------------------------- 1 | globalThis.vscode = require("vscode"); 2 | -------------------------------------------------------------------------------- /src-bindings/vscode_languageclient/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name vscode_languageclient) 3 | (public_name vscode.languageclient) 4 | (preprocess 5 | (pps gen_js_api.ppx)) 6 | (js_of_ocaml 7 | (javascript_files vscode_languageclient_stub.js)) 8 | (libraries vscode-interop vscode promise_jsoo jsonoo js_of_ocaml gen_js_api) 9 | (modes byte)) 10 | -------------------------------------------------------------------------------- /src-bindings/vscode_languageclient/vscode_languageclient_stub.js: -------------------------------------------------------------------------------- 1 | globalThis.vscode_languageclient = require("vscode-languageclient"); 2 | -------------------------------------------------------------------------------- /src/ast_editor.mli: -------------------------------------------------------------------------------- 1 | (** Register the custom editor *) 2 | 3 | val register : Vscode.ExtensionContext.t -> Extension_instance.t -> unit 4 | -------------------------------------------------------------------------------- /src/ast_editor_state.mli: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | (** This module's interface is private to [Ast_editor.t]. It is in a separate 4 | module to avoid circular dependencies with [Extension_instance.t] *) 5 | 6 | type t 7 | 8 | type ast_mode = 9 | | Original_ast 10 | | Preprocessed_ast 11 | 12 | val make : unit -> t 13 | val find_original_doc_by_pp_uri : t -> Uri.t -> string option 14 | val find_webview_by_doc : t -> Uri.t -> WebView.t option 15 | val associate_origin_and_pp : t -> origin_uri:Uri.t -> pp_doc_uri:Uri.t -> unit 16 | val get_current_ast_mode : t -> ast_mode 17 | val set_current_ast_mode : t -> ast_mode -> unit 18 | val get_hover_disposable : t -> Disposable.t option 19 | val set_hover_disposable : t -> Disposable.t option -> unit 20 | val entry_exists : t -> origin_doc:Uri.t -> pp_doc:Uri.t -> bool 21 | val on_origin_update_content : t -> Uri.t -> unit 22 | val pp_status : t -> Uri.t -> [ `Absent_or_pped | `Original ] 23 | val remove_doc_entries : t -> Uri.t -> unit 24 | val set_webview : t -> Uri.t -> WebView.t -> unit 25 | val remove_dirty_original_doc : t -> pp_uri:Uri.t -> unit 26 | val remove_webview : t -> Uri.t -> unit 27 | -------------------------------------------------------------------------------- /src/cm_editor.ml: -------------------------------------------------------------------------------- 1 | open Import 2 | open Interop 3 | 4 | module Cm_document : sig 5 | include CustomDocument.T 6 | 7 | val content : t -> instance:Extension_instance.t -> (string, string) result Promise.t 8 | val onDidChange : t -> unit Event.t 9 | val create : uri:Uri.t -> t 10 | end = struct 11 | include CustomDocument 12 | 13 | include 14 | [%js: 15 | val onDidChange : t -> unit Event.t [@@js.get] 16 | 17 | val create : uri:Uri.t -> onDidChange:unit Event.t -> dispose:(unit -> unit) -> t 18 | [@@js.builder]] 19 | 20 | let content t ~instance = 21 | let uri = uri t in 22 | let file_path = Uri.fsPath uri in 23 | let command = 24 | let sandbox = Extension_instance.sandbox instance in 25 | Sandbox.get_command sandbox "ocamlobjinfo" [ file_path ] 26 | in 27 | Cmd.output command 28 | ;; 29 | 30 | let create ~(uri : Uri.t) = 31 | let module EventEmitter = EventEmitter.Make (Js.Unit) in 32 | let onDidChange_event_emitter = EventEmitter.make () in 33 | let onDidChange_event = EventEmitter.event onDidChange_event_emitter in 34 | let watcher = 35 | Workspace.createFileSystemWatcher 36 | (`String (Uri.fsPath uri)) 37 | ~ignoreCreateEvents:true 38 | ~ignoreDeleteEvents:true 39 | () 40 | in 41 | let disposable = 42 | FileSystemWatcher.onDidChange 43 | watcher 44 | ~listener:(fun _ -> EventEmitter.fire onDidChange_event_emitter ()) 45 | () 46 | in 47 | create ~uri ~onDidChange:onDidChange_event ~dispose:(fun () -> 48 | Disposable.dispose disposable) 49 | ;; 50 | end 51 | 52 | let resolveCustomEditor instance ~document ~webviewPanel ~token:_ : unit Promise.t = 53 | let open Promise.Syntax in 54 | let disposables = Stack.create () in 55 | let update_content document = 56 | let+ content = Cm_document.content document ~instance in 57 | match content with 58 | | Ok file_content -> 59 | let webview = WebviewPanel.webview webviewPanel in 60 | WebView.set_html webview (Printf.sprintf "
%s
" file_content) 61 | | Error e -> 62 | let uri = Cm_document.uri document in 63 | let path = Uri.path uri in 64 | let file_name = Node.Path.basename path in 65 | let (_ : 'a option Promise.t) = 66 | Window.showErrorMessage 67 | ~message: 68 | (Printf.sprintf 69 | "Error while trying to read content from %s file. %s" 70 | file_name 71 | e) 72 | () 73 | in 74 | () 75 | in 76 | let (_ : unit) = 77 | Cm_document.onDidChange 78 | document 79 | ~listener:(fun () -> 80 | let (_ : unit Promise.t) = update_content document in 81 | ()) 82 | () 83 | |> Stack.push disposables 84 | in 85 | let (_ : unit) = 86 | WebviewPanel.onDidDispose 87 | webviewPanel 88 | ~listener:(fun () -> 89 | Disposable.from (Stack.to_list disposables) |> Disposable.dispose) 90 | () 91 | |> Stack.push disposables 92 | in 93 | update_content document 94 | ;; 95 | 96 | let openCustomDocument ~(uri : Uri.t) ~openContext:_ ~token:_ : Cm_document.t Promise.t = 97 | let document = Cm_document.create ~uri in 98 | Promise.resolve document 99 | ;; 100 | 101 | let register (extension : ExtensionContext.t) (instance : Extension_instance.t) = 102 | let module CustomReadonlyEditorProvider = CustomReadonlyEditorProvider.Make (Cm_document) 103 | in 104 | let editorProvider = 105 | CustomReadonlyEditorProvider.create 106 | ~resolveCustomEditor:(resolveCustomEditor instance) 107 | ~openCustomDocument 108 | in 109 | let disposable = 110 | Vscode.Window.registerCustomReadonlyEditorProvider 111 | (module Cm_document) 112 | ~viewType:"cm-files-editor" 113 | ~provider:editorProvider 114 | ~options: 115 | (Vscode.RegisterCustomEditorProviderOptions.create 116 | ~supportsMultipleEditorsPerDocument:true 117 | ()) 118 | () 119 | in 120 | Vscode.ExtensionContext.subscribe extension ~disposable 121 | ;; 122 | -------------------------------------------------------------------------------- /src/cm_editor.mli: -------------------------------------------------------------------------------- 1 | val register : Vscode.ExtensionContext.t -> Extension_instance.t -> unit 2 | -------------------------------------------------------------------------------- /src/cmd.mli: -------------------------------------------------------------------------------- 1 | (** Interface for running system commands *) 2 | 3 | open Import 4 | 5 | type spawn = 6 | { bin : Path.t 7 | ; args : string list 8 | } 9 | 10 | type t = 11 | | Shell of string 12 | | Spawn of spawn 13 | 14 | type stdout = string 15 | type stderr = string 16 | 17 | (* surround a string with quotes if it has spaces *) 18 | val quote : string -> string 19 | val to_spawn : t -> spawn 20 | val append : spawn -> string list -> spawn 21 | val check_spawn : ?env:string Interop.Dict.t -> spawn -> spawn Or_error.t Promise.t 22 | val check : ?env:string Interop.Dict.t -> t -> t Or_error.t Promise.t 23 | val log : ?result:ChildProcess.return -> t -> unit 24 | 25 | val output 26 | : ?cwd:Path.t 27 | -> ?env:string Interop.Dict.t 28 | -> ?stdin:string 29 | -> t 30 | -> (stdout, stderr) result Promise.t 31 | 32 | val equal_spawn : spawn -> spawn -> bool 33 | 34 | val run 35 | : ?cwd:Path.t 36 | -> ?env:string Interop.Dict.t 37 | -> ?stdin:stderr 38 | -> t 39 | -> ChildProcess.return Promise.t 40 | -------------------------------------------------------------------------------- /src/custom_requests.mli: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | (** Module to include custom requests to the language server, i.e., requests 4 | that are not originally in LSP but are customly added by a specific language 5 | server. 6 | 7 | If you want to add support for a custom request, add [custom_request] for 8 | the custom request to the implementation of this module. *) 9 | 10 | type ('params, 'response) custom_request 11 | 12 | val send_request 13 | : LanguageClient.t 14 | -> ('params, 'resp) custom_request 15 | -> 'params 16 | -> 'resp Promise.t 17 | 18 | val switchImplIntf : (string, string array) custom_request 19 | val inferIntf : (string, string) custom_request 20 | val typedHoles : (Uri.t, Range.t list) custom_request 21 | 22 | module Type_selection : sig 23 | type params 24 | 25 | type response = 26 | { index : int 27 | ; type_ : string 28 | ; enclosings : Range.t array 29 | } 30 | 31 | val make 32 | : uri:Uri.t 33 | -> at:[ `Position of Position.t | `Range of Range.t ] 34 | -> index:int 35 | -> verbosity:int 36 | -> params 37 | 38 | val request : (params, response) custom_request 39 | end 40 | 41 | module Construct : sig 42 | type params 43 | 44 | type response = 45 | { position : Range.t 46 | ; result : string list 47 | } 48 | 49 | val make 50 | : uri:Uri.t 51 | -> position:Position.t 52 | -> ?depth:int option 53 | -> ?with_values:[ `None | `Local ] option 54 | -> unit 55 | -> params 56 | 57 | val request : (params, response) custom_request 58 | end 59 | 60 | module Merlin_jump : sig 61 | type params 62 | type response = (string * Position.t) list 63 | 64 | val make : uri:Uri.t -> position:Position.t -> params 65 | val request : (params, response) custom_request 66 | end 67 | 68 | module Type_search : sig 69 | type type_search_result = 70 | { name : string 71 | ; typ : string 72 | ; loc : Range.t 73 | ; doc : MarkupContent.t option 74 | ; cost : int 75 | ; constructible : string 76 | } 77 | 78 | type params 79 | type response = type_search_result list 80 | 81 | val make 82 | : uri:Uri.t 83 | -> position:Position.t 84 | -> limit:int 85 | -> query:string 86 | -> with_doc:bool 87 | -> ?doc_format:MarkupKind.t option 88 | -> unit 89 | -> params 90 | 91 | val request : (params, response) custom_request 92 | end 93 | -------------------------------------------------------------------------------- /src/documentation_server.ml: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | type t' = 4 | { server : Polka.Server.t 5 | ; path : Path.t 6 | } 7 | 8 | type t = t' option ref 9 | 10 | let start ~path = 11 | Promise.make 12 | @@ fun ~resolve ~reject:_ -> 13 | let polka = Polka.create () in 14 | let options = Polka.Sirv.Options.create ~dev:true in 15 | let serve = 16 | polka 17 | |> Polka.use [ Polka.Sirv.serve (path |> Path.to_string) ~options () ] 18 | |> Polka.listen 0 ~hostname:"localhost" ~callback:(fun () -> 19 | let server = Polka.server polka in 20 | resolve (Ok (ref (Some { server; path })))) 21 | in 22 | let polka = serve () in 23 | let server = Polka.server polka in 24 | Polka.Server.on server (`Error (fun ~err -> resolve (Error err))) 25 | ;; 26 | 27 | let get (t : t) = 28 | match !t with 29 | | None -> failwith "document server: already disposed" 30 | | Some t -> t 31 | ;; 32 | 33 | let path (t : t) = (get t).path 34 | 35 | let address t = 36 | match Polka.Server.address (get t).server with 37 | | None -> 38 | (* if it's [None], server must not be listening, but we aim to have server 39 | only if it's listening *) 40 | assert false 41 | | Some a -> a 42 | ;; 43 | 44 | let port t = Polka.Server.Address.port (address t) 45 | let host t = Polka.Server.Address.address (address t) 46 | 47 | let dispose (t : t) = 48 | match !t with 49 | | None -> Disposable.make ~dispose:(fun () -> ()) 50 | | Some server -> 51 | t := None; 52 | Disposable.make ~dispose:(fun () -> 53 | let (_ : Polka.Server.t) = 54 | Polka.Server.close 55 | server.server 56 | ~callback:(fun error -> 57 | match error with 58 | | None -> () 59 | | Some error -> 60 | show_message 61 | `Warn 62 | "Error closing server: %s" 63 | (Ojs.string_of_js (Promise.error_to_js error))) 64 | () 65 | in 66 | ()) 67 | ;; 68 | -------------------------------------------------------------------------------- /src/documentation_server.mli: -------------------------------------------------------------------------------- 1 | type t 2 | 3 | val start : path:Path.t -> (t, Node.JsError.t) result Promise.t 4 | val path : t -> Path.t 5 | val port : t -> int 6 | val host : t -> string 7 | val dispose : t -> Vscode.Disposable.t 8 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name vscode_ocaml_platform) 3 | (preprocess 4 | (pps gen_js_api.ppx)) 5 | (libraries 6 | base 7 | gen_js_api 8 | js_of_ocaml 9 | jsonoo 10 | node 11 | ocaml-version 12 | opam-file-format 13 | polka 14 | promise_jsoo 15 | vscode 16 | vscode_languageclient 17 | ppxlib 18 | ppx_tools) 19 | (modes js)) 20 | -------------------------------------------------------------------------------- /src/dune_formatter.ml: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | let get_formatter instance ~document ~options:_ ~token:_ = 4 | let endLine = TextDocument.lineCount document - 1 in 5 | let endCharacter = 6 | TextDocument.lineAt document ~line:endLine |> TextLine.text |> String.length 7 | in 8 | (* selects entire document range *) 9 | let range = 10 | Range.makeCoordinates ~startLine:0 ~startCharacter:0 ~endLine ~endCharacter 11 | in 12 | (* text of entire document *) 13 | let document_text = TextDocument.getText document ~range () in 14 | let command = 15 | let sandbox = Extension_instance.sandbox instance in 16 | Sandbox.get_command sandbox "dune" [ "format-dune-file" ] 17 | in 18 | let output = 19 | let open Promise.Result.Syntax in 20 | let* command = Cmd.check command in 21 | Cmd.output ~stdin:document_text command 22 | in 23 | let promise = 24 | let open Promise.Syntax in 25 | let+ output = output in 26 | match output with 27 | | Ok newText -> Some [ TextEdit.replace ~range ~newText ] 28 | | Error msg -> 29 | show_message `Error "Dune formatting failed: %s" msg; 30 | Some [] 31 | in 32 | `Promise promise 33 | ;; 34 | 35 | let register extension instance = 36 | [ "dune"; "dune-project"; "dune-workspace" ] 37 | |> List.map ~f:(fun language -> 38 | let selector = `Filter (DocumentFilter.create ~scheme:"file" ~language ()) in 39 | let provider = 40 | DocumentFormattingEditProvider.create 41 | ~provideDocumentFormattingEdits:(get_formatter instance) 42 | in 43 | Languages.registerDocumentFormattingEditProvider ~selector ~provider) 44 | |> List.iter ~f:(fun disposable -> ExtensionContext.subscribe extension ~disposable) 45 | ;; 46 | -------------------------------------------------------------------------------- /src/dune_formatter.mli: -------------------------------------------------------------------------------- 1 | (** register formatters for dune, dune-project, and dune-workspace files; takes 2 | care of subscribing of the disposable to the execution context *) 3 | val register : Vscode.ExtensionContext.t -> Extension_instance.t -> unit 4 | -------------------------------------------------------------------------------- /src/dune_task_provider.ml: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | let task_type = "dune" 4 | let definition = TaskDefinition.create ~type_:task_type () 5 | let source = task_type 6 | let problemMatchers = [ "$ocamlc" ] 7 | 8 | (* the ocamlc matcher is not able to parse ocaml compiler errors unless they 9 | follow the short style. *) 10 | let env = Interop.Dict.of_alist [ "OCAML_ERROR_STYLE", "short" ] 11 | 12 | module Setting = struct 13 | type t = bool 14 | 15 | let of_json json = 16 | let open Jsonoo.Decode in 17 | bool json 18 | ;; 19 | 20 | let to_json (t : t) = 21 | let open Jsonoo.Encode in 22 | bool t 23 | ;; 24 | 25 | let t = 26 | Settings.create_setting ~scope:Workspace ~key:"dune.autoDetect" ~of_json ~to_json 27 | ;; 28 | end 29 | 30 | let folder_relative_path folders file = 31 | List.fold_left ~init:None folders ~f:(fun acc (folder : WorkspaceFolder.t) -> 32 | match acc with 33 | | Some _ -> acc 34 | | None -> 35 | let prefix = Uri.fsPath (WorkspaceFolder.uri folder) in 36 | (match String.chop_prefix file ~prefix with 37 | | None -> acc 38 | | Some without_prefix -> Some (folder, without_prefix))) 39 | ;; 40 | 41 | let get_shell_execution sandbox options = 42 | let command = Sandbox.get_command sandbox "dune" [ "build" ] in 43 | Cmd.log command; 44 | match command with 45 | | Shell commandLine -> ShellExecution.makeCommandLine ~commandLine ~options () 46 | | Spawn { bin; args } -> 47 | let command = `String (Path.to_string bin) in 48 | let args = List.map ~f:(fun a -> `String a) args in 49 | ShellExecution.makeCommandArgs ~command ~args ~options () 50 | ;; 51 | 52 | let compute_tasks token sandbox = 53 | let open Promise.Syntax in 54 | let folders = Workspace.workspaceFolders () in 55 | let excludes = 56 | (* ignoring dune files from _build, _opam, _esy *) 57 | `String "{**/_*}" 58 | in 59 | let includes = `String "**/{dune,dune-project,dune-workspace}" in 60 | let+ dunes = Workspace.findFiles ~includes ~excludes ~token () in 61 | let tasks = 62 | List.map dunes ~f:(fun dune -> 63 | let scope, relative_path = 64 | match folder_relative_path folders (Uri.fsPath dune) with 65 | | None -> TaskScope.Workspace, Uri.fsPath dune 66 | | Some (folder, relative_path) -> TaskScope.Folder folder, relative_path 67 | in 68 | let name = Printf.sprintf "build %s" relative_path in 69 | let execution = 70 | let cwd = Stdlib.Filename.dirname (Uri.fsPath dune) in 71 | let options = ShellExecutionOptions.create ~env ~cwd () in 72 | get_shell_execution sandbox options 73 | in 74 | let task = 75 | Task.make 76 | ~definition 77 | ~scope 78 | ~source 79 | ~name 80 | ~problemMatchers 81 | ~execution:(`ShellExecution execution) 82 | () 83 | in 84 | Task.set_group task TaskGroup.build; 85 | task) 86 | in 87 | Some tasks 88 | ;; 89 | 90 | let provide_tasks instance ~token = 91 | match Settings.get ~section:"ocaml" Setting.t with 92 | | None | Some false -> `Promise (Promise.return None) 93 | | Some true -> 94 | let sandbox = Extension_instance.sandbox instance in 95 | `Promise (compute_tasks token sandbox) 96 | ;; 97 | 98 | let resolve_task ~task ~token:_ = `Value (Some task) 99 | 100 | let register extension instance = 101 | let provideTasks = provide_tasks instance in 102 | let resolveTask = resolve_task in 103 | let provider = TaskProvider.Default.create ~provideTasks ~resolveTask in 104 | let disposable = Tasks.registerTaskProvider ~type_:task_type ~provider in 105 | ExtensionContext.subscribe extension ~disposable 106 | ;; 107 | -------------------------------------------------------------------------------- /src/dune_task_provider.mli: -------------------------------------------------------------------------------- 1 | (** Basic TaskProvider for dune. 2 | 3 | This provider creates one task per dune, dune-project, dune-workspace, file 4 | that it can find in the workspace. It doesn't parse the dune files. *) 5 | 6 | (** registers dune task provider within the vscode task providers; takes care of 7 | subscribing the disposable to the extension context *) 8 | val register : Vscode.ExtensionContext.t -> Extension_instance.t -> unit 9 | -------------------------------------------------------------------------------- /src/earlybird.ml: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | module VariableGetClosureCodeLocation = struct 4 | let command = "variableGetClosureCodeLocation" 5 | 6 | module Args = struct 7 | type t = { handle : int } [@@js] 8 | end 9 | 10 | module Result = struct 11 | type position = int * int [@@js] 12 | 13 | let position_to_vscode (line, character) = 14 | Position.make ~line:(line - 1) ~character:(character - 1) 15 | ;; 16 | 17 | type range = 18 | { source : string 19 | ; pos : position 20 | ; end_ : position [@js "end_"] 21 | } 22 | [@@js] 23 | 24 | let range_to_vscode { pos; end_; _ } = 25 | let start = position_to_vscode pos in 26 | let end_ = position_to_vscode end_ in 27 | Range.makePositions ~start ~end_ 28 | ;; 29 | 30 | type t = { location : range option } [@@js] 31 | end 32 | end 33 | 34 | let debugType = Extension_consts.Debuggers.earlybird 35 | 36 | let check_earlybird_available sandbox = 37 | let earlybird_help = 38 | (* earlybird <= 1.1.0 doesn't have --version *) 39 | Sandbox.get_command sandbox "ocamlearlybird" [ "--help" ] 40 | in 41 | Cmd.output earlybird_help 42 | |> Promise.Result.fold 43 | ~ok:(fun (_ : string) -> ()) 44 | ~error:(fun (_ : string) -> 45 | "Debugging failed: `earlybird` is not installed in the current sandbox.\n\n\ 46 | Hint: $ opam install earlybird") 47 | ;; 48 | 49 | let createDebugAdapterDescriptor ~instance ~session:_ ~executable:_ = 50 | let sandbox = Extension_instance.sandbox instance in 51 | let promise = 52 | let open Promise.Syntax in 53 | let* res = check_earlybird_available sandbox in 54 | match res with 55 | | Ok () -> 56 | let command = Sandbox.get_command sandbox "ocamlearlybird" [ "debug" ] in 57 | let { Cmd.bin; args } = Cmd.to_spawn command in 58 | let result = DebugAdapterExecutable.make ~command:(Path.to_string bin) ~args () in 59 | Promise.return (Some (`Executable result)) 60 | | Error s -> Promise.reject (Ojs.string_to_js s) 61 | in 62 | `Promise promise 63 | ;; 64 | 65 | let register extension instance = 66 | let createDebugAdapterDescriptor = createDebugAdapterDescriptor ~instance in 67 | let factory = DebugAdapterDescriptorFactory.create ~createDebugAdapterDescriptor in 68 | let disposable = Debug.registerDebugAdapterDescriptorFactory ~debugType ~factory in 69 | ExtensionContext.subscribe extension ~disposable; 70 | let callback ~args:_ = 71 | let open Promise.Syntax in 72 | let defaultUri = 73 | Option.map (Workspace.rootPath ()) ~f:(fun path -> Uri.parse path ()) 74 | in 75 | let filters = Interop.Dict.singleton "OCaml Bytecode Executable" [ "bc" ] in 76 | let options = 77 | OpenDialogOptions.create 78 | ~canSelectFiles:true 79 | ~canSelectFolders:false 80 | ~canSelectMany:false 81 | ?defaultUri 82 | ~filters 83 | ~openLabel:"Debug" 84 | ~title:"OCaml earlybird (experimental)" 85 | () 86 | in 87 | let result = 88 | let+ uri = Window.showOpenDialog ~options () in 89 | match uri with 90 | | Some [ uri ] -> Some (Uri.fsPath uri) 91 | | _ -> None 92 | in 93 | [%js.of: string option Promise.t] result 94 | in 95 | let disposable = 96 | Commands.registerCommandReturn 97 | ~command:Extension_consts.Commands.ask_debug_program 98 | ~callback 99 | in 100 | ExtensionContext.subscribe extension ~disposable 101 | ;; 102 | -------------------------------------------------------------------------------- /src/earlybird.mli: -------------------------------------------------------------------------------- 1 | module VariableGetClosureCodeLocation : sig 2 | val command : string 3 | 4 | module Args : sig 5 | type t = { handle : int } 6 | 7 | val t_to_js : t -> Ojs.t 8 | end 9 | 10 | module Result : sig 11 | type position 12 | 13 | type range = 14 | { source : string 15 | ; pos : position 16 | ; end_ : position 17 | } 18 | 19 | val range_to_vscode : range -> Vscode.Range.t 20 | 21 | type t = { location : range option } 22 | 23 | val t_of_js : Ojs.t -> t 24 | end 25 | end 26 | 27 | val register : Vscode.ExtensionContext.t -> Extension_instance.t -> unit 28 | -------------------------------------------------------------------------------- /src/esy.mli: -------------------------------------------------------------------------------- 1 | (** Provide an interface to Esy. 2 | 3 | The functions in this module either use the result of esy commands, or use 4 | the filesystem to get the state of esy configurations. *) 5 | 6 | open Import 7 | 8 | module Manifest : sig 9 | type t 10 | 11 | val equal : t -> t -> bool 12 | val path : t -> Path.t 13 | val of_path : Path.t -> t 14 | end 15 | 16 | (** A package installed in an Esy sandbox *) 17 | module Package : sig 18 | type t 19 | 20 | (** {4 Properties} *) 21 | 22 | val name : t -> string 23 | val version : t -> string 24 | val documentation : t -> string option 25 | val synopsis : t -> string option 26 | val has_dependencies : t -> bool 27 | val dependencies : t -> (t list, string) result Promise.t 28 | end 29 | 30 | type t 31 | 32 | val make : unit -> t option Promise.t 33 | 34 | type discover = 35 | { manifest : Manifest.t 36 | ; status : (unit, string) result 37 | } 38 | 39 | val discover : dir:Path.t -> discover list Promise.t 40 | val find_manifest_in_dir : Path.t -> Manifest.t option Promise.t 41 | val setup_sandbox : t -> Manifest.t -> unit Or_error.t Promise.t 42 | 43 | (** {4 Working with packages} *) 44 | 45 | (** Return the list of installed packages in the given opam switch. *) 46 | val packages : t -> Manifest.t -> (Package.t list, string) result Promise.t 47 | 48 | (** Return the list of root packages in the given opam switch. 49 | 50 | Root packages are packages that have been installed by the user, as opposed 51 | to packages that have been installed as a dependency of another package. *) 52 | val root_packages : t -> Manifest.t -> (Package.t list, string) result Promise.t 53 | 54 | (** Install a package in the Esy sandbox. *) 55 | val install : t -> Manifest.t -> packages:string list -> Cmd.t 56 | 57 | (** {4 General utilities} *) 58 | 59 | (** Execute an esy sub-command with in the given sandbox. *) 60 | val exec : t -> Manifest.t -> args:string list -> Cmd.t 61 | 62 | (** Check that two instances of [Esy] are equal. *) 63 | val equal : t -> t -> bool 64 | -------------------------------------------------------------------------------- /src/extension_commands.mli: -------------------------------------------------------------------------------- 1 | (** Module to manage commands[1] across the extension. 2 | 3 | Module does not have public API for command creation on purpose. One should 4 | only create new commands in [extension_commands.ml] using [command] function 5 | and expose them here if they want to. 6 | 7 | All commands are registered using [register_all_commands] in 8 | [Vscode_ocaml_platform.activate]. 9 | 10 | [1] https://code.visualstudio.com/api/references/vscode-api#commands *) 11 | 12 | (** Registers commands with vscode. Should be called in 13 | [Vscode_ocaml_platform.activate]. It subscribes the disposables to the 14 | extension context provided. *) 15 | val register_all_commands : Vscode.ExtensionContext.t -> Extension_instance.t -> unit 16 | 17 | val register : id:string -> (Extension_instance.t -> args:Ojs.t list -> unit) -> unit 18 | 19 | val register_text_editor 20 | : id:string 21 | -> (Extension_instance.t 22 | -> textEditor:Vscode.TextEditor.t 23 | -> edit:Vscode.TextEditorEdit.t 24 | -> args:Ojs.t list 25 | -> unit) 26 | -> unit 27 | -------------------------------------------------------------------------------- /src/extension_consts.ml: -------------------------------------------------------------------------------- 1 | let ocaml_prefixed key = "ocaml." ^ key 2 | 3 | module Commands = struct 4 | let select_sandbox = ocaml_prefixed "select-sandbox" 5 | let install_ocaml_lsp_server = ocaml_prefixed "install-ocaml-lsp-server" 6 | let upgrade_ocaml_lsp_server = ocaml_prefixed "update-ocaml-lsp-server" 7 | let restart_language_server = ocaml_prefixed "server.restart" 8 | let select_sandbox_and_open_terminal = ocaml_prefixed "open-terminal-select" 9 | let open_terminal = ocaml_prefixed "open-terminal" 10 | let switch_impl_intf = ocaml_prefixed "switch-impl-intf" 11 | let remove_switch = ocaml_prefixed "remove-switch" 12 | let refresh_switches = ocaml_prefixed "refresh-switches" 13 | let refresh_sandbox = ocaml_prefixed "refresh-sandbox" 14 | let upgrade_sandbox = ocaml_prefixed "upgrade-sandbox" 15 | let install_sandbox = ocaml_prefixed "install-sandbox" 16 | let uninstall_sandbox_package = ocaml_prefixed "uninstall-sandbox-package" 17 | let open_switches_documentation = ocaml_prefixed "open-switches-documentation" 18 | let open_sandbox_documentation = ocaml_prefixed "open-sandbox-documentation" 19 | let stop_documentation_server = ocaml_prefixed "stop-documentation-server" 20 | let generate_sandbox_documentation = ocaml_prefixed "generate-sandbox-documentation" 21 | let open_current_dune_file = ocaml_prefixed "current-dune-file" 22 | let evaluate_selection = ocaml_prefixed "evaluate-selection" 23 | let open_repl = ocaml_prefixed "open-repl" 24 | let next_hole = ocaml_prefixed "next-hole" 25 | let prev_hole = ocaml_prefixed "prev-hole" 26 | let open_ast_explorer_to_the_side = ocaml_prefixed "open-ast-explorer-to-the-side" 27 | let reveal_ast_node = ocaml_prefixed "reveal-ast-node" 28 | let switch_hover_mode = ocaml_prefixed "switch-hover-mode" 29 | let show_preprocessed_document = ocaml_prefixed "show-preprocessed-document" 30 | let open_pp_editor_and_ast_explorer = ocaml_prefixed "open-pp-editor-and-ast-explorer" 31 | let open_ocamllsp_output = ocaml_prefixed "open-ocamllsp-output" 32 | let open_ocaml_platform_ext_output = ocaml_prefixed "open-ocaml-platform-ext-output" 33 | let open_ocaml_commands_output = ocaml_prefixed "open-ocaml-commands-output" 34 | let start_debugging = ocaml_prefixed "start-debugging" 35 | let goto_closure_code_location = ocaml_prefixed "goto-closure-code-location" 36 | let ask_debug_program = ocaml_prefixed "ask-debug-program" 37 | let copy_type_under_cursor = ocaml_prefixed "copy-type-under-cursor" 38 | let construct = ocaml_prefixed "construct" 39 | let merlin_jump = ocaml_prefixed "jump" 40 | let search_by_type = ocaml_prefixed "search-by-type" 41 | let navigate_typed_holes = ocaml_prefixed "navigate-typed-holes" 42 | let type_selection = ocaml_prefixed "type-selection" 43 | let type_previous_selection = ocaml_prefixed "type-previous-selection" 44 | let augment_selection_type_verbosity = ocaml_prefixed "augment-selection-type-verbosity" 45 | end 46 | 47 | module Command_errors = struct 48 | let text_editor_must_be_active ~expl cmd_name = 49 | Printf.sprintf 50 | "The command \"OCaml: %s\" should be run only with a file open in the editor. %s" 51 | cmd_name 52 | expl 53 | ;; 54 | end 55 | 56 | module Debuggers = struct 57 | let earlybird = ocaml_prefixed "earlybird" 58 | end 59 | 60 | (* TODO: Refactor the code so that we don't need any "constants" module *) 61 | -------------------------------------------------------------------------------- /src/extension_instance.mli: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | type t 4 | 5 | val make : unit -> t 6 | val sandbox : t -> Sandbox.t 7 | val set_sandbox : t -> Sandbox.t -> unit 8 | val language_client : t -> LanguageClient.t option 9 | val ocaml_lsp : t -> Ocaml_lsp.t option 10 | val check_ocaml_lsp_available : Sandbox.t -> (unit, string) result Promise.t 11 | 12 | val start_documentation_server 13 | : t 14 | -> path:Path.t 15 | -> (Documentation_server.t, unit) result Promise.t 16 | 17 | val stop_documentation_server : t -> unit 18 | val lsp_client : t -> (LanguageClient.t * Ocaml_lsp.t) option 19 | val ocaml_version_exn : t -> Ocaml_version.t 20 | val start_language_server : t -> unit Promise.t 21 | val install_ocaml_lsp_server : Sandbox.t -> unit Promise.t 22 | val upgrade_ocaml_lsp_server : Sandbox.t -> unit Promise.t 23 | 24 | val set_configuration 25 | : t 26 | -> ?codelens:bool option 27 | -> ?extended_hover:bool option 28 | -> ?standard_hover:bool option 29 | -> ?dune_diagnostics:bool option 30 | -> ?syntax_documentation:bool option 31 | -> unit 32 | -> unit 33 | 34 | val open_terminal : Sandbox.t -> unit 35 | val disposable : t -> Disposable.t 36 | val repl : t -> Terminal_sandbox.t option 37 | val set_repl : t -> Terminal.t -> unit 38 | val close_repl : t -> unit 39 | val update_ocaml_info : t -> unit Promise.t 40 | val ast_editor_state : t -> Ast_editor_state.t 41 | -------------------------------------------------------------------------------- /src/ocaml_lsp.mli: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | type t 4 | 5 | val of_initialize_result : LanguageClient.InitializeResult.t -> t 6 | val is_version_up_to_date : t -> Ocaml_version.t -> (unit, [ `Msg of string ]) result 7 | val can_handle_switch_impl_intf : t -> bool 8 | val can_handle_infer_intf : t -> bool 9 | val can_handle_typed_holes : t -> bool 10 | val can_handle_type_selection : t -> bool 11 | val can_handle_construct : t -> bool 12 | val can_handle_merlin_jump : t -> bool 13 | val can_handle_search_by_type : t -> bool 14 | val suggest_to_upgrade_ocaml_lsp_server : ?message:string -> unit -> unit Promise.t 15 | 16 | module OcamllspSettingEnable : sig 17 | include Ojs.T 18 | 19 | val enable : t -> bool option 20 | val create : enable:bool -> t 21 | end 22 | 23 | module OcamllspSettings : sig 24 | include Ojs.T 25 | 26 | val codelens : t -> OcamllspSettingEnable.t option 27 | val extendedHover : t -> OcamllspSettingEnable.t option 28 | val standardHover : t -> OcamllspSettingEnable.t option 29 | val duneDiagnostics : t -> OcamllspSettingEnable.t option 30 | val syntaxDocumentation : t -> OcamllspSettingEnable.t option 31 | 32 | val create 33 | : codelens:OcamllspSettingEnable.t option 34 | -> extendedHover:OcamllspSettingEnable.t option 35 | -> standardHover:OcamllspSettingEnable.t option 36 | -> duneDiagnostics:OcamllspSettingEnable.t option 37 | -> syntaxDocumentation:OcamllspSettingEnable.t option 38 | -> t 39 | end 40 | -------------------------------------------------------------------------------- /src/ocaml_windows.ml: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | let ocaml_env_command = { Cmd.bin = Path.of_string "ocaml-env"; args = [] } 4 | 5 | let ocaml_env_setting = 6 | Settings.create_setting 7 | ~scope:Global 8 | ~key:"ocaml.useOcamlEnv" 9 | ~of_json:Jsonoo.Decode.bool 10 | ~to_json:Jsonoo.Encode.bool 11 | ;; 12 | 13 | let use_ocaml_env () = 14 | match Platform.t, Settings.get ocaml_env_setting with 15 | | Win32, Some true -> 16 | let open Promise.Syntax in 17 | let+ checked = Cmd.check_spawn ocaml_env_command in 18 | Result.is_ok checked 19 | | _ -> Promise.return false 20 | ;; 21 | 22 | let spawn_ocaml_env args = { ocaml_env_command with args = "exec" :: "--" :: args } 23 | 24 | let cygwin_home () = 25 | let open Promise.Syntax in 26 | let spawn = spawn_ocaml_env [ "cygpath"; "--windows"; "~" ] in 27 | let+ output = Cmd.output (Cmd.Spawn spawn) in 28 | match output with 29 | | Error _ as e -> e 30 | | Ok output -> 31 | let lines = String.split_on_chars ~on:[ '\n' ] output in 32 | (match lines with 33 | | home :: _ -> Ok home 34 | | _ -> 35 | let msg = Printf.sprintf "Unexpected output for Cygin home directory: %s" output in 36 | Error msg) 37 | ;; 38 | -------------------------------------------------------------------------------- /src/ocaml_windows.mli: -------------------------------------------------------------------------------- 1 | (* Whether to use ocaml-env for opam on Windows *) 2 | val use_ocaml_env : unit -> bool Promise.t 3 | 4 | (* Spawn command from OCaml for Windows using ocaml-env *) 5 | val spawn_ocaml_env : string list -> Cmd.spawn 6 | 7 | (* Path to home directory from OCaml for Windows *) 8 | val cygwin_home : unit -> (string, string) result Promise.t 9 | -------------------------------------------------------------------------------- /src/odig.ml: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | type t = { cache_dir : Path.t } 4 | 5 | let make_odig_cmd sandbox = Sandbox.get_command sandbox "odig" 6 | 7 | (** TODO: propose to install odig. See 8 | https://github.com/ocamllabs/vscode-ocaml-platform/pull/771#discussion_r765297112 *) 9 | let of_sandbox (sandbox : Sandbox.t) = 10 | let make_odig_cmd = make_odig_cmd sandbox in 11 | let odig_version = make_odig_cmd [ "--version" ] in 12 | let open Promise.Syntax in 13 | let* output = Cmd.output odig_version in 14 | match output with 15 | | Ok _ -> 16 | let+ cache_dir = Cmd.output (make_odig_cmd [ "cache"; "path" ]) in 17 | (match cache_dir with 18 | | Ok cache_dir -> 19 | let cache_dir = cache_dir |> String.strip |> Path.of_string in 20 | Ok { cache_dir } 21 | | Error _ -> Error "OCaml: Failed to retrieve odig cache_dir") 22 | | Error _ -> 23 | Promise.resolve 24 | (Error 25 | "OCaml: the \"odig\" binary must be available in the current sandbox to \ 26 | generate documentation.") 27 | ;; 28 | 29 | let cmd_output ~sandbox ~args = Cmd.output (make_odig_cmd sandbox args) 30 | let html_dir t = Path.(t.cache_dir / "/html/") 31 | 32 | let odoc_exec t ~sandbox ~package_name = 33 | let open Promise.Syntax in 34 | let* ouput = cmd_output ~sandbox ~args:[ "odoc"; package_name ] in 35 | let+ result = 36 | match ouput with 37 | | Error _ as e -> Promise.resolve e 38 | | Ok _ -> 39 | let html_dir = html_dir t in 40 | let package_html_dir = Path.(html_dir / package_name) |> Path.to_string in 41 | let open Promise.Syntax in 42 | let+ dir_exists = Fs.exists package_html_dir in 43 | if dir_exists 44 | then Ok () 45 | else 46 | Error 47 | (Printf.sprintf 48 | "Directory %s should have been created but it doesn\'t exist." 49 | package_html_dir) 50 | in 51 | Result.map_error result ~f:(fun error -> 52 | let () = 53 | log "Failed to generate documentation for package %s. Error: %s" package_name error 54 | in 55 | error) 56 | ;; 57 | -------------------------------------------------------------------------------- /src/odig.mli: -------------------------------------------------------------------------------- 1 | (** Provide an interface to Odig. *) 2 | 3 | type t 4 | 5 | val of_sandbox : Sandbox.t -> (t, string) result Promise.t 6 | 7 | (** Generates the odoc API documentation and manual of [package_name]. Interface 8 | to the [odig odoc package_name] command *) 9 | val odoc_exec 10 | : t 11 | -> sandbox:Sandbox.t 12 | -> package_name:string 13 | -> (unit, string) result Promise.t 14 | 15 | val html_dir : t -> Path.t 16 | -------------------------------------------------------------------------------- /src/opam.mli: -------------------------------------------------------------------------------- 1 | (** Provide an interface to Opam. 2 | 3 | The functions in this module either use the result of opam commands, or use 4 | the filesystem to get the state of opam configurations. *) 5 | 6 | (** An Opam switch *) 7 | module Switch : sig 8 | type t = 9 | | Local of Path.t 10 | | Named of string 11 | 12 | (** {4 Constructors} *) 13 | 14 | val of_string : string -> t option 15 | 16 | (** {4 Properties} *) 17 | 18 | val name : t -> string 19 | 20 | (** {4 Utilities} *) 21 | 22 | val equal : t -> t -> bool 23 | end 24 | 25 | (** A package installed in an Opam switch *) 26 | module Package : sig 27 | type t 28 | 29 | (** {4 Properties} *) 30 | 31 | val name : t -> string 32 | val version : t -> string 33 | val documentation : t -> string option 34 | val synopsis : t -> string option 35 | val has_dependencies : t -> bool 36 | val dependencies : t -> (t list, string) result Promise.t 37 | end 38 | 39 | type t 40 | 41 | val make : ?root:Path.t -> unit -> t option Promise.t 42 | 43 | (** Install new packages in a switch *) 44 | val install : t -> Switch.t -> packages:string list -> Cmd.t 45 | 46 | (** Update the opam repository *) 47 | val update : t -> Switch.t -> Cmd.t 48 | 49 | (** Upgrade packages in a switch *) 50 | val upgrade : ?packages:string list -> t -> Switch.t -> Cmd.t 51 | 52 | (* Remove a list of packages from a switch *) 53 | val remove : t -> Switch.t -> string list -> Cmd.t 54 | 55 | (* Initialize a new Opam environment. *) 56 | val init : t -> Cmd.t 57 | 58 | (** {4 Working with switches} *) 59 | 60 | (** Create a new switch. *) 61 | val switch_create : t -> name:string -> args:string list -> Cmd.t 62 | 63 | (** List the opam switches available on the system. *) 64 | val switch_list : t -> Switch.t list Promise.t 65 | 66 | (** Checks that the given switch exists. *) 67 | val switch_exists : t -> Switch.t -> bool Promise.t 68 | 69 | (** Return the current opam switch. *) 70 | val switch_show : ?cwd:Path.t -> t -> Switch.t option Promise.t 71 | 72 | (** Return the compiler version used by the given switch. *) 73 | val switch_compiler : t -> Switch.t -> string option Promise.t 74 | 75 | (** Remove the given switch by calling [opam switch remove]. *) 76 | val switch_remove : t -> Switch.t -> Cmd.t 77 | 78 | (** {4 Working with packages} *) 79 | 80 | (** Return the list of installed packages in the given opam switch. *) 81 | val packages : t -> Switch.t -> (Package.t list, string) result Promise.t 82 | 83 | (** Return the list of root packages in the given opam switch. 84 | 85 | Root packages are packages that have been installed by the user, as opposed 86 | to packages that have been installed as a dependency of another package. *) 87 | val root_packages : t -> Switch.t -> (Package.t list, string) result Promise.t 88 | 89 | (** Uninstall a package from a switch by calling [opam uninstall]. *) 90 | val package_remove : t -> Switch.t -> Package.t list -> Cmd.t 91 | 92 | (** {4 General utilities} *) 93 | 94 | (** Execute an opam sub-command with in the given switch. *) 95 | val exec : t -> Switch.t -> args:string list -> Cmd.t 96 | 97 | (** Check that two instances of [Opam] are equal. *) 98 | val equal : t -> t -> bool 99 | -------------------------------------------------------------------------------- /src/output.ml: -------------------------------------------------------------------------------- 1 | let language_server_output_channel = 2 | lazy (Vscode.Window.createOutputChannel ~name:"OCaml Language Server" ()) 3 | ;; 4 | 5 | let extension_output_channel = 6 | lazy (Vscode.Window.createOutputChannel ~name:"OCaml Platform Extension" ()) 7 | ;; 8 | 9 | let command_output_channel = 10 | lazy (Vscode.Window.createOutputChannel ~name:"OCaml Commands" ()) 11 | ;; 12 | -------------------------------------------------------------------------------- /src/output.mli: -------------------------------------------------------------------------------- 1 | (** [language_server_output_channel] is the output channel that should be used 2 | by all ocamllsp LanguageClients *) 3 | val language_server_output_channel : Vscode.OutputChannel.t Lazy.t 4 | 5 | (** [extension_output_channel] is the output channel that should be used for 6 | logs by the extension *) 7 | val extension_output_channel : Vscode.OutputChannel.t Lazy.t 8 | 9 | (** [command_output_channel] is the output channel for user-friendly logs of 10 | shell commands *) 11 | val command_output_channel : Vscode.OutputChannel.t Lazy.t 12 | -------------------------------------------------------------------------------- /src/path.ml: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | type t = string 4 | 5 | let equal = String.equal 6 | let iequal p0 p1 = String.equal (String.lowercase p0) (String.lowercase p1) 7 | let of_string s = s 8 | let to_string s = s 9 | let delimiter = Node.Path.delimiter 10 | let sep = Node.Path.sep 11 | let is_absolute t = Node.Path.isAbsolute t 12 | let compare = String.compare 13 | let dirname = Node.Path.dirname 14 | let extname = Node.Path.extname 15 | let basename = Node.Path.basename 16 | let join x y = Node.Path.join [ x; y ] 17 | let ( / ) = join 18 | let relative = join 19 | let relative_all p xs = List.fold_left xs ~f:join ~init:p 20 | let with_ext x ~ext = x ^ ext 21 | 22 | let is_root = function 23 | | "" -> true 24 | | x -> equal (dirname x) x 25 | ;; 26 | 27 | let parent x = if is_root x then None else Some (dirname x) 28 | let asset name = of_string (Node.__filename () ^ "/../../assets/" ^ name) 29 | -------------------------------------------------------------------------------- /src/path.mli: -------------------------------------------------------------------------------- 1 | (** Representation of path *) 2 | 3 | type t 4 | 5 | val equal : t -> t -> bool 6 | 7 | (** [iequal p0 p1] is [true] if and only if [p1] and [p2] are equal but case 8 | insensitive to any ASCII-uppercase/-lowercase differences. *) 9 | val iequal : t -> t -> bool 10 | 11 | val of_string : string -> t 12 | val to_string : t -> string 13 | val delimiter : char 14 | val sep : char 15 | val is_absolute : t -> bool 16 | val compare : t -> t -> int 17 | val dirname : t -> string 18 | val extname : t -> string 19 | val basename : t -> string 20 | val join : t -> t -> t 21 | val ( / ) : t -> string -> t 22 | val relative : t -> string -> t 23 | val relative_all : t -> string list -> t 24 | val with_ext : t -> ext:string -> t 25 | val parent : t -> t option 26 | val is_root : t -> bool 27 | val asset : string -> t 28 | -------------------------------------------------------------------------------- /src/platform.ml: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | type t = 4 | | Win32 5 | | Darwin 6 | | Linux 7 | | Other 8 | 9 | let of_string = function 10 | | "win32" -> Win32 11 | | "darwin" -> Darwin 12 | | "linux" -> Linux 13 | | _ -> Other 14 | ;; 15 | 16 | let t = of_string Process.platform 17 | 18 | module Map = struct 19 | type 'a t = 20 | { win32 : 'a 21 | ; darwin : 'a 22 | ; linux : 'a 23 | ; other : 'a 24 | } 25 | 26 | let find { win32; darwin; linux; other } = function 27 | | Win32 -> win32 28 | | Darwin -> darwin 29 | | Linux -> linux 30 | | Other -> other 31 | ;; 32 | end 33 | 34 | type arch = 35 | | Arm 36 | | Arm64 37 | | Ia32 38 | | Mips 39 | | Mipsel 40 | | Ppc 41 | | Ppc64 42 | | S390 43 | | S390x 44 | | X32 45 | | X64 46 | 47 | let arch_of_string = function 48 | | "arm" -> Arm 49 | | "arm64" -> Arm64 50 | | "ia32" -> Ia32 51 | | "mips" -> Mips 52 | | "mipsel" -> Mipsel 53 | | "ppc" -> Ppc 54 | | "ppc64" -> Ppc64 55 | | "s390" -> S390 56 | | "s390x" -> S390x 57 | | "x32" -> X32 58 | | "x64" -> X64 59 | | _ -> assert false 60 | ;; 61 | 62 | let arch = Node.Process.arch |> arch_of_string 63 | 64 | type shell = 65 | | Sh of Path.t 66 | | PowerShell of Path.t 67 | 68 | let shell = 69 | let sh = Sh (Path.of_string "/bin/sh") in 70 | let powershell = 71 | PowerShell 72 | (Path.of_string "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") 73 | in 74 | Map.find { win32 = powershell; darwin = sh; linux = sh; other = sh } t 75 | ;; 76 | -------------------------------------------------------------------------------- /src/platform.mli: -------------------------------------------------------------------------------- 1 | type t = 2 | | Win32 3 | | Darwin 4 | | Linux 5 | | Other 6 | 7 | val t : t 8 | 9 | module Map : sig 10 | type platform 11 | 12 | type 'a t = 13 | { win32 : 'a 14 | ; darwin : 'a 15 | ; linux : 'a 16 | ; other : 'a 17 | } 18 | 19 | val find : 'a t -> platform -> 'a 20 | end 21 | with type platform := t 22 | 23 | type arch = 24 | | Arm 25 | | Arm64 26 | | Ia32 27 | | Mips 28 | | Mipsel 29 | | Ppc 30 | | Ppc64 31 | | S390 32 | | S390x 33 | | X32 34 | | X64 35 | 36 | val arch : arch 37 | 38 | type shell = 39 | | Sh of Path.t 40 | | PowerShell of Path.t 41 | 42 | val shell : shell 43 | -------------------------------------------------------------------------------- /src/ppx_tools/dumpast.mli: -------------------------------------------------------------------------------- 1 | open Ppxlib 2 | 3 | (** [transform s k] transforms syntactically correct code containted in [s] into 4 | its AST serialized as a [Jsonoo.t] value. The kind [k] specifies if it is an 5 | interface or an implementation. [Error err_msg] is returned on syntax error *) 6 | val transform : string -> [ `Intf | `Impl ] -> (Jsonoo.t, string) result 7 | 8 | (** [from_structure str] serializes [str] as a [Jsonoo.t] value *) 9 | val from_structure : Parsetree.structure -> (Jsonoo.t, string) result 10 | 11 | (** [reparse str str'] creates a serialized AST as a [Jsonoo.t] value that has 12 | {i double locations} that come both from [str] and [str'] . This is possible 13 | under the assumption that [str] [str'] are structurally identical modulo 14 | [Location.t] values; If it's not the case, the result will only contain 15 | simple locations coming from [str] starting from the subtree that is 16 | structurally different. The result should a priori always be an [Ok json], 17 | since all possibles cases of [Error e] are accounted for. *) 18 | val reparse 19 | : Parsetree.structure_item list 20 | -> Parsetree.structure_item list 21 | -> (Jsonoo.t, string) result 22 | 23 | (** [reparse_signature sg sg'] does the same tranformation as {!reparse} but the 24 | starting point is [Parsetree.signature] *) 25 | val reparse_signature 26 | : Parsetree.signature_item list 27 | -> Parsetree.signature_item list 28 | -> (Jsonoo.t, string) result 29 | -------------------------------------------------------------------------------- /src/ppx_tools/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name ppx_tools) 3 | (libraries base vscode jsonoo promise_jsoo ppxlib) 4 | (modes byte)) 5 | -------------------------------------------------------------------------------- /src/ppx_tools/ppx_tools.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | module Dumpast = Dumpast 3 | 4 | let get_preprocessed_ast path = 5 | match Ppxlib.Ast_io.read_binary path with 6 | | exception Sys_error s -> Error s 7 | | Ok s -> Ok s 8 | | Error e -> Error e 9 | ;; 10 | 11 | let get_reparsed_code_from_pp_file ~path = 12 | get_preprocessed_ast path 13 | |> Result.map ~f:(fun source -> 14 | match Ppxlib.Ast_io.get_ast source with 15 | | Impl structure -> 16 | let structure = Ppxlib_ast.Selected_ast.To_ocaml.copy_structure structure in 17 | Stdlib.Format.asprintf "%a" Pprintast.structure structure 18 | | Intf signature -> 19 | let signature = Ppxlib_ast.Selected_ast.To_ocaml.copy_signature signature in 20 | Stdlib.Format.asprintf "%a" Pprintast.signature signature) 21 | ;; 22 | -------------------------------------------------------------------------------- /src/ppx_tools/ppx_tools.mli: -------------------------------------------------------------------------------- 1 | module Dumpast : module type of Dumpast 2 | 3 | val get_reparsed_code_from_pp_file : path:string -> (string, string) result 4 | val get_preprocessed_ast : string -> (Ppxlib.Ast_io.t, string) result 5 | -------------------------------------------------------------------------------- /src/repl.mli: -------------------------------------------------------------------------------- 1 | val register : Vscode.ExtensionContext.t -> Extension_instance.t -> unit 2 | -------------------------------------------------------------------------------- /src/sandbox.mli: -------------------------------------------------------------------------------- 1 | (** Sandbox.ml exposes functions that let us 1. Run initial checks in the 2 | environment looking for a reliable sandbox ([Sandbox.init]) 3 | 4 | 2. Run a setup that would setup the sandbox provided that basic requirements 5 | are met ([Sandbox.run_setup]) 6 | 7 | 3. Helper functions that extract the tools from the setup sandbox. This 8 | includes just [ocamllsp] right now, but in future could include others like 9 | debuggers, REPLs etc that could be shipped with [vscode-ocaml-platform] 10 | plugin itself 11 | 12 | The separation between [init], [run_setup] and extraction helpers exist so 13 | that we can handle missing tools gracefully (ie provide degraded 14 | performance, direct user to install missing tools etc). Having a single 15 | [Sandbox.make()], for instance, would not make it this flexible. *) 16 | 17 | module Package : sig 18 | type t 19 | 20 | val name : t -> string 21 | val version : t -> string 22 | val synopsis : t -> string option 23 | val documentation : t -> string option 24 | val has_dependencies : t -> bool 25 | val dependencies : t -> (t list, string) result Promise.t 26 | end 27 | 28 | type t = 29 | | Opam of Opam.t * Opam.Switch.t 30 | | Esy of Esy.t * Esy.Manifest.t 31 | | Global 32 | | Custom of string 33 | 34 | val equal : t -> t -> bool 35 | val to_string : t -> string 36 | val to_pretty_string : t -> string 37 | val of_settings : unit -> t option Promise.t 38 | val detect : unit -> t option Promise.t 39 | val of_settings_or_detect : unit -> t option Promise.t 40 | val save_to_settings : t -> unit Promise.t 41 | 42 | (** [select_sandbox_and_save] requires the process environment the plugin is 43 | being run in (ie VSCode's process environment) and the project root and 44 | produces a promise of resources available that can later be passed on to 45 | [run_setup] that can be called to install the sandbox. *) 46 | val select_sandbox_and_save : unit -> t option Promise.t 47 | 48 | (** [select_sandbox] is the same as [select_sandbox_and_save] but does not save 49 | the sandbox configuration *) 50 | val select_sandbox : unit -> t option Promise.t 51 | 52 | (* Helper utils *) 53 | 54 | (** Extract command to run with the sandbox *) 55 | val get_command : t -> string -> string list -> Cmd.t 56 | 57 | (** Command to install dependencies in the sandbox *) 58 | val get_install_command : t -> string list -> Cmd.t option 59 | 60 | (** Command to exec a process in the sandbox *) 61 | val get_exec_command : t -> string list -> Cmd.t option 62 | 63 | (** The packages installed in the sandbox *) 64 | val packages : t -> (Package.t list, string) result Promise.t 65 | 66 | (** The packages that have been installed manually by the user in the sandbox *) 67 | val root_packages : t -> (Package.t list, string) result Promise.t 68 | 69 | (** Uninstall existing packages from the sandbox *) 70 | val uninstall_packages : t -> Package.t list -> unit Promise.t 71 | 72 | (** Install new packages in the sandbox *) 73 | val install_packages : t -> string list -> unit Promise.t 74 | 75 | (** Upgrade packages in the sandbox *) 76 | val upgrade_packages : ?packages:string list -> t -> unit Promise.t 77 | 78 | (** [ocaml_version] returns the version of the ocaml compiler installed in given 79 | sandbox. *) 80 | val ocaml_version : t -> (string, string) result Promise.t 81 | 82 | (** Focus on the command output channel. If [sandbox] is provided, the output 83 | channel will only be focused if the given sandbox supports package commands. *) 84 | val focus_on_package_command : ?sandbox:t -> unit -> unit 85 | -------------------------------------------------------------------------------- /src/settings.mli: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | (** This module is more of a "settings manager", which works with VS Code's 4 | [WorkspaceConfiguration] to retrieve and set settings. A setting is 5 | represented by ['value setting] type defined below. *) 6 | 7 | (** ['value setting] represents a setting (word used as a singular of 8 | "settings"), which has a value of type ['value] *) 9 | type 'value setting 10 | 11 | (* TODO: improve the treatment of "section" 12 | 13 | All settings in the extension are defined using a dot-separated path, e.g., 14 | [ocaml.dune.autoDetect]. In some places, we treat [key] as this whole path 15 | for the setting, [ocaml.dune.autoDetect], and in some places we use 16 | [autoDetect] as the key and [ocaml.dune] as the "section". VS Code allows 17 | this, but this non uniform treatment is bad. We should enforce a nicer 18 | API. *) 19 | 20 | val get : ?section:string -> 'value setting -> 'value option 21 | val set : ?section:string -> 'value setting -> 'value -> unit Promise.t 22 | 23 | val create_setting 24 | : scope:ConfigurationTarget.t 25 | -> key:string 26 | -> of_json:(Jsonoo.t -> 'value) 27 | -> to_json:('value -> Jsonoo.t) 28 | -> 'value setting 29 | 30 | (** replace workspaceFolder:folder_name variables with workspace folder paths *) 31 | val resolve_workspace_vars : string -> string 32 | 33 | (** replace workspace folder paths with workspaceFolder:folder_name variables *) 34 | val substitute_workspace_vars : string -> string 35 | 36 | val server_extraEnv : unit -> string Interop.Dict.t option 37 | val server_args_setting : string list setting 38 | val server_codelens_setting : bool setting 39 | val server_extendedHover_setting : bool setting 40 | val server_duneDiagnostics_setting : bool setting 41 | val server_syntaxDocumentation_setting : bool setting 42 | val server_typedHolesConstructAfterNavigate_setting : bool setting 43 | val server_constructRecursiveCalls_setting : bool setting 44 | -------------------------------------------------------------------------------- /src/switch_impl_intf.ml: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | let insert_inferred_intf ~source_uri client text_editor = 4 | let open Promise.Syntax in 5 | match 6 | String.is_suffix source_uri ~suffix:".ml" || String.is_suffix source_uri ~suffix:".re" 7 | with 8 | | false -> Promise.return () 9 | | true -> 10 | (* If the source file was a .ml or .re, infer the interface *) 11 | let* inferred_intf = 12 | Custom_requests.send_request client Custom_requests.inferIntf source_uri 13 | in 14 | let+ edit_applied = 15 | TextEditor.edit 16 | text_editor 17 | ~callback:(fun ~editBuilder -> 18 | TextEditorEdit.insert 19 | editBuilder 20 | ~location:(Position.make ~line:1 ~character:1) 21 | ~value:inferred_intf) 22 | () 23 | in 24 | if not edit_applied then show_message `Error "Unable to insert inferred interface" 25 | ;; 26 | 27 | let request_switch client document = 28 | let open Promise.Syntax in 29 | let source_uri = Uri.toString (TextDocument.uri document) () in 30 | let fill_intf_if_empty_untitled text_editor = 31 | let doc = TextEditor.document text_editor in 32 | let is_empty_doc doc = TextDocument.getText doc () |> String.is_empty in 33 | if TextDocument.isUntitled doc && is_empty_doc doc 34 | then insert_inferred_intf ~source_uri client text_editor 35 | else Promise.return () 36 | in 37 | let* arr = 38 | Custom_requests.send_request client Custom_requests.switchImplIntf source_uri 39 | in 40 | match Array.to_list arr with 41 | | [] -> 42 | (* 'ocamllsp/switchImplIntf' command's response array cannot be empty *) 43 | assert false 44 | | [ file_uri ] -> 45 | let* text_editor = open_file_in_text_editor file_uri in 46 | fill_intf_if_empty_untitled text_editor 47 | | first_candidate :: other_candidates as candidates -> 48 | let first_candidate_item = 49 | QuickPickItem.create 50 | ~label:(Stdlib.Filename.basename first_candidate) 51 | ~picked:true 52 | () 53 | in 54 | let rest_candidate_items = 55 | List.map 56 | ~f:(fun c -> QuickPickItem.create ~label:(Stdlib.Filename.basename c) ()) 57 | other_candidates 58 | in 59 | let candidate_items_with_names = 60 | List.zip_exn (first_candidate_item :: rest_candidate_items) candidates 61 | in 62 | let file_options = 63 | QuickPickOptions.create ~canPickMany:false ~placeHolder:"Open a file..." () 64 | in 65 | let open Promise.Syntax in 66 | let* choice = 67 | Window.showQuickPickItems 68 | ~choices:candidate_items_with_names 69 | ~options:file_options 70 | () 71 | in 72 | (match choice with 73 | | None -> Promise.return () 74 | | Some file_uri -> 75 | let* text_editor = open_file_in_text_editor file_uri in 76 | fill_intf_if_empty_untitled text_editor) 77 | ;; 78 | -------------------------------------------------------------------------------- /src/switch_impl_intf.mli: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | val request_switch : LanguageClient.t -> Vscode.TextDocument.t -> unit Promise.t 4 | -------------------------------------------------------------------------------- /src/terminal_sandbox.ml: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | module ShellPath = struct 4 | type t = string option 5 | 6 | let of_json json = 7 | let open Jsonoo.Decode in 8 | nullable string json 9 | ;; 10 | 11 | let to_json (t : t) = 12 | let open Jsonoo.Encode in 13 | nullable string t 14 | ;; 15 | 16 | let key = 17 | let linux = "shell.linux" in 18 | let map = 19 | { Platform.Map.win32 = "shell.windows"; darwin = "shell.osx"; linux; other = linux } 20 | in 21 | Platform.Map.find map Platform.t 22 | ;; 23 | 24 | let t = Settings.create_setting ~scope:Global ~key ~of_json ~to_json 25 | end 26 | 27 | module ShellArgs = struct 28 | type t = string list option 29 | 30 | let of_json json = 31 | let open Jsonoo.Decode in 32 | nullable (list string) json 33 | ;; 34 | 35 | let to_json (t : t) = 36 | let open Jsonoo.Encode in 37 | nullable (list string) t 38 | ;; 39 | 40 | let key = 41 | let linux = "shellArgs.linux" in 42 | let map = 43 | { Platform.Map.win32 = "shellArgs.windows" 44 | ; darwin = "shellArgs.osx" 45 | ; linux 46 | ; other = linux 47 | } 48 | in 49 | Platform.Map.find map Platform.t 50 | ;; 51 | 52 | let t = Settings.create_setting ~scope:Global ~key ~of_json ~to_json 53 | end 54 | 55 | let get_shell_path () = 56 | let shell_path = Option.join (Settings.get ~section:"ocaml.terminal" ShellPath.t) in 57 | match shell_path with 58 | | Some path -> path 59 | | None -> Env.shell () 60 | ;; 61 | 62 | let get_shell_args () = 63 | let get_args section = Option.join (Settings.get ~section ShellArgs.t) in 64 | match get_args "ocaml.terminal" with 65 | | Some args -> args 66 | | None -> 67 | (match get_args "terminal.integrated" with 68 | | Some args -> args 69 | | None -> []) 70 | ;; 71 | 72 | type t = Terminal.t 73 | 74 | let create ?name ?command sandbox = 75 | let ({ Cmd.bin; args } as command) = 76 | match command with 77 | | Some command -> command 78 | | None -> 79 | let shell_path = get_shell_path () in 80 | let shell_args = get_shell_args () in 81 | let command = Sandbox.get_command sandbox shell_path shell_args in 82 | Cmd.to_spawn command 83 | in 84 | Cmd.log (Spawn command); 85 | let name = Option.value name ~default:(Sandbox.to_pretty_string sandbox) in 86 | let shellPath = Path.to_string bin in 87 | let shellArgs = `Strings args in 88 | Window.createTerminal ~name ~shellPath ~shellArgs () 89 | ;; 90 | 91 | let dispose = Terminal.dispose 92 | let show ~preserveFocus t = Terminal.show ~preserveFocus t () 93 | 94 | let send t text = 95 | let addNewLine = not (String.is_suffix text ~suffix:"\n") in 96 | Terminal.sendText t ~text ~addNewLine () 97 | ;; 98 | -------------------------------------------------------------------------------- /src/terminal_sandbox.mli: -------------------------------------------------------------------------------- 1 | type t = Vscode.Terminal.t 2 | 3 | val create : ?name:string -> ?command:Cmd.spawn -> Sandbox.t -> t 4 | val dispose : t -> unit 5 | val show : preserveFocus:bool -> t -> unit 6 | val send : t -> string -> unit 7 | -------------------------------------------------------------------------------- /src/treeview_commands.ml: -------------------------------------------------------------------------------- 1 | let select_sandbox_item = 2 | let icon = 3 | `LightDark 4 | Vscode.LightDarkIcon. 5 | { light = `String (Path.asset "collection-light.svg" |> Path.to_string) 6 | ; dark = `String (Path.asset "collection-dark.svg" |> Path.to_string) 7 | } 8 | in 9 | let label = `TreeItemLabel (Vscode.TreeItemLabel.create ~label:"Select a Sandbox" ()) in 10 | let item = Vscode.TreeItem.make_label ~label () in 11 | let command = 12 | Vscode.Command.create ~title:"Select a Sandbox" ~command:"ocaml.select-sandbox" () 13 | in 14 | Vscode.TreeItem.set_iconPath item icon; 15 | Vscode.TreeItem.set_command item command; 16 | item 17 | ;; 18 | 19 | let terminal_item = 20 | let icon = 21 | `LightDark 22 | Vscode.LightDarkIcon. 23 | { light = `String (Path.asset "terminal-light.svg" |> Path.to_string) 24 | ; dark = `String (Path.asset "terminal-dark.svg" |> Path.to_string) 25 | } 26 | in 27 | let label = 28 | `TreeItemLabel (Vscode.TreeItemLabel.create ~label:"Open a sandboxed terminal" ()) 29 | in 30 | let item = Vscode.TreeItem.make_label ~label () in 31 | let command = 32 | Vscode.Command.create 33 | ~title:"Open a sandboxed terminal" 34 | ~command:"ocaml.open-terminal" 35 | () 36 | in 37 | Vscode.TreeItem.set_iconPath item icon; 38 | Vscode.TreeItem.set_command item command; 39 | item 40 | ;; 41 | 42 | let construct_item = 43 | let icon = `ThemeIcon (Vscode.ThemeIcon.make ~id:"tools" ()) in 44 | let label = 45 | `TreeItemLabel 46 | (Vscode.TreeItemLabel.create 47 | ~label:"List values that can fill the selected typed-hole" 48 | ()) 49 | in 50 | let item = Vscode.TreeItem.make_label ~label () in 51 | let command = Vscode.Command.create ~title:"Construct" ~command:"ocaml.construct" () in 52 | Vscode.TreeItem.set_iconPath item icon; 53 | Vscode.TreeItem.set_command item command; 54 | item 55 | ;; 56 | 57 | let jump_item = 58 | let icon = `ThemeIcon (Vscode.ThemeIcon.make ~id:"fold-up" ()) in 59 | let label = 60 | `TreeItemLabel (Vscode.TreeItemLabel.create ~label:"Jump to a specific target" ()) 61 | in 62 | let item = Vscode.TreeItem.make_label ~label () in 63 | let command = Vscode.Command.create ~title:"MerlinJump" ~command:"ocaml.jump" () in 64 | Vscode.TreeItem.set_iconPath item icon; 65 | Vscode.TreeItem.set_command item command; 66 | item 67 | ;; 68 | 69 | let type_search_item = 70 | let icon = `ThemeIcon (Vscode.ThemeIcon.make ~id:"search-view-icon" ()) in 71 | let label = 72 | `TreeItemLabel 73 | (Vscode.TreeItemLabel.create ~label:"Search a value by type or polarity" ()) 74 | in 75 | let item = Vscode.TreeItem.make_label ~label () in 76 | let command = 77 | Vscode.Command.create 78 | ~title:"Search a value by type or polarity" 79 | ~command:"ocaml.search-by-type" 80 | () 81 | in 82 | Vscode.TreeItem.set_iconPath item icon; 83 | Vscode.TreeItem.set_command item command; 84 | item 85 | ;; 86 | 87 | let navigate_holes_item = 88 | let icon = `ThemeIcon (Vscode.ThemeIcon.make ~id:"breakpoints-activate" ()) in 89 | let label = 90 | `TreeItemLabel (Vscode.TreeItemLabel.create ~label:"Navigate between typed holes" ()) 91 | in 92 | let item = Vscode.TreeItem.make_label ~label () in 93 | let command = 94 | Vscode.Command.create 95 | ~title:"Navigate typed holes" 96 | ~command:"ocaml.navigate-typed-holes" 97 | () 98 | in 99 | Vscode.TreeItem.set_iconPath item icon; 100 | Vscode.TreeItem.set_command item command; 101 | item 102 | ;; 103 | 104 | let items = 105 | [ select_sandbox_item 106 | ; terminal_item 107 | ; construct_item 108 | ; jump_item 109 | ; type_search_item 110 | ; navigate_holes_item 111 | ] 112 | ;; 113 | 114 | let getTreeItem ~element = `Value element 115 | 116 | let getChildren ?element () = 117 | match element with 118 | | None -> `Value (Some items) 119 | | Some _ -> `Value (Some []) 120 | ;; 121 | 122 | let register extension = 123 | let module TreeDataProvider = Vscode.TreeDataProvider.Make (Vscode.TreeItem) in 124 | let treeDataProvider = TreeDataProvider.create ~getTreeItem ~getChildren () in 125 | let disposable = 126 | Vscode.Window.registerTreeDataProvider 127 | (module Vscode.TreeItem) 128 | ~viewId:"ocaml-commands" 129 | ~treeDataProvider 130 | in 131 | Vscode.ExtensionContext.subscribe extension ~disposable 132 | ;; 133 | -------------------------------------------------------------------------------- /src/treeview_commands.mli: -------------------------------------------------------------------------------- 1 | (** Register the ocaml-commands tree view. *) 2 | val register : Vscode.ExtensionContext.t -> unit 3 | -------------------------------------------------------------------------------- /src/treeview_help.ml: -------------------------------------------------------------------------------- 1 | let discord_item = 2 | let icon = 3 | `LightDark 4 | Vscode.LightDarkIcon. 5 | { light = `String (Path.asset "discord-light.svg" |> Path.to_string) 6 | ; dark = `String (Path.asset "discord-dark.svg" |> Path.to_string) 7 | } 8 | in 9 | let label = `TreeItemLabel (Vscode.TreeItemLabel.create ~label:"Chat on Discord" ()) in 10 | let item = Vscode.TreeItem.make_label ~label () in 11 | let command = 12 | Vscode.Command.create 13 | ~title:"Open" 14 | ~command:"vscode.open" 15 | ~arguments: 16 | [ Vscode.Uri.parse "https://discord.gg/cCYQbqN" () |> Vscode.Uri.t_to_js ] 17 | () 18 | in 19 | Vscode.TreeItem.set_iconPath item icon; 20 | Vscode.TreeItem.set_command item command; 21 | item 22 | ;; 23 | 24 | let discuss_item = 25 | let icon = 26 | `LightDark 27 | Vscode.LightDarkIcon. 28 | { light = `String (Path.asset "chat-light.svg" |> Path.to_string) 29 | ; dark = `String (Path.asset "chat-dark.svg" |> Path.to_string) 30 | } 31 | in 32 | let label = 33 | `TreeItemLabel (Vscode.TreeItemLabel.create ~label:"Ask a question on Discuss" ()) 34 | in 35 | let item = Vscode.TreeItem.make_label ~label () in 36 | let command = 37 | Vscode.Command.create 38 | ~title:"Open" 39 | ~command:"vscode.open" 40 | ~arguments: 41 | [ Vscode.Uri.parse "https://discuss.ocaml.org/" () |> Vscode.Uri.t_to_js ] 42 | () 43 | in 44 | Vscode.TreeItem.set_iconPath item icon; 45 | Vscode.TreeItem.set_command item command; 46 | item 47 | ;; 48 | 49 | let github_item = 50 | let icon = 51 | `LightDark 52 | Vscode.LightDarkIcon. 53 | { light = `String (Path.asset "github-light.svg" |> Path.to_string) 54 | ; dark = `String (Path.asset "github-dark.svg" |> Path.to_string) 55 | } 56 | in 57 | let label = 58 | `TreeItemLabel (Vscode.TreeItemLabel.create ~label:"Open an issue on Github" ()) 59 | in 60 | let item = Vscode.TreeItem.make_label ~label () in 61 | let command = 62 | Vscode.Command.create 63 | ~title:"Open" 64 | ~command:"vscode.open" 65 | ~arguments: 66 | [ Vscode.Uri.parse "https://github.com/ocamllabs/vscode-ocaml-platform" () 67 | |> Vscode.Uri.t_to_js 68 | ] 69 | () 70 | in 71 | Vscode.TreeItem.set_iconPath item icon; 72 | Vscode.TreeItem.set_command item command; 73 | item 74 | ;; 75 | 76 | let items = [ discord_item; discuss_item; github_item ] 77 | let getTreeItem ~element = `Value element 78 | 79 | let getChildren ?element () = 80 | match element with 81 | | None -> `Value (Some items) 82 | | Some _ -> `Value (Some []) 83 | ;; 84 | 85 | let register extension = 86 | let module TreeDataProvider = Vscode.TreeDataProvider.Make (Vscode.TreeItem) in 87 | let treeDataProvider = TreeDataProvider.create ~getTreeItem ~getChildren () in 88 | let disposable = 89 | Vscode.Window.registerTreeDataProvider 90 | (module Vscode.TreeItem) 91 | ~viewId:"ocaml-help" 92 | ~treeDataProvider 93 | in 94 | Vscode.ExtensionContext.subscribe extension ~disposable 95 | ;; 96 | -------------------------------------------------------------------------------- /src/treeview_help.mli: -------------------------------------------------------------------------------- 1 | (** Register the ocaml-help tree view. *) 2 | val register : Vscode.ExtensionContext.t -> unit 3 | -------------------------------------------------------------------------------- /src/treeview_sandbox.mli: -------------------------------------------------------------------------------- 1 | (** Register the ocaml-sandbox tree view *) 2 | val register : Vscode.ExtensionContext.t -> Extension_instance.t -> unit 3 | -------------------------------------------------------------------------------- /src/treeview_switches.mli: -------------------------------------------------------------------------------- 1 | (** Register the ocaml-switches tree view *) 2 | val register : Vscode.ExtensionContext.t -> Extension_instance.t -> unit 3 | -------------------------------------------------------------------------------- /src/vscode_ocaml_platform.ml: -------------------------------------------------------------------------------- 1 | open Import 2 | 3 | let suggest_to_pick_sandbox () = 4 | let open Promise.Syntax in 5 | let select_pm_button_text = "Select package manager and sandbox" in 6 | let+ selection = 7 | Window.showInformationMessage 8 | ~message: 9 | "OCaml Platform is using the package manager and sandbox available in the \ 10 | environment. Pick a particular package manager and sandbox by clicking the \ 11 | button below" 12 | ~choices:[ select_pm_button_text, () ] 13 | () 14 | in 15 | Option.iter selection ~f:(fun () -> 16 | let (_ : Ojs.t option Promise.t) = 17 | Vscode.Commands.executeCommand 18 | ~command:Extension_consts.Commands.select_sandbox 19 | ~args:[] 20 | in 21 | ()) 22 | ;; 23 | 24 | let notify_configuration_changes instance = 25 | Workspace.onDidChangeConfiguration 26 | ~listener:(fun _event -> 27 | let codelens = Settings.(get server_codelens_setting) in 28 | let extended_hover = Settings.(get server_extendedHover_setting) in 29 | let dune_diagnostics = Settings.(get server_duneDiagnostics_setting) in 30 | let syntax_documentation = Settings.(get server_syntaxDocumentation_setting) in 31 | Extension_instance.set_configuration 32 | instance 33 | ~codelens 34 | ~extended_hover 35 | ~dune_diagnostics 36 | ~syntax_documentation 37 | ()) 38 | () 39 | ;; 40 | 41 | let activate (extension : ExtensionContext.t) = 42 | let open Promise.Syntax in 43 | let instance = Extension_instance.make () in 44 | ExtensionContext.subscribe 45 | extension 46 | ~disposable:(Extension_instance.disposable instance); 47 | ExtensionContext.subscribe extension ~disposable:(notify_configuration_changes instance); 48 | Dune_formatter.register extension instance; 49 | Dune_task_provider.register extension instance; 50 | Extension_commands.register_all_commands extension instance; 51 | Treeview_switches.register extension instance; 52 | Treeview_sandbox.register extension instance; 53 | Treeview_commands.register extension; 54 | Treeview_help.register extension; 55 | Ast_editor.register extension instance; 56 | Cm_editor.register extension instance; 57 | Repl.register extension instance; 58 | Earlybird.register extension instance; 59 | let sandbox_opt = Sandbox.of_settings_or_detect () in 60 | let (_ : unit Promise.t) = 61 | let* sandbox_opt = sandbox_opt in 62 | let is_fallback = Option.is_none sandbox_opt in 63 | if 64 | is_fallback 65 | (* if the sandbox we just set up is a fallback sandbox, we create a pop-up 66 | message to offer the user to pick a sandbox they want; note: if the 67 | user picks another sandbox in the pop-up, we redo part of work we have 68 | just done; this is the case because we can't wait or rely on user to 69 | pick a sandbox: they may ignore the pop-up leaving the extension 70 | hanging, so we use fallback; w/ a proper detection mechanism, we would 71 | redo work in rare cases *) 72 | then suggest_to_pick_sandbox () 73 | else Promise.return () 74 | in 75 | let (_ : unit Promise.t) = 76 | let* sandbox_opt = sandbox_opt in 77 | let sandbox = Option.value sandbox_opt ~default:Sandbox.Global in 78 | Extension_instance.set_sandbox instance sandbox; 79 | let* () = Extension_instance.update_ocaml_info instance in 80 | let+ () = Extension_instance.start_language_server instance in 81 | () 82 | in 83 | Promise.return () 84 | ;; 85 | 86 | (* see {{:https://code.visualstudio.com/api/references/vscode-api#Extension} 87 | activate() *) 88 | let () = 89 | let open Js_of_ocaml.Js in 90 | export "activate" (wrap_callback activate) 91 | ;; 92 | -------------------------------------------------------------------------------- /src/vscode_ocaml_platform.mli: -------------------------------------------------------------------------------- 1 | (** Entry point *) 2 | -------------------------------------------------------------------------------- /syntaxes/META.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "source.ocaml.META", 3 | "fileTypes": ["META"], 4 | "patterns": [{ "include": "#comments" }, { "include": "#entries" }], 5 | "repository": { 6 | "comments": { 7 | "comment": "line comment", 8 | "name": "comment.line.META", 9 | "begin": "#", 10 | "end": "$" 11 | }, 12 | "entries": { 13 | "patterns": [ 14 | { 15 | "comment": "assignment or addition", 16 | "match": "\\b([[:word:].]+)[[:space:]]*(\\+?=)[[:space:]]*(\".*\")", 17 | "captures": { 18 | "1": { "name": "entity.name.tag.META" }, 19 | "2": { "name": "keyword.operator.META" }, 20 | "3": { "name": "string.quoted.double.META" } 21 | } 22 | }, 23 | { 24 | "comment": "assignment or addition with formal predicates", 25 | "begin": "\\b([[:word:].]+)[[:space:]]*\\(", 26 | "beginCaptures": { 27 | "1": { "name": "entity.name.tag.META" } 28 | }, 29 | "end": "\\)[[:space:]]*(\\+?=)[[:space:]]*(\".*\")", 30 | "endCaptures": { 31 | "1": { "name": "keyword.operator.META" }, 32 | "2": { "name": "string.quoted.double.META" } 33 | }, 34 | "patterns": [ 35 | { "match": "-", "name": "keyword.operator.META" }, 36 | { 37 | "comment": "standard predicates", 38 | "match": "\\b(byte|native|toploop|mt|mt_posix|mt_vm|gprof|autolink)\\b", 39 | "name": "constant.language.META" 40 | } 41 | ] 42 | }, 43 | { 44 | "comment": "subpackage", 45 | "begin": "\\b(package)[[:space:]]*(\"[^.]*\")[[:space:]]*\\(", 46 | "beginCaptures": { 47 | "1": { "name": "keyword.other.META" }, 48 | "2": { "name": "string.quoted.double.META" } 49 | }, 50 | "end": "\\)", 51 | "patterns": [{ "include": "$self" }] 52 | } 53 | ] 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /syntaxes/atd.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ATD", 3 | "scopeName": "source.atd", 4 | "fileTypes": ["atd"], 5 | "patterns": [ 6 | { "include": "source.ocaml#comments" }, 7 | { "include": "source.ocaml#strings" }, 8 | { "include": "#annotations" }, 9 | { "include": "#definitions" }, 10 | { "include": "#keywords" }, 11 | { "include": "#types" }, 12 | { "include": "source.ocaml#operators" }, 13 | { "include": "source.ocaml#identifiers" } 14 | ], 15 | "repository": { 16 | "annotations": { 17 | "patterns": [ 18 | { 19 | "begin": "(<)[[:space:]]*([[:lower:]_][[:word:]']*)", 20 | "end": ">", 21 | "beginCaptures": { 22 | "1": { "name": "keyword.other.atd" }, 23 | "2": { "name": "keyword.other.atd" } 24 | }, 25 | "endCaptures": [{ "name": "keyword.other.atd" }], 26 | "patterns": [{ "include": "$self" }] 27 | } 28 | ] 29 | }, 30 | "definitions": { 31 | "match": "\\b(type)[[:space:]]+('[[:alpha:]][[:word:]']*[[:space:]]+|\\(.*\\)[[:space:]]*)?([[:lower:]_][[:word:]']*)", 32 | "captures": { 33 | "1": { "name": "keyword.other.atd" }, 34 | "2": { "patterns": [{ "include": "$self" }] }, 35 | "3": { "name": "entity.name.function.binding.atd" } 36 | } 37 | }, 38 | "keywords": { 39 | "name": "keyword.other.atd", 40 | "match": "\\b(type|of|inherit)\\b(?!')" 41 | }, 42 | "types": { 43 | "patterns": [ 44 | { 45 | "comment": "type parameter", 46 | "name": "storage.type.ocaml.atd", 47 | "match": "'[[:alpha:]][[:word:]']*\\b" 48 | }, 49 | { 50 | "comment": "builtin type", 51 | "name": "support.type.ocaml.atd", 52 | "match": "\\b(unit|bool|int|float|string|abstract)\\b" 53 | } 54 | ] 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /syntaxes/menhir-action.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "source.action.menhir", 3 | "injectionSelector": "L:source.embedded-action.menhir", 4 | "patterns": [ 5 | { 6 | "begin": "(\\$(?:startpos|endpos|startofs|endofs|loc))[[:space:]]*\\(", 7 | "beginCaptures": { 8 | "1": { "name": "keyword.other.menhir" } 9 | }, 10 | "end": "\\)", 11 | "patterns": [ 12 | { "include": "#anon-capture" }, 13 | { "include": "source.ocaml.menhir#token-name" }, 14 | { "include": "source.ocaml.menhir#production-name" } 15 | ] 16 | }, 17 | { 18 | "match": "\\$(?:startpos|endpos|symbolstartpos|startofs|endofs|symbolstartofs|loc|sloc)\\b", 19 | "name": "keyword.other.menhir" 20 | }, 21 | { "include": "#anon-capture" } 22 | ], 23 | "repository": { 24 | "anon-capture": { 25 | "match": "\\$[[:digit:]]+\\b", 26 | "name": "keyword.other.menhir" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /syntaxes/merlin.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "source.ocaml.merlin", 3 | "fileTypes": ["merlin"], 4 | "patterns": [ 5 | { "include": "#comment" }, 6 | { "include": "#directory" }, 7 | { "include": "#flag" }, 8 | { "include": "#package" } 9 | ], 10 | "repository": { 11 | "comment": { 12 | "begin": "#", 13 | "end": "$", 14 | "name": "comment.line.merlin" 15 | }, 16 | "directory": { 17 | "begin": "\\b(B|S)\\b", 18 | "end": "$", 19 | "beginCaptures": { 20 | "1": { "name": "keyword.other.merlin" } 21 | }, 22 | "name": "string.other.merlin", 23 | "patterns": [ 24 | { 25 | "match": "[:./ \\\\]", 26 | "name": "keyword.other.path.merlin" 27 | }, 28 | { 29 | "match": "[*?]", 30 | "name": "keyword.other.glob.merlin" 31 | } 32 | ] 33 | }, 34 | "flag": { 35 | "begin": "\\b(FLG)\\b", 36 | "end": "$", 37 | "beginCaptures": { 38 | "1": { "name": "keyword.other.merlin" } 39 | }, 40 | "patterns": [ 41 | { 42 | "begin": "\"", 43 | "end": "\"", 44 | "name": "string.quoted.double.merlin" 45 | }, 46 | { 47 | "match": "([\\-\\+])\\b(?:([[:digit:]]+)|([[:lower:]][[:word:]]*))\\b", 48 | "captures": { 49 | "1": { "name": "keyword.other.merlin" }, 50 | "2": { "name": "constant.language.merlin" }, 51 | "3": { "name": "variable.parameter.merlin" } 52 | } 53 | } 54 | ] 55 | }, 56 | "package": { 57 | "begin": "\\b(PKG)\\b", 58 | "end": "$", 59 | "beginCaptures": { 60 | "1": { "name": "storage.type.merlin" } 61 | }, 62 | "patterns": [ 63 | { 64 | "match": "(?", 34 | "beginCaptures": [{ "name": "keyword.operator.ocamlbuild" }], 35 | "endCaptures": [{ "name": "keyword.operator.ocamlbuild" }], 36 | "name": "string.quoted.double.ocamlbuild", 37 | "patterns": [{ "include": "#patterns" }] 38 | }, 39 | { 40 | "comment": "quoted string", 41 | "name": "string.quoted.double.ocamlbuild", 42 | "begin": "\"", 43 | "end": "\"" 44 | }, 45 | { 46 | "comment": "boolean", 47 | "name": "constant.language.ocamlbuild", 48 | "match": "\\b(true|false)\\b" 49 | }, 50 | { 51 | "comment": "operators", 52 | "name": "keyword.operator.ocamlbuild", 53 | "match": "\\b(or|and|not)\\b" 54 | }, 55 | { "include": "#variables" }, 56 | { "include": "$self" } 57 | ] 58 | }, 59 | 60 | "patterns": { 61 | "comment": "glob patterns", 62 | "patterns": [ 63 | { "name": "keyword.operator.ocamlbuild", "match": "\\?" }, 64 | { "name": "constant.language.ocamlbuild", "match": "\\*" }, 65 | { 66 | "begin": "{", 67 | "end": "}", 68 | "beginCaptures": [{ "name": "keyword.operator.ocamlbuild" }], 69 | "endCaptures": [{ "name": "keyword.operator.ocamlbuild" }], 70 | "patterns": [ 71 | { "name": "keyword.operator.ocamlbuild", "match": "," }, 72 | { "include": "#patterns" } 73 | ] 74 | }, 75 | { 76 | "begin": "\\[\\^?", 77 | "end": "\\]", 78 | "beginCaptures": [{ "name": "keyword.operator.ocamlbuild" }], 79 | "endCaptures": [{ "name": "keyword.operator.ocamlbuild" }] 80 | } 81 | ] 82 | }, 83 | 84 | "variables": { 85 | "comment": "pattern variables", 86 | "patterns": [ 87 | { 88 | "match": "(%)\\([^:)]*\\)", 89 | "captures": { 90 | "1": { "name": "keyword.operator.ocamlbuild" } 91 | }, 92 | "patterns": [{ "include": "#expressions" }] 93 | }, 94 | { 95 | "begin": "(%)\\([^:]*(:)", 96 | "end": "\\)", 97 | "beginCaptures": { 98 | "1": { "name": "keyword.operator.ocamlbuild" }, 99 | "2": { "name": "keyword.operator.ocamlbuild" } 100 | }, 101 | "patterns": [{ "include": "#expressions" }] 102 | }, 103 | { 104 | "name": "keyword.operator.ocamlbuild", 105 | "match": "%" 106 | } 107 | ] 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /syntaxes/ocamllex.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OCamllex", 3 | "scopeName": "source.ocaml.ocamllex", 4 | "fileTypes": ["mll"], 5 | "foldingStartMarker": "{", 6 | "foldingStopMarker": "}", 7 | "keyEquivalent": "^~O", 8 | "patterns": [ 9 | { 10 | "include": "source.ocaml#comments" 11 | }, 12 | { 13 | "include": "source.ocaml#strings" 14 | }, 15 | { 16 | "include": "source.ocaml#characters" 17 | }, 18 | 19 | { 20 | "include": "#rules" 21 | }, 22 | { 23 | "include": "#keywords" 24 | }, 25 | { 26 | "include": "#actions" 27 | }, 28 | { 29 | "include": "#regex" 30 | }, 31 | 32 | { 33 | "match": "(’|‘|“|”)", 34 | "name": "invalid.illegal.unrecognized-character.ocamllex" 35 | } 36 | ], 37 | "repository": { 38 | "rules": { 39 | "match": "\\b(rule|and)[[:space:]]+([[:lower:]][[:word:]']*('|\\b))", 40 | "captures": { 41 | "1": { 42 | "name": "keyword.other.ocamllex" 43 | }, 44 | "2": { 45 | "name": "entity.name.function.rule.ocamllex" 46 | } 47 | } 48 | }, 49 | 50 | "keywords": { 51 | "patterns": [ 52 | { 53 | "comment": "ocamllex reserved keywords", 54 | "name": "keyword.other.ocamllex", 55 | "match": "\\b(let|as|rule|and|parse|shortest|refill)\\b(?!')" 56 | }, 57 | { 58 | "comment": "assignment operator", 59 | "match": "=", 60 | "name": "keyword.operator.symbol.ocamllex" 61 | } 62 | ] 63 | }, 64 | 65 | "actions": { 66 | "patterns": [ 67 | { 68 | "comment": "embedded ocaml source", 69 | "begin": "{", 70 | "beginCaptures": [{ "name": "keyword.other.ocamllex" }], 71 | "end": "}", 72 | "endCaptures": [{ "name": "keyword.other.ocamllex" }], 73 | "patterns": [{ "include": "source.ocaml" }] 74 | } 75 | ] 76 | }, 77 | 78 | "regex": { 79 | "patterns": [ 80 | { 81 | "comment": "regex character set", 82 | "name": "punctuation.character-set.ocamllex", 83 | "match": "\\[|\\]" 84 | }, 85 | { 86 | "comment": "regex group", 87 | "name": "punctuation.group.ocamllex", 88 | "match": "\\(|\\)" 89 | }, 90 | { 91 | "comment": "regex operators", 92 | "name": "keyword.operator.ocamllex", 93 | "match": "\\^|#|\\*|\\+|\\?|\\||-" 94 | }, 95 | { 96 | "comment": "end-of-file token", 97 | "name": "constant.language.eof.ocamllex", 98 | "match": "\\beof\\b" 99 | }, 100 | { 101 | "comment": "reference to regex pattern", 102 | "name": "entity.name.type.reference.ocamllex", 103 | "match": "\\b[[:alpha:]][[:word:]']*('|\\b)" 104 | } 105 | ] 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /syntaxes/opam-install.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "source.ocaml.opam-install", 3 | "fileTypes": ["install"], 4 | "patterns": [ 5 | { 6 | "name": "comment.line.opam-install", 7 | "begin": "#", 8 | "end": "$" 9 | }, 10 | { 11 | "match": "^[[:space:]]*(lib|lib_root|libexec|libexec_root|bin|sbin|toplevel|share|share_root|etc|doc|stublibs|man|misc)[[:space:]]*(:)", 12 | "captures": { 13 | "1": { "name": "entity.name.tag.opam-install" }, 14 | "2": { "name": "keyword.operator.opam-install" } 15 | } 16 | }, 17 | { 18 | "name": "string.quoted.double.opam-install", 19 | "begin": "\"", 20 | "end": "\"" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /syntaxes/opam.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "source.ocaml.opam", 3 | "fileTypes": ["opam"], 4 | "patterns": [ 5 | { "include": "#comments" }, 6 | { "include": "#fields" }, 7 | { "include": "#values" } 8 | ], 9 | "repository": { 10 | "comments": { 11 | "patterns": [ 12 | { 13 | "comment": "block comment", 14 | "name": "comment.block.opam", 15 | "begin": "\\(\\*", 16 | "end": "\\*\\)" 17 | }, 18 | { 19 | "comment": "line comment", 20 | "name": "comment.line.opam", 21 | "begin": "#", 22 | "end": "$" 23 | } 24 | ] 25 | }, 26 | 27 | "fields": { 28 | "comment": "labeled field", 29 | "match": "^([[:word:]-]*[[:alpha:]][[:word:]-]*)(:)", 30 | "captures": { 31 | "1": { "name": "entity.name.tag.opam" }, 32 | "2": { "name": "keyword.operator.opam" } 33 | } 34 | }, 35 | 36 | "values": { 37 | "patterns": [ 38 | { 39 | "comment": "boolean literal", 40 | "name": "constant.language.opam", 41 | "match": "\\b(true|false)\\b" 42 | }, 43 | { 44 | "comment": "integer literal", 45 | "name": "constant.numeric.decimal.opam", 46 | "match": "(\\b|\\-?)[[:digit:]]+\\b" 47 | }, 48 | { 49 | "comment": "double-quote string literal", 50 | "name": "string.quoted.double.opam", 51 | "begin": "\"", 52 | "end": "\"", 53 | "patterns": [{ "include": "#string-elements" }] 54 | }, 55 | { 56 | "comment": "triple-double-quote string literal", 57 | "name": "string.quoted.triple-double.opam", 58 | "begin": "\"\"\"", 59 | "end": "\"\"\"", 60 | "patterns": [{ "include": "#string-elements" }] 61 | }, 62 | { 63 | "comment": "operator", 64 | "name": "keyword.operator.opam", 65 | "match": "[!=<>\\|&?:]+" 66 | }, 67 | { 68 | "comment": "identifier", 69 | "match": "\\b([[:word:]+-]+)\\b", 70 | "name": "variable.parameter.opam" 71 | } 72 | ] 73 | }, 74 | 75 | "string-elements": { 76 | "patterns": [ 77 | { 78 | "comment": "escaped backslash", 79 | "name": "constant.character.escape.opam", 80 | "match": "\\\\\\\\" 81 | }, 82 | { 83 | "comment": "escaped quote or whitespace", 84 | "name": "constant.character.escape.opam", 85 | "match": "\\\\[\"ntbr\\n]" 86 | }, 87 | { 88 | "comment": "character from decimal ASCII code", 89 | "name": "constant.character.escape.opam", 90 | "match": "\\\\[[:digit:]]{3}" 91 | }, 92 | { 93 | "comment": "character from hexadecimal ASCII code", 94 | "name": "constant.character.escape.opam", 95 | "match": "\\\\x[[:xdigit:]]{2}" 96 | }, 97 | { 98 | "comment": "variable interpolation", 99 | "name": "constant.variable.opam", 100 | "begin": "%\\{", 101 | "end": "}\\%" 102 | }, 103 | { 104 | "comment": "unknown escape sequence", 105 | "name": "invalid.illegal.unknown-escape.opam", 106 | "match": "\\\\." 107 | } 108 | ] 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /syntaxes/reason-markdown-codeblock.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [], 3 | "injectionSelector": "L:text.html.markdown", 4 | "patterns": [{ "include": "#reason-code-block" }], 5 | "repository": { 6 | "reason-code-block": { 7 | "begin": "(^|\\G)([[:space:]]*)(\\`{3,}|~{3,})[[:space:]]*(?i:(re|reason|reasonml)([[:space:]]+[^`~]*)?$)", 8 | "name": "markup.fenced_code.block.markdown", 9 | "end": "(^|\\G)(\\2|[[:space:]]{0,3})(\\3)[[:space:]]*$", 10 | "beginCaptures": { 11 | "3": { "name": "punctuation.definition.markdown" }, 12 | "4": { "name": "fenced_code.block.language.markdown" }, 13 | "5": { "name": "fenced_code.block.language.attributes.markdown" } 14 | }, 15 | "endCaptures": { 16 | "3": { "name": "punctuation.definition.markdown" } 17 | }, 18 | "patterns": [ 19 | { 20 | "begin": "(^|\\G)([[:space:]]*)(.*)", 21 | "while": "(^|\\G)(?![[:space:]]*([`~]{3,})[[:space:]]*$)", 22 | "contentName": "meta.embedded.block.reason", 23 | "patterns": [ 24 | { 25 | "include": "source.reason" 26 | } 27 | ] 28 | } 29 | ] 30 | } 31 | }, 32 | "scopeName": "markdown.reason.codeblock" 33 | } 34 | -------------------------------------------------------------------------------- /tests/fixtures/sample-opam/foo.ml: -------------------------------------------------------------------------------- 1 | let hello () = print_endline "hey there" 2 | -------------------------------------------------------------------------------- /tests/fixtures/sample-opam/main.ml: -------------------------------------------------------------------------------- 1 | let () = Foo.hello () 2 | 3 | let () = Printf.printf "hello" 4 | -------------------------------------------------------------------------------- /tests/fixtures/sample-opam/sample-opam.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | synopsis: "Synopsis" 3 | maintainer: "foobar" 4 | authors: "foobar" 5 | license: "ISC" 6 | homepage: "foo" 7 | bug-reports: "https//example.com" 8 | depends: ["ocaml"] 9 | -------------------------------------------------------------------------------- /tests/suite/basic/problems.test.js: -------------------------------------------------------------------------------- 1 | const assert = require("node:assert/strict"); 2 | 3 | const problemLocations = { 4 | 'File "file.ml", line 4, characters 6-7:': [ 5 | "file.ml", 6 | "4", 7 | undefined, 8 | "6", 9 | "7", 10 | ], 11 | 12 | 'File "helloworld.ml", lines 4-7, characters 6-3:': [ 13 | "helloworld.ml", 14 | "4", 15 | "7", 16 | "6", 17 | "3", 18 | ], 19 | 20 | 'File "src/intf_error.ml", line 1:': [ 21 | "src/intf_error.ml", 22 | "1", 23 | undefined, 24 | undefined, 25 | undefined, 26 | ], 27 | }; 28 | 29 | const problemMessages = { 30 | "Error: This expression has type int": [ 31 | "Error", 32 | undefined, 33 | "This expression has type int", 34 | ], 35 | 36 | "Warning: Cannot safely evaluate the definition of the following cycle": [ 37 | "Warning", 38 | undefined, 39 | "Cannot safely evaluate the definition of the following cycle", 40 | ], 41 | 42 | "Warning 26: unused variable y.": ["Warning", "26", "unused variable y."], 43 | 44 | "Error (warning 8): this pattern-matching is not exhaustive.": [ 45 | "Error", 46 | "8", 47 | "this pattern-matching is not exhaustive.", 48 | ], 49 | }; 50 | 51 | suite("basic", () => { 52 | test("problem matcher", () => { 53 | const locationRegex = 54 | /^\s*\bFile\b\s*"(.*)",\s*\blines?\b\s*(\d+)(?:-(\d+))?(?:,\s*\bcharacters\b\s*(\d+)-(\d+)\s*)?:\s*$/; 55 | 56 | const messageRegex = 57 | /^(?:\s*\bParse\b\s*)?\s*\b([Ee]rror|Warning)\b\s*(?:(?:\(\s*\bwarning\b\s*)?(\d+)\)?)?\s*:\s*(.*)$/; 58 | 59 | for (const [problem, expected] of Object.entries(problemLocations)) { 60 | const captures = problem.match(locationRegex); 61 | assert.notStrictEqual( 62 | captures, 63 | null, 64 | `Location regex should match: ${problem}`, 65 | ); 66 | assert.deepStrictEqual(captures?.slice(1), expected); 67 | } 68 | 69 | for (const [problem, expected] of Object.entries(problemMessages)) { 70 | const captures = problem.match(messageRegex); 71 | assert.notStrictEqual( 72 | captures, 73 | null, 74 | `Message regex should match: ${problem}`, 75 | ); 76 | assert.deepStrictEqual(captures?.slice(1), expected); 77 | } 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /tests/suite/opam/languageId.test.js: -------------------------------------------------------------------------------- 1 | const assert = require("node:assert/strict"); 2 | const path = require("node:path"); 3 | const vscode = require("vscode"); 4 | 5 | const root = path.resolve(__dirname, "../../../"); 6 | const fixtureSrcDir = path.join(root, "tests", "fixtures"); 7 | const sampleOpamSrc = path.join(fixtureSrcDir, "sample-opam"); 8 | 9 | suite("opam", () => { 10 | test("languageId", async () => { 11 | const foo = await vscode.workspace.openTextDocument( 12 | vscode.Uri.file(path.join(sampleOpamSrc, "foo.ml")), 13 | ); 14 | const main = await vscode.workspace.openTextDocument( 15 | vscode.Uri.file(path.join(sampleOpamSrc, "main.ml")), 16 | ); 17 | 18 | assert.strictEqual( 19 | foo.languageId, 20 | "ocaml", 21 | "Must be identified as an OCaml document", 22 | ); 23 | assert.strictEqual( 24 | main.languageId, 25 | "ocaml", 26 | "Must be identified as an OCaml document", 27 | ); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@tsconfig/strictest/tsconfig.json", 4 | "@tsconfig/node24/tsconfig.json" 5 | ], 6 | "compilerOptions": { 7 | "noEmit": true 8 | }, 9 | "exclude": ["_build", "_opam", "astexplorer", "dist", "node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /vscode-interop.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: 4 | "Interop library compatible with the vscode and vscode-node bindings" 5 | maintainer: ["Rudi Grinberg "] 6 | authors: [ 7 | "Rudi Grinberg " 8 | "Sora Morimoto " 9 | "Max Lantas " 10 | "Manas " 11 | "Rusty Key " 12 | "Louis Roché " 13 | "Ulugbek Abdullaev " 14 | "Javier Chávarri " 15 | ] 16 | license: "ISC" 17 | homepage: "https://github.com/ocamllabs/vscode-ocaml-platform" 18 | bug-reports: "https://github.com/ocamllabs/vscode-ocaml-platform/issues" 19 | depends: [ 20 | "dune" {>= "3.19"} 21 | "ocaml" {>= "4.08"} 22 | "js_of_ocaml" {>= "6.0"} 23 | "gen_js_api" {= "1.1.4"} 24 | "ocamlformat" {= "0.27.0" & with-dev-setup} 25 | "ocaml-lsp-server" {with-dev-setup} 26 | "odoc" {with-doc} 27 | ] 28 | build: [ 29 | ["dune" "subst"] {dev} 30 | [ 31 | "dune" 32 | "build" 33 | "-p" 34 | name 35 | "-j" 36 | jobs 37 | "@install" 38 | "@runtest" {with-test} 39 | "@doc" {with-doc} 40 | ] 41 | ] 42 | dev-repo: "git+https://github.com/ocamllabs/vscode-ocaml-platform.git" 43 | x-maintenance-intent: ["(latest)"] 44 | -------------------------------------------------------------------------------- /vscode-node.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: 4 | "Bindings for Node.js that are used by the Visual Studio Code extension" 5 | maintainer: ["Rudi Grinberg "] 6 | authors: [ 7 | "Rudi Grinberg " 8 | "Sora Morimoto " 9 | "Max Lantas " 10 | "Manas " 11 | "Rusty Key " 12 | "Louis Roché " 13 | "Ulugbek Abdullaev " 14 | "Javier Chávarri " 15 | ] 16 | license: "ISC" 17 | homepage: "https://github.com/ocamllabs/vscode-ocaml-platform" 18 | bug-reports: "https://github.com/ocamllabs/vscode-ocaml-platform/issues" 19 | depends: [ 20 | "dune" {>= "3.19"} 21 | "ocaml" {>= "4.08"} 22 | "js_of_ocaml" {>= "6.0"} 23 | "gen_js_api" {= "1.1.4"} 24 | "promise_jsoo" {>= "0.4.3"} 25 | "jsonoo" {>= "0.3"} 26 | "vscode-interop" {= version} 27 | "ocamlformat" {= "0.27.0" & with-dev-setup} 28 | "ocaml-lsp-server" {with-dev-setup} 29 | "odoc" {with-doc} 30 | ] 31 | build: [ 32 | ["dune" "subst"] {dev} 33 | [ 34 | "dune" 35 | "build" 36 | "-p" 37 | name 38 | "-j" 39 | jobs 40 | "@install" 41 | "@runtest" {with-test} 42 | "@doc" {with-doc} 43 | ] 44 | ] 45 | dev-repo: "git+https://github.com/ocamllabs/vscode-ocaml-platform.git" 46 | x-maintenance-intent: ["(latest)"] 47 | -------------------------------------------------------------------------------- /vscode-ocaml-platform.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "Visual Studio Code extension for OCaml" 4 | maintainer: ["Rudi Grinberg "] 5 | authors: [ 6 | "Rudi Grinberg " 7 | "Sora Morimoto " 8 | "Max Lantas " 9 | "Manas " 10 | "Rusty Key " 11 | "Louis Roché " 12 | "Ulugbek Abdullaev " 13 | "Javier Chávarri " 14 | ] 15 | license: "ISC" 16 | homepage: "https://github.com/ocamllabs/vscode-ocaml-platform" 17 | bug-reports: "https://github.com/ocamllabs/vscode-ocaml-platform/issues" 18 | depends: [ 19 | "dune" {>= "3.19"} 20 | "vscode" {= version} 21 | "ocaml" {>= "5.3"} 22 | "js_of_ocaml" {>= "6.0"} 23 | "gen_js_api" {= "1.1.4"} 24 | "base" {>= "v0.17"} 25 | "promise_jsoo" {>= "0.4.3"} 26 | "jsonoo" {>= "0.3"} 27 | "ocaml-version" {>= "4.0"} 28 | "ppxlib" {>= "0.35"} 29 | "opam-file-format" {>= "2.1.6"} 30 | "ocamlformat" {= "0.27.0" & with-dev-setup} 31 | "ocaml-lsp-server" {with-dev-setup} 32 | "odoc" {with-doc} 33 | ] 34 | build: [ 35 | ["dune" "subst"] {dev} 36 | [ 37 | "dune" 38 | "build" 39 | "-p" 40 | name 41 | "-j" 42 | jobs 43 | "@install" 44 | "@runtest" {with-test} 45 | "@doc" {with-doc} 46 | ] 47 | ] 48 | dev-repo: "git+https://github.com/ocamllabs/vscode-ocaml-platform.git" 49 | x-maintenance-intent: ["(latest)"] 50 | -------------------------------------------------------------------------------- /vscode.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "Visual Studio Code Bindings" 4 | maintainer: ["Rudi Grinberg "] 5 | authors: [ 6 | "Rudi Grinberg " 7 | "Sora Morimoto " 8 | "Max Lantas " 9 | "Manas " 10 | "Rusty Key " 11 | "Louis Roché " 12 | "Ulugbek Abdullaev " 13 | "Javier Chávarri " 14 | ] 15 | license: "ISC" 16 | homepage: "https://github.com/ocamllabs/vscode-ocaml-platform" 17 | bug-reports: "https://github.com/ocamllabs/vscode-ocaml-platform/issues" 18 | depends: [ 19 | "dune" {>= "3.19"} 20 | "ocaml" {>= "4.08"} 21 | "js_of_ocaml" {>= "6.0"} 22 | "gen_js_api" {= "1.1.4"} 23 | "promise_jsoo" {>= "0.4.3"} 24 | "jsonoo" {>= "0.3"} 25 | "vscode-interop" {= version} 26 | "ocamlformat" {= "0.27.0" & with-dev-setup} 27 | "ocaml-lsp-server" {with-dev-setup} 28 | "odoc" {with-doc} 29 | ] 30 | build: [ 31 | ["dune" "subst"] {dev} 32 | [ 33 | "dune" 34 | "build" 35 | "-p" 36 | name 37 | "-j" 38 | jobs 39 | "@install" 40 | "@runtest" {with-test} 41 | "@doc" {with-doc} 42 | ] 43 | ] 44 | dev-repo: "git+https://github.com/ocamllabs/vscode-ocaml-platform.git" 45 | x-maintenance-intent: ["(latest)"] 46 | --------------------------------------------------------------------------------