Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split standard input line history per session (notebook) #13944

Merged
8 changes: 7 additions & 1 deletion packages/cells/src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,11 @@ export namespace Cell {
*/
maxNumberOutputs?: number;

/**
* Whether to split stdin line history by kernel session or keep globally accessible.
*/
inputHistoryScope?: 'global' | 'session';

/**
* Whether this cell is a placeholder for future rendering.
*/
Expand Down Expand Up @@ -986,7 +991,8 @@ export class CodeCell extends Cell<ICodeCellModel> {
contentFactory: contentFactory,
maxNumberOutputs: this.maxNumberOutputs,
translator: this.translator,
promptOverlay: true
promptOverlay: true,
inputHistoryScope: options.inputHistoryScope
}));
output.addClass(CELL_OUTPUT_AREA_CLASS);
output.toggleScrolling.connect(() => {
Expand Down
7 changes: 7 additions & 0 deletions packages/notebook-extension/schema/tracker.json
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,13 @@
"type": "boolean",
"default": false
},
"inputHistoryScope": {
"type": "string",
"default": "global",
"enum": ["global", "session"],
"title": "Input History Scope",
"description": "Whether the line history for standard input (e.g. the ipdb prompt) should kept separately for different kernel sessions (`session`) or combined (`global`)."
},
"kernelShutdown": {
"title": "Shut down kernel",
"description": "Whether to shut down or not the kernel when closing a notebook.",
Expand Down
3 changes: 3 additions & 0 deletions packages/notebook-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1783,6 +1783,9 @@ function activateNotebookHandler(
defaultCell: settings.get('defaultCell').composite as nbformat.CellType,
recordTiming: settings.get('recordTiming').composite as boolean,
overscanCount: settings.get('overscanCount').composite as number,
inputHistoryScope: settings.get('inputHistoryScope').composite as
| 'global'
| 'session',
maxNumberOutputs: settings.get('maxNumberOutputs').composite as number,
showEditorForReadOnlyMarkdown: settings.get(
'showEditorForReadOnlyMarkdown'
Expand Down
7 changes: 7 additions & 0 deletions packages/notebook/src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,7 @@ export class StaticNotebook extends WindowedList {
const options: CodeCell.IOptions = {
contentFactory,
editorConfig,
inputHistoryScope: this.notebookConfig.inputHistoryScope,
maxNumberOutputs: this.notebookConfig.maxNumberOutputs,
model,
placeholder: this._notebookConfig.windowingMode !== 'none',
Expand Down Expand Up @@ -1049,6 +1050,11 @@ export namespace StaticNotebook {
*/
maxNumberOutputs: number;

/**
* Whether to split stdin line history by kernel session or keep globally accessible.
*/
inputHistoryScope: 'global' | 'session';

/**
* Number of cells to render in addition to those
* visible in the viewport.
Expand Down Expand Up @@ -1119,6 +1125,7 @@ export namespace StaticNotebook {
scrollPastEnd: true,
defaultCell: 'code',
recordTiming: false,
inputHistoryScope: 'global',
maxNumberOutputs: 50,
showEditorForReadOnlyMarkdown: true,
disableDocumentWideUndoRedo: true,
Expand Down
86 changes: 65 additions & 21 deletions packages/outputarea/src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class OutputArea extends Widget {
this.rendermime = options.rendermime;
this._maxNumberOutputs = options.maxNumberOutputs ?? Infinity;
this._translator = options.translator ?? nullTranslator;
this._inputHistoryScope = options.inputHistoryScope ?? 'global';

const model = (this.model = options.model);
for (
Expand Down Expand Up @@ -439,7 +440,8 @@ export class OutputArea extends Widget {
prompt: stdinPrompt,
password,
future,
translator: this._translator
translator: this._translator,
inputHistoryScope: this._inputHistoryScope
});
input.addClass(OUTPUT_AREA_OUTPUT_CLASS);
panel.addWidget(input);
Expand Down Expand Up @@ -727,6 +729,7 @@ export class OutputArea extends Widget {
namespace: UUID.uuid4()
});
private _translator: ITranslator;
private _inputHistoryScope: 'global' | 'session' = 'global';
}

export class SimplifiedOutputArea extends OutputArea {
Expand Down Expand Up @@ -789,6 +792,11 @@ export namespace OutputArea {
* Translator
*/
readonly translator?: ITranslator;

/**
* Whether to split stdin line history by kernel session or keep globally accessible.
*/
inputHistoryScope?: 'global' | 'session';
}

/**
Expand Down Expand Up @@ -942,41 +950,52 @@ export interface IStdin extends Widget {
* The default stdin widget.
*/
export class Stdin extends Widget implements IStdin {
private static _history: string[] = [];
private static _history: Map<string, string[]> = new Map();

private static _historyIx(ix: number): number | undefined {
const len = Stdin._history.length;
private static _historyIx(key: string, ix: number): number | undefined {
const history = Stdin._history.get(key);
if (!history) {
return undefined;
}
const len = history.length;
// wrap nonpositive ix to nonnegative ix
if (ix <= 0) {
return len + ix;
}
}

private static _historyAt(ix: number): string | undefined {
const len = Stdin._history.length;
const ixpos = Stdin._historyIx(ix);
private static _historyAt(key: string, ix: number): string | undefined {
const history = Stdin._history.get(key);
if (!history) {
return undefined;
}
const len = history.length;
const ixpos = Stdin._historyIx(key, ix);

if (ixpos !== undefined && ixpos < len) {
return Stdin._history[ixpos];
return history[ixpos];
}
// return undefined if ix is out of bounds
}

private static _historyPush(line: string): void {
Stdin._history.push(line);
if (Stdin._history.length > 1000) {
private static _historyPush(key: string, line: string): void {
const history = Stdin._history.get(key)!;
history.push(line);
if (history.length > 1000) {
// truncate line history if it's too long
Stdin._history.shift();
history.shift();
}
}

private static _historySearch(
key: string,
pat: string,
ix: number,
reverse = true
): number | undefined {
const len = Stdin._history.length;
const ixpos = Stdin._historyIx(ix);
const history = Stdin._history.get(key)!;
const len = history.length;
const ixpos = Stdin._historyIx(key, ix);
const substrFound = (x: string) => x.search(pat) !== -1;

if (ixpos === undefined) {
Expand All @@ -989,7 +1008,7 @@ export class Stdin extends Widget implements IStdin {
return;
}

const ixFound = (Stdin._history.slice(0, ixpos) as any).findLastIndex(
const ixFound = (history.slice(0, ixpos) as any).findLastIndex(
substrFound
);
if (ixFound !== -1) {
Expand All @@ -1002,7 +1021,7 @@ export class Stdin extends Widget implements IStdin {
return;
}

const ixFound = Stdin._history.slice(ixpos + 1).findIndex(substrFound);
const ixFound = history.slice(ixpos + 1).findIndex(substrFound);
if (ixFound !== -1) {
// wrap ix to negative and adjust for slice
return ixFound - len + ixpos + 1;
Expand All @@ -1020,6 +1039,10 @@ export class Stdin extends Widget implements IStdin {
this.addClass(STDIN_CLASS);
this._future = options.future;
this._historyIndex = 0;
this._historyKey =
options.inputHistoryScope === 'session'
? options.parent_header.session
: '';
this._historyPat = '';
this._parentHeader = options.parent_header;
this._password = options.password;
Expand All @@ -1031,6 +1054,11 @@ export class Stdin extends Widget implements IStdin {
this._input.placeholder = this._trans.__(
'↑↓ for history. Search history with c-↑/c-↓'
);

// initialize line history
if (!Stdin._history.has(this._historyKey)) {
Stdin._history.set(this._historyKey, []);
}
}

/**
Expand Down Expand Up @@ -1068,7 +1096,7 @@ export class Stdin extends Widget implements IStdin {
this._value += '········';
} else {
this._value += input.value;
Stdin._historyPush(input.value);
Stdin._historyPush(this._historyKey, input.value);
}
this._promise.resolve(void 0);
} else if (event.key === 'Escape') {
Expand All @@ -1086,13 +1114,17 @@ export class Stdin extends Widget implements IStdin {

const reverse = event.key === 'ArrowUp';
const searchHistoryIx = Stdin._historySearch(
this._historyKey,
this._historyPat,
this._historyIndex,
reverse
);

if (searchHistoryIx !== undefined) {
const historyLine = Stdin._historyAt(searchHistoryIx);
const historyLine = Stdin._historyAt(
this._historyKey,
searchHistoryIx
);
if (historyLine !== undefined) {
if (this._historyIndex === 0) {
this._valueCache = input.value;
Expand All @@ -1105,7 +1137,10 @@ export class Stdin extends Widget implements IStdin {
} else if (event.key === 'ArrowUp') {
this.resetSearch();

const historyLine = Stdin._historyAt(this._historyIndex - 1);
const historyLine = Stdin._historyAt(
this._historyKey,
this._historyIndex - 1
);
if (historyLine) {
if (this._historyIndex === 0) {
this._valueCache = input.value;
Expand All @@ -1122,7 +1157,10 @@ export class Stdin extends Widget implements IStdin {
input.value = this._valueCache;
++this._historyIndex;
} else {
const historyLine = Stdin._historyAt(this._historyIndex + 1);
const historyLine = Stdin._historyAt(
this._historyKey,
this._historyIndex + 1
);
if (historyLine) {
input.value = historyLine;
++this._historyIndex;
Expand Down Expand Up @@ -1153,11 +1191,12 @@ export class Stdin extends Widget implements IStdin {

private _future: Kernel.IShellFuture;
private _historyIndex: number;
private _historyKey: string;
private _historyPat: string;
private _input: HTMLInputElement;
private _parentHeader: KernelMessage.IInputReplyMsg['parent_header'];
private _password: boolean;
private _promise = new PromiseDelegate<void>();
private _historyPat: string;
private _trans: TranslationBundle;
private _value: string;
private _valueCache: string;
Expand Down Expand Up @@ -1192,6 +1231,11 @@ export namespace Stdin {
* Translator
*/
readonly translator?: ITranslator;

/**
* Whether to split stdin line history by kernel session or keep globally accessible.
*/
inputHistoryScope?: 'global' | 'session';
}
}

Expand Down