Skip to content

Commit

Permalink
Windows compatibility, write docs, measure performance on elm-spa-exa…
Browse files Browse the repository at this point in the history
…mple
  • Loading branch information
ryan-haskell committed Feb 26, 2023
1 parent bb1219e commit ea90848
Show file tree
Hide file tree
Showing 24 changed files with 267 additions and 1,022 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
# Elm Plugin for Visual Studio Code

## Install
## __Install__

Visual Studio Code is available for Mac, Windows, and Linux.

- [Download VS Code](https://code.visualstudio.com/)
- [Install the "Elm Land" plugin](https://marketplace.visualstudio.com/items?itemName=elm-land.elm-land)

## Highlighted Features
## __Highlighted Features__

- Syntax highlighting
- Format on save
- Error highlighting

## Additional Features
## __Additional Features__

- Jump-to-definition
- Find all usages
- Offline-friendly package docs
- Module import autocomplete
- Convert HTML to Elm

__More documentation__

- 🧠 Learn more about [all of the features](https://github.com/elm-land/vscode/blob/main/docs/README.md#features)
- 📊 View this plugin's [performance benchmarks](https://github.com/elm-land/vscode/blob/main/docs/README.md#performance-table)
- 💖 Meet [the wonderful Elm folks](https://github.com/elm-land/vscode/blob/main/docs/README.md#thank-you-elm-community) that made this project possible
62 changes: 62 additions & 0 deletions build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const path = require('path')
const child_process = require('child_process')

// Cross platform build script
let elmApps = {
elmToAst: {
name: 'elm-to-ast',
folder: path.join(__dirname, 'src', 'features', 'shared', 'elm-to-ast'),
entrypoint: path.join(__dirname, 'src', 'features', 'shared', 'elm-to-ast', 'src', 'Worker.elm'),
dist_output: path.join(__dirname, 'dist', 'features', 'shared', 'elm-to-ast', 'worker.min.js'),
},
offlinePackageDocs: {
name: 'offline-package-docs',
folder: path.join(__dirname, 'src', 'features', 'offline-package-docs'),
entrypoint: path.join(__dirname, 'src', 'features', 'offline-package-docs', 'src', 'Main.elm'),
dist_output: path.join(__dirname, 'dist', 'features', 'offline-package-docs', 'elm.compiled.js'),
},
htmlToElm: {
name: 'html-to-elm',
folder: path.join(__dirname, 'src', 'features', 'html-to-elm'),
entrypoint: path.join(__dirname, 'src', 'features', 'html-to-elm', 'src', 'Main.elm'),
dist_output: path.join(__dirname, 'dist', 'features', 'html-to-elm', 'elm.compiled.js'),
}
}

let bold = str => '\033[36m' + str + '\033[0m'

let copyElmFindScripts = () => {
let isWindows = process.platform === 'win32'

let command = isWindows
? `xcopy /sy src\\experiments\\find-usages\\elm-find\\scripts\\* dist\\experiments\\find-usages\\elm-find\\scripts\\`
: `cp -r src/experiments/find-usages/elm-find/scripts/* dist/experiments/find-usages/elm-find/scripts`


child_process.exec(command, (err, stdout, stderr) => {
if (err) {
console.error(err)
process.exit(err.code)
} else {
console.log(` ✅ Copied ${bold('elm-find')} scripts`)
}
})
}

const buildElmProject = ({ name, folder, entrypoint, dist_output }) => {
child_process.exec(`cd ${folder} && npx elm make ${entrypoint} --optimize --output=${dist_output} && npx terser ${dist_output} --compress "pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe" | npx terser --mangle --output ${dist_output}`,
(err, stdout, stderr) => {
if (err) {
console.error(err)
process.exit(err.code)
} else {
console.log(` ✅ Compiled ${bold(name)} project`)
}
}
)
}

console.log(`Building Elm projects...`)
buildElmProject(elmApps.elmToAst)
buildElmProject(elmApps.offlinePackageDocs)
buildElmProject(elmApps.htmlToElm)
177 changes: 177 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# Documentation
> Documentation for the Elm Land VS code plugin
### __Table of contents__

- 📚 [Features](#features)
- [Syntax highlighting](#syntax-highlighting)
- [Format on save](#format-on-save)
- [Error highlighting](#error-highlighting)
- [Jump-to-definition](#jump-to-definition)
- [Offline package docs](#offline-package-docs)
- [Module import autocomplete](#module-import-autocomplete)
- [Convert HTML to Elm](#convert-html-to-elm)
- 📊 [Performance Table](#performance-table)
- 💖 [Thank you, Elm community](#thank-you-elm-community)

## __Features__

With the Elm Land plugin, every feature (except "Syntax highlighting") is fully optional. By default, all features are enabled. If you prefer a more minimal editing experience, you can disable any feature in your [VS Code "User Settings"](https://code.visualstudio.com/docs/getstarted/settings).

This section breaks down what each feature does, and the VS code configuration setting that enable/disable it.

---

### __Syntax highlighting__

__Setting:__ _None_

Provides basic [syntax highlighting](https://en.wikipedia.org/wiki/Syntax_highlighting) to help you visually scan your Elm code, increase readability, and provide context. This feature is the only one that cannot be disabled.

![Syntax highlighting demo](./syntax-highlighting.jpg)

---

### __Format on save__

__Setting:__ `formatOnSave`

Uses [elm-format](https://github.com/avh4/elm-format) to automatically format your code on save. This requires the `elm-format` command to be installed on your computer, and the VS code plugin can take care of that for you if you already have [Node.js](https://nodejs.org/en/download/) installed.

![Format on save demo](./format-on-save.gif)

---

### __Error highlighting__

__Setting:__ `elmLand.feature.errorHighlighting`

If your Elm code doesn't compile, this feature will underline the relevant problems in your editor. It depends on a local installation of `elm` on your computer.

When you open an Elm file, or save one, you'll see a red underline under each compiler error.

![Error highlighting demo](./error-highlighting.gif)

__Note:__ The Elm Land plugin will also check the `elmLand.entrypointFilepaths` setting to compile the top-level Elm program. This allows the editor to report errors in other files. If your project doesn't use `src/Main.elm` as the program's entrypoint, change the `elmLand.entrypointFilepaths` settings in that workspace's `.vscode/settings.json` file.

---

### __Jump to definition__

__Setting:__ `elmLand.feature.jumpToDefinition`

You can jump to the definition of any function or value, even if it's defined in another module. This is helpful for quickly navigating around.

![Jump to definition](./jump-to-definition.gif)

---

### __Offline package docs__

__Setting:__ `elmLand.feature.offlinePackageDocs`

Every Elm package in the ecosystem comes with built-in documentation. With this feature, even if you're offline, you can access this documentation from within your editor.

![Offline package docs demo](./offline-package-docs.gif)

---

### __Module import autocomplete__

__Setting:__ `elmLand.feature.autocomplete`

Every Elm module in your project, and any Elm package you installed will provide autocomplete information for the exposed types, functions, and values. To see more detailed documentation, you can even toggle details panel for each autocomplete suggestion.

![Autocomplete demo](./autocomplete.gif)

---

### __Convert HTML to Elm__

__Setting:__ `elmLand.feature.htmlToElm`

To help you convert HTML snippets to Elm code and help newcomers learn the syntax of Elm, this plugin comes with a built-in "HTML to Elm" action whenever you highlight over a snippet of HTML code.

![HTML to Elm demo with a TailwindCSS snippet](./html-to-elm.gif)

---

## __Performance Table__

Elm's [editor plugins repo](https://github.com/elm/editor-plugins) recommends doing performance profiling to help others learn how different editors implement features, and also to help try to think of ways to bring down costs.

This VS code plugin was specifically designed to have __near-zero memory overhead [¹](#1-ram-overhead)__, and to __avoid in-memory indexing__ that cache your codebase before invoking features. For this reason, it's been very effective at [Vendr](https://vendr.com), even though the frontend codebase is __over 400k lines__ of Elm code.

---

### `rtfeldman/elm-spa-example` (4K LOC, 34 files)

These benchmarks were taken on a __Windows PC [²](#2-pc-specs)__ testing this plugin against [rtfeldman/elm-spa-example](https://github.com/rtfeldman/elm-spa-example) repository, which has 3.8k lines of Elm code across 34 files.

Feature | Average Speed | Constant RAM Overhead | Cumulative CPU Costs | Battery Implications
:------ | :------------ | :-------------------- | :------------------- | :-------------------
__Format on save__ | <500ms | _None_ | On command | notable
__Error highlighting__| <500ms | _None_ | On file open and save | minimal
__Jump-to-definition__ | <150ms | _None_ | On file open and save | notable
__Offline package docs__ | <100ms | _None_ | On command | minimal
__Module import autocomplete__ | <100ms | _None_ | On key stroke | minimal
__Convert HTML to Elm__ | <100ms | _None_ | On command | minimal


#### __1. RAM overhead__

The only in-memory overhead from this plugin comes from caching the contents of your `elm.json` files within the current workspace, and any `docs.json` files for packages that you are using.

For example, if your project is using `elm/[email protected]`, the contents of `$ELM_HOME/0.19.1/packages/elm/http/2.0.0/docs.json` would be cached in working RAM to improve performance for the [Offline package docs](#offline-package-docs), [Module import autocomplete](#module-import-autocomplete), and [Jump-to-definition](#jump-to-definition) features.

This means a __tiny project with 10 lines of Elm code__ and a __huge project with 500k+ lines of Elm code__, would have __the same RAM overhead__, assuming they had the same Elm package dependencies!

#### __2. PC Specs__

The Windows PC has the following specifications:
- __OS:__: Windows 11 Home 64-bit
- __Processor__: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz (16 CPUs), ~2.4GHz
- __Memory:__ 16GB RAM

## __Thank you, Elm community!__

This VS Code plugin was made possible by the following open-source projects. Thank you to everyone for doing the hard work of making compilation, formatting, syntax highlighting, and AST parsing a solved problem:

### __Evan Czaplicki__ ([@evancz](https://github.com/evancz))

Evan laid an incredible foundation for this plugin project. This includes everything from helpful READMEs like the ones in [elm/editor-plugins](https://github.com/elm/editor-plugins) to the design choices like storing documentation offline in the `ELM_HOME` directory.

I couldn't have done __Error highlighting__ without the [elm/compiler](https://github.com/elm/compiler), nor
implemented the __Offline package docs__ UI without helpful packages like [elm/project-metadata-utils](https://github.com/elm/project-metadata-utils).

Thanks so much, Evan- you made the plugin authoring experience a breeze!

### __Mats Stijlaart__ ([@stil4m](https://github.com/stil4m))

The [stil4m/elm-syntax](https://github.com/stil4m/elm-syntax) package made it possible for me to include __Jump to definition__, __Module import autocomplete__, and the __Offline Package Docs__ features. Creating a reliable Elm parser that I could run within my VS code extension would have been a difficult hurdle for me.

Thank you, Mats! This AST parser was a _huge_ part of the plugin work.

### __Aaron VonderHaar__ ([@avh4](https://github.com/avh4))

Aaron's work on [avh4/elm-format](https://github.com/avh4/elm-format) made it possible for me to quickly provide the __Format on save__ feature by running your CLI tool directly. The performance is great, and the NPM installer makes it easy for folks to install it on their machines.

Thank you, Aaron, `elm-format` is awesome!

### __Kolja Lampe__ ([@razzeee](https://github.com/razzeee))

Kolja's work on the [elm-tooling/elm-language-client-vscode](https://github.com/elm-tooling/elm-language-client-vscode) made __Syntax highlighting__ possible. The [`elm-syntax.json`](https://github.com/elm-tooling/elm-language-client-vscode/blob/23bf1ae459f7053cc100aa129e2c4d8faca0dabf/syntaxes/elm-syntax.json) and [`codeblock.json`](https://github.com/elm-tooling/elm-language-client-vscode/blob/23bf1ae459f7053cc100aa129e2c4d8faca0dabf/syntaxes/codeblock.json) were already battle-tested and reliable from the existing [Elm LS plugin](https://marketplace.visualstudio.com/items?itemName=Elmtooling.elm-ls-vscode).

Thank you Kolja, and the folks in `elm-community`, for providing this open-source project for tooling authors like me to learn from and build!

### __The Sett__ ([@the-sett](https://github.com/the-sett))

When adding the __HTML to Elm__ feature, both the [the-sett/elm-pretty-printer](https://github.com/the-sett/elm-pretty-printer) and [the-sett/elm-syntax-dsl](https://github.com/the-sett/elm-syntax-dsl) allowed me to turn an Elm AST into an `elm-format` compatible string.

Thank you [Rupert](https://github.com/rupertlssmith) and [pwentz](https://github.com/pwentz) for your contributions to these repos!

### __Jim Sagevid__ ([@jims](https://github.com/jims))

Jim provided the HTML parser that powers the __HTML to Elm__ feature. The [jims/html-parser](https://github.com/jims/html-parser) package made it easy for to add the feature to help lower the learning curve for newcomers to Elm.

Thank you, Jim! Your Elm package rocks!
Binary file added docs/autocomplete.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/error-highlighting.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/format-on-save.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/html-to-elm.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/jump-to-definition.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/offline-package-docs.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/syntax-highlighting.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 5 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "elm-land",
"displayName": "Elm Land",
"description": "A minimal plugin for Elm",
"version": "0.1.4",
"version": "0.1.5",
"icon": "src/elm-land-plugin.png",
"publisher": "elm-land",
"repository": "https://github.com/elm-land/elm-land",
Expand All @@ -12,23 +12,17 @@
"scripts": {
"start": "npm install && npm run watch",
"build": "npm run build:elm && npm run build:typescript",
"build:elm": "npm run build:elm-to-ast && npm run build:elm-offline-docs && npm run build:elm-html-to-elm && npm run build:elm-find",
"build:elm-to-ast": "(cd src/features/shared/elm-to-ast && elm make src/Worker.elm --output=dist/worker.js --optimize && mkdir -p ../../../../dist/features/shared/elm-to-ast && terser dist/worker.js --compress 'pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe' | terser --mangle --output ../../../../dist/features/shared/elm-to-ast/worker.min.js)",
"build:elm-offline-docs": "(cd src/features/offline-package-docs && mkdir -p ../../../dist/features/offline-package-docs && elm make src/Main.elm --optimize --output=../../../dist/features/offline-package-docs/elm.compiled.js && terser ../../../dist/features/offline-package-docs/elm.compiled.js --compress 'pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe' | terser --mangle --output ../../../dist/features/offline-package-docs/elm.compiled.js)",
"build:elm-html-to-elm": "(cd src/features/html-to-elm && mkdir -p ../../../dist/features/html-to-elm && elm make src/Main.elm --optimize --output=../../../dist/features/html-to-elm/elm.compiled.js && terser ../../../dist/features/html-to-elm/elm.compiled.js --compress 'pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe' | terser --mangle --output ../../../dist/features/html-to-elm/elm.compiled.js)",
"build:elm-find": "(cd src/features/find-usages/elm-find && mkdir -p ../../../../dist/features/find-usages/elm-find/scripts && cp scripts/* ../../../../dist/features/find-usages/elm-find/scripts/)",
"build:elm": "node build.js",
"build:typescript": "tsc",
"watch": "npm run build && (npm run watch:elm & npm run watch:typescript)",
"watch:elm": "chokidar src/features/offline-package-docs/src -c \"npm run build:elm-offline-docs\"",
"watch": "npm run build && npm run watch:typescript",
"watch:typescript": "tsc -w",
"vscode:prepublish": "npm run build"
},
"categories": [
"Programming Languages"
],
"activationEvents": [
"workspaceContains:**/elm.json",
"onLanguage:elm"
"workspaceContains:**/elm.json"
],
"main": "./dist/extension.js",
"contributes": {
Expand Down Expand Up @@ -62,9 +56,7 @@
"[elm]": {
"editor.tabSize": 4,
"editor.formatOnSave": true,
"editor.folding": false,
"editor.wordBasedSuggestions": false,
"editor.semanticHighlighting.enabled": true
"editor.wordBasedSuggestions": false
}
},
"configuration": [
Expand All @@ -86,12 +78,6 @@
"type": "boolean",
"default": true
},
"elmLand.feature.findUsages": {
"order": 2,
"description": "Enable the 'Find Usages' feature",
"type": "boolean",
"default": true
},
"elmLand.feature.errorHighlighting": {
"order": 3,
"description": "Enable the 'Error Highlighting' feature",
Expand Down
2 changes: 0 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as ErrorHighlighting from "./features/error-highlighting"
import * as JumpToDefinition from "./features/jump-to-definition"
import * as OfflinePackageDocs from "./features/offline-package-docs"
import * as TypeDrivenAutocomplete from './features/type-driven-autocomplete'
import * as FindUsages from "./features/find-usages"
import * as HtmlToElm from './features/html-to-elm'

export async function activate(context: vscode.ExtensionContext) {
Expand All @@ -27,7 +26,6 @@ export async function activate(context: vscode.ExtensionContext) {
JumpToDefinition.feature({ globalState, context })
OfflinePackageDocs.feature({ globalState, context })
TypeDrivenAutocomplete.feature({ globalState, context })
FindUsages.feature({ globalState, context })
HtmlToElm.feature({ globalState, context })
}

Expand Down
6 changes: 4 additions & 2 deletions src/features/elm-format-on-save.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ const provideDocumentFormattingEdits = async (
options: vscode.FormattingOptions,
token: vscode.CancellationToken
) => {
const start = Date.now()
// User should disable this feature in the `[elm]` language settings
try {
let text = await runElmFormat(document)
console.info('formatOnSave', `${Date.now()-start}ms`)
return [vscode.TextEdit.replace(getFullDocRange(document), text)]
} catch (_) {
return []
Expand All @@ -38,8 +40,8 @@ function runElmFormat(document: vscode.TextDocument): Promise<string> {
const process_ = child_process.exec(command, async (err, stdout, stderr) => {
if (err) {
const ELM_FORMAT_BINARY_NOT_FOUND = 127
if (err.code === ELM_FORMAT_BINARY_NOT_FOUND) {
let response = await vscode.window.showErrorMessage(
if (err.code === ELM_FORMAT_BINARY_NOT_FOUND || err.message.includes(`'elm-format' is not recognized`)) {
let response = await vscode.window.showInformationMessage(
'Format on save requires "elm-format"',
{ modal: true, detail: 'Please click "Install" or disable "Format on save" in your settings.' },
'Install'
Expand Down
Loading

0 comments on commit ea90848

Please sign in to comment.