1- import { commands } from "vscode" ;
2- import { commandRunner , NoProgressTask } from "../../commandRunner" ;
1+ import { commands , Disposable } from "vscode" ;
32import { CommandFunction , CommandManager } from "../../packages/commands" ;
4- import { OutputChannelLogger } from "../logging" ;
3+ import { extLogger , OutputChannelLogger } from "../logging" ;
4+ import {
5+ asError ,
6+ getErrorMessage ,
7+ getErrorStack ,
8+ } from "../../pure/helpers-pure" ;
9+ import { redactableError } from "../../pure/errors" ;
10+ import { UserCancellationException } from "../../progress" ;
11+ import {
12+ showAndLogExceptionWithTelemetry ,
13+ showAndLogWarningMessage ,
14+ } from "../../helpers" ;
15+ import { telemetryListener } from "../../telemetry" ;
516
617/**
718 * Create a command manager for VSCode, wrapping the commandRunner
@@ -10,11 +21,65 @@ import { OutputChannelLogger } from "../logging";
1021export function createVSCodeCommandManager <
1122 Commands extends Record < string , CommandFunction > ,
1223> ( outputLogger ?: OutputChannelLogger ) : CommandManager < Commands > {
13- return new CommandManager ( ( commandId , task : NoProgressTask ) => {
14- return commandRunner ( commandId , task , outputLogger ) ;
24+ return new CommandManager ( ( commandId , task ) => {
25+ return registerCommandWithErrorHandling ( commandId , task , outputLogger ) ;
1526 } , wrapExecuteCommand ) ;
1627}
1728
29+ /**
30+ * A wrapper for command registration. This wrapper adds uniform error handling for commands.
31+ *
32+ * @param commandId The ID of the command to register.
33+ * @param task The task to run. It is passed directly to `commands.registerCommand`. Any
34+ * arguments to the command handler are passed on to the task.
35+ */
36+ export function registerCommandWithErrorHandling (
37+ commandId : string ,
38+ task : ( ...args : any [ ] ) => Promise < any > ,
39+ outputLogger = extLogger ,
40+ ) : Disposable {
41+ return commands . registerCommand ( commandId , async ( ...args : any [ ] ) => {
42+ const startTime = Date . now ( ) ;
43+ let error : Error | undefined ;
44+
45+ try {
46+ return await task ( ...args ) ;
47+ } catch ( e ) {
48+ error = asError ( e ) ;
49+ const errorMessage = redactableError ( error ) `${
50+ getErrorMessage ( e ) || e
51+ } (${ commandId } )`;
52+ const errorStack = getErrorStack ( e ) ;
53+ if ( e instanceof UserCancellationException ) {
54+ // User has cancelled this action manually
55+ if ( e . silent ) {
56+ void outputLogger . log ( errorMessage . fullMessage ) ;
57+ } else {
58+ void showAndLogWarningMessage ( errorMessage . fullMessage , {
59+ outputLogger,
60+ } ) ;
61+ }
62+ } else {
63+ // Include the full stack in the error log only.
64+ const fullMessage = errorStack
65+ ? `${ errorMessage . fullMessage } \n${ errorStack } `
66+ : errorMessage . fullMessage ;
67+ void showAndLogExceptionWithTelemetry ( errorMessage , {
68+ outputLogger,
69+ fullMessage,
70+ extraTelemetryProperties : {
71+ command : commandId ,
72+ } ,
73+ } ) ;
74+ }
75+ return undefined ;
76+ } finally {
77+ const executionTime = Date . now ( ) - startTime ;
78+ telemetryListener ?. sendCommandUsage ( commandId , executionTime , error ) ;
79+ }
80+ } ) ;
81+ }
82+
1883/**
1984 * wrapExecuteCommand wraps commands.executeCommand to satisfy that the
2085 * type is a Promise. Type script does not seem to be smart enough
0 commit comments