Skip to content

Commit c31d16f

Browse files
committed
reimplement #138
1 parent 3a6b5fb commit c31d16f

File tree

6 files changed

+204
-95
lines changed

6 files changed

+204
-95
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { has } from "@pretty-ts-errors/utils";
2+
import { formatDiagnostic } from "@pretty-ts-errors/vscode-formatter";
3+
import {
4+
ExtensionContext,
5+
languages,
6+
MarkdownString,
7+
Range,
8+
window,
9+
Uri,
10+
} from "vscode";
11+
import { createConverter } from "vscode-languageclient/lib/common/codeConverter";
12+
import { hoverProvider } from "./provider/hoverProvider";
13+
import { uriStore } from "./provider/uriStore";
14+
15+
const cache = new Map();
16+
const CACHE_SIZE_MAX = 100;
17+
const supportedDiagnosticSources = [
18+
"ts",
19+
"ts-plugin",
20+
"deno-ts",
21+
"js",
22+
"glint",
23+
];
24+
const registeredLanguages = new Set<string>();
25+
26+
export function registerOnDidChangeDiagnostics(context: ExtensionContext) {
27+
const converter = createConverter();
28+
context.subscriptions.push(
29+
languages.onDidChangeDiagnostics(async (e) => {
30+
e.uris.forEach((uri) => {
31+
const diagnostics = languages.getDiagnostics(uri);
32+
const supportedDiagnostics = diagnostics.filter(
33+
(diagnostic) =>
34+
diagnostic.source &&
35+
has(supportedDiagnosticSources, diagnostic.source)
36+
);
37+
38+
const items: {
39+
range: Range;
40+
contents: MarkdownString[];
41+
}[] = supportedDiagnostics.map((diagnostic) => {
42+
// formatDiagnostic converts message based on LSP Diagnostic type, not VSCode Diagnostic type, so it can be used in other IDEs.
43+
// Here we convert VSCode Diagnostic to LSP Diagnostic to make formatDiagnostic recognize it.
44+
let formattedMessage = cache.get(diagnostic.message);
45+
46+
if (!formattedMessage) {
47+
const markdownString = new MarkdownString(
48+
formatDiagnostic(converter.asDiagnostic(diagnostic))
49+
);
50+
51+
markdownString.isTrusted = true;
52+
markdownString.supportHtml = true;
53+
54+
formattedMessage = markdownString;
55+
cache.set(diagnostic.message, formattedMessage);
56+
57+
if (cache.size > CACHE_SIZE_MAX) {
58+
const firstCacheKey = cache.keys().next().value;
59+
cache.delete(firstCacheKey);
60+
}
61+
}
62+
63+
return {
64+
range: diagnostic.range,
65+
contents: [formattedMessage],
66+
};
67+
});
68+
69+
uriStore.set(uri.fsPath, items);
70+
71+
if (items.length > 0) {
72+
ensureHoverProviderIsRegistered(uri, context);
73+
}
74+
});
75+
})
76+
);
77+
}
78+
79+
function ensureHoverProviderIsRegistered(uri: Uri, context: ExtensionContext) {
80+
const editor = window.visibleTextEditors.find(
81+
(editor) => editor.document.uri.toString() === uri.toString()
82+
);
83+
if (editor && !registeredLanguages.has(editor.document.languageId)) {
84+
registeredLanguages.add(editor.document.languageId);
85+
context.subscriptions.push(
86+
languages.registerHoverProvider(
87+
{
88+
language: editor.document.languageId,
89+
},
90+
hoverProvider
91+
)
92+
);
93+
}
94+
}
Lines changed: 9 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,15 @@
1-
import { has } from "@pretty-ts-errors/utils";
2-
import { formatDiagnostic } from "@pretty-ts-errors/vscode-formatter";
3-
import {
4-
ExtensionContext,
5-
languages,
6-
MarkdownString,
7-
Range,
8-
window,
9-
} from "vscode";
10-
import { createConverter } from "vscode-languageclient/lib/common/codeConverter";
11-
import { hoverProvider } from "./provider/hoverProvider";
1+
import { ExtensionContext } from "vscode";
122
import { registerSelectedTextHoverProvider } from "./provider/selectedTextHoverProvider";
13-
import { uriStore } from "./provider/uriStore";
14-
15-
const cache = new Map();
3+
import { registerOnDidChangeDiagnostics } from "./diagnostics";
4+
import { logger } from "./logger";
165

176
export function activate(context: ExtensionContext) {
18-
const registeredLanguages = new Set<string>();
19-
const converter = createConverter();
20-
7+
logger.info("activating");
8+
context.subscriptions.push(logger);
219
registerSelectedTextHoverProvider(context);
10+
registerOnDidChangeDiagnostics(context);
11+
}
2212

23-
context.subscriptions.push(
24-
languages.onDidChangeDiagnostics(async (e) => {
25-
e.uris.forEach((uri) => {
26-
const diagnostics = languages.getDiagnostics(uri);
27-
28-
const items: {
29-
range: Range;
30-
contents: MarkdownString[];
31-
}[] = [];
32-
33-
let hasTsDiagnostic = false;
34-
35-
diagnostics
36-
.filter((diagnostic) =>
37-
diagnostic.source
38-
? has(
39-
["ts", "ts-plugin", "deno-ts", "js", "glint"],
40-
diagnostic.source
41-
)
42-
: false
43-
)
44-
.forEach(async (diagnostic) => {
45-
// formatDiagnostic converts message based on LSP Diagnostic type, not VSCode Diagnostic type, so it can be used in other IDEs.
46-
// Here we convert VSCode Diagnostic to LSP Diagnostic to make formatDiagnostic recognize it.
47-
let formattedMessage = cache.get(diagnostic.message);
48-
49-
if (!formattedMessage) {
50-
const markdownString = new MarkdownString(
51-
formatDiagnostic(converter.asDiagnostic(diagnostic))
52-
);
53-
54-
markdownString.isTrusted = true;
55-
markdownString.supportHtml = true;
56-
57-
formattedMessage = markdownString;
58-
cache.set(diagnostic.message, formattedMessage);
59-
60-
if (cache.size > 100) {
61-
const firstCacheKey = cache.keys().next().value;
62-
cache.delete(firstCacheKey);
63-
}
64-
}
65-
66-
items.push({
67-
range: diagnostic.range,
68-
contents: [formattedMessage],
69-
});
70-
71-
hasTsDiagnostic = true;
72-
});
73-
74-
uriStore[uri.fsPath] = items;
75-
76-
if (hasTsDiagnostic) {
77-
const editor = window.visibleTextEditors.find(
78-
(editor) => editor.document.uri.toString() === uri.toString()
79-
);
80-
if (editor && !registeredLanguages.has(editor.document.languageId)) {
81-
registeredLanguages.add(editor.document.languageId);
82-
context.subscriptions.push(
83-
languages.registerHoverProvider(
84-
{
85-
language: editor.document.languageId,
86-
},
87-
hoverProvider
88-
)
89-
);
90-
}
91-
}
92-
});
93-
})
94-
);
13+
export function deactivate() {
14+
logger.info("deactivating");
9515
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { LogOutputChannel, window } from "vscode";
2+
3+
let instance: null | LogOutputChannel = null;
4+
5+
function getLogger(): LogOutputChannel {
6+
if (instance !== null) {
7+
return instance;
8+
}
9+
instance = window.createOutputChannel("Pretty TypeScript Errors", {
10+
log: true,
11+
});
12+
return instance;
13+
}
14+
15+
function info(...args: Parameters<LogOutputChannel["info"]>) {
16+
getLogger().info(...args);
17+
}
18+
19+
function trace(...args: Parameters<LogOutputChannel["trace"]>) {
20+
getLogger().trace(...args);
21+
}
22+
23+
function debug(...args: Parameters<LogOutputChannel["debug"]>) {
24+
getLogger().debug(...args);
25+
}
26+
function warn(...args: Parameters<LogOutputChannel["warn"]>) {
27+
getLogger().warn(...args);
28+
}
29+
30+
function error(...args: Parameters<LogOutputChannel["error"]>) {
31+
getLogger().error(...args);
32+
}
33+
34+
type LogLevel = "info" | "trace" | "debug" | "warn" | "error";
35+
36+
const defaultThresholds: Record<LogLevel, number> = {
37+
error: 5000,
38+
warn: 1000,
39+
info: 100,
40+
debug: 50,
41+
trace: 0,
42+
};
43+
44+
/**
45+
* Both in the browser and Node >= 16 (vscode 1.77 has node >= 16) have `performance` available as a global
46+
* But `@types/node` is missing its global declaration, this fixes the type error we get from using it
47+
*/
48+
declare const performance: import("perf_hooks").Performance;
49+
50+
/**
51+
* Measures the time it took to run `task` and reports it to the `logger` based on `logLevelThresholds`.
52+
*
53+
* NOTE: supports synchronous `task`s only
54+
* @see {@link defaultThresholds} for the default thresholds
55+
*/
56+
function measure<T = unknown>(
57+
name: string,
58+
task: () => T,
59+
logLevelThresholds: Partial<Record<LogLevel, number>> = {}
60+
): T {
61+
const start = performance.now();
62+
const result = task();
63+
const end = performance.now();
64+
const duration = end - start;
65+
logLevelThresholds = Object.assign({}, defaultThresholds, logLevelThresholds);
66+
const thresholds = Object.entries(logLevelThresholds) as [LogLevel, number][];
67+
// sort thresholds from high to low
68+
// { info: 100, warn: 1000, trace: 0 } => [[warn, 1000], [info, 100], [trace, 0]]
69+
thresholds.sort(([_a, a], [_b, b]) => b - a);
70+
const level: LogLevel =
71+
thresholds.find(([_, threshold]) => duration > threshold)?.[0] || "trace";
72+
getLogger()[level](`${name} took ${duration.toFixed(3)}ms`);
73+
return result;
74+
}
75+
76+
function dispose() {
77+
if (instance !== null) {
78+
instance.dispose();
79+
instance = null;
80+
}
81+
}
82+
83+
export const logger = {
84+
trace,
85+
debug,
86+
info,
87+
warn,
88+
error,
89+
measure,
90+
dispose,
91+
};

apps/vscode-extension/src/provider/hoverProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { uriStore } from "./uriStore";
33

44
export const hoverProvider: HoverProvider = {
55
provideHover(document, position, _token) {
6-
const itemsInUriStore = uriStore[document.uri.fsPath];
6+
const itemsInUriStore = uriStore.get(document.uri.fsPath);
77

88
if (!itemsInUriStore) {
99
return null;

apps/vscode-extension/src/provider/selectedTextHoverProvider.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ import { createConverter } from "vscode-languageclient/lib/common/codeConverter"
1414
* It format selected text and help test things visually easier.
1515
*/
1616
export function registerSelectedTextHoverProvider(context: ExtensionContext) {
17-
const converter = createConverter();
18-
1917
if (context.extensionMode !== ExtensionMode.Development) {
2018
return;
2119
}
2220

21+
const converter = createConverter();
2322
context.subscriptions.push(
2423
languages.registerHoverProvider(
2524
{
@@ -29,6 +28,11 @@ export function registerSelectedTextHoverProvider(context: ExtensionContext) {
2928
{
3029
provideHover(document, position) {
3130
const editor = window.activeTextEditor;
31+
32+
if (!editor) {
33+
return;
34+
}
35+
3236
const range = document.getWordRangeAtPosition(position);
3337
const message = editor ? document.getText(editor.selection) : "";
3438

@@ -61,7 +65,7 @@ export function registerSelectedTextHoverProvider(context: ExtensionContext) {
6165
);
6266
}
6367

64-
const debugHoverHeader = d/*html*/ `
68+
const debugHoverHeader = d/*html*/ `
6569
<span style="color:#f96363;">
6670
<span class="codicon codicon-debug"></span>
6771
Formatted selected text (debug only)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { MarkdownString, Range, Uri } from "vscode";
22

3-
export const uriStore: Record<
3+
export const uriStore = new Map<
44
Uri["path"],
55
{
66
range: Range;
77
contents: MarkdownString[];
88
}[]
9-
> = {};
9+
>();

0 commit comments

Comments
 (0)