Skip to content

Commit c77630d

Browse files
authored
Add up / down console input history navigation actions so users can bind custom keys to them (#8101)
Addresses #6650 by adding up / down console input history navigation actions so that users can bind custom keys to them, if desired.
1 parent 90690ad commit c77630d

File tree

5 files changed

+303
-60
lines changed

5 files changed

+303
-60
lines changed

src/vs/workbench/contrib/positronConsole/browser/components/consoleInput.tsx

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -300,27 +300,31 @@ export const ConsoleInput = (props: ConsoleInputProps) => {
300300
};
301301

302302
/**
303-
* Consumes an event.
303+
* Navigates the history up.
304+
* @param e The optional keyboard event that triggered the navigation.
304305
*/
305-
const consumeKbdEvent = (e: IKeyboardEvent) => {
306-
e.preventDefault();
307-
e.stopPropagation();
308-
};
309-
const navigateHistoryUp = (e: IKeyboardEvent) => {
310-
// If the history browser is present, Up should select the
311-
// previous history item.
306+
const navigateHistoryUp = (e: IKeyboardEvent | undefined = undefined) => {
307+
// If the history browser is present, select the previous history item.
312308
if (historyBrowserActiveRef.current) {
313-
setHistoryBrowserSelectedIndex(Math.max(
314-
0, historyBrowserSelectedIndexRef.current - 1));
315-
consumeKbdEvent(e);
309+
// Select the previous history item.
310+
setHistoryBrowserSelectedIndex(Math.max(0, historyBrowserSelectedIndexRef.current - 1));
311+
312+
// Consume the event and return.
313+
if (e) {
314+
e.preventDefault();
315+
e.stopPropagation();
316+
}
316317
return;
317318
}
318319

319320
// Get the position. If it's at line number 1, allow backward history navigation.
320321
const position = codeEditorWidgetRef.current.getPosition();
321322
if (position?.lineNumber === 1) {
322323
// Consume the event.
323-
consumeKbdEvent(e);
324+
if (e) {
325+
e.preventDefault();
326+
e.stopPropagation();
327+
}
324328

325329
// If there are history entries, process the event.
326330
if (historyNavigatorRef.current) {
@@ -344,14 +348,24 @@ export const ConsoleInput = (props: ConsoleInputProps) => {
344348
}
345349
};
346350

347-
const navigateHistoryDown = (e: IKeyboardEvent) => {
348-
351+
/**
352+
* Navigates the history down.
353+
* @param e The optional keyboard event that triggered the navigation.
354+
*/
355+
const navigateHistoryDown = (e: IKeyboardEvent | undefined = undefined) => {
349356
// If the history browser is up, update the selected index.
350357
if (historyBrowserActiveRef.current) {
358+
// Select the next history item.
351359
setHistoryBrowserSelectedIndex(Math.min(
352360
historyItemsRef.current.length - 1,
353-
historyBrowserSelectedIndexRef.current + 1));
354-
consumeKbdEvent(e);
361+
historyBrowserSelectedIndexRef.current + 1
362+
));
363+
364+
// Consume the event and return.
365+
if (e) {
366+
e.preventDefault();
367+
e.stopPropagation();
368+
}
355369
return;
356370
}
357371

@@ -361,7 +375,10 @@ export const ConsoleInput = (props: ConsoleInputProps) => {
361375
const textModel = codeEditorWidgetRef.current.getModel();
362376
if (position?.lineNumber === textModel?.getLineCount()) {
363377
// Consume the event.
364-
consumeKbdEvent(e);
378+
if (e) {
379+
e.preventDefault();
380+
e.stopPropagation();
381+
}
365382

366383
// If there are history entries, process the event.
367384
if (historyNavigatorRef.current) {
@@ -954,6 +971,28 @@ export const ConsoleInput = (props: ConsoleInputProps) => {
954971
codeEditorWidget.focus();
955972
}));
956973

974+
// Add the onDidNavigateInputHistoryDown event handler.
975+
disposableStore.add(props.positronConsoleInstance.onDidNavigateInputHistoryDown(() => {
976+
navigateHistoryDown();
977+
codeEditorWidget.focus();
978+
}));
979+
980+
// Add the onDidNavigateInputHistoryUp event handler.
981+
disposableStore.add(props.positronConsoleInstance.onDidNavigateInputHistoryUp(e => {
982+
// If the history browser is not active, engage the history browser with the prefix match
983+
// strategy. Otherwise, navigate history up.
984+
if (e.usingPrefixMatch && !historyBrowserActiveRef.current) {
985+
engageHistoryBrowser(new HistoryPrefixMatchStrategy(positronConsoleContext.executionHistoryService.getInputEntries(
986+
props.positronConsoleInstance.runtimeMetadata.languageId
987+
)));
988+
} else {
989+
navigateHistoryUp();
990+
}
991+
992+
// Focus the code editor widget.
993+
codeEditorWidget.focus();
994+
}));
995+
957996
// Add the onDidClearInputHistory event handler.
958997
disposableStore.add(props.positronConsoleInstance.onDidClearInputHistory(() => {
959998
// Discard the history navigator.

src/vs/workbench/contrib/positronConsole/browser/positronConsoleActions.ts

Lines changed: 157 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ const enum PositronConsoleCommandId {
4040
ClearInputHistory = 'workbench.action.positronConsole.clearInputHistory',
4141
ExecuteCode = 'workbench.action.positronConsole.executeCode',
4242
FocusConsole = 'workbench.action.positronConsole.focusConsole',
43+
NavigateInputHistoryDown = 'workbench.action.positronConsole.navigateInputHistoryDown',
44+
NavigateInputHistoryUp = 'workbench.action.positronConsole.navigateInputHistoryUp',
45+
NavigateInputHistoryUpUsingPrefixMatch = 'workbench.action.positronConsole.navigateInputHistoryUpUsingPrefixMatch',
4346
}
4447

4548
/**
@@ -113,46 +116,6 @@ export function registerPositronConsoleActions() {
113116
}
114117
});
115118

116-
/**
117-
* Register the focus console action. This action places focus in the active console,
118-
* if one exists.
119-
*
120-
* This action is equivalent to the `workbench.panel.positronConsole.focus` command.
121-
*/
122-
registerAction2(class extends Action2 {
123-
/**
124-
* Constructor.
125-
*/
126-
constructor() {
127-
super({
128-
id: PositronConsoleCommandId.FocusConsole,
129-
title: {
130-
value: localize('workbench.action.positronConsole.focusConsole', "Focus Console"),
131-
original: 'Focus Console'
132-
},
133-
f1: true,
134-
keybinding: {
135-
weight: KeybindingWeight.WorkbenchContrib,
136-
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyF)
137-
},
138-
category,
139-
});
140-
}
141-
142-
/**
143-
* Runs action; places focus in the console's input control.
144-
*
145-
* @param accessor The services accessor.
146-
*/
147-
async run(accessor: ServicesAccessor) {
148-
const viewsService = accessor.get(IViewsService);
149-
150-
// Ensure that the panel and console are visible. This is essentially
151-
// equivalent to what `workbench.panel.positronConsole.focus` does.
152-
await viewsService.openView(POSITRON_CONSOLE_VIEW_ID, true);
153-
}
154-
});
155-
156119
/**
157120
* Register the clear input history action. This action removes everything from the active
158121
* console language's input history.
@@ -602,4 +565,158 @@ export function registerPositronConsoleActions() {
602565
model.pushEditOperations([], [editOperation], () => []);
603566
}
604567
});
568+
569+
/**
570+
* Register the focus console action. This action places focus in the active console,
571+
* if one exists.
572+
*
573+
* This action is equivalent to the `workbench.panel.positronConsole.focus` command.
574+
*/
575+
registerAction2(class extends Action2 {
576+
/**
577+
* Constructor.
578+
*/
579+
constructor() {
580+
super({
581+
id: PositronConsoleCommandId.FocusConsole,
582+
title: {
583+
value: localize('workbench.action.positronConsole.focusConsole', "Focus Console"),
584+
original: 'Focus Console'
585+
},
586+
f1: true,
587+
keybinding: {
588+
weight: KeybindingWeight.WorkbenchContrib,
589+
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyF)
590+
},
591+
category,
592+
});
593+
}
594+
595+
/**
596+
* Runs action; places focus in the console's input control.
597+
*
598+
* @param accessor The services accessor.
599+
*/
600+
async run(accessor: ServicesAccessor) {
601+
const viewsService = accessor.get(IViewsService);
602+
603+
// Ensure that the panel and console are visible. This is essentially
604+
// equivalent to what `workbench.panel.positronConsole.focus` does.
605+
await viewsService.openView(POSITRON_CONSOLE_VIEW_ID, true);
606+
}
607+
});
608+
609+
/**
610+
* Register the navigate input history down action. This action moves the cursor to the next
611+
* entry in the input history.
612+
*/
613+
registerAction2(class extends Action2 {
614+
/**
615+
* Constructor.
616+
*/
617+
constructor() {
618+
super({
619+
id: PositronConsoleCommandId.NavigateInputHistoryDown,
620+
title: {
621+
value: localize('workbench.action.positronConsole.navigateInputHistoryDown', "Navigate Input History Down"),
622+
original: 'Navigate Input History Down'
623+
},
624+
f1: true,
625+
category,
626+
});
627+
}
628+
629+
/**
630+
* Runs action.
631+
* @param accessor The services accessor.
632+
*/
633+
async run(accessor: ServicesAccessor) {
634+
const positronConsoleService = accessor.get(IPositronConsoleService);
635+
if (positronConsoleService.activePositronConsoleInstance) {
636+
positronConsoleService.activePositronConsoleInstance.navigateInputHistoryDown();
637+
} else {
638+
accessor.get(INotificationService).notify({
639+
severity: Severity.Info,
640+
message: localize('positron.navigateInputHistory.noActiveConsole', "Cannot navigate input history. A console is not active."),
641+
sticky: false
642+
});
643+
}
644+
}
645+
});
646+
647+
/**
648+
* Register the navigate input history up action. This action moves the cursor to the previous
649+
* entry in the input history.
650+
*/
651+
registerAction2(class extends Action2 {
652+
/**
653+
* Constructor.
654+
*/
655+
constructor() {
656+
super({
657+
id: PositronConsoleCommandId.NavigateInputHistoryUp,
658+
title: {
659+
value: localize('workbench.action.positronConsole.navigateInputHistoryUp', "Navigate Input History Up"),
660+
original: 'Navigate Input History Up'
661+
},
662+
f1: true,
663+
category,
664+
});
665+
}
666+
667+
/**
668+
* Runs action.
669+
* @param accessor The services accessor.
670+
*/
671+
async run(accessor: ServicesAccessor) {
672+
const positronConsoleService = accessor.get(IPositronConsoleService);
673+
if (positronConsoleService.activePositronConsoleInstance) {
674+
positronConsoleService.activePositronConsoleInstance.navigateInputHistoryUp(false);
675+
} else {
676+
accessor.get(INotificationService).notify({
677+
severity: Severity.Info,
678+
message: localize('positron.navigateInputHistory.noActiveConsole', "Cannot navigate input history. A console is not active."),
679+
sticky: false
680+
});
681+
}
682+
}
683+
});
684+
685+
/**
686+
* Register the navigate history up prefix match action. This action moves the cursor to the previous
687+
* entry in the input history using the prefix match strategy.
688+
*/
689+
registerAction2(class extends Action2 {
690+
/**
691+
* Constructor.
692+
*/
693+
constructor() {
694+
super({
695+
id: PositronConsoleCommandId.NavigateInputHistoryUpUsingPrefixMatch,
696+
title: {
697+
value: localize('workbench.action.positronConsole.navigateInputHistoryUpUsingPrefixMatch', "Navigate Input History Up (Prefix Match)"),
698+
original: 'Navigate Input History Up (Prefix Match)'
699+
},
700+
f1: true,
701+
category,
702+
});
703+
}
704+
705+
/**
706+
* Runs action.
707+
* @param accessor The services accessor.
708+
*/
709+
async run(accessor: ServicesAccessor) {
710+
const positronConsoleService = accessor.get(IPositronConsoleService);
711+
if (positronConsoleService.activePositronConsoleInstance) {
712+
positronConsoleService.activePositronConsoleInstance.navigateInputHistoryUp(true);
713+
} else {
714+
accessor.get(INotificationService).notify({
715+
severity: Severity.Info,
716+
message: localize('positron.navigateInputHistory.noActiveConsole', "Cannot navigate input history. A console is not active."),
717+
sticky: false
718+
});
719+
}
720+
}
721+
});
605722
}

src/vs/workbench/services/positronConsole/browser/interfaces/positronConsoleService.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ export interface IPositronConsoleService {
6565
*/
6666
readonly onDidStartPositronConsoleInstance: Event<IPositronConsoleInstance>;
6767

68-
6968
/**
7069
* The onDidDeletePositronConsoleInstance event.
7170
*/
@@ -162,6 +161,16 @@ export enum SessionAttachMode {
162161
Connected = 'connected',
163162
}
164163

164+
/**
165+
* Event arguments for the onDidNavigateInputHistoryUp event.
166+
*/
167+
export type DidNavigateInputHistoryUpEventArgs = {
168+
/**
169+
* A value which indicates whether to use the prefix match strategy.
170+
*/
171+
usingPrefixMatch: boolean;
172+
};
173+
165174
/**
166175
* IPositronConsoleInstance interface.
167176
*/
@@ -272,6 +281,16 @@ export interface IPositronConsoleInstance {
272281
*/
273282
readonly onDidClearConsole: Event<void>;
274283

284+
/**
285+
* The onDidNavigateInputHistoryDown event.
286+
*/
287+
readonly onDidNavigateInputHistoryDown: Event<void>;
288+
289+
/**
290+
* The onDidNavigateInputHistoryUp event.
291+
*/
292+
readonly onDidNavigateInputHistoryUp: Event<DidNavigateInputHistoryUpEventArgs>;
293+
275294
/**
276295
* The onDidClearInputHistory event.
277296
*/
@@ -354,6 +373,17 @@ export interface IPositronConsoleInstance {
354373
*/
355374
clearConsole(): void;
356375

376+
/**
377+
* Navigates the input history down.
378+
*/
379+
navigateInputHistoryDown(): void;
380+
381+
/**
382+
* Navigates the input history up.
383+
* @param usingPrefixMatch A value which indicates whether to use the prefix match strategy.
384+
*/
385+
navigateInputHistoryUp(usingPrefixMatch: boolean): void;
386+
357387
/**
358388
* Clears the input history.
359389
*/

0 commit comments

Comments
 (0)