@jupyterlab/fileeditor
Version:
JupyterLab - Editor Widget
211 lines • 6.34 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { CodeEditorWrapper } from '@jupyterlab/codeeditor';
import { ABCWidgetFactory, DocumentWidget } from '@jupyterlab/docregistry';
import { textEditorIcon } from '@jupyterlab/ui-components';
import { PromiseDelegate } from '@lumino/coreutils';
import { StackedLayout, Widget } from '@lumino/widgets';
/**
* The data attribute added to a widget that can run code.
*/
const CODE_RUNNER = 'jpCodeRunner';
/**
* The data attribute added to a widget that can undo.
*/
const UNDOER = 'jpUndoer';
/**
* A widget for editors.
*/
export class FileEditor extends Widget {
/**
* Construct a new editor widget.
*/
constructor(options) {
super();
this._ready = new PromiseDelegate();
this.addClass('jp-FileEditor');
const context = (this._context = options.context);
this._mimeTypeService = options.mimeTypeService;
const editorWidget = (this._editorWidget = new CodeEditorWrapper({
factory: options.factory,
model: context.model,
editorOptions: {
config: FileEditor.defaultEditorConfig
}
}));
this._editorWidget.addClass('jp-FileEditorCodeWrapper');
this._editorWidget.node.dataset[CODE_RUNNER] = 'true';
this._editorWidget.node.dataset[UNDOER] = 'true';
this.editor = editorWidget.editor;
this.model = editorWidget.model;
void context.ready.then(() => {
this._onContextReady();
});
// Listen for changes to the path.
this._onPathChanged();
context.pathChanged.connect(this._onPathChanged, this);
const layout = (this.layout = new StackedLayout());
layout.addWidget(editorWidget);
}
/**
* Get the context for the editor widget.
*/
get context() {
return this._context;
}
/**
* A promise that resolves when the file editor is ready.
*/
get ready() {
return this._ready.promise;
}
/**
* Handle the DOM events for the widget.
*
* @param event - The DOM event sent to the widget.
*
* #### Notes
* This method implements the DOM `EventListener` interface and is
* called in response to events on the widget's node. It should
* not be called directly by user code.
*/
handleEvent(event) {
if (!this.model) {
return;
}
switch (event.type) {
case 'mousedown':
this._ensureFocus();
break;
default:
break;
}
}
/**
* Handle `after-attach` messages for the widget.
*/
onAfterAttach(msg) {
super.onAfterAttach(msg);
const node = this.node;
node.addEventListener('mousedown', this);
}
/**
* Handle `before-detach` messages for the widget.
*/
onBeforeDetach(msg) {
const node = this.node;
node.removeEventListener('mousedown', this);
}
/**
* Handle `'activate-request'` messages.
*/
onActivateRequest(msg) {
this._ensureFocus();
}
/**
* Ensure that the widget has focus.
*/
_ensureFocus() {
if (!this.editor.hasFocus()) {
this.editor.focus();
}
}
/**
* Handle actions that should be taken when the context is ready.
*/
_onContextReady() {
if (this.isDisposed) {
return;
}
// Prevent the initial loading from disk from being in the editor history.
this.editor.clearHistory();
// Resolve the ready promise.
this._ready.resolve(undefined);
}
/**
* Handle a change to the path.
*/
_onPathChanged() {
const editor = this.editor;
const localPath = this._context.localPath;
editor.model.mimeType =
this._mimeTypeService.getMimeTypeByFilePath(localPath);
}
}
/**
* The namespace for editor widget statics.
*/
(function (FileEditor) {
/**
* File editor default configuration.
*/
FileEditor.defaultEditorConfig = {
lineNumbers: true,
scrollPastEnd: true
};
})(FileEditor || (FileEditor = {}));
/**
* A document widget for file editor widgets.
*/
export class FileEditorWidget extends DocumentWidget {
/**
* Set URI fragment identifier for text files
*/
async setFragment(fragment) {
const parsedFragments = fragment.split('=');
// TODO: expand to allow more schemes of Fragment Identification Syntax
// reference: https://datatracker.ietf.org/doc/html/rfc5147#section-3
if (parsedFragments[0] !== '#line') {
return;
}
const positionOrRange = parsedFragments[1];
let firstLine;
if (positionOrRange.includes(',')) {
// Only respect range start for now.
firstLine = positionOrRange.split(',')[0] || '0';
}
else {
firstLine = positionOrRange;
}
// Reveal the line
return this.context.ready.then(() => {
const position = {
line: parseInt(firstLine, 10),
column: 0
};
this.content.editor.setCursorPosition(position);
this.content.editor.revealPosition(position);
});
}
}
/**
* A widget factory for editors.
*/
export class FileEditorFactory extends ABCWidgetFactory {
/**
* Construct a new editor widget factory.
*/
constructor(options) {
super(options.factoryOptions);
this._services = options.editorServices;
}
/**
* Create a new widget given a context.
*/
createNewWidget(context) {
const func = this._services.factoryService.newDocumentEditor;
const factory = options => {
// Use same id as document factory
return func(options);
};
const content = new FileEditor({
factory,
context,
mimeTypeService: this._services.mimeTypeService
});
content.title.icon = textEditorIcon;
const widget = new FileEditorWidget({ content, context });
return widget;
}
}
//# sourceMappingURL=widget.js.map