From 25c05a16440b19acb7c553edfbf103f44abaff26 Mon Sep 17 00:00:00 2001 From: Moosieus Date: Tue, 1 Apr 2025 13:27:26 -0400 Subject: [PATCH 1/2] Documentation housekeeping: * Move install instructions to a single page. * Centralize architecture docs and expand on them. * Add script for connecting to project nodes, which I've found useful in the past. --- README.md | 221 +---- apps/server/bin/project_shell.sh | 8 + .../bin/{debug_shell.sh => server_shell.sh} | 0 apps/server/lib/mix/tasks/package.ex | 3 +- assets/expert_10k_view.excalidraw | 874 ++++++++++++++++++ assets/expert_10k_view.svg | 2 + pages/architecture.md | 70 +- pages/development.md | 92 ++ pages/installation.md | 305 +++++- 9 files changed, 1311 insertions(+), 264 deletions(-) create mode 100755 apps/server/bin/project_shell.sh rename apps/server/bin/{debug_shell.sh => server_shell.sh} (100%) create mode 100644 assets/expert_10k_view.excalidraw create mode 100644 assets/expert_10k_view.svg create mode 100644 pages/development.md diff --git a/README.md b/README.md index 5f880e2b..8ddb9df1 100644 --- a/README.md +++ b/README.md @@ -1,212 +1,21 @@ -[![Discord](https://img.shields.io/badge/Discord-5865F3?style=flat&logo=discord&logoColor=white&link=https://discord.gg/FvdkuVyted)](https://discord.gg/FvdkuVyted) -![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/lexical-lsp/lexical/elixir.yml) +# Expert -Lexical logo: Lexi the lynx +The official language server for the Elixir programming language. -# Lexical - -Lexical is a next-generation language server for the Elixir programming language. - -


- -### Note: Development on Lexical will slow while we transition to the new combined effort, Expert. +## Getting Started +Expert is under active development. See the [installation guide](pages/installation.md) for details. ## Features - - * Context aware code completion - * As-you-type compilation - * Advanced error highlighting - * Code actions - * Code Formatting - * Go To Definition - * Completely isolated build environment - -## What makes Lexical different? -There are a couple things that lexical does differently than other language servers. Let's look at what separates it from -the pack. - -#### Architecture - -When lexical starts, it boots an erlang virtual machine that runs the language server and its code. It then boots a -separate virtual machine that runs your project code and connects the two via distribution. This provides the following benefits: - - * None of lexical's dependencies will conflict with your project. This means that lexical can make use of dependencies to make developing in it easier without having to "vendor" them. It also means that you can use lexical to work on your project, **even if lexical depends on your project**. - * Your project can depend on a different version of elixir and erlang than lexical itself. This means that lexical can make use of the latest versions of elixir and erlang while still supporting projects that run on older versions. - * The build environment for your project is only aware of your project, which enables as-you-type compilation and error reporting. - * In the future, there is a possibility of having the lexical vm instance control multiple projects - -#### Ease of contribution - -Lexical has been designed to be easy to contribute to and work on. It features: - - * A consistent data model that represents the Language Server Protocol and `mix` tasks to generate new Language Server models. - * A clearly defined separation between the language server and project code - * A set of utilities that deal with manipulating code - * A set of unit tests and test cases that make testing new features easy. - -#### Focus on developer productivity - -Lexical is also built with an eye on increasing developer productivity, and approaches some common features a little bit -differently. For example, Lexical's code completion is _context aware_, which means that if you type `alias MyModule.|` -you will only receive completions for modules and not the names of functions in `MyModule`. This awareness will extend -to other areas, which means: - - * You won't see completions for random functions and types in strings. In fact, when extended to string literals, Lexical will only show you completions if you're inside of an interpolation (`"hello there #{na|}'`). - * If you're inside of a struct reference (`%StructModule.|`), you will only see modules listed that define structs, or are the parents of modules that define structs. - -Because of this focus, Lexical aims to deliver depth of features rather than breadth of them. We'll likely spend -more time making sure each thing we add works and feels _just right_ rather than adding a whole slew of features -that people mostly won't use. - -#### As you type compilation -Because your project is run in a separate virtual machine, we can compile the code that you're working on as you -type. This means you see errors _immediately_ rather than having to wait for a save. The result is you see and -fix typos, warnings, unused variables and a whole host of errors when they occur, which makes your code better, -faster. - -## Installation - -Follow the [Detailed Installation Instructions](pages/installation.md) - -``` -mix package -``` - -Lexical will now be available in `_build/dev/package/lexical` - -If you would like to change the output directory, you can do so with the `--path` option - -``` -mix package --path /path/to/lexical -``` - -Lexical will be available in `/path/to/lexical`. - -## Development - -Lexical is intended to run on any version of Erlang 24+ and Elixir -1.13+. Before beginning development, you should install Erlang -`24.3.4.12` and Elixir `1.13.4` and use those versions when you're -building code. - -You should also look at the [complete compatibility -matrix](pages/installation.md#caveats) do see which versions are -supported. - -You're going to need a local instance in order to develop lexical, so follow the [Detailed Installation Instructions](pages/installation.md) first. - -Then, install the git hooks with - -``` -mix hooks -``` - -These are pre-commit hooks that will check for correct formatting and run credo for you. - -After this, you're ready to put together a pull request for Lexical! - -#### Benchmarks - -The `remote_control` project has a set of benchmarks that measure the speed of various internal functions and data structures. In order to use them, you first need to install [git large file storage](https://docs.github.com/en/repositories/working-with-files/managing-large-files/installing-git-large-file-storage), and then run `git pull`. Benchmarks are stored in the `benchmarks` subdirectory, and can be run via - -``` -mix benchmark /benchmarks/.exs -``` - -### Logging - -When lexical starts up, it creates a `.lexical` directory in the root -directory of a project. Inside that directory are two log files, -`lexical.log` and `project.log`. The `.lexical.log` log file contains -logging and OTP messages from the language server, while the -`project.log` file contains logging and OTP messages from the -project's node. While developing lexical, it's helpful to open up a -terminal and tail both of these log files so you can see any errors -and messages that lexical emits. To do that, run the following in a -terminal while in the project's root directory: - -```shell -tail -f .lexical/*.log -``` - -Note: These log files roll over when they reach 1 megabyte, so after a -time, it will be necessary to re-run the above command. - -### Debugging - -Lexical supports a debug shell, which will connect a remote shell to a -currently-running language server process. To use it, `cd` into your -lexical installation directory and run - -``` -./bin/debug_shell.sh -``` - -For example, if I would like to run the debug server for a server -running in your `lexical` project, run: - -``` -./bin/debug_shell.sh lexical -``` - -...and you will be connected to a remote IEx session _inside_ my -language server. This allows you to investigate processes, make -changes to the running code, or run `:observer`. - -While in the debugging shell, all the functions in -`Lexical.Server.IEx.Helpers` are imported for you, and some common -modules, like `Lexical.Project` and `Lexical.Document` are -aliased. - -You can also start the lexical server in interactive mode via -`./bin/start_lexical.sh iex`. Combining this with the helpers that are -imported will allow you to run projects and do completions entirely in -the shell. - - *Note*: The helpers assume that all of your projects are in folders that are siblings with your lexical project. - -Consider the example shell session: - -``` -./bin/start_lexical.sh iex -iex(1)> start_project :other -# the project in the ../other directory is started -compile_project(:other) -# the other project is compiled -iex(2)> complete :other, "defmo|" -[ - #Protocol.Types.Completion.Item<[ - detail: "", - insert_text: "defmacro ${1:name}($2) do\n $0\nend\n", - insert_text_format: :snippet, - kind: :class, - label: "defmacro (Define a macro)", - sort_text: "093_defmacro (Define a macro)" - ]>, - #Protocol.Types.Completion.Item<[ - detail: "", - insert_text: "defmacrop ${1:name}($2) do\n $0\nend\n", - insert_text_format: :snippet, - kind: :class, - label: "defmacrop (Define a private macro)", - sort_text: "094_defmacrop (Define a private macro)" - ]>, - #Protocol.Types.Completion.Item<[ - detail: "", - insert_text: "defmodule ${1:module name} do\n $0\nend\n", - insert_text_format: :snippet, - kind: :class, - label: "defmodule (Define a module)", - sort_text: "092_defmodule (Define a module)" - ]> -] -``` - -The same kind of support is available when you run `iex -S mix` in the -lexical directory, and is helpful for narrowing down issues without -disturbing your editor flow. - -### Other resources - +* Context aware code completion +* As-you-type compilation +* Advanced error highlighting +* Code actions +* Code Formatting +* Go To Definition +* Completely isolated build environment + +## Guides and Resources +* [Installation Guide](pages/installation.md) +* [Development and Contributing](pages/development.md) * [Architecture](pages/architecture.md) * [Glossary](pages/glossary.md) diff --git a/apps/server/bin/project_shell.sh b/apps/server/bin/project_shell.sh new file mode 100755 index 00000000..b6aa796f --- /dev/null +++ b/apps/server/bin/project_shell.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +project_name=$1 +node_name=$(epmd -names | grep project-"$project_name" | awk '{print $2}') + +iex --name "shell@127.0.0.1" \ + --remsh "${node_name}" \ + --cookie lexical diff --git a/apps/server/bin/debug_shell.sh b/apps/server/bin/server_shell.sh similarity index 100% rename from apps/server/bin/debug_shell.sh rename to apps/server/bin/server_shell.sh diff --git a/apps/server/lib/mix/tasks/package.ex b/apps/server/lib/mix/tasks/package.ex index 522aa723..4518ab7c 100644 --- a/apps/server/lib/mix/tasks/package.ex +++ b/apps/server/lib/mix/tasks/package.ex @@ -42,7 +42,8 @@ defmodule Mix.Tasks.Package do ```text bin/ start_lexical.sh - debug_shell.sh + server_shell.sh + project_shell.sh lib/ lx_common.sh lx_remote_control.sh diff --git a/assets/expert_10k_view.excalidraw b/assets/expert_10k_view.excalidraw new file mode 100644 index 00000000..ebd7c649 --- /dev/null +++ b/assets/expert_10k_view.excalidraw @@ -0,0 +1,874 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "1NvYTRSXIkj-MmSXEZDpA", + "type": "text", + "x": 646.354499347093, + "y": 213.41198670336638, + "width": 223.28334045410156, + "height": 35, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2G", + "roundness": null, + "seed": 948242292, + "version": 141, + "versionNonce": 1481068020, + "isDeleted": false, + "boundElements": null, + "updated": 1743517427555, + "link": null, + "locked": false, + "text": "Editor of choice", + "fontSize": 28, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Editor of choice", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "gU0i61m5SUR3PCGjab1Nk", + "type": "rectangle", + "x": 543.1835537802674, + "y": 251.93959361624746, + "width": 429.62523158775275, + "height": 141.6070674834077, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2V", + "roundness": { + "type": 3 + }, + "seed": 1703624564, + "version": 453, + "versionNonce": 81510772, + "isDeleted": false, + "boundElements": [ + { + "id": "aSaE2lm6JbDpSIT1_-mrq", + "type": "arrow" + } + ], + "updated": 1743519534161, + "link": null, + "locked": false + }, + { + "id": "349WyD6iIwNKeBdYfS6mL", + "type": "rectangle", + "x": 563.8344491924255, + "y": 272.09791402213943, + "width": 388.32344076343657, + "height": 48.80692735757242, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": { + "type": 3 + }, + "seed": 389613044, + "version": 290, + "versionNonce": 730951540, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "t87a7XaMcyjRtusiycx-7" + }, + { + "id": "aSaE2lm6JbDpSIT1_-mrq", + "type": "arrow" + } + ], + "updated": 1743517427555, + "link": null, + "locked": false + }, + { + "id": "t87a7XaMcyjRtusiycx-7", + "type": "text", + "x": 650.2961726259016, + "y": 284.0013777009257, + "width": 215.39999389648438, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3V", + "roundness": null, + "seed": 317153396, + "version": 299, + "versionNonce": 293011828, + "isDeleted": false, + "boundElements": null, + "updated": 1743518150400, + "link": null, + "locked": false, + "text": "Language server client", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "349WyD6iIwNKeBdYfS6mL", + "originalText": "Language server client", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "NR6dRuCidBN-KxXZG6_aw", + "type": "rectangle", + "x": 1287.1425892127688, + "y": 250.66402484013014, + "width": 429.62523158775275, + "height": 228.98539191473935, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4V", + "roundness": { + "type": 3 + }, + "seed": 1634338636, + "version": 753, + "versionNonce": 1718020300, + "isDeleted": false, + "boundElements": [ + { + "id": "aSaE2lm6JbDpSIT1_-mrq", + "type": "arrow" + }, + { + "id": "lr6qZGc-__IXXttM80oht", + "type": "arrow" + } + ], + "updated": 1743519932483, + "link": null, + "locked": false + }, + { + "id": "6vuq3ch811_hdlK9ylah2", + "type": "text", + "x": 1350.0698556121179, + "y": 212.95981427758574, + "width": 289.1666564941406, + "height": 35, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 264453580, + "version": 273, + "versionNonce": 1679775436, + "isDeleted": false, + "boundElements": [], + "updated": 1743519519372, + "link": null, + "locked": false, + "text": "Expert \"Server\" Node", + "fontSize": 28, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Expert \"Server\" Node", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "aSaE2lm6JbDpSIT1_-mrq", + "type": "arrow", + "x": 961.2823164528735, + "y": 292.53998489039753, + "width": 316.1532939369878, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aB", + "roundness": { + "type": 2 + }, + "seed": 2050332276, + "version": 835, + "versionNonce": 1558899828, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "j1Hw4ft9vRxTsLJrfesNN" + } + ], + "updated": 1743519944774, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 316.1532939369878, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "349WyD6iIwNKeBdYfS6mL", + "focus": -0.16232912108996012, + "gap": 9.124426497011427 + }, + "endBinding": { + "elementId": "NR6dRuCidBN-KxXZG6_aw", + "focus": 0.6342477596487058, + "gap": 9.706978822907558 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "j1Hw4ft9vRxTsLJrfesNN", + "type": "text", + "x": 1019.8357540980714, + "y": 281.9931035123528, + "width": 200.63333129882812, + "height": 35, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aBV", + "roundness": null, + "seed": 409961716, + "version": 44, + "versionNonce": 605435508, + "isDeleted": false, + "boundElements": null, + "updated": 1743519871620, + "link": null, + "locked": false, + "text": "jsonRPC/stdio", + "fontSize": 28, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "aSaE2lm6JbDpSIT1_-mrq", + "originalText": "jsonRPC/stdio", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "lr6qZGc-__IXXttM80oht", + "type": "arrow", + "x": 1723.9912041711523, + "y": 334.56083930343334, + "width": 253.29831782496717, + "height": 4.061746494699037e-9, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aD", + "roundness": { + "type": 2 + }, + "seed": 1015255884, + "version": 1208, + "versionNonce": 663859060, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "j0krzACXYPXxjeo6LrIv0" + } + ], + "updated": 1743519958076, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 253.29831782496717, + -4.061746494699037e-9 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "NR6dRuCidBN-KxXZG6_aw", + "focus": -0.26722998565365214, + "gap": 8.244711749983026 + }, + "endBinding": { + "elementId": "_eedVYZPmF5hGCEplDYf8", + "focus": 0.0347031388738286, + "gap": 9.916858255821808 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "j0krzACXYPXxjeo6LrIv0", + "type": "text", + "x": 1794.0735205157525, + "y": 294.3476178798876, + "width": 156.61666870117188, + "height": 70, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aE", + "roundness": null, + "seed": 1741375948, + "version": 60, + "versionNonce": 633332340, + "isDeleted": false, + "boundElements": [], + "updated": 1743517450150, + "link": null, + "locked": false, + "text": "Distributed\nElixir", + "fontSize": 28, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "lr6qZGc-__IXXttM80oht", + "originalText": "Distributed\nElixir", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "5HcUppyI2SC3TWCCM1jOV", + "type": "rectangle", + "x": 1305.3067731670992, + "y": 266.40586498030893, + "width": 391.89790284040436, + "height": 48.80692735757242, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aJ", + "roundness": { + "type": 3 + }, + "seed": 1559308364, + "version": 344, + "versionNonce": 68381900, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "WTk2ad5qb7OEruDpviwlW" + } + ], + "updated": 1743519898347, + "link": null, + "locked": false + }, + { + "id": "WTk2ad5qb7OEruDpviwlW", + "type": "text", + "x": 1446.0723902367154, + "y": 278.3093286590952, + "width": 110.36666870117188, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aK", + "roundness": null, + "seed": 800997068, + "version": 338, + "versionNonce": 1876170316, + "isDeleted": false, + "boundElements": [], + "updated": 1743519898347, + "link": null, + "locked": false, + "text": "Task queue", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "5HcUppyI2SC3TWCCM1jOV", + "originalText": "Task queue", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "hvo7ShHBxsrPQBfl-aTiz", + "type": "rectangle", + "x": 1303.1888169453453, + "y": 395.78237876287426, + "width": 396.1338152839121, + "height": 37.699358811964544, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aM", + "roundness": { + "type": 3 + }, + "seed": 802265036, + "version": 522, + "versionNonce": 1801431500, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "6CP3QGCJDW6hrSu12oE2H" + } + ], + "updated": 1743519898347, + "link": null, + "locked": false + }, + { + "id": "6CP3QGCJDW6hrSu12oE2H", + "type": "text", + "x": 1311.8723932884732, + "y": 402.13205816885653, + "width": 378.76666259765625, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aN", + "roundness": null, + "seed": 1004846668, + "version": 691, + "versionNonce": 857994060, + "isDeleted": false, + "boundElements": [], + "updated": 1743519898347, + "link": null, + "locked": false, + "text": "Configured options + Client capabilities", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "hvo7ShHBxsrPQBfl-aTiz", + "originalText": "Configured options + Client capabilities", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "_eedVYZPmF5hGCEplDYf8", + "type": "rectangle", + "x": 1986.4799755638846, + "y": 256.1962571446804, + "width": 429.62523158775275, + "height": 162.363694135002, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "EN7vNx8KYUIPyAXVZDzYc" + ], + "frameId": null, + "index": "aNG", + "roundness": { + "type": 3 + }, + "seed": 2044007796, + "version": 721, + "versionNonce": 1599049204, + "isDeleted": false, + "boundElements": [ + { + "id": "lr6qZGc-__IXXttM80oht", + "type": "arrow" + } + ], + "updated": 1743519958076, + "link": null, + "locked": false + }, + { + "id": "EYF8kocFl9whY4kTostn9", + "type": "text", + "x": 2044.0592539554177, + "y": 217.44256401890897, + "width": 304.5, + "height": 35, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "EN7vNx8KYUIPyAXVZDzYc" + ], + "frameId": null, + "index": "aNV", + "roundness": null, + "seed": 1996113652, + "version": 449, + "versionNonce": 907256052, + "isDeleted": false, + "boundElements": [], + "updated": 1743519958076, + "link": null, + "locked": false, + "text": "Expert \"Project\" Node", + "fontSize": 28, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Expert \"Project\" Node", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "Ut0h46hQ2blGw_2_RXuTG", + "type": "rectangle", + "x": 2006.6470310856312, + "y": 269.6861085604571, + "width": 388.32344076343657, + "height": 48.80692735757242, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "EN7vNx8KYUIPyAXVZDzYc" + ], + "frameId": null, + "index": "aNl", + "roundness": { + "type": 3 + }, + "seed": 350662516, + "version": 398, + "versionNonce": 1381720692, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "_IQEu5Ogsdnts0huKevej" + } + ], + "updated": 1743519958076, + "link": null, + "locked": false + }, + { + "id": "_IQEu5Ogsdnts0huKevej", + "type": "text", + "x": 2163.292085054996, + "y": 281.58957223924335, + "width": 75.03333282470703, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "EN7vNx8KYUIPyAXVZDzYc" + ], + "frameId": null, + "index": "aO", + "roundness": null, + "seed": 818454772, + "version": 371, + "versionNonce": 1664021492, + "isDeleted": false, + "boundElements": [], + "updated": 1743519958076, + "link": null, + "locked": false, + "text": "Indexer", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Ut0h46hQ2blGw_2_RXuTG", + "originalText": "Indexer", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "0EU0e3X4kKh-7Lo2Zsu4k", + "type": "rectangle", + "x": 2008.197424664871, + "y": 333.147304147417, + "width": 388.32344076343657, + "height": 40.88870350377853, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "EN7vNx8KYUIPyAXVZDzYc" + ], + "frameId": null, + "index": "aOV", + "roundness": { + "type": 3 + }, + "seed": 1895011828, + "version": 479, + "versionNonce": 110384500, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "Un2aARxwjbs0h7rL9fNyC" + } + ], + "updated": 1743519958076, + "link": null, + "locked": false + }, + { + "id": "Un2aARxwjbs0h7rL9fNyC", + "type": "text", + "x": 2053.7008045924877, + "y": 341.09165589930626, + "width": 297.3166809082031, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "EN7vNx8KYUIPyAXVZDzYc" + ], + "frameId": null, + "index": "aP", + "roundness": null, + "seed": 2004137844, + "version": 512, + "versionNonce": 701597428, + "isDeleted": false, + "boundElements": [], + "updated": 1743519958076, + "link": null, + "locked": false, + "text": "Code intelligence + completions", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "0EU0e3X4kKh-7Lo2Zsu4k", + "originalText": "Code intelligence + completions", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "CQ1sprcnITn0n7HO3ioiY", + "type": "rectangle", + "x": 1303.7429460084256, + "y": 330.6600846877198, + "width": 395.0255571577517, + "height": 48.80692735757242, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aQ", + "roundness": { + "type": 3 + }, + "seed": 1349765876, + "version": 440, + "versionNonce": 1350608588, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "5TKi85Te4EUDnNBn72wtH" + } + ], + "updated": 1743519898347, + "link": null, + "locked": false + }, + { + "id": "5TKi85Te4EUDnNBn72wtH", + "type": "text", + "x": 1423.8890558861297, + "y": 342.563548366506, + "width": 154.73333740234375, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aR", + "roundness": null, + "seed": 1126583412, + "version": 580, + "versionNonce": 1071681612, + "isDeleted": false, + "boundElements": [], + "updated": 1743519898347, + "link": null, + "locked": false, + "text": "Document store", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "CQ1sprcnITn0n7HO3ioiY", + "originalText": "Document store", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/assets/expert_10k_view.svg b/assets/expert_10k_view.svg new file mode 100644 index 00000000..cbfe3993 --- /dev/null +++ b/assets/expert_10k_view.svg @@ -0,0 +1,2 @@ +Editor of choiceLanguage server clientExpert "Server" NodejsonRPC/stdioDistributedElixirTask queueConfigured options + Client capabilitiesExpert "Project" NodeIndexerCode intelligence + completionsDocument store \ No newline at end of file diff --git a/pages/architecture.md b/pages/architecture.md index c338597a..086f77b1 100644 --- a/pages/architecture.md +++ b/pages/architecture.md @@ -1,33 +1,71 @@ # Architecture +When Expert starts, it boots an erlang virtual machine that runs the language server and its code (AKA the "manager" node). It then boots a separate virtual machine that builds your project code (AKA the "project" or "remote" node) and connects the two via distribution. This provides the following benefits: + + * None of Expert's dependencies will conflict with your project. This means that Expert can make use of dependencies to make developing in it easier without having to "vendor" them. It also means that you can use Expert to work on your project, *even if Expert depends on your project*. + * Your project can depend on a different version of Elixir and Erlang than Expert itself. This means that Expert can make use of the latest versions of Elixir and Erlang while still supporting projects that run on older versions. + * The build environment for your project is only aware of your project, which enables as-you-type compilation and error reporting. + * In the future, there is a possibility of having the Expert vm instance control multiple projects. *For now, a manager and project node will start for each editor that starts a language server.* + ## Project Structure -Lexical is designed to keep your application isolated from lexical's code. Because of this, lexical is structured as an umbrella app, with the following sub-apps: +Expert is designed to keep your application isolated from Expert's code. Because of this, Expert is structured a set of independent mix applications residing under the `apps` directory: - * `common`: Contains all code common to the other applications. - * `proto`: Used by `protocol` to generate the Elixir representation of LSP data structures. - * `protocol`: Code related to speaking the language server protocol. - * `remote_control`: The application that's injected into a project's code, which - gives lexical an API to do things in the context of your app. - * `server` The language server itself. + * `server` - The language server itself. + * `remote_control` - The application that's injected into a project's code, which + * `protocol` - Code related to speaking the language server protocol. + * `proto` - Used by `protocol` to generate the Elixir representation of LSP data structures. Gives Expert an API to do things in the context of a project. + * `common` - Contains all code common to the other applications. -Lexical is an umbrella app so we can control how many dependencies the remote control app has. By separating lexical into sub-applications, each is built as a separate archive, and we can pick and choose which of these applications (and their dependencies) are injected into the project's VM, thus reducing how much contamination the project sees. If lexical was a standard application, adding dependencies to lexical would cause those dependencies to appear in the project's VM, which might cause build issues, version conflicts in mix or other inconsistencies. +By separating Expert into sub-applications, each is built as a separate archive, and we can pick and choose which of these applications (and their dependencies) are injected into the project's VM, thus reducing how much contamination the project sees. If Expert was a standard application, adding dependencies to Expert would cause those dependencies to appear in the project's VM, which might cause build issues, version conflicts in mix or other inconsistencies. Since the `remote_control` app only depends on `common`, `path_glob` and `elixir_sense`, only those applications pollute the project's vm. Keeping `remote_control`'s dependencies to a minimum is a design goal of this architecture. +## 10,000ft View -## Language Server +![A simplified diagram of Expert](/assets/expert_10k_view.svg) -The language server (the `server` app) is the entry point to Lexical. When started by the `start_lexical.sh` command, it sets up a [transport](https://github.com/lexical-lsp/lexical/blob/main/apps/server/lib/lexical/server/transport.ex) that [reads JsonRPC from standard input and writes responses to standard output](https://github.com/lexical-lsp/lexical/blob/main/apps/server/lib/lexical/server/transport/std_io.ex). +* [Language server](#language-server) + * [Task queue](#task-queue) + * [Document store](#document-store) + * [Configured options + capabilities](#configured-options) +* [Project node](#project-node) + * [Indexer](#indexer) + * [Code intelligence + completions](#code-intelligence--completions) -When a message is received, it is parsed into either a [LSP Request](https://github.com/lexical-lsp/lexical/blob/main/apps/protocol/lib/lexical/protocol/requests.ex) or a [LSP Notification](https://github.com/lexical-lsp/lexical/blob/main/apps/protocol/lib/lexical/protocol/notifications.ex) and then it's handed to the [language server](https://github.com/lexical-lsp/lexical/blob/main/apps/server/lib/lexical/server.ex) to process. +## Language Server -The only messages the [lexical server process](https://github.com/lexical-lsp/lexical/blob/main/apps/server/lib/lexical/server.ex) handles directly are those related to the lifecycle of the language server itself: +The language server (the `server` app) is the entry point to Expert. When started by the `start_lexical.sh` command, it sets up a [transport](https://github.com/elixir-lang/expert/blob/main/apps/server/lib/lexical/server/transport.ex) that [reads and writes JsonRPC from stdio](https://github.com/elixir-lang/expert/blob/main/apps/server/lib/lexical/server/transport/std_io.ex). When a message is received, it is parsed into either a [LSP Request](https://github.com/elixir-lang/expert/blob/main/apps/protocol/lib/lexical/protocol/requests.ex) or a [LSP Notification](https://github.com/elixir-lang/expert/blob/main/apps/protocol/lib/Expert/protocol/notifications.ex) and subsequently processed. -- Synchronizing document states. -- Processing LSP configuration changes. -- Performing initialization and shutdown. +The only messages the [Expert server node](https://github.com/elixir-lang/expert/blob/main/apps/server/lib/lexical/server.ex) handles directly are those related to the lifecycle of the language server itself: -All other messages are delegated to a _Provider Handler_. This delegation is accomplished by the server process adding the request to the [provider queue](https://github.com/lexical-lsp/lexical/blob/main/apps/server/lib/lexical/server/provider/queue.ex). The provider queue asks the `Lexical.Server.Provider.Handlers.for_request/1` function which handler is configured to handle the request, creates a task for the handler and starts it. +- Performing initialization and shutdown +- Synchronizing document states +- Processing configuration changes + +All other messages are delegated to a _Provider Handler_. This delegation is accomplished by the server process adding the request to the [provider queue](https://github.com/elixir-lang/expert/blob/main/apps/server/lib/lexical/server/provider/queue.ex). The provider queue asks the `Lexical.Server.Provider.Handlers.for_request/1` function which handler is configured to handle the request, creates a task for the handler and starts it. A _Provider Handler_ is just a module that defines a function of arity 2 that takes the request to handle and a `%Lexical.Server.Configuration{}`. These functions can reply to the request, ignore it, or do some other action. + +### Task Queue +All requests made to Expert are placed into *the* task queue and processed concurrently. Tasks are cancellable. + +### Document Store +[Documents](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocuments) are the formal structure LSP uses to handle files of source code. Documents (and edits to them) are transmitted to the language server from the language client, after which language servers are responsible for persisting them. + +**Expert's server process is responsible for storing documents and synchronizing them when edits are made.** + +It's important to note that documents are located at **URIs**, and not strictly files on a system. For example, Visual Studio Code uses URIs with the `untitled://` scheme to represent unsaved files. + +### Configured Options + Capabilities +The LSP specification defines an expansive set of features that not all editors or language servers may support. The protocol therefore defines 'Capabilities' to allow language servers and language clients to inform each other of their capabilities. These capabilities are always exchanged upon initialization of the language server. + +Language servers also have options which users may switch at runtime, which Expert retains in-memory. *Editors are responsible for persisting options between sessions.* + +## Project Node + +### Indexer +Indexes a project's source code and stores it in a manner that's useful for lookup and analysis. Powers features such as code completions, go-to-definition, outlines, and symbol navigation. *ETS is tentatively used as the backing store for the indexer, but this may be subject to change.* + +### Code Intelligence + Completions +Processing for code intelligence and completions are performed on the project node. diff --git a/pages/development.md b/pages/development.md new file mode 100644 index 00000000..28d33edd --- /dev/null +++ b/pages/development.md @@ -0,0 +1,92 @@ +# Development +You'll need a local instance in order to develop Expert, so follow the [Installation guide](pages/installation.md) first. + +Then, install the git hooks with: +```sh +mix hooks +``` + +These are pre-commit hooks that will check for correct formatting and run credo for you. + +After this, you're ready to put together a pull request for Expert! + +### Logging + +When lexical starts up, it creates a `.lexical` directory in the root directory of a project. Inside that directory are two log files, `lexical.log` and `project.log`. The `.lexical.log` log file contains logging and OTP messages from the language server, while the `project.log` file contains logging and OTP messages from the project's node. While developing lexical, it's helpful to open up a terminal and tail both of these log files so you can see any errors and messages that lexical emits. To do that, run the following in a terminal while in the project's root directory: + +```shell +tail -f .lexical/*.log +``` + +Note: These log files roll over when they reach 1 megabyte, so after a time, it will be necessary to re-run the above command. + +### Debugging + +Lexical supports a debug shell, which will connect a remote shell to a currently-running language server process. To use it, `cd` into your lexical installation directory and run + +```sh +./bin/server_shell.sh +``` + +For example, if I would like to run the debug server for a server running in your `lexical` project, run: + +``` +./bin/server_shell.sh lexical +``` + +...and you will be connected to a remote IEx session _inside_ my language server. This allows you to investigate processes, make changes to the running code, or run `:observer`. + +While in the debugging shell, all the functions in `Lexical.Server.IEx.Helpers` are imported for you, and some common modules, like `Lexical.Project` and `Lexical.Document` are aliased. + +You can also start the lexical server in interactive mode via `./bin/start_lexical.sh iex`. Combining this with the helpers that are imported will allow you to run projects and do completions entirely in the shell. + + *Note*: The helpers assume that all of your projects are in folders that are siblings with your lexical project. + +Consider the example shell session: + +``` +./bin/start_lexical.sh iex +iex(1)> start_project :other +# the project in the ../other directory is started +compile_project(:other) +# the other project is compiled +iex(2)> complete :other, "defmo|" +[ + #Protocol.Types.Completion.Item<[ + detail: "", + insert_text: "defmacro ${1:name}($2) do\n $0\nend\n", + insert_text_format: :snippet, + kind: :class, + label: "defmacro (Define a macro)", + sort_text: "093_defmacro (Define a macro)" + ]>, + #Protocol.Types.Completion.Item<[ + detail: "", + insert_text: "defmacrop ${1:name}($2) do\n $0\nend\n", + insert_text_format: :snippet, + kind: :class, + label: "defmacrop (Define a private macro)", + sort_text: "094_defmacrop (Define a private macro)" + ]>, + #Protocol.Types.Completion.Item<[ + detail: "", + insert_text: "defmodule ${1:module name} do\n $0\nend\n", + insert_text_format: :snippet, + kind: :class, + label: "defmodule (Define a module)", + sort_text: "092_defmodule (Define a module)" + ]> +] +``` + +The same kind of support is available when you run `iex -S mix` in the lexical directory, and is helpful for narrowing down issues without disturbing your editor flow. + +*You can also connect to the project's vm using `./bin/project_shell.sh` sans the above helpers.* + +### Benchmarks + +The `remote_control` project has a set of benchmarks that measure the speed of various internal functions and data structures. In order to use them, you first need to install [git large file storage](https://docs.github.com/en/repositories/working-with-files/managing-large-files/installing-git-large-file-storage), and then run `git pull`. Benchmarks are stored in the `benchmarks` subdirectory, and can be run via + +```sh +mix benchmark /benchmarks/.exs +``` diff --git a/pages/installation.md b/pages/installation.md index 3fae4365..cbff1f4c 100644 --- a/pages/installation.md +++ b/pages/installation.md @@ -1,74 +1,55 @@ # Installation -The following instructions document how to install Lexical after -building from source. Some editors, like Visual Studio Code, have the -ability to automatically install the latest version of Lexical for -you. +Expert aims to support Elixir versions `1.15.3` with Erlang `25.0` and later (*subject to change*). **You must compile Expert under the lowest version of Elixir and Erlang that you intend to use in your projects.** -## Caveats - -Lexical supports the following versions of Elixir and Erlang: - -| Erlang | Version range | Notes | -| ----------- |----------------- | ------ | -| 24 | `>= 24.3.4.12` | Might run on older versions; this was the lowest that would compile on arm | -| 25 | `>= 25.0` | | -| 26 | `>= 26.0.2` | | -| 27 | `>= 27.0` | Will use dramatically more memory due to a bug in Erlang's ETS table compression | +Caveats with the following versions of Elixir and Erlang are documented below: | Elixir | Version Range | Notes | | -------- | -------------- | -------- | -| 1.13 | `>= 1.13.4` | | -| 1.14 | `all` | | -| 1.15 | `>= 1.15.3` | `1.15.0` - `1.15.2` had compiler bugs that prevented lexical from working | -| 1.16 | `>= 1.16.0` | | +| 1.18 | `>= 1.18.0` | | | 1.17 | `>= 1.17.0` | | +| 1.16 | `>= 1.16.0` | | +| 1.15 | `>= 1.15.3` | `1.15.0` - `1.15.2` have compiler bugs that prevent Expert from working. | -Lexical can run projects in any version of Elixir and Erlang that it -supports, but it's important to understand that Lexical needs to be -compiled under the lowest version of elixir and erlang that you intend -to use it with. That means if you have the following projects: - - * `first`: elixir `1.14.4` erlang `24.3.2` - * `second`: elixir `1.14.3` erlang `25.0` - * `third`: elixir: `1.13.3` erlang `25.2.3` +| Erlang | Version range | Notes | +| ----------- |----------------- | ------ | +| 27 | `>= 27.0` | Expert will use dramatically more memory due to a bug in Erlang's ETS table compression. | +| 26 | `>= 26.0.2` | | +| 25 | `>= 25.0` | | -Lexical would need to be compiled with Erlang `24.3.2` and Elixir `1.13.3`. -Lexical's prepackaged builds use Erlang `24.3.4.12` and Elixir `1.13.4` +## Building Expert -## Prerequisites -First, Install git LFS by [following these instructions](https://docs.github.com/en/repositories/working-with-files/managing-large-files/installing-git-large-file-storage). +First, install git LFS by [following these instructions](https://docs.github.com/en/repositories/working-with-files/managing-large-files/installing-git-large-file-storage). -Then, clone the git repository. Do this with +Then, clone Expert's git repository: ```elixir -git clone git@github.com:lexical-lsp/lexical.git +git clone git@github.com:elixir-lang/expert.git ``` -Then change to the lexical directory +Then change to the lexical directory: ```shell -cd lexical +cd expert ``` -Then fetch lexical's dependencies +Then fetch Expert's dependencies: ```shell mix deps.get ``` -...and build the project +...and build the project: ```shell mix package ``` -If things complete successfully, you will then have a release in your -`_build/dev/package/lexical` directory. If you see errors, please file a -bug. +*The output directory may also be changed with the `--path` option.* + +If things complete successfully, you will then have a release in your `_build/dev/package/lexical` directory. If you see errors, please file a bug. -For the following examples, assume the absolute path to your Lexical -source code is `/my/home/projects/lexical`. +For the following examples, assume the absolute path to your Lexical source code is `/my/home/projects/lexical`. ## Editor-specific setup 1. [Vanilla Emacs with lsp-mode](#vanilla-emacs-with-lsp-mode) @@ -322,3 +303,245 @@ You'll need to add a key called `"clients"` in the top-level `LSP.sublime-settin _note: you can name elixir-lexical whatever you like, it's just for your own identification_ Upon saving the configuration, LSP-Sublime should enable the new `elixir-lexical` LSP server. Go into an Elixir file and you should now see `elixir-lexical` in the lower left of the status bar. If not, invoke the command palette and select `LSP: Enable Language Server Globally/In Project` and it should run. + +### Vanilla Emacs with lsp-mode +The emacs instructions assume you're using `use-package`, which you +really should be. In your `.emacs.d/init.el` (or wherever you put your +emacs configuration), insert the following code: + +```lisp +(use-package lsp-mode + :ensure t + :config + (setq lsp-modeline-code-actions-segments '(count icon name)) + + :init + '(lsp-mode)) + + +(use-package elixir-mode + :ensure t + :custom + (lsp-elixir-server-command '("/my/home/projects/_build/dev/package/lexical/bin/start_lexical.sh"))) + +``` + +Restart emacs, and Lexical should start when you open a file with a +`.ex` extension. + + +### Vanilla Emacs with eglot + +Eglot in Emacs 30 already has built-in support for Lexical after +commit 9e0524a8820fbb8fdb155b1ca58919dcfcaffd63. + +If you're using Emacs 30 and before that commit, it's recommended to +update Emacs, but you can add lexical support in the following way: + +```emacs-lisp +(with-eval-after-load 'eglot + (setf (alist-get '(elixir-mode elixir-ts-mode heex-ts-mode) + eglot-server-programs + nil nil #'equal) + (if (and (fboundp 'w32-shell-dos-semantics) + (w32-shell-dos-semantics)) + '("language_server.bat") + (eglot-alternatives + '("language_server.sh" "start_lexical.sh"))))) +``` + +For versions before 30, you can add Eglot support for Lexical in the +following way: + +```emacs-lisp +(with-eval-after-load 'eglot + (setf (alist-get 'elixir-mode eglot-server-programs) + (if (and (fboundp 'w32-shell-dos-semantics) + (w32-shell-dos-semantics)) + '("language_server.bat") + (eglot-alternatives + '("language_server.sh" "start_lexical.sh"))))) +``` + +If you're using `elixir-ts-mode` on Emacs 29, you can add a new entry +for Eglot: + +```emacs-lisp +(with-eval-after-load 'eglot + (add-to-list 'eglot-server-programs + `((elixir-ts-mode heex-ts-mode) . + ,(if (and (fboundp 'w32-shell-dos-semantics) + (w32-shell-dos-semantics)) + '("language_server.bat") + (eglot-alternatives + '("language_server.sh" "start_lexical.sh")))))) +``` + + +### Visual Studio Code + +Click on the extensions button on the sidebar, then search for +`lexical`, then click `install`. By default, the extension will automatically +download the latest version of Lexical. + +To change to a local executable, go to `Settings -> Extensions -> Lexical` and +type `/my/home/projects/lexical/_build/dev/package/lexical/bin` into the text box in +the `Server: Release path override` section. + +### neovim + +Lexical requires neovim `>= 0.9.0`. + +In version `>= 0.9.0`, the key is to append the custom LS +configuration to +[lspconfig](https://github.com/neovim/nvim-lspconfig), so regardless +of whether you are using mason or others, you can use this +configuration below as a reference: + +```lua +require('lspconfig').lexical.setup { + cmd = { "my/home/projects/_build/dev/package/lexical/bin/start_lexical.sh" }, + root_dir = function(fname) + return util.root_pattern("mix.exs", ".git")(fname) or vim.loop.cwd() + end, + filetypes = { "elixir", "eelixir", "heex" }, + -- optional settings + settings = {} +} +``` + +If the configuration above doesn't work for you, please try this minimal [neovim configuration](https://github.com/scottming/nvim-mini-for-lexical), It can eliminate other plugin factors. + +### LunarVim + +[LunarVim](https://www.lunarvim.org) is a neovim configuration package with a lot of goodies built-in, while remaining very configurable. + +First, add this to your configuration: + +```lua +-- Add `elixirls` to `skipped_servers` list +vim.list_extend(lvim.lsp.automatic_configuration.skipped_servers, { "elixirls" }) + +-- Remove `lexical` from `skipped_servers` list +lvim.lsp.automatic_configuration.skipped_servers = vim.tbl_filter(function(server) + return server ~= "lexical" +end, lvim.lsp.automatic_configuration.skipped_servers) +``` + +This is necessary because LunarVim defaults to `elixirls` so we must ignore it first. Otherwise you'll have both `lexical` and `elixirls` running when you open Elixir files. + +Remove `elixirls` from the `lvim.lsp.installer.setup.ensure_installed = { ... }` list so it does not get automatically reinstalled. + +Optionally run `:LspUninstall elixirls` from within neovim if you don't want to keep `elixirls` around. + +Then use the same configuration as the one in the [neovim](#neovim) section. + +### Vim + ALE + +[ALE](https://github.com/dense-analysis/ale) includes built-in LSP support for Lexical. +To enable it, you'll need to tell ALE where your Lexical release is located (including +the `bin` directory) and add `lexical` to the list of enabled Elixir linters. + +A good way to do this is to add the following to a `~/.vim/after/ftplugin/elixir.vim` +file: + +```viml +let b:ale_linters = ['lexical', 'mix'] +let b:ale_elixir_lexical_release = '/my/home/projects/_build/dev/package/lexical/bin' +``` + +That will automatically enable the `lexical` and `mix` linters for all buffers with +the `elixir` file type. + +### Vim + Vim-LSP + +An example of configuring Lexical as the Elixir language server for +[Vim-LSP](https://github.com/prabirshrestha/vim-lsp). Uses the newer vim9script syntax but +can be converted to Vim 8 etc (`:h vim9script`). + +```vim9script + +# Loading vim-lsp with minpac: +call minpac#add("prabirshrestha/vim-lsp") +# ...or use your package manager of choice/Vim native packages + +# Useful for debugging vim-lsp: +# g:lsp_log_verbose = 1 +# g:lsp_log_file = expand('~/vim-lsp.log') + +# Configure as the elixir language server +if executable("elixir") + augroup lsp_lexical + autocmd! + autocmd User lsp_setup call lsp#register_server({ name: "lexical", cmd: (server_info) => "{{path_to_lexical}}/lexical-lsp/lexical/_build/dev/package/lexical/bin/start_lexical.sh", allowlist: ["elixir", "eelixir"] }) + autocmd FileType elixir setlocal omnifunc=lsp#complete + autocmd FileType eelixir setlocal omnifunc=lsp#complete + augroup end +endif + +``` + +If you use [Vim-LSP-Settings](mattn/vim-lsp-settings) for installing and configuring language servers, +you can use the following flag to disable prompts to install elixir-ls: + +```viml +g:lsp_settings_filetype_elixir = ["lexical"] + +``` + +For more config, debugging help, or getting vim-lsp to work with ALE, see +[this example vimrc](https://github.com/jHwls/dotfiles/blob/4425a4feef823512d96b92e5fd64feaf442485c9/vimrc#L239). + +### Helix + +*Note: This configuration is applicable for Helix version 23.09 and above.* + +Add the language server to your `~/.config/helix/languages.toml` config. +In the case that the file doesn't exist yet, you can create a new file at this location. + +```toml +[language-server.lexical] +command = "/my/home/projects/_build/dev/package/lexical/bin/start_lexical.sh" + +[[language]] +name = "elixir" +language-servers = ["lexical"] + +[[language]] +name = "heex" +language-servers = ["lexical"] +``` + +### Sublime Text + +#### Background + +Lexical can be used with Sublime Text via the [LSP-Sublime](https://lsp.sublimetext.io/) package, which integrates Language Servers with Sublime Text. If you don't have the LSP-Sublime package installed already, [install it with Package Control](https://packagecontrol.io/packages/LSP). + +There is currently no [language server package](https://lsp.sublimetext.io/language_servers/) specifically for Lexical that works with LSP-Sublime so we'll need to create a [custom client configuration](https://lsp.sublimetext.io/client_configuration/). + +#### Installation + +First, ensure that you have Lexical [installed from source](https://github.com/lexical-lsp/lexical/blob/main/pages/installation.md#prerequisites) correctly and note the full path of the directory with Lexical's executables: + +Print the full path of the directory holding the Lexical executables: +`cd {directory_you_cloned_lexical_to}/_build/dev/package/lexical/bin/ && pwd` + +Then, install LSP-Sublime with Package Control if you haven't already. + +Next, open up the LSP settings in Sublime. You can do this by invoking the command palette (`ctrl/cmd + shift + p`) and selecting `Preferences: LSP Settings`. + +You'll need to add a key called `"clients"` in the top-level `LSP.sublime-settings` JSON dictionary that is as follows: + +```json +"clients": { + "elixir-lexical": { + "enabled": true, + "command": ["{output_from_pwd_cmd_above}/start_lexical.sh", ""], + "selector": "source.elixir" + } +} +``` +_note: you can name elixir-lexical whatever you like, it's just for your own identification_ + +Upon saving the configuration, LSP-Sublime should enable the new `elixir-lexical` LSP server. Go into an Elixir file and you should now see `elixir-lexical` in the lower left of the status bar. If not, invoke the command palette and select `LSP: Enable Language Server Globally/In Project` and it should run. From 85e17e93d3fa8481eb33ccc2d3566a62bfe51cea Mon Sep 17 00:00:00 2001 From: Moosieus Date: Wed, 2 Apr 2025 14:08:38 -0400 Subject: [PATCH 2/2] ammend install instructions --- pages/installation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/installation.md b/pages/installation.md index cbff1f4c..60b9e7c8 100644 --- a/pages/installation.md +++ b/pages/installation.md @@ -27,10 +27,10 @@ Then, clone Expert's git repository: git clone git@github.com:elixir-lang/expert.git ``` -Then change to the lexical directory: +Then change into the server app directory: ```shell -cd expert +cd expert/apps/server ``` Then fetch Expert's dependencies: @@ -47,7 +47,7 @@ mix package *The output directory may also be changed with the `--path` option.* -If things complete successfully, you will then have a release in your `_build/dev/package/lexical` directory. If you see errors, please file a bug. +If things complete successfully, you will then have a release in your `apps/server/_build/dev/package/lexical` directory. If you see errors, please file a bug. For the following examples, assume the absolute path to your Lexical source code is `/my/home/projects/lexical`.