Skip to content

Commit 789dae1

Browse files
committed
feat: show expected error message a bit lightly
1 parent 9dad069 commit 789dae1

File tree

5 files changed

+88
-21
lines changed

5 files changed

+88
-21
lines changed

denops/fall/error.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { Denops } from "jsr:@denops/std@^7.3.2";
2+
import { AssertError } from "jsr:@core/unknownutil@^4.3.0/assert";
3+
4+
/**
5+
* Application error that is used to represent an expected error.
6+
*/
7+
export class ExpectedError extends Error {
8+
constructor(message: string, public source?: unknown) {
9+
super(message);
10+
}
11+
}
12+
13+
export async function handleError(
14+
denops: Denops,
15+
error: unknown,
16+
): Promise<void> {
17+
if (error instanceof ExpectedError) {
18+
await denops.cmd(
19+
`redraw | echohl Error | echomsg '[fall] ${error.message}' | echohl None`,
20+
);
21+
return;
22+
}
23+
if (error instanceof AssertError) {
24+
await denops.cmd(
25+
`redraw | echohl Error | echomsg '[fall] ${error.message}' | echohl None`,
26+
);
27+
return;
28+
}
29+
console.error(error);
30+
}
31+
32+
export function withHandleError<T, A extends unknown[]>(
33+
denops: Denops,
34+
fn: (...args: A) => T | Promise<T>,
35+
): (...args: A) => Promise<T | undefined> {
36+
return async (...args: A): Promise<T | undefined> => {
37+
try {
38+
return await fn(...args);
39+
} catch (e) {
40+
// NOTE:
41+
// This setTimeout is required in Neovim to show the error message
42+
// properly. Otherwise, the error message is not shown maybe because
43+
// the msgarea is hidden during the picker execution.
44+
setTimeout(
45+
() => handleError(denops, e).catch((err) => console.error(err)),
46+
0,
47+
);
48+
}
49+
};
50+
}

denops/fall/main/config.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,22 @@ import {
66
loadUserConfig,
77
recacheUserConfig,
88
} from "../config.ts";
9+
import { withHandleError } from "../error.ts";
910

1011
export const main: Entrypoint = (denops) => {
1112
denops.dispatcher = {
1213
...denops.dispatcher,
13-
"config:edit": (options = {}) => {
14+
"config:edit": withHandleError(denops, (options = {}) => {
1415
assert(options, isEditOptions);
1516
return editUserConfig(denops, options);
16-
},
17-
"config:reload": (options = {}) => {
17+
}),
18+
"config:reload": withHandleError(denops, (options = {}) => {
1819
assert(options, isReloadOptions);
1920
return loadUserConfig(denops, { verbose: options.verbose, reload: true });
20-
},
21-
"config:recache": () => {
21+
}),
22+
"config:recache": withHandleError(denops, () => {
2223
return recacheUserConfig(denops, { signal: denops.interrupted });
23-
},
24+
}),
2425
};
2526
};
2627

denops/fall/main/event.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import type { Entrypoint } from "jsr:@denops/std@^7.3.2";
22
import { as, ensure, is, type Predicate } from "jsr:@core/unknownutil@^4.3.0";
33

44
import { dispatch, type Event } from "../event.ts";
5+
import { withHandleError } from "../error.ts";
56

67
export const main: Entrypoint = (denops) => {
78
denops.dispatcher = {
89
...denops.dispatcher,
9-
"event:dispatch": (event) => {
10+
"event:dispatch": withHandleError(denops, (event) => {
1011
dispatch(ensure(event, isEventComplement));
11-
},
12+
}),
1213
};
1314
};
1415

denops/fall/main/picker.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { action as buildActionSource } from "../extension/source/action.ts";
2121
import { Picker } from "../picker.ts";
2222
import type { SubmatchContext } from "./submatch.ts";
23+
import { ExpectedError, withHandleError } from "../error.ts";
2324

2425
let zindex = 50;
2526

@@ -32,29 +33,36 @@ export const main: Entrypoint = (denops) => {
3233
assert(options, isOptions);
3334
return startPicker(denops, args, itemPickerParams, options);
3435
},
35-
"picker:command": async (args) => {
36+
"picker:command": withHandleError(denops, async (args) => {
3637
await loadUserConfig(denops);
3738
// Split the command arguments
3839
const [name, ...sourceArgs] = ensure(args, isStringArray);
3940
// Load user config
4041
const itemPickerParams = getItemPickerParams(name);
4142
if (!itemPickerParams) {
42-
throw new Error(`Config for picker "${name}" is not found`);
43+
throw new ExpectedError(
44+
`No item picker "${name}" is found. Available item pickers are: ${
45+
listItemPickerNames().join(", ")
46+
}`,
47+
);
4348
}
4449
await startPicker(
4550
denops,
4651
sourceArgs,
4752
itemPickerParams,
4853
{ signal: denops.interrupted },
4954
);
50-
},
51-
"picker:command:complete": async (arglead, cmdline, cursorpos) => {
52-
await loadUserConfig(denops);
53-
assert(arglead, is.String);
54-
assert(cmdline, is.String);
55-
assert(cursorpos, is.Number);
56-
return listItemPickerNames().filter((name) => name.startsWith(arglead));
57-
},
55+
}),
56+
"picker:command:complete": withHandleError(
57+
denops,
58+
async (arglead, cmdline, cursorpos) => {
59+
await loadUserConfig(denops);
60+
assert(arglead, is.String);
61+
assert(cmdline, is.String);
62+
assert(cursorpos, is.Number);
63+
return listItemPickerNames().filter((name) => name.startsWith(arglead));
64+
},
65+
),
5866
// TODO: Remove this API prior to release v1.0.0
5967
// DEPRECATED: Use "submatch:start" instead
6068
"picker:start": async (_args, _screen, params, options) => {
@@ -155,7 +163,13 @@ async function startPicker(
155163
// Execute the action
156164
const action = itemPickerParams.actions[actionName];
157165
if (!action) {
158-
throw new Error(`Action "${actionName}" is not found`);
166+
throw new ExpectedError(
167+
`No action "${actionName}" is found. Available actions are: ${
168+
Object.keys(itemPickerParams.actions).join(
169+
", ",
170+
)
171+
}`,
172+
);
159173
}
160174
const actionParams = {
161175
// TODO: Drop the following attributes prior to release v1.0.0

denops/fall/main/submatch.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
isTheme,
2525
} from "../util/predicate.ts";
2626
import { list as buildListSource } from "../extension/source/list.ts";
27+
import { withHandleError } from "../error.ts";
2728

2829
export type SubmatchContext = InvokeParams<Detail> & {
2930
readonly _submatch: {
@@ -45,12 +46,12 @@ type SubmatchParams = {
4546
export const main: Entrypoint = (denops) => {
4647
denops.dispatcher = {
4748
...denops.dispatcher,
48-
"submatch": async (context, params, options) => {
49+
"submatch": withHandleError(denops, async (context, params, options) => {
4950
assert(context, isSubmatchContext);
5051
assert(params, isSubmatchParams);
5152
assert(options, isOptions);
5253
return await submatchStart(denops, context, params, options);
53-
},
54+
}),
5455
};
5556
};
5657

0 commit comments

Comments
 (0)