Skip to content

Commit c016a9f

Browse files
Merge pull request #184 from crystal-lang-tools/nobody/small-error-handling
Release 0.9.1
2 parents 41ae652 + 898ac1f commit c016a9f

File tree

13 files changed

+799
-708
lines changed

13 files changed

+799
-708
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Change Log
22

3+
## [0.9.1] - 2024-02-06
4+
5+
### Fix
6+
7+
- V 0.9 won't catch some errors [#183](https://github.com/crystal-lang-tools/vscode-crystal-lang/issues/183)
8+
- Spawn problem tool if `crystal tool dependencies` failed
9+
- General formatting
10+
311
## [0.9.0] - 2024-02-01
412

513
### Fix

README.md

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,83 @@
11
# VSCode Extension for Crystal Language
22

33
![Publish Extension](https://github.com/crystal-lang-tools/vscode-crystal-lang/workflows/Publish%20Extension/badge.svg)
4-
---
5-
**This is the official version of the plugin for Crystal Language support in VS Code.**
6-
**Remove all old versions before installing this one.**
7-
---
84

95
This extension provides support for the [Crystal](https://github.com/crystal-lang) programming language.
106

11-
![vscode-crystal-lang](https://i.imgur.com/ZxIsOWB.gif)
7+
![vscode-crystal-lang](./images/vscode-example.gif)
128

13-
## Wiki
9+
## Requirements
1410

15-
1. [Requirements](https://github.com/crystal-lang-tools/vscode-crystal-lang/wiki/Requirements)
16-
2. [Features](https://github.com/crystal-lang-tools/vscode-crystal-lang/wiki/Features)
17-
3. [Settings](https://github.com/crystal-lang-tools/vscode-crystal-lang/wiki/Settings)
18-
4. [Useful extensions](https://github.com/crystal-lang-tools/vscode-crystal-lang/wiki/Useful-extensions)
19-
5. [Known issues](https://github.com/crystal-lang-tools/vscode-crystal-lang/wiki/Known-Issues)
20-
6. [Roadmap](https://github.com/crystal-lang-tools/vscode-crystal-lang/wiki/Roadmap)
11+
It is recommended to install the [Crystal programming language](https://crystal-lang.org/) (platform dependendant). No other dependencies are required.
12+
For debugging support, it's recommended to follow the guide [here](https://dev.to/bcardiff/debug-crystal-in-vscode-via-codelldb-3lf).
13+
14+
## Features
15+
16+
- Syntax highlighting:
17+
- Syntax highlighting support for Crystal, [Slang](https://github.com/jeromegn/slang), and [ECR](https://crystal-lang.org/api/latest/ECR.html) (contributions for other Crystal templating languages welcome)
18+
- Auto indentation:
19+
- Automatically indent while coding (after `do`, `if`, etc.)
20+
- Snippets:
21+
- Helpful code completions for common use-cases
22+
- Formatting
23+
- Allows for "format on save" and manual formatting, and works even if the editor isn't saved to disk
24+
- Problems finder
25+
- When enabled, on opening or saving a file the project will be compiled to find any problems with the code, and these problems are reported in the editor. Depending on the project this can be slow and memory intensive, so there is an option to disable it.
26+
- Document symbols
27+
- Allows for easier code navigation through breadcrumbs at the top of the file, as well as being able to view and jump to the documents symbols
28+
- Peek / go to definition
29+
- When enabled, this allows for quickly jumping to the definition(s) for a method using the `crystal tool implementations` command
30+
- Show type on hover
31+
- When enabled, this allows for viewing the type of a variable or return type of a method on hover, using the `crystal tool context` command
32+
- Tasks
33+
- Enables executing various `crystal`/`shards` commands directly from VSCode
34+
35+
## Settings
36+
37+
- `compiler` - set a custom absolute path for the Crystal compiler
38+
- `definitions` - enables jump-to-definition (reload required)
39+
- `dependencies` - use the dependencies tool to determine the main for each file, required if there's multiple entrypoints to the project, can be slow
40+
- `flags` - flags to pass to the compiler
41+
- `hover` - show type information on hover (reload required)
42+
- `main` - set a main executable to use for the current project (`${workspaceRoot}/src/main.cr`)
43+
- `problems` - runs the compiler on save and reports any issues (reload required)
44+
- `server` - absolute path to an LSP executable to use instead of the custom features provided by this extension, like [Crystalline](https://github.com/elbywan/crystalline) (reload required)
45+
- `shards` - set a custom absolute path for the shards executable
46+
- `spec-explorer` - enable the built-in testing UI for specs, recommended for Crystal >= 1.11 due to `--dry-run` flag (reload required)
47+
- `spec-tags` - specific tags to pass to the spec runner
48+
49+
By default, the problems runner, hover provider, and definitions provider are turned on. This may not be ideal for larger projects due to compile times and memory usage, so it is recommended to turn them off in the vscode settings. That can be done per-project by creating a `.vscode/settings.json` file with:
50+
51+
```json
52+
// .vscode/settings.json
53+
{
54+
// Turn off slow/memory-intensive features for larger projects
55+
"crystal-lang.definitions": false,
56+
"crystal-lang.dependencies": false,
57+
"crystal-lang.hover": false,
58+
"crystal-lang.problems": false,
59+
"crystal-lang.spec-explorer": false,
60+
}
61+
```
62+
63+
## Supported Platforms
64+
65+
This extension has been tested on / should work on the following platforms:
66+
67+
- Linux (Arch / Ubuntu / Fedora)
68+
- MacOS (Intel / Apple Silicon)
69+
- Windows (10 Native / 10 WSL2 / 11)
70+
- GitHub Codespaces
71+
72+
## Roadmap
73+
74+
These are some features that are planned or would be nice for the future of this project or others:
75+
76+
- Better / faster LSP support
77+
- Better completion algorithm
78+
- Better symbol detection
79+
- Integrated debugger support
80+
- Refactored task runner
2181

2282
## Release Notes
2383

images/vscode-example.gif

2.04 MB
Loading

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "crystal-lang",
33
"displayName": "Crystal Language",
44
"description": "The Crystal Programming Language",
5-
"version": "0.9.0",
5+
"version": "0.9.1",
66
"publisher": "crystal-lang-tools",
77
"icon": "images/icon.gif",
88
"license": "MIT",

src/definitions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ async function spawnImplTool(
107107
const config = workspace.getConfiguration('crystal-lang');
108108
const cursor = getCursorPath(document, position);
109109
const main = await getShardMainPath(document);
110+
if (!main) return;
111+
110112
const cmd = `${shellEscape(compiler)} tool implementations -c ${shellEscape(cursor)} ${shellEscape(main)} -f json --no-color ${config.get<string>("flags")}`
111113
const folder: WorkspaceFolder = getWorkspaceFolder(document.uri)
112114

src/format.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,14 @@ async function spawnFormatTool(document: TextDocument): Promise<string> {
8585

8686
child.on('close', () => {
8787
if (err.length > 0) {
88-
const err_resp = err.join('')
88+
const err_resp = err.join('') + "\n" + out.join('')
8989
findProblemsRaw(err_resp, document.uri)
9090
rej(err_resp);
91-
return;
91+
} else {
92+
const out_resp = out.join('')
93+
findProblemsRaw(out_resp, document.uri)
94+
res(out_resp);
9295
}
93-
const out_resp = out.join('')
94-
findProblemsRaw(out_resp, document.uri)
95-
res(out_resp);
9696
})
9797
});
9898
}

src/hover.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ async function spawnContextTool(
269269
// Spec files shouldn't have main set to something in src/
270270
// but are instead their own main files
271271
const main = await getShardMainPath(document);
272+
if (!main) return;
273+
272274
const cmd = `${shellEscape(compiler)} tool context -c ${shellEscape(cursor)} ${shellEscape(main)} -f json --no-color ${config.get<string>("flags")}`
273275
const folder = getWorkspaceFolder(document.uri)
274276

src/macro.ts

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,51 @@ import { crystalOutputChannel, execAsync, findProblems, getCompilerPath, getCurs
44
export const macroOutputChannel = window.createOutputChannel("Crystal Macro", "markdown")
55

66
export function registerMacroExpansion() {
7-
commands.registerCommand('crystal-lang.showMacroExpansion', async function () {
8-
const activeEditor = window.activeTextEditor;
9-
const document = activeEditor.document;
10-
const position = activeEditor.selection.active;
11-
12-
const response = await spawnMacroExpandTool(document, position)
13-
.then((resp) => {
14-
if (resp) {
15-
return resp
16-
} else {
17-
return undefined
18-
}
19-
})
20-
21-
if (response) {
22-
macroOutputChannel.appendLine("```crystal\n" + response + "```\n")
7+
commands.registerCommand('crystal-lang.showMacroExpansion', async function () {
8+
const activeEditor = window.activeTextEditor;
9+
const document = activeEditor.document;
10+
const position = activeEditor.selection.active;
11+
12+
const response = await spawnMacroExpandTool(document, position)
13+
.then((resp) => {
14+
if (resp) {
15+
return resp
2316
} else {
24-
macroOutputChannel.appendLine("# No macro expansion found")
17+
return undefined
2518
}
26-
macroOutputChannel.show()
27-
})
19+
})
20+
21+
if (response) {
22+
macroOutputChannel.appendLine("```crystal\n" + response + "```\n")
23+
} else {
24+
macroOutputChannel.appendLine("# No macro expansion found")
25+
}
26+
macroOutputChannel.show()
27+
})
2828
}
2929

3030
export async function spawnMacroExpandTool(document: TextDocument, position: Position): Promise<string | void> {
31-
const compiler = await getCompilerPath();
32-
const main = await getShardMainPath(document);
33-
const cursor = getCursorPath(document, position);
34-
const folder = getWorkspaceFolder(document.uri);
35-
const config = workspace.getConfiguration('crystal-lang');
36-
37-
const cmd = `${shellEscape(compiler)} tool expand ${shellEscape(main)} --cursor ${shellEscape(cursor)} ${config.get<string>("flags")}`
38-
39-
crystalOutputChannel.appendLine(`[Macro Expansion] (${folder.name}) $ ` + cmd)
40-
return await execAsync(cmd, folder.uri.fsPath)
41-
.then((response) => {
42-
return response;
31+
const compiler = await getCompilerPath();
32+
const main = await getShardMainPath(document);
33+
if (!main) return;
34+
35+
const cursor = getCursorPath(document, position);
36+
const folder = getWorkspaceFolder(document.uri);
37+
const config = workspace.getConfiguration('crystal-lang');
38+
39+
const cmd = `${shellEscape(compiler)} tool expand ${shellEscape(main)} --cursor ${shellEscape(cursor)} ${config.get<string>("flags")}`
40+
41+
crystalOutputChannel.appendLine(`[Macro Expansion] (${folder.name}) $ ` + cmd)
42+
return await execAsync(cmd, folder.uri.fsPath)
43+
.then((response) => {
44+
return response;
45+
})
46+
.catch(async (err) => {
47+
const new_cmd = cmd + ' -f json'
48+
await execAsync(new_cmd, folder.uri.fsPath)
49+
.catch((err) => {
50+
findProblems(err.stderr, document.uri)
51+
crystalOutputChannel.appendLine(`[Macro Expansion] Error: ${err.message}`)
4352
})
44-
.catch(async (err) => {
45-
const new_cmd = cmd + ' -f json'
46-
await execAsync(new_cmd, folder.uri.fsPath)
47-
.catch((err) => {
48-
findProblems(err.stderr, document.uri)
49-
crystalOutputChannel.appendLine(`[Macro Expansion] Error: ${err.message}`)
50-
})
51-
});
53+
});
5254
}

src/problems.ts

Lines changed: 65 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,71 @@ import { TextDocument, workspace } from "vscode";
22
import { setStatusBar, compiler_mutex, crystalOutputChannel, diagnosticCollection, execAsync, findProblems, getCompilerPath, getShardMainPath, getWorkspaceFolder, shellEscape } from "./tools";
33

44
export function registerProblems(): void {
5-
workspace.onDidSaveTextDocument((e) => {
6-
if (e.uri && e.uri.scheme === "file" && (e.fileName.endsWith(".cr") || e.fileName.endsWith(".ecr"))) {
7-
if (compiler_mutex.isLocked()) return;
8-
9-
const dispose = setStatusBar('finding problems...');
10-
11-
compiler_mutex.acquire()
12-
.then((release) => {
13-
spawnProblemsTool(e)
14-
.catch((err) => {
15-
crystalOutputChannel.appendLine(`[Problems] Error: ${JSON.stringify(err)}`)
16-
})
17-
.finally(() => {
18-
release()
19-
})
20-
})
21-
.finally(() => {
22-
dispose()
23-
})
24-
}
25-
})
26-
27-
return;
5+
workspace.onDidOpenTextDocument((e) => handleDocument(e))
6+
workspace.onDidSaveTextDocument((e) => handleDocument(e))
7+
8+
return;
9+
}
10+
11+
/**
12+
* Determines whether a document should have the problems tool run on it. If it should,
13+
* acquires the compiler mutex and executes the problems tool.
14+
*
15+
* @param {TextDocument} document
16+
* @return {*} {Promise<void>}
17+
*/
18+
async function handleDocument(document: TextDocument): Promise<void> {
19+
if (document.uri && document.uri.scheme === "file" && (document.fileName.endsWith(".cr") || document.fileName.endsWith(".ecr"))) {
20+
if (compiler_mutex.isLocked()) return;
21+
22+
const dispose = setStatusBar('finding problems...');
23+
24+
compiler_mutex.acquire()
25+
.then((release) => {
26+
spawnProblemsTool(document)
27+
.catch((err) => {
28+
crystalOutputChannel.appendLine(`[Problems] Error: ${JSON.stringify(err)}`)
29+
})
30+
.finally(() => {
31+
release()
32+
})
33+
})
34+
.finally(() => {
35+
dispose()
36+
})
37+
}
2838
}
2939

30-
async function spawnProblemsTool(document: TextDocument): Promise<void> {
31-
const compiler = await getCompilerPath();
32-
const main = await getShardMainPath(document);
33-
const folder = getWorkspaceFolder(document.uri).uri.fsPath;
34-
const config = workspace.getConfiguration('crystal-lang');
35-
36-
// If document is in a folder of the same name as the document, it will throw an
37-
// error about not being able to use an output filename of '...' as it's a folder.
38-
// This is probably a bug as the --no-codegen flag is set, there is no output.
39-
//
40-
// Error: can't use `...` as output filename because it's a directory
41-
//
42-
const output = process.platform === "win32" ? "nul" : "/dev/null"
43-
44-
const cmd = `${shellEscape(compiler)} build ${shellEscape(main)} --no-debug --no-color --no-codegen --error-trace -f json -o ${output} ${config.get<string>("flags")}`
45-
46-
crystalOutputChannel.appendLine(`[Problems] (${getWorkspaceFolder(document.uri).name}) $ ` + cmd)
47-
await execAsync(cmd, folder)
48-
.then((response) => {
49-
diagnosticCollection.clear()
50-
crystalOutputChannel.appendLine("[Problems] No problems found.")
51-
}).catch((err) => {
52-
findProblems(err.stderr, document.uri)
53-
try {
54-
const parsed = JSON.parse(err.stderr)
55-
crystalOutputChannel.appendLine(`[Problems] Error: ${err.stderr}`)
56-
} catch {
57-
crystalOutputChannel.appendLine(`[Problems] Error: ${JSON.stringify(err)}`)
58-
}
59-
});
40+
export async function spawnProblemsTool(document: TextDocument, mainFile: string = undefined): Promise<void> {
41+
const compiler = await getCompilerPath();
42+
const main = mainFile || await getShardMainPath(document);
43+
if (!main) return;
44+
45+
const folder = getWorkspaceFolder(document.uri).uri.fsPath;
46+
const config = workspace.getConfiguration('crystal-lang');
47+
48+
// If document is in a folder of the same name as the document, it will throw an
49+
// error about not being able to use an output filename of '...' as it's a folder.
50+
// This is probably a bug as the --no-codegen flag is set, there is no output.
51+
//
52+
// Error: can't use `...` as output filename because it's a directory
53+
//
54+
const output = process.platform === "win32" ? "nul" : "/dev/null"
55+
56+
const cmd = `${shellEscape(compiler)} build ${shellEscape(main)} --no-debug --no-color --no-codegen --error-trace -f json -o ${output} ${config.get<string>("flags")}`
57+
58+
crystalOutputChannel.appendLine(`[Problems] (${getWorkspaceFolder(document.uri).name}) $ ` + cmd)
59+
await execAsync(cmd, folder)
60+
.then((response) => {
61+
diagnosticCollection.clear()
62+
crystalOutputChannel.appendLine("[Problems] No problems found.")
63+
}).catch((err) => {
64+
findProblems(err.stderr, document.uri)
65+
try {
66+
const parsed = JSON.parse(err.stderr)
67+
crystalOutputChannel.appendLine(`[Problems] Error: ${err.stderr}`)
68+
} catch {
69+
crystalOutputChannel.appendLine(`[Problems] Error: ${JSON.stringify(err)}`)
70+
}
71+
});
6072
}

0 commit comments

Comments
 (0)