diff --git a/README.md b/README.md index d3c6b71..47b6244 100644 --- a/README.md +++ b/README.md @@ -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 \ No newline at end of file diff --git a/build.js b/build.js new file mode 100644 index 0000000..2e95411 --- /dev/null +++ b/build.js @@ -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) \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..ba253d2 --- /dev/null +++ b/docs/README.md @@ -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/http@2.0.0`, 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! diff --git a/docs/autocomplete.gif b/docs/autocomplete.gif new file mode 100644 index 0000000..08daf3b Binary files /dev/null and b/docs/autocomplete.gif differ diff --git a/docs/error-highlighting.gif b/docs/error-highlighting.gif new file mode 100644 index 0000000..c12acd6 Binary files /dev/null and b/docs/error-highlighting.gif differ diff --git a/docs/format-on-save.gif b/docs/format-on-save.gif new file mode 100644 index 0000000..ce8a7b7 Binary files /dev/null and b/docs/format-on-save.gif differ diff --git a/docs/html-to-elm.gif b/docs/html-to-elm.gif new file mode 100644 index 0000000..1a892d3 Binary files /dev/null and b/docs/html-to-elm.gif differ diff --git a/docs/jump-to-definition.gif b/docs/jump-to-definition.gif new file mode 100644 index 0000000..284fbae Binary files /dev/null and b/docs/jump-to-definition.gif differ diff --git a/docs/offline-package-docs.gif b/docs/offline-package-docs.gif new file mode 100644 index 0000000..2d3314c Binary files /dev/null and b/docs/offline-package-docs.gif differ diff --git a/docs/syntax-highlighting.jpg b/docs/syntax-highlighting.jpg new file mode 100644 index 0000000..f94a1b4 Binary files /dev/null and b/docs/syntax-highlighting.jpg differ diff --git a/package.json b/package.json index 99209c2..9ca686c 100644 --- a/package.json +++ b/package.json @@ -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", @@ -12,14 +12,9 @@ "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" }, @@ -27,8 +22,7 @@ "Programming Languages" ], "activationEvents": [ - "workspaceContains:**/elm.json", - "onLanguage:elm" + "workspaceContains:**/elm.json" ], "main": "./dist/extension.js", "contributes": { @@ -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": [ @@ -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", diff --git a/src/extension.ts b/src/extension.ts index e0cdc95..3ad9a14 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -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) { @@ -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 }) } diff --git a/src/features/elm-format-on-save.ts b/src/features/elm-format-on-save.ts index fc4c95c..7f3f8b0 100644 --- a/src/features/elm-format-on-save.ts +++ b/src/features/elm-format-on-save.ts @@ -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 [] @@ -38,8 +40,8 @@ function runElmFormat(document: vscode.TextDocument): Promise { 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' diff --git a/src/features/error-highlighting.ts b/src/features/error-highlighting.ts index 175955f..c94605a 100644 --- a/src/features/error-highlighting.ts +++ b/src/features/error-highlighting.ts @@ -17,7 +17,6 @@ export const feature: Feature = ({ globalState, context }) => { terminal.show() }) ) - vscode.window.onDidChangeTerminalState((e) => console.log({ e })) context.subscriptions.push( vscode.workspace.onDidOpenTextDocument(document => run(globalState, diagnostics, document, 'open')) @@ -52,6 +51,7 @@ const run = async ( document: vscode.TextDocument, event: 'open' | 'save' ) => { + let start = Date.now() // Allow user to disable this feature const isEnabled: boolean = vscode.workspace.getConfiguration('elmLand').feature.errorHighlighting if (!isEnabled) { @@ -80,7 +80,6 @@ const run = async ( collection.set(vscode.Uri.file(fsPath), diagnostics) } } - } // Remove stale errors @@ -92,6 +91,7 @@ const run = async ( if (elmFilesToCompile.length > 0) { await compileElmFile(elmJsonFile, elmFilesToCompile) + console.info('errorHighlighting', `${Date.now()-start}ms`) } } else { console.error(`Couldn't find an elm.json file for ${uri.fsPath}`) @@ -125,8 +125,8 @@ const Elm = { child_process.exec(command, async (err, _, stderr) => { if (err) { const ELM_BINARY_NOT_FOUND = 127 - if (err.code === ELM_BINARY_NOT_FOUND) { - let response = await vscode.window.showErrorMessage( + if (err.code === ELM_BINARY_NOT_FOUND || err.message.includes(`'elm' is not recognized`)) { + let response = await vscode.window.showInformationMessage( 'Error highlighting requires "elm"', { modal: true, detail: 'Click "Install" or disable "Error highlighting" in your settings.' }, 'Install' diff --git a/src/features/find-usages.ts b/src/features/find-usages.ts deleted file mode 100644 index 99836f7..0000000 --- a/src/features/find-usages.ts +++ /dev/null @@ -1,677 +0,0 @@ -import * as vscode from "vscode" -import { GlobalState } from "./shared/autodetect-elm-json" -import * as ElmToAst from "./shared/elm-to-ast" -import * as ElmSyntax from "./shared/elm-to-ast/elm-syntax" -import Grep from "./find-usages/elm-grep" -import sharedLogic, { Feature } from "./shared/logic" - -export const feature: Feature = ({ globalState, context }) => { - context.subscriptions.push( - vscode.languages.registerReferenceProvider("elm", provider(globalState)) - ) -} - -const provider = (globalState: GlobalState) => { - return { - provideReferences: async ( - document: vscode.TextDocument, - position: vscode.Position, - context: vscode.ReferenceContext, - token: vscode.CancellationToken - ) => { - // Allow user to disable this feature - const isEnabled: boolean = - vscode.workspace.getConfiguration("elmLand").feature.findUsages - if (!isEnabled) return - - const start = Date.now() - let locations: vscode.Location[] = [] - const elmJson = sharedLogic.findElmJsonFor(globalState, document.uri) - - if (elmJson) { - const text = document.getText() - const ast = await ElmToAst.run(text) - - if (ast) { - const moduleName = ElmSyntax.toModuleName(ast) - const result = getDeclarationNameAndKindAtPosition(ast, position) - - if (result) { - const referencesInFile = findLocalInstancesOf( - ast, - result.declarationName, - result.kind - ) - - let usageLocationsFromCurrentModule = referencesInFile.map( - (range) => new vscode.Location(document.uri, range) - ) - - if ( - ElmSyntax.isExposedFromThisModule(ast, result.declarationName) - ) { - let grepStart = Date.now() - const output = await Grep.findElmFiles({ - moduleName, - typeOrValueName: result.declarationName, - folders: elmJson.sourceDirectories, - }) - const filepathsImportingModule = output.fsPathsToScan - console.info(`grep duration`, `${Date.now() - grepStart}ms`) - - let openiningFilesStart = Date.now() - let fullyQualifiedName = [ - moduleName, - result.declarationName, - ].join(".") - let astsForOtherFilepaths = await Promise.all( - filepathsImportingModule.map(fromFilepathToAst) - ) - console.info( - `parsingFiles duration`, - `${Date.now() - openiningFilesStart}ms` - ) - - let scanningAstsStart = Date.now() - let usageLocationsInOtherModules = astsForOtherFilepaths.flatMap( - (item) => { - if (item) { - let { uri, ast } = item - let importDetails = findImportDetailsFor({ - moduleName, - ast, - typeOrValueName: result.declarationName, - }) - if (importDetails) { - let { isExposed, alias } = importDetails - let ranges: vscode.Range[] = [] - - if (isExposed) { - ranges = ranges.concat( - findRemoteInstancesOf( - ast, - result.declarationName, - result.kind - ) - ) - } - - if (alias) { - ranges = ranges.concat( - findRemoteInstancesOf( - ast, - [alias, result.declarationName].join("."), - result.kind - ) - ) - } else { - ranges = ranges.concat( - findRemoteInstancesOf( - ast, - fullyQualifiedName, - result.kind - ) - ) - } - return ranges.map( - (range) => new vscode.Location(uri, range) - ) - } - } - - return [] - } - ) - console.info( - `scanningAsts duration`, - `${Date.now() - scanningAstsStart}ms` - ) - locations = usageLocationsFromCurrentModule - .concat(output.matches) - .concat(usageLocationsInOtherModules) - } else { - locations = usageLocationsFromCurrentModule - } - } - } - } - - console.info(`findUsages`, `${Date.now() - start}ms`) - return locations - }, - } -} - -// -// Determine if an import is using any aliases or may be exposing a specific value from another module -// -const findImportDetailsFor = ({ - moduleName, - ast, - typeOrValueName, -}: { - moduleName: string - typeOrValueName: string - ast: ElmSyntax.Ast -}): { isExposed: boolean; alias: string | undefined } | null => { - for (let import_ of ast.imports) { - if (import_.value.moduleName.value.join(".") === moduleName) { - let isExposed = - import_.value.exposingList === null - ? false - : ElmSyntax.isPotentiallyExposed( - import_.value.exposingList, - typeOrValueName - ) - - return { - isExposed, - alias: import_.value.moduleAlias?.value.join("."), - } - } - } - return null -} - -const fromFilepathToAst = async ( - fsPath: string -): Promise<{ uri: vscode.Uri; ast: ElmSyntax.Ast } | null> => { - let uri = vscode.Uri.file(fsPath) - let document = await vscode.workspace.openTextDocument(uri) - if (document) { - let ast = await ElmToAst.run(document.getText()) - if (ast) { - return { uri, ast } - } - } - return null -} - -const getDeclarationNameAndKindAtPosition = ( - ast: ElmSyntax.Ast, - position: vscode.Position -): { declarationName: string; kind: "value" | "type" } | null => { - let getDeclarationNameAndKind = ( - declaration: ElmSyntax.Node - ): { declarationName: string; kind: "value" | "type" } | null => { - switch (declaration.value.type) { - case "destructuring": - return null - case "function": - let signatureNameValue = - declaration.value.function.signature?.value.name - let signatureNameRange = signatureNameValue - ? sharedLogic.fromElmRange(signatureNameValue.range) - : undefined - - let declarationNameValue = - declaration.value.function.declaration.value.name - let declarationNameRange = sharedLogic.fromElmRange( - declarationNameValue.range - ) - - let cursorInSignatureName = - signatureNameRange && signatureNameRange.contains(position) - if (declarationNameRange.contains(position) || cursorInSignatureName) { - return { - declarationName: declarationNameValue.value, - kind: "value", - } - } - return null - case "infix": - return null - case "port": - let range = sharedLogic.fromElmRange(declaration.value.port.name.range) - if (range.contains(position)) { - return { - declarationName: declaration.value.port.name.value, - kind: "value", - } - } - return null - case "typeAlias": - let range2 = sharedLogic.fromElmRange( - declaration.value.typeAlias.name.range - ) - if (range2.contains(position)) { - return { - declarationName: declaration.value.typeAlias.name.value, - kind: "type", - } - } - return null - case "typedecl": - let range3 = sharedLogic.fromElmRange( - declaration.value.typedecl.name.range - ) - if (range3.contains(position)) { - return { - declarationName: declaration.value.typedecl.name.value, - kind: "type", - } - } - return null - } - } - - for (let declaration of ast.declarations) { - let returnValue = getDeclarationNameAndKind(declaration) - if (returnValue) { - return returnValue - } - } - - return null -} - -const findRemoteInstancesOf = ( - ast: ElmSyntax.Ast, - valueName: string, - kind: "type" | "value" -): vscode.Range[] => { - // Check if this declaration is already redefined - // - // This prevents us from confusing usage of a local `text` function - // with one that might be from `import Html exposing (..)` - let isLocallyDefined = ast.declarations.some( - ElmSyntax.isDefinedAgainByDeclaration(valueName) - ) - if (isLocallyDefined) { - return [] - } - - return findLocalInstancesOf(ast, valueName, kind) -} - -const findLocalInstancesOf = ( - ast: ElmSyntax.Ast, - valueName: string, - kind: "type" | "value" -): vscode.Range[] => { - switch (kind) { - case "value": - return ast.declarations.flatMap( - findRangesOfNamedValueInDeclaration(valueName) - ) - case "type": - return ast.declarations.flatMap( - findRangesOfNamedTypeInDeclaration(valueName) - ) - } -} - -const findRangesOfNamedValueInDeclaration = - (valueName: string) => - (node: ElmSyntax.Node): vscode.Range[] => { - switch (node.value.type) { - case "destructuring": - return [ - ...findRangesOfNamedValueInExpression(valueName)( - node.value.destructuring.expression - ), - ] - case "function": - let hasLocallyScopedVersion = - node.value.function.declaration.value.arguments.some( - ElmSyntax.isDefinedAgainByPattern(valueName) - ) - let matchesFromExpression: vscode.Range[] = [] - if (!hasLocallyScopedVersion) { - matchesFromExpression = findRangesOfNamedValueInExpression(valueName)( - node.value.function.declaration.value.expression - ) - } - return [ - ...node.value.function.declaration.value.arguments.flatMap( - findRangesOfNamedValueInPattern(valueName) - ), - ...matchesFromExpression, - ] - case "infix": - return [] - case "port": - return [] - case "typeAlias": - return [] - case "typedecl": - return [] - } - } - -const findRangesOfNamedTypeInDeclaration = - (typeName: string) => - (node: ElmSyntax.Node): vscode.Range[] => { - switch (node.value.type) { - case "destructuring": - return findRangesOfNamedTypeInExpression(typeName)( - node.value.destructuring.expression - ) - case "function": - return [ - ...(node.value.function.signature - ? findRangesOfNamedTypeInAnnotation(typeName)( - node.value.function.signature.value.typeAnnotation - ) - : []), - ...findRangesOfNamedTypeInExpression(typeName)( - node.value.function.declaration.value.expression - ), - ] - case "infix": - return [] - case "port": - return findRangesOfNamedTypeInAnnotation(typeName)( - node.value.port.typeAnnotation - ) - case "typeAlias": - return findRangesOfNamedTypeInAnnotation(typeName)( - node.value.typeAlias.typeAnnotation - ) - case "typedecl": - return node.value.typedecl.constructors - .flatMap((x) => x.value.arguments) - .flatMap(findRangesOfNamedTypeInAnnotation(typeName)) - } - } - -const findRangesOfNamedValueInExpression = - (valueName: string) => - (node: ElmSyntax.Node): vscode.Range[] => { - switch (node.value.type) { - case "application": - return node.value.application.flatMap( - findRangesOfNamedValueInExpression(valueName) - ) - case "case": - let fromPatterns = node.value.case.cases - .map((case_) => case_.pattern) - .flatMap(findRangesOfNamedValueInPattern(valueName)) - let fromExpressions = [ - node.value.case.expression, - ...node.value.case.cases.flatMap((case_) => - ElmSyntax.isDefinedAgainByPattern(valueName)(case_.pattern) - ? [] - : [case_.expression] - ), - ].flatMap(findRangesOfNamedValueInExpression(valueName)) - return fromExpressions.concat(fromPatterns) - case "charLiteral": - return [] - case "float": - return [] - case "functionOrValue": - let fullName = [ - ...node.value.functionOrValue.moduleName, - node.value.functionOrValue.name, - ].join(".") - if (valueName === fullName) { - return [sharedLogic.fromElmRange(node.range)] - } else { - return [] - } - case "glsl": - return [] - case "hex": - return [] - case "ifBlock": - return [ - node.value.ifBlock.clause, - node.value.ifBlock.then, - node.value.ifBlock.else, - ].flatMap(findRangesOfNamedValueInExpression(valueName)) - case "integer": - return [] - case "lambda": - let isLocallyDefined = node.value.lambda.patterns.some( - ElmSyntax.isDefinedAgainByPattern(valueName) - ) - let rangesFromExpressions = isLocallyDefined - ? [] - : findRangesOfNamedValueInExpression(valueName)( - node.value.lambda.expression - ) - return [ - ...node.value.lambda.patterns.flatMap( - findRangesOfNamedValueInPattern(valueName) - ), - ...rangesFromExpressions, - ] - case "let": - let isLocallyDefined2 = node.value.let.declarations.some( - ElmSyntax.isDefinedAgainByDeclaration(valueName) - ) - let rangesFromExpressions2 = isLocallyDefined2 - ? [] - : findRangesOfNamedValueInExpression(valueName)( - node.value.let.expression - ) - return [ - ...node.value.let.declarations.flatMap( - findRangesOfNamedValueInDeclaration(valueName) - ), - ...rangesFromExpressions2, - ] - case "list": - return node.value.list.flatMap( - findRangesOfNamedValueInExpression(valueName) - ) - case "literal": - return [] - case "negation": - return findRangesOfNamedValueInExpression(valueName)( - node.value.negation - ) - case "operator": - return [] - case "operatorapplication": - return [ - node.value.operatorapplication.left, - node.value.operatorapplication.right, - ].flatMap(findRangesOfNamedValueInExpression(valueName)) - case "parenthesized": - return findRangesOfNamedValueInExpression(valueName)( - node.value.parenthesized - ) - case "prefixoperator": - return [] - case "record": - return node.value.record - .map((field) => field.value.expression) - .flatMap(findRangesOfNamedValueInExpression(valueName)) - case "recordAccess": - return findRangesOfNamedValueInExpression(valueName)( - node.value.recordAccess.expression - ) - case "recordAccessFunction": - return [] - case "recordUpdate": - return node.value.recordUpdate.updates - .map((x) => x.value.expression) - .flatMap(findRangesOfNamedValueInExpression(valueName)) - case "tupled": - return node.value.tupled.flatMap( - findRangesOfNamedValueInExpression(valueName) - ) - case "unit": - return [] - } - } - -const findRangesOfNamedTypeInExpression = - (typeName: string) => - (node: ElmSyntax.Node): vscode.Range[] => { - switch (node.value.type) { - case "application": - return node.value.application.flatMap( - findRangesOfNamedTypeInExpression(typeName) - ) - case "case": - return [ - node.value.case.expression, - ...node.value.case.cases.map((case_) => case_.expression), - ].flatMap(findRangesOfNamedTypeInExpression(typeName)) - case "charLiteral": - return [] - case "float": - return [] - case "functionOrValue": - return [] - case "glsl": - return [] - case "hex": - return [] - case "ifBlock": - return [ - node.value.ifBlock.clause, - node.value.ifBlock.then, - node.value.ifBlock.else, - ].flatMap(findRangesOfNamedTypeInExpression(typeName)) - case "integer": - return [] - case "lambda": - return findRangesOfNamedTypeInExpression(typeName)( - node.value.lambda.expression - ) - case "let": - return [ - ...node.value.let.declarations.flatMap( - findRangesOfNamedValueInDeclaration(typeName) - ), - ...findRangesOfNamedTypeInExpression(typeName)( - node.value.let.expression - ), - ] - case "list": - return node.value.list.flatMap( - findRangesOfNamedTypeInExpression(typeName) - ) - case "literal": - return [] - case "negation": - return findRangesOfNamedTypeInExpression(typeName)(node.value.negation) - case "operator": - return [] - case "operatorapplication": - return [ - node.value.operatorapplication.left, - node.value.operatorapplication.right, - ].flatMap(findRangesOfNamedTypeInExpression(typeName)) - case "parenthesized": - return findRangesOfNamedTypeInExpression(typeName)( - node.value.parenthesized - ) - case "prefixoperator": - return [] - case "record": - return node.value.record - .map((field) => field.value.expression) - .flatMap(findRangesOfNamedTypeInExpression(typeName)) - case "recordAccess": - return findRangesOfNamedTypeInExpression(typeName)( - node.value.recordAccess.expression - ) - case "recordAccessFunction": - return [] - case "recordUpdate": - return node.value.recordUpdate.updates - .map((x) => x.value.expression) - .flatMap(findRangesOfNamedTypeInExpression(typeName)) - case "tupled": - return node.value.tupled.flatMap( - findRangesOfNamedTypeInExpression(typeName) - ) - case "unit": - return [] - } - } - -const findRangesOfNamedTypeInAnnotation = - (typeName: string) => - (node: ElmSyntax.Node): vscode.Range[] => { - switch (node.value.type) { - case "function": - return [node.value.function.left, node.value.function.right].flatMap( - findRangesOfNamedTypeInAnnotation(typeName) - ) - case "generic": - return [] - case "genericRecord": - return node.value.genericRecord.values.value - .map((node) => node.value.typeAnnotation) - .flatMap(findRangesOfNamedTypeInAnnotation(typeName)) - case "record": - return node.value.record.value - .map((node) => node.value.typeAnnotation) - .flatMap(findRangesOfNamedTypeInAnnotation(typeName)) - case "tupled": - return node.value.tupled.values.flatMap( - findRangesOfNamedTypeInAnnotation(typeName) - ) - case "typed": - let ranges: vscode.Range[] = node.value.typed.args.flatMap( - findRangesOfNamedTypeInAnnotation(typeName) - ) - let moduleNameAndName = node.value.typed.moduleNameAndName - let name = ElmSyntax.getNameFromModuleNameAndName(moduleNameAndName) - if (typeName.includes(name)) { - ranges.push(sharedLogic.fromElmRange(moduleNameAndName.range)) - } - return ranges - case "unit": - return [] - } - } - -const findRangesOfNamedValueInPattern = - (valueName: string) => - (node: ElmSyntax.Node): vscode.Range[] => { - switch (node.value.type) { - case "all": - return [] - case "as": - return findRangesOfNamedValueInPattern(valueName)(node.value.as.pattern) - case "char": - return [] - case "float": - return [] - case "hex": - return [] - case "int": - return [] - case "list": - return node.value.list.value.flatMap( - findRangesOfNamedValueInPattern(valueName) - ) - case "named": - let ranges: vscode.Range[] = node.value.named.patterns.flatMap( - findRangesOfNamedValueInPattern(valueName) - ) - let fullName = [ - ...node.value.named.qualified.moduleName, - node.value.named.qualified.name, - ].join(".") - if (valueName === fullName) { - ranges.push(sharedLogic.fromElmRange(node.range)) - } - return ranges - case "parentisized": - return findRangesOfNamedValueInPattern(valueName)( - node.value.parentisized.value - ) - case "record": - return [] - case "string": - return [] - case "tuple": - node.value.tuple.value.flatMap( - findRangesOfNamedValueInPattern(valueName) - ) - case "uncons": - return [] - case "unit": - return [] - case "var": - return [] - } - } diff --git a/src/features/find-usages/elm-find/index.ts b/src/features/find-usages/elm-find/index.ts deleted file mode 100644 index 6ee3317..0000000 --- a/src/features/find-usages/elm-find/index.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { spawn } from "child_process" -import * as path from "path" - -export type Output = { - qualified: QualifiedItem[] - aliased: AliasedItem[] - exposed: ExposedItem[] -} - -export type QualifiedItem = { - fsPath: string - lineNumber: number - usageLine: string -} -export type AliasedItem = { - aliasName: string - matches: QualifiedItem[] -} -export type ExposedItem = { fsPath: string; importLine: string } - -export const elmFind = async (options: { - folder: string - typeOrValueName: string - moduleName: string -}): Promise => { - let { folder, typeOrValueName } = options - let moduleName = options.moduleName.split(".").join("\\.") - - let runScript = (filename: string): Promise => - new Promise((resolve) => { - let pathToScript = path.join(__dirname, "scripts", filename) - let cmd = spawn(`sh`, [pathToScript, folder, moduleName, typeOrValueName]) - - let lines = "" - cmd.stdout.on("data", (line: string) => { - lines += line.toString() - }) - - cmd.on("close", () => resolve(lines)) - }) - - return Promise.all([ - runScript("1.sh").then((line) => line.split("\n").filter((x) => x)), - runScript("2.sh").then((line) => - line.split("\n\n").filter((x) => x.trim().split("\n").length > 1) - ), - runScript("3.sh").then((line) => line.split("\n").filter((x) => x)), - ]).then(toOutput) -} - -let toOutput = ([qualified, aliased, exposed]: [ - string[], - string[], - string[] -]): Output => ({ - qualified: qualified.flatMap(toQualifiedItem), - aliased: aliased.flatMap(toAliasedItem), - exposed: exposed.flatMap(toExposedItem), -}) - -// -// Example: "/usr/code/src/Main.elm:45: Ui.Button.view { ... }" -// -const toQualifiedItem = (line: string): QualifiedItem[] => { - let indexOfFirstColon = line.indexOf(":") - let indexOfSecondColon = - indexOfFirstColon + 1 + line.substring(indexOfFirstColon + 1).indexOf(":") - try { - let fsPath = line.substring(0, indexOfFirstColon) - let lineNumber = parseInt( - line.substring(indexOfFirstColon + 1, indexOfSecondColon) - ) - let usageLine = line.substring(indexOfSecondColon + 1) - if (fsPath && usageLine && lineNumber) { - return [{ fsPath, usageLine, lineNumber }] - } - } catch (_) {} - return [] -} - -// -// Example: -// Btn -// /usr/code/src/Main.elm:45: Btn.view { ... } -// /usr/code/src/Main.elm:58: Btn.view { ... } -// -const toAliasedItem = (line: string): AliasedItem[] => { - let [aliasName, ...matchingLines] = line.trim().split("\n") - - if (aliasName && matchingLines.length) { - return [{ aliasName, matches: matchingLines.flatMap(toQualifiedItem) }] - } - - return [] -} - -// -// Example: "/usr/code/src/Main.elm:import Ui.Button as Button exposing (..)" -// -const toExposedItem = (line: string): ExposedItem[] => { - let indexOfFirstColon = line.indexOf(":") - - let fsPath = line.substring(0, indexOfFirstColon) - let importLine = line.substring(indexOfFirstColon + 1) - if (fsPath && importLine) { - return [{ fsPath, importLine }] - } else { - return [] - } -} diff --git a/src/features/find-usages/elm-find/scripts/1.sh b/src/features/find-usages/elm-find/scripts/1.sh deleted file mode 100644 index a20f436..0000000 --- a/src/features/find-usages/elm-find/scripts/1.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -folders=$1 -moduleName=$2 -typeOrValueName=$3 - -grep -rlx "import $moduleName" $folders --include='*.elm' | cat | xargs -I {} grep -rnw "$moduleName\.$typeOrValueName" {} | cat \ No newline at end of file diff --git a/src/features/find-usages/elm-find/scripts/2.sh b/src/features/find-usages/elm-find/scripts/2.sh deleted file mode 100644 index 9bfabfa..0000000 --- a/src/features/find-usages/elm-find/scripts/2.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -folders=$1 -moduleName=$2 -typeOrValueName=$3 - -grep -Erx "import $moduleName as \w+" $folders --include='*.elm' | cat | sed "s/import $moduleName as //" | while IFS=: read -r fsPath aliasName; do - echo - echo $aliasName - grep -rnw "$aliasName\.$typeOrValueName" $fsPath | cat -done \ No newline at end of file diff --git a/src/features/find-usages/elm-find/scripts/3.sh b/src/features/find-usages/elm-find/scripts/3.sh deleted file mode 100644 index 83f7230..0000000 --- a/src/features/find-usages/elm-find/scripts/3.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -folders=$1 -moduleName=$2 -typeOrValueName=$3 - -grep -Erx "import $moduleName( as \w+)? exposing .*" $folders --include='*.elm' | cat \ No newline at end of file diff --git a/src/features/find-usages/elm-grep/README.md b/src/features/find-usages/elm-grep/README.md deleted file mode 100644 index 7785bcd..0000000 --- a/src/features/find-usages/elm-grep/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# elm-grep - -This is a tiny helper module for implementing the "Find usages" feature. - -It allows us to scan all `*.elm` files in a project to look for lines that match `import XYZ` in a cross-platform way. - -From there, we can scan the AST of the matched files, and help Elm developers find out where code is being used in their application. - -```ts -import * as ElmGrep from './elm-grep' - -const matchingFilepaths : string[] = - await findElmFilesImportingModule({ - moduleName: 'Http', - folders: [ - '/Users/ryan/code/elm-hello-world/src' - ] - }) -``` - -The `elm-grep` package overmatches as a starting point, and the idea is to scan each file afterwards to determine if the AST is using a giving import like `Http.get` or `Html.text`. \ No newline at end of file diff --git a/src/features/find-usages/elm-grep/index.ts b/src/features/find-usages/elm-grep/index.ts deleted file mode 100644 index 5912692..0000000 --- a/src/features/find-usages/elm-grep/index.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { exec } from "node:child_process" -import * as vscode from "vscode" -import { elmFind, Output, QualifiedItem } from "../elm-find" - -export type Input = { - folders: string[] - moduleName: string - typeOrValueName: string -} - -export type FindElmFilesOutput = { - matches: vscode.Location[] - fsPathsToScan: string[] -} - -export const findElmFiles = async ({ - folders, - moduleName, - typeOrValueName, -}: Input): Promise => { - const isWindows = process.platform === "win32" - - if (isWindows) { - // ๐Ÿข Slower version ( needs an "elmFind" implementation for findstr, cat, etc ) - let fsPaths = await findWindowsElmFilesImportingModuleWithValueName({ - folders, - moduleName, - typeOrValueName, - }) - return { matches: [], fsPathsToScan: fsPaths } - } else { - // ๐Ÿ‡ Faster version (but requires grep, sed, xargs, cat) - let results = await Promise.all( - folders.map((folder) => elmFind({ folder, moduleName, typeOrValueName })) - ) - - let fromItemToLocation = ( - item: QualifiedItem, - usage: string - ): vscode.Location => { - let index = item.usageLine.indexOf(usage) - return new vscode.Location( - vscode.Uri.file(item.fsPath), - new vscode.Range( - new vscode.Position(item.lineNumber - 1, index), - new vscode.Position(item.lineNumber - 1, index + usage.length) - ) - ) - } - - let toMatch = ({ qualified, aliased }: Output): vscode.Location[] => { - let qualifiedMatches = qualified.map((item) => { - let usage = `${moduleName}.${typeOrValueName}` - return fromItemToLocation(item, usage) - }) - let aliasedMatches = aliased.flatMap((item) => { - let usage = `${item.aliasName}.${typeOrValueName}` - return item.matches.map((match) => fromItemToLocation(match, usage)) - }) - return qualifiedMatches.concat(aliasedMatches) - } - - let fsPathsToScan = results.flatMap((result) => - result.exposed.map((x) => x.fsPath) - ) - - return { - matches: results.flatMap(toMatch), - fsPathsToScan, - } - } -} - -// -// In Elm, the only way module "XYZ" could be used is if -// there is at least one line starting with "import XYZ" -// -// Note: This function may overmatch on Windows, because I couldn't find -// a way to make "findstr" distinguish "import XYZ" from "import XYZABC" -// or "import XYZ.ABC" -// -// findElmFilesImportingModule({ -// moduleName: 'Http', -// folders: [ -// '/Users/ryan/code/elm-land/vscode/examples/01-hello-world/src' -// ] -// }) -// - -const findWindowsElmFilesImportingModule = async ({ - folders, - moduleName, -}: { - folders: string[] - moduleName: string -}): Promise => { - // - // findstr /srm /c:"^import Html" /d:folder1\src;folder2\src *.elm - // - const { command, args } = { - command: "findstr", - args: [ - `/srm`, - `/c:"^import ${moduleName}"`, - `/d:${folders.join(";")}`, - `*.elm`, - ], - } - - return new Promise((resolve) => { - exec([command, ...args].join(" "), (err, stdout) => { - if (err) { - resolve([]) - } else { - let filepaths = stdout.split("\n").filter((a) => a) - resolve(filepaths) - } - }) - }) -} - -const findWindowsElmFilesImportingModuleWithValueName = async ({ - folders, - moduleName, - typeOrValueName, -}: Input): Promise => { - // Find all filepaths importing the module - let filepathsImportingModule = await findWindowsElmFilesImportingModule({ - folders, - moduleName, - }) - - // - // findstr /srm /c:"Model" filepath1.elm filepath2.elm - // - const { command, args } = { - command: "findstr", - args: [`/srm`, `/c:"${typeOrValueName}"`, ...filepathsImportingModule], - } - - return new Promise((resolve) => { - exec([command, ...args].join(" "), (err, stdout) => { - if (err) { - resolve([]) - } else { - let filepaths = stdout.split("\n").filter((a) => a) - resolve(filepaths) - } - }) - }) -} - -export default { - findElmFiles, -} diff --git a/src/features/html-to-elm.ts b/src/features/html-to-elm.ts index 5512559..6ac2205 100644 --- a/src/features/html-to-elm.ts +++ b/src/features/html-to-elm.ts @@ -32,6 +32,7 @@ export const feature: Feature = ({ context, globalState }) => { ) let disposable = vscode.commands.registerCommand('elmLand.htmlToElm', async () => { + let start = Date.now() // Get the current text editor let editor = vscode.window.activeTextEditor if (!editor) { @@ -48,9 +49,10 @@ export const feature: Feature = ({ context, globalState }) => { // Replace the HTML with Elm code if (elmCode) { - editor.edit(editBuilder => { + await editor.edit(editBuilder => { editBuilder.replace(selection, elmCode || '') }) + console.info(`htmlToElm`, `${Date.now()-start}ms`) } }) diff --git a/src/features/offline-package-docs.ts b/src/features/offline-package-docs.ts index 0f072dd..4174016 100644 --- a/src/features/offline-package-docs.ts +++ b/src/features/offline-package-docs.ts @@ -421,7 +421,7 @@ export const feature: Feature = ({ globalState, context }) => { try { let [author, package_, version] = input.docsJsonFsPath - .split("/") + .split(path.sep) .slice(-4, -1) const panel = vscode.window.createWebviewPanel( diff --git a/src/features/shared/autodetect-elm-json.ts b/src/features/shared/autodetect-elm-json.ts index 2ef64e3..49a23b4 100644 --- a/src/features/shared/autodetect-elm-json.ts +++ b/src/features/shared/autodetect-elm-json.ts @@ -1,3 +1,4 @@ +import * as os from 'os' import * as path from 'path' import * as vscode from 'vscode' import { Dependency, ElmJsonFile } from './elm-json-file' @@ -114,7 +115,7 @@ const toElmJsonFiles = ({ settings, elmJsonFileUris }: Input): Promise