|
@@ -29,25 +29,27 @@ import { Message } from '@phosphor/messaging';
|
|
|
/**
|
|
|
* The Output Log Registry token.
|
|
|
*/
|
|
|
-export const IOutputLogRegistry = new Token<IOutputLogRegistry>(
|
|
|
- '@jupyterlab/outputconsole:IOutputLogRegistry'
|
|
|
+export const ILoggerRegistry = new Token<ILoggerRegistry>(
|
|
|
+ '@jupyterlab/logconsole:ILoggerRegistry'
|
|
|
);
|
|
|
|
|
|
-export interface IOutputLogRegistry {
|
|
|
- getLogger(name: string): ILogger;
|
|
|
+export interface ILoggerRegistry {
|
|
|
+ getLogger(source: string): ILogger;
|
|
|
getLoggers(): ILogger[];
|
|
|
|
|
|
/**
|
|
|
* A signal emitted when the log registry changes.
|
|
|
*/
|
|
|
- readonly registryChanged: ISignal<this, ILogRegistryChange>;
|
|
|
+ readonly registryChanged: ISignal<this, ILoggerRegistryChange>;
|
|
|
}
|
|
|
|
|
|
-export interface IOutputWithTimestamp extends nbformat.IBaseOutput {
|
|
|
+export interface ITimestampedOutput extends nbformat.IBaseOutput {
|
|
|
timestamp: number;
|
|
|
}
|
|
|
|
|
|
-type IOutputWithTimestampType = nbformat.IOutput | IOutputWithTimestamp;
|
|
|
+type IOutputWithTimestamp = nbformat.IOutput | ITimestampedOutput;
|
|
|
+
|
|
|
+export type ILoggerChange = 'append' | 'clear';
|
|
|
|
|
|
export interface ILogger {
|
|
|
log(log: nbformat.IOutput): void;
|
|
@@ -66,6 +68,34 @@ export interface ILogger {
|
|
|
readonly outputAreaModel: LoggerOutputAreaModel;
|
|
|
}
|
|
|
|
|
|
+export class LogOutputModel extends OutputModel {
|
|
|
+ constructor(options: LogOutputModel.IOptions) {
|
|
|
+ super(options);
|
|
|
+
|
|
|
+ this.timestamp = new Date(options.value.timestamp as number);
|
|
|
+ }
|
|
|
+
|
|
|
+ timestamp: Date = null;
|
|
|
+}
|
|
|
+
|
|
|
+export namespace LogOutputModel {
|
|
|
+ export interface IOptions extends IOutputModel.IOptions {
|
|
|
+ value: IOutputWithTimestamp;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * The default implementation of `IContentFactory`.
|
|
|
+ */
|
|
|
+export class LogConsoleModelContentFactory extends OutputAreaModel.ContentFactory {
|
|
|
+ /**
|
|
|
+ * Create an output model.
|
|
|
+ */
|
|
|
+ createOutputModel(options: IOutputModel.IOptions): LogOutputModel {
|
|
|
+ return new LogOutputModel(options);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
export class Logger implements ILogger {
|
|
|
constructor(source: string) {
|
|
|
this.source = source;
|
|
@@ -83,7 +113,7 @@ export class Logger implements ILogger {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * A signal emitted when the log model changes.
|
|
|
+ * A signal emitted when the rendermime changes.
|
|
|
*/
|
|
|
get rendermimeChanged(): ISignal<this, void> {
|
|
|
return this._rendermimeChanged;
|
|
@@ -111,34 +141,32 @@ export class Logger implements ILogger {
|
|
|
return this._rendermime;
|
|
|
}
|
|
|
|
|
|
- private _logChanged = new Signal<this, ILoggerChange>(this);
|
|
|
- private _rendermimeChanged = new Signal<this, void>(this);
|
|
|
readonly source: string;
|
|
|
readonly outputAreaModel = new LoggerOutputAreaModel({
|
|
|
- contentFactory: new LoggerModelFactory()
|
|
|
+ contentFactory: new LogConsoleModelContentFactory()
|
|
|
});
|
|
|
-
|
|
|
- _rendermime: IRenderMimeRegistry | null = null;
|
|
|
+ private _logChanged = new Signal<this, ILoggerChange>(this);
|
|
|
+ private _rendermimeChanged = new Signal<this, void>(this);
|
|
|
+ private _rendermime: IRenderMimeRegistry | null = null;
|
|
|
}
|
|
|
|
|
|
-export type ILogRegistryChange = 'append' | 'remove';
|
|
|
-export type ILoggerChange = 'append' | 'clear';
|
|
|
+export type ILoggerRegistryChange = 'append';
|
|
|
|
|
|
-export class OutputLogRegistry implements IOutputLogRegistry {
|
|
|
+export class LoggerRegistry implements ILoggerRegistry {
|
|
|
constructor(defaultRendermime: IRenderMimeRegistry) {
|
|
|
this._defaultRendermime = defaultRendermime;
|
|
|
}
|
|
|
|
|
|
- getLogger(name: string): ILogger {
|
|
|
+ getLogger(source: string): ILogger {
|
|
|
const loggers = this._loggers;
|
|
|
- let logger = loggers.get(name);
|
|
|
+ let logger = loggers.get(source);
|
|
|
if (logger) {
|
|
|
return logger;
|
|
|
}
|
|
|
|
|
|
- logger = new Logger(name);
|
|
|
+ logger = new Logger(source);
|
|
|
logger.rendermime = this._defaultRendermime;
|
|
|
- loggers.set(name, logger);
|
|
|
+ loggers.set(source, logger);
|
|
|
|
|
|
this._registryChanged.emit('append');
|
|
|
|
|
@@ -152,16 +180,39 @@ export class OutputLogRegistry implements IOutputLogRegistry {
|
|
|
/**
|
|
|
* A signal emitted when the log registry changes.
|
|
|
*/
|
|
|
- get registryChanged(): ISignal<this, ILogRegistryChange> {
|
|
|
+ get registryChanged(): ISignal<this, ILoggerRegistryChange> {
|
|
|
return this._registryChanged;
|
|
|
}
|
|
|
|
|
|
private _loggers = new Map<string, Logger>();
|
|
|
- private _registryChanged = new Signal<this, ILogRegistryChange>(this);
|
|
|
+ private _registryChanged = new Signal<this, ILoggerRegistryChange>(this);
|
|
|
private _defaultRendermime: IRenderMimeRegistry = null;
|
|
|
}
|
|
|
|
|
|
-export class LoggerOutputArea extends OutputArea {
|
|
|
+/**
|
|
|
+ * The default output prompt implementation
|
|
|
+ */
|
|
|
+class LogConsoleOutputPrompt extends Widget implements IOutputPrompt {
|
|
|
+ constructor() {
|
|
|
+ super();
|
|
|
+
|
|
|
+ this._timestampNode = document.createElement('div');
|
|
|
+ this.node.append(this._timestampNode);
|
|
|
+ }
|
|
|
+
|
|
|
+ set timestamp(value: Date) {
|
|
|
+ this._timestampNode.innerHTML = value.toLocaleTimeString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The execution count for the prompt.
|
|
|
+ */
|
|
|
+ executionCount: nbformat.ExecutionCount;
|
|
|
+
|
|
|
+ private _timestampNode: HTMLDivElement;
|
|
|
+}
|
|
|
+
|
|
|
+export class LogConsoleOutputArea extends OutputArea {
|
|
|
/**
|
|
|
* Handle an input request from a kernel by doing nothing.
|
|
|
*/
|
|
@@ -175,9 +226,10 @@ export class LoggerOutputArea extends OutputArea {
|
|
|
/**
|
|
|
* Create an output item with a prompt and actual output
|
|
|
*/
|
|
|
- protected createOutputItem(model: LoggerOutputModel): Widget | null {
|
|
|
+ protected createOutputItem(model: LogOutputModel): Widget | null {
|
|
|
const panel = super.createOutputItem(model) as Panel;
|
|
|
- (panel.widgets[0] as LoggerOutputPrompt).timestamp = model.timestamp;
|
|
|
+ // first widget in panel is prompt of type LoggerOutputPrompt
|
|
|
+ (panel.widgets[0] as LogConsoleOutputPrompt).timestamp = model.timestamp;
|
|
|
return panel;
|
|
|
}
|
|
|
|
|
@@ -185,7 +237,6 @@ export class LoggerOutputArea extends OutputArea {
|
|
|
* The rendermime instance used by the widget.
|
|
|
*/
|
|
|
rendermime: IRenderMimeRegistry;
|
|
|
-
|
|
|
readonly model: LoggerOutputAreaModel;
|
|
|
}
|
|
|
|
|
@@ -194,66 +245,50 @@ export class LoggerOutputAreaModel extends OutputAreaModel {
|
|
|
super(options);
|
|
|
}
|
|
|
|
|
|
- set messageLimit(limit: number) {
|
|
|
- this._messageLimit = limit;
|
|
|
+ set entryLimit(limit: number) {
|
|
|
+ this._entryLimit = limit;
|
|
|
this.applyLimit();
|
|
|
}
|
|
|
|
|
|
applyLimit() {
|
|
|
- if (this.list.length > this._messageLimit) {
|
|
|
- const diff = this.list.length - this._messageLimit;
|
|
|
+ if (this.list.length > this._entryLimit) {
|
|
|
+ const diff = this.list.length - this._entryLimit;
|
|
|
this.list.removeRange(0, diff);
|
|
|
this.trusted = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private _messageLimit: number = 1000;
|
|
|
-}
|
|
|
-
|
|
|
-export class LoggerOutputModel extends OutputModel {
|
|
|
- constructor(options: LoggerOutputModel.IOptions) {
|
|
|
- super(options);
|
|
|
-
|
|
|
- this.timestamp = new Date(options.value.timestamp as number);
|
|
|
- }
|
|
|
-
|
|
|
- timestamp: Date = null;
|
|
|
-}
|
|
|
-
|
|
|
-export namespace LoggerOutputModel {
|
|
|
- export interface IOptions extends IOutputModel.IOptions {
|
|
|
- value: IOutputWithTimestampType;
|
|
|
- }
|
|
|
+ private _entryLimit: number = 1000;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* The default implementation of `IContentFactory`.
|
|
|
*/
|
|
|
-export class LoggerModelFactory extends OutputAreaModel.ContentFactory {
|
|
|
+export class LogConsoleContentFactory extends OutputArea.ContentFactory {
|
|
|
/**
|
|
|
- * Create an output model.
|
|
|
+ * Create the output prompt for the widget.
|
|
|
*/
|
|
|
- createOutputModel(options: IOutputModel.IOptions): LoggerOutputModel {
|
|
|
- return new LoggerOutputModel(options);
|
|
|
+ createOutputPrompt(): LogConsoleOutputPrompt {
|
|
|
+ return new LogConsoleOutputPrompt();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* A List View widget that shows Output Console logs.
|
|
|
*/
|
|
|
-export class OutputLoggerView extends StackedPanel {
|
|
|
+export class LogConsolePanel extends StackedPanel {
|
|
|
/**
|
|
|
* Construct an OutputConsoleView instance.
|
|
|
*/
|
|
|
- constructor(outputLogRegistry: IOutputLogRegistry) {
|
|
|
+ constructor(loggerRegistry: ILoggerRegistry) {
|
|
|
super();
|
|
|
|
|
|
- this._outputLogRegistry = outputLogRegistry;
|
|
|
+ this._loggerRegistry = loggerRegistry;
|
|
|
this.addClass('jlab-output-logger-view');
|
|
|
this.node.style.overflowY = 'auto'; // TODO: use CSS class
|
|
|
|
|
|
- outputLogRegistry.registryChanged.connect(
|
|
|
- (sender: IOutputLogRegistry, args: ILogRegistryChange) => {
|
|
|
+ loggerRegistry.registryChanged.connect(
|
|
|
+ (sender: ILoggerRegistry, args: ILoggerRegistryChange) => {
|
|
|
this._bindLoggerSignals();
|
|
|
},
|
|
|
this
|
|
@@ -269,47 +304,49 @@ export class OutputLoggerView extends StackedPanel {
|
|
|
}
|
|
|
|
|
|
protected onAfterAttach(msg: Message): void {
|
|
|
- this._updateOutputViews();
|
|
|
+ this._updateOutputAreas();
|
|
|
this._showOutputFromSource(this._activeSource);
|
|
|
this._showPlaceholderIfNoMessage();
|
|
|
}
|
|
|
|
|
|
private _bindLoggerSignals() {
|
|
|
- const loggers = this._outputLogRegistry.getLoggers();
|
|
|
+ const loggers = this._loggerRegistry.getLoggers();
|
|
|
for (let logger of loggers) {
|
|
|
// TODO: optimize
|
|
|
logger.logChanged.connect((sender: ILogger, args: ILoggerChange) => {
|
|
|
- this._updateOutputViews();
|
|
|
+ this._updateOutputAreas();
|
|
|
this._showPlaceholderIfNoMessage();
|
|
|
}, this);
|
|
|
|
|
|
logger.rendermimeChanged.connect((sender: ILogger) => {
|
|
|
const viewId = `source:${sender.source}`;
|
|
|
- const view = this._outputViews.get(viewId);
|
|
|
- if (view) {
|
|
|
- view.rendermime = sender.rendermime;
|
|
|
+ const outputArea = this._outputAreas.get(viewId);
|
|
|
+ if (outputArea) {
|
|
|
+ outputArea.rendermime = sender.rendermime;
|
|
|
}
|
|
|
}, this);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- get outputLogRegistry(): IOutputLogRegistry {
|
|
|
- return this._outputLogRegistry;
|
|
|
+ get loggerRegistry(): ILoggerRegistry {
|
|
|
+ return this._loggerRegistry;
|
|
|
}
|
|
|
|
|
|
private _showOutputFromSource(source: string) {
|
|
|
const viewId = `source:${source}`;
|
|
|
|
|
|
- this._outputViews.forEach((outputView: LoggerOutputArea, name: string) => {
|
|
|
- if (outputView.id === viewId) {
|
|
|
- outputView.show();
|
|
|
- setTimeout(() => {
|
|
|
- this._scrollOuputAreaToBottom(outputView);
|
|
|
- }, 50);
|
|
|
- } else {
|
|
|
- outputView.hide();
|
|
|
+ this._outputAreas.forEach(
|
|
|
+ (outputArea: LogConsoleOutputArea, name: string) => {
|
|
|
+ if (outputArea.id === viewId) {
|
|
|
+ outputArea.show();
|
|
|
+ setTimeout(() => {
|
|
|
+ this._scrollOuputAreaToBottom(outputArea);
|
|
|
+ }, 50);
|
|
|
+ } else {
|
|
|
+ outputArea.hide();
|
|
|
+ }
|
|
|
}
|
|
|
- });
|
|
|
+ );
|
|
|
|
|
|
const title = source ? `Log: ${source}` : 'Log Console';
|
|
|
this.title.label = title;
|
|
@@ -329,7 +366,7 @@ export class OutputLoggerView extends StackedPanel {
|
|
|
private _showPlaceholderIfNoMessage() {
|
|
|
const noMessage =
|
|
|
!this.activeSource ||
|
|
|
- this._outputLogRegistry.getLogger(this.activeSource).length === 0;
|
|
|
+ this._loggerRegistry.getLogger(this.activeSource).length === 0;
|
|
|
|
|
|
if (noMessage) {
|
|
|
this._placeholder.show();
|
|
@@ -338,17 +375,17 @@ export class OutputLoggerView extends StackedPanel {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private _scrollOuputAreaToBottom(outputView: LoggerOutputArea) {
|
|
|
- outputView.node.scrollTo({
|
|
|
+ private _scrollOuputAreaToBottom(outputArea: LogConsoleOutputArea) {
|
|
|
+ outputArea.node.scrollTo({
|
|
|
left: 0,
|
|
|
- top: outputView.node.scrollHeight,
|
|
|
+ top: outputArea.node.scrollHeight,
|
|
|
behavior: 'smooth'
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- private _updateOutputViews() {
|
|
|
+ private _updateOutputAreas() {
|
|
|
const loggerIds = new Set<string>();
|
|
|
- const loggers = this._outputLogRegistry.getLoggers();
|
|
|
+ const loggers = this._loggerRegistry.getLoggers();
|
|
|
|
|
|
for (let logger of loggers) {
|
|
|
const viewId = `source:${logger.source}`;
|
|
@@ -356,43 +393,43 @@ export class OutputLoggerView extends StackedPanel {
|
|
|
|
|
|
// add view for logger if not exist
|
|
|
// TODO: or rendermime changed
|
|
|
- if (!this._outputViews.has(viewId)) {
|
|
|
- const outputView = new LoggerOutputArea({
|
|
|
+ if (!this._outputAreas.has(viewId)) {
|
|
|
+ const outputArea = new LogConsoleOutputArea({
|
|
|
rendermime: logger.rendermime,
|
|
|
- contentFactory: new LoggerContentFactory(),
|
|
|
+ contentFactory: new LogConsoleContentFactory(),
|
|
|
model: logger.outputAreaModel
|
|
|
});
|
|
|
- outputView.id = viewId;
|
|
|
- outputView.model.messageLimit = this.messageLimit;
|
|
|
+ outputArea.id = viewId;
|
|
|
+ outputArea.model.entryLimit = this.entryLimit;
|
|
|
|
|
|
logger.logChanged.connect((sender: ILogger, args: ILoggerChange) => {
|
|
|
- this._scrollOuputAreaToBottom(outputView);
|
|
|
+ this._scrollOuputAreaToBottom(outputArea);
|
|
|
}, this);
|
|
|
|
|
|
- outputView.outputLengthChanged.connect(
|
|
|
- (sender: LoggerOutputArea, args: number) => {
|
|
|
- outputView.model.applyLimit();
|
|
|
+ outputArea.outputLengthChanged.connect(
|
|
|
+ (sender: LogConsoleOutputArea, args: number) => {
|
|
|
+ outputArea.model.applyLimit();
|
|
|
clearTimeout(this._scrollTimer);
|
|
|
this._scrollTimer = setTimeout(() => {
|
|
|
- this._scrollOuputAreaToBottom(outputView);
|
|
|
+ this._scrollOuputAreaToBottom(outputArea);
|
|
|
}, 50);
|
|
|
},
|
|
|
this
|
|
|
);
|
|
|
|
|
|
- this.addWidget(outputView);
|
|
|
- this._outputViews.set(viewId, outputView);
|
|
|
+ this.addWidget(outputArea);
|
|
|
+ this._outputAreas.set(viewId, outputArea);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// remove views that do not have corresponding loggers anymore
|
|
|
- const viewIds = this._outputViews.keys();
|
|
|
+ const viewIds = this._outputAreas.keys();
|
|
|
|
|
|
for (let viewId of viewIds) {
|
|
|
if (!loggerIds.has(viewId)) {
|
|
|
- const outputView = this._outputViews.get(viewId);
|
|
|
- outputView.dispose();
|
|
|
- this._outputViews.delete(viewId);
|
|
|
+ const outputArea = this._outputAreas.get(viewId);
|
|
|
+ outputArea.dispose();
|
|
|
+ this._outputAreas.delete(viewId);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -400,63 +437,27 @@ export class OutputLoggerView extends StackedPanel {
|
|
|
/**
|
|
|
* Message entry limit.
|
|
|
*/
|
|
|
- get messageLimit(): number {
|
|
|
- return this._messageLimit;
|
|
|
+ get entryLimit(): number {
|
|
|
+ return this._entryLimit;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Sets message entry limit.
|
|
|
*/
|
|
|
- set messageLimit(limit: number) {
|
|
|
+ set entryLimit(limit: number) {
|
|
|
if (limit > 0) {
|
|
|
- this._outputViews.forEach((outputView: LoggerOutputArea) => {
|
|
|
- const model = outputView.model;
|
|
|
- model.messageLimit = limit;
|
|
|
+ this._outputAreas.forEach((outputView: LogConsoleOutputArea) => {
|
|
|
+ outputView.model.entryLimit = limit;
|
|
|
});
|
|
|
|
|
|
- this._messageLimit = limit;
|
|
|
+ this._entryLimit = limit;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private _outputLogRegistry: IOutputLogRegistry;
|
|
|
- private _outputViews = new Map<string, LoggerOutputArea>();
|
|
|
+ private _loggerRegistry: ILoggerRegistry;
|
|
|
+ private _outputAreas = new Map<string, LogConsoleOutputArea>();
|
|
|
private _activeSource: string = null;
|
|
|
- private _messageLimit: number = 1000;
|
|
|
+ private _entryLimit: number = 1000;
|
|
|
private _scrollTimer: number = null;
|
|
|
private _placeholder: Widget;
|
|
|
}
|
|
|
-
|
|
|
-/**
|
|
|
- * The default output prompt implementation
|
|
|
- */
|
|
|
-class LoggerOutputPrompt extends Widget implements IOutputPrompt {
|
|
|
- constructor() {
|
|
|
- super();
|
|
|
-
|
|
|
- this._timestampNode = document.createElement('div');
|
|
|
- this.node.append(this._timestampNode);
|
|
|
- }
|
|
|
-
|
|
|
- set timestamp(value: Date) {
|
|
|
- this._timestampNode.innerHTML = value.toLocaleTimeString();
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * The execution count for the prompt.
|
|
|
- */
|
|
|
- executionCount: nbformat.ExecutionCount;
|
|
|
-
|
|
|
- private _timestampNode: HTMLDivElement;
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * The default implementation of `IContentFactory`.
|
|
|
- */
|
|
|
-export class LoggerContentFactory extends OutputArea.ContentFactory {
|
|
|
- /**
|
|
|
- * Create the output prompt for the widget.
|
|
|
- */
|
|
|
- createOutputPrompt(): LoggerOutputPrompt {
|
|
|
- return new LoggerOutputPrompt();
|
|
|
- }
|
|
|
-}
|