Skip to content

Commit 8d961d7

Browse files
authored
Merge pull request microsoft#199357 from hsfzxjy/terminal-si-help-duration-199170
Add duration to the terminal command SI tooltip
2 parents 5c0d2f4 + e27f03e commit 8d961d7

File tree

7 files changed

+106
-8
lines changed

7 files changed

+106
-8
lines changed

src/vs/base/common/date.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,37 @@ export function fromNow(date: number | Date, appendAgoLabel?: boolean, useFullTi
199199
}
200200
}
201201

202+
/**
203+
* Gets a readable duration with intelligent/lossy precision. For example "40ms" or "3.040s")
204+
* @param ms The duration to get in milliseconds.
205+
* @param useFullTimeWords Whether to use full words (eg. seconds) instead of
206+
* shortened (eg. secs).
207+
*/
208+
export function getDurationString(ms: number, useFullTimeWords?: boolean) {
209+
const seconds = Math.abs(ms / 1000);
210+
if (seconds < 1) {
211+
return useFullTimeWords
212+
? localize('duration.ms.full', '{0} milliseconds', ms)
213+
: localize('duration.ms', '{0}ms', ms);
214+
}
215+
if (seconds < minute) {
216+
return useFullTimeWords
217+
? localize('duration.s.full', '{0} seconds', Math.round(ms) / 1000)
218+
: localize('duration.s', '{0}s', Math.round(ms) / 1000);
219+
}
220+
if (seconds < hour) {
221+
return useFullTimeWords
222+
? localize('duration.m.full', '{0} minutes', Math.round(ms / (1000 * minute)))
223+
: localize('duration.m', '{0} mins', Math.round(ms / (1000 * minute)));
224+
}
225+
if (seconds < day) {
226+
return useFullTimeWords
227+
? localize('duration.h.full', '{0} hours', Math.round(ms / (1000 * hour)))
228+
: localize('duration.h', '{0} hrs', Math.round(ms / (1000 * hour)));
229+
}
230+
return localize('duration.d', '{0} days', Math.round(ms / (1000 * day)));
231+
}
232+
202233
export function toLocalISOString(date: Date): string {
203234
return date.getFullYear() +
204235
'-' + String(date.getMonth() + 1).padStart(2, '0') +

src/vs/base/test/common/date.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { strictEqual } from 'assert';
7-
import { fromNow } from 'vs/base/common/date';
7+
import { fromNow, getDurationString } from 'vs/base/common/date';
88
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
99

1010
suite('Date', () => {
@@ -27,4 +27,29 @@ suite('Date', () => {
2727
strictEqual(fromNow(Date.now() - 5000, undefined, undefined, true), '5 secs');
2828
});
2929
});
30+
31+
suite('getDurationString', () => {
32+
test('basic', () => {
33+
strictEqual(getDurationString(1), '1ms');
34+
strictEqual(getDurationString(999), '999ms');
35+
strictEqual(getDurationString(1000), '1s');
36+
strictEqual(getDurationString(1000 * 60 - 1), '59.999s');
37+
strictEqual(getDurationString(1000 * 60), '1 mins');
38+
strictEqual(getDurationString(1000 * 60 * 60 - 1), '60 mins');
39+
strictEqual(getDurationString(1000 * 60 * 60), '1 hrs');
40+
strictEqual(getDurationString(1000 * 60 * 60 * 24 - 1), '24 hrs');
41+
strictEqual(getDurationString(1000 * 60 * 60 * 24), '1 days');
42+
});
43+
test('useFullTimeWords', () => {
44+
strictEqual(getDurationString(1, true), '1 milliseconds');
45+
strictEqual(getDurationString(999, true), '999 milliseconds');
46+
strictEqual(getDurationString(1000, true), '1 seconds');
47+
strictEqual(getDurationString(1000 * 60 - 1, true), '59.999 seconds');
48+
strictEqual(getDurationString(1000 * 60, true), '1 minutes');
49+
strictEqual(getDurationString(1000 * 60 * 60 - 1, true), '60 minutes');
50+
strictEqual(getDurationString(1000 * 60 * 60, true), '1 hours');
51+
strictEqual(getDurationString(1000 * 60 * 60 * 24 - 1, true), '24 hours');
52+
strictEqual(getDurationString(1000 * 60 * 60 * 24, true), '1 days');
53+
});
54+
});
3055
});

src/vs/platform/terminal/common/capabilities/capabilities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ interface IBaseTerminalCommand {
242242
command: string;
243243
isTrusted: boolean;
244244
timestamp: number;
245+
duration: number;
245246

246247
// Optional serializable
247248
cwd: string | undefined;

src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface ITerminalCommandProperties {
1414
command: string;
1515
isTrusted: boolean;
1616
timestamp: number;
17+
duration: number;
1718
marker: IXtermMarker | undefined;
1819
cwd: string | undefined;
1920
exitCode: number | undefined;
@@ -34,6 +35,7 @@ export class TerminalCommand implements ITerminalCommand {
3435
get command() { return this._properties.command; }
3536
get isTrusted() { return this._properties.isTrusted; }
3637
get timestamp() { return this._properties.timestamp; }
38+
get duration() { return this._properties.duration; }
3739
get promptStartMarker() { return this._properties.promptStartMarker; }
3840
get marker() { return this._properties.marker; }
3941
get endMarker() { return this._properties.endMarker; }
@@ -77,6 +79,7 @@ export class TerminalCommand implements ITerminalCommand {
7779
executedMarker,
7880
executedX: serialized.executedX,
7981
timestamp: serialized.timestamp,
82+
duration: serialized.duration,
8083
cwd: serialized.cwd,
8184
commandStartLineContent: serialized.commandStartLineContent,
8285
exitCode: serialized.exitCode,
@@ -101,6 +104,7 @@ export class TerminalCommand implements ITerminalCommand {
101104
exitCode: this.exitCode,
102105
commandStartLineContent: this.commandStartLineContent,
103106
timestamp: this.timestamp,
107+
duration: this.duration,
104108
markProperties: this.markProperties,
105109
};
106110
}
@@ -251,6 +255,9 @@ export class PartialTerminalCommand implements ICurrentPartialCommand {
251255
commandExecutedMarker?: IMarker;
252256
commandExecutedX?: number;
253257

258+
private commandExecutedTimestamp?: number;
259+
private commandDuration?: number;
260+
254261
commandFinishedMarker?: IMarker;
255262

256263
currentContinuationMarker?: IMarker;
@@ -284,6 +291,7 @@ export class PartialTerminalCommand implements ICurrentPartialCommand {
284291
exitCode: undefined,
285292
commandStartLineContent: undefined,
286293
timestamp: 0,
294+
duration: 0,
287295
markProperties: undefined
288296
};
289297
}
@@ -305,6 +313,7 @@ export class PartialTerminalCommand implements ICurrentPartialCommand {
305313
executedMarker: this.commandExecutedMarker,
306314
executedX: this.commandExecutedX,
307315
timestamp: Date.now(),
316+
duration: this.commandDuration || 0,
308317
cwd,
309318
exitCode,
310319
commandStartLineContent: this.commandStartLineContent,
@@ -315,6 +324,18 @@ export class PartialTerminalCommand implements ICurrentPartialCommand {
315324
return undefined;
316325
}
317326

327+
markExecutedTime() {
328+
if (this.commandExecutedTimestamp === undefined) {
329+
this.commandExecutedTimestamp = Date.now();
330+
}
331+
}
332+
333+
markFinishedTime() {
334+
if (this.commandDuration === undefined && this.commandExecutedTimestamp !== undefined) {
335+
this.commandDuration = Date.now() - this.commandExecutedTimestamp;
336+
}
337+
}
338+
318339
getPromptRowCount(): number {
319340
return getPromptRowCount(this, this._xterm.buffer.active);
320341
}

src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,9 +303,11 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
303303

304304
handleCommandExecuted(options?: IHandleCommandOptions): void {
305305
this._ptyHeuristics.value.handleCommandExecuted(options);
306+
this._currentCommand.markExecutedTime();
306307
}
307308

308309
handleCommandFinished(exitCode: number | undefined, options?: IHandleCommandOptions): void {
310+
this._currentCommand.markFinishedTime();
309311
this._ptyHeuristics.value.preHandleCommandFinished?.();
310312

311313
this._logService.debug('CommandDetectionCapability#handleCommandFinished', this._terminal.buffer.active.cursorX, options?.marker?.line, this._currentCommand.command, this._currentCommand);

src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import * as dom from 'vs/base/browser/dom';
77
import { Delayer } from 'vs/base/common/async';
8-
import { fromNow } from 'vs/base/common/date';
8+
import { fromNow, getDurationString } from 'vs/base/common/date';
99
import { MarkdownString } from 'vs/base/common/htmlContent';
1010
import { combinedDisposable, Disposable, IDisposable } from 'vs/base/common/lifecycle';
1111
import { localize } from 'vs/nls';
@@ -71,14 +71,29 @@ export class TerminalDecorationHoverManager extends Disposable {
7171
} else {
7272
return;
7373
}
74-
} else if (command.exitCode) {
75-
if (command.exitCode === -1) {
76-
hoverContent += localize('terminalPromptCommandFailed', 'Command executed {0} and failed', fromNow(command.timestamp, true));
74+
} else {
75+
if (command.duration) {
76+
const durationText = getDurationString(command.duration);
77+
if (command.exitCode) {
78+
if (command.exitCode === -1) {
79+
hoverContent += localize('terminalPromptCommandFailed.duration', 'Command executed {0}, took {1} and failed', fromNow(command.timestamp, true), durationText);
80+
} else {
81+
hoverContent += localize('terminalPromptCommandFailedWithExitCode.duration', 'Command executed {0}, took {1} and failed (Exit Code {2})', fromNow(command.timestamp, true), durationText, command.exitCode);
82+
}
83+
} else {
84+
hoverContent += localize('terminalPromptCommandSuccess.duration', 'Command executed {0} and took {1}', fromNow(command.timestamp, true), durationText);
85+
}
7786
} else {
78-
hoverContent += localize('terminalPromptCommandFailedWithExitCode', 'Command executed {0} and failed (Exit Code {1})', fromNow(command.timestamp, true), command.exitCode);
87+
if (command.exitCode) {
88+
if (command.exitCode === -1) {
89+
hoverContent += localize('terminalPromptCommandFailed', 'Command executed {0} and failed', fromNow(command.timestamp, true));
90+
} else {
91+
hoverContent += localize('terminalPromptCommandFailedWithExitCode', 'Command executed {0} and failed (Exit Code {1})', fromNow(command.timestamp, true), command.exitCode);
92+
}
93+
} else {
94+
hoverContent += localize('terminalPromptCommandSuccess', 'Command executed {0}', fromNow(command.timestamp, true));
95+
}
7996
}
80-
} else {
81-
hoverContent += localize('terminalPromptCommandSuccess', 'Command executed {0}', fromNow(command.timestamp, true));
8297
}
8398
this._hoverService.showHover({ content: new MarkdownString(hoverContent), target: element });
8499
});

src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ suite('Workbench - TerminalLinkOpeners', () => {
142142
isTrusted: true,
143143
cwd: '/initial/cwd',
144144
timestamp: 0,
145+
duration: 0,
145146
executedX: undefined,
146147
startX: undefined,
147148
marker: {
@@ -279,6 +280,7 @@ suite('Workbench - TerminalLinkOpeners', () => {
279280
isTrusted: true,
280281
cwd,
281282
timestamp: 0,
283+
duration: 0,
282284
executedX: undefined,
283285
startX: undefined,
284286
marker: {
@@ -541,6 +543,7 @@ suite('Workbench - TerminalLinkOpeners', () => {
541543
executedX: undefined,
542544
startX: undefined,
543545
timestamp: 0,
546+
duration: 0,
544547
marker: {
545548
line: 0
546549
} as Partial<IXtermMarker> as any,

0 commit comments

Comments
 (0)