Pārlūkot izejas kodu

wip clean up code editor

Steven Silvester 8 gadi atpakaļ
vecāks
revīzija
7e6ce929cd
1 mainītis faili ar 112 papildinājumiem un 396 dzēšanām
  1. 112 396
      src/codeeditor/editor.ts

+ 112 - 396
src/codeeditor/editor.ts

@@ -5,22 +5,17 @@ import {
   IDisposable
 } from 'phosphor/lib/core/disposable';
 
-import {
-  Message
-} from 'phosphor/lib/core/messaging';
-
 import {
   ISignal, clearSignalData, defineSignal
 } from 'phosphor/lib/core/signaling';
 
-import {
-  Widget
-} from 'phosphor/lib/ui/widget';
-
 import {
   IChangedArgs
 } from '../common/interfaces';
 
+import {
+  ObservableString
+} from '../common/observablestring';
 
 /**
  * A namespace for code editors.
@@ -40,12 +35,12 @@ namespace CodeEditor {
     /**
      * The cursor line number.
      */
-    line: number;
+    readonly line: number;
 
     /**
      * The cursor column number.
      */
-    column: number;
+    readonly column: number;
   }
 
   /**
@@ -54,14 +49,14 @@ namespace CodeEditor {
   export
   interface IDimension {
     /**
-     * The width of an element in pixels.  
+     * The width of an element in pixels.
      */
-    width: number;
+    readonly width: number;
 
     /**
      * The height of an element in pixels.
      */
-    height: number;
+    readonly height: number;
   }
 
   /**
@@ -102,7 +97,7 @@ namespace CodeEditor {
      * If this position is greater than [end] then the range is considered
      * to be backward.
      */
-    start: IPosition;
+    readonly start: IPosition;
 
     /**
      * The position of the last character in the current range.
@@ -111,7 +106,7 @@ namespace CodeEditor {
      * If this position is less than [start] then the range is considered
      * to be backward.
      */
-    end: IPosition;
+    readonly end: IPosition;
   }
 
   /**
@@ -138,24 +133,24 @@ namespace CodeEditor {
     /**
      * The uuid of the text selection owner.
      */
-    uuid: string;
+    readonly uuid: string;
 
     /**
      * The style of this selection.
      */
-    style?: ISelectionStyle;
+    readonly style?: ISelectionStyle;
   }
 
   /**
    * An interface to manage selections by selection owners.
-   * 
+   *
    * #### Definitions
    * - a user code that has an associated uuid is called a selection owner, see `CodeEditor.ISelectionOwner`
    * - a selection belongs to a selection owner only if it is associated with the owner by an uuid, see `CodeEditor.ITextSelection`
-   * 
+   *
    * #### Read access
    * - any user code can observe any selection
-   * 
+   *
    * #### Write access
    * - if a user code is a selection owner then:
    *   - it can change selections beloging to it
@@ -176,12 +171,20 @@ namespace CodeEditor {
     readonly uuids: string[];
 
     /**
-     * Gets the selections for all the cursors in ascending order. 
+     * Gets the selections for all the cursors in ascending order.
+     *
+     * @param uuid - The id of the selection owner.
+     *
+     * @returns A new array of text selections.
      */
     getSelections(uuid: string): ITextSelection[];
 
     /**
      * Sets the selections for all the cursors.
+     *
+     * @param uuid - The id of the selection owner.
+     *
+     * @param newSelections - The replacement text selections.
      */
     setSelections(uuid: string, newSelections: ITextSelection[]): void;
   }
@@ -215,8 +218,7 @@ namespace CodeEditor {
    * Default implementation of `ISelections`.
    */
   export
-  class Selections implements ISelections {
-
+  class Selections implements ISelections, IDisposable {
     /**
      * A signal emitted when selections changes.
      */
@@ -230,7 +232,29 @@ namespace CodeEditor {
     }
 
     /**
-     * Gets the selections for all the cursors in ascending order. 
+     * Test whether the selections are disposed.
+     */
+    get isDisposed(): boolean {
+      return this._isDisposed;
+    }
+
+    /**
+     * Dispose of the resources used by the selections.
+     */
+    dispose(): void {
+      if (this.isDisposed) {
+        return;
+      }
+      this._selections = {};
+      clearSignalData(this);
+    }
+
+    /**
+     * Gets the selections for all the cursors in ascending order.
+     *
+     * @param uuid - The id of the selection owner.
+     *
+     * @returns A new array of text selections.
      */
     getSelections(uuid: string): ITextSelection[] {
       const selections = this._selections[uuid];
@@ -239,6 +263,10 @@ namespace CodeEditor {
 
     /**
      * Sets the selections for all the cursors.
+     *
+     * @param uuid - The id of the selection owner.
+     *
+     * @param newSelections - The replacement text selections.
      */
     setSelections(uuid: string, newSelections: ITextSelection[]): void {
       const oldSelections = this.getSelections(uuid);
@@ -268,11 +296,15 @@ namespace CodeEditor {
       delete this._selections[uuid];
     }
 
+    private _isDisposed = false;
     private _selections: {
       [key: string]: ITextSelection[] | null
     } = {};
   }
 
+  /**
+   * Define the signals for the `Selections` class.
+   */
   defineSignal(Selections.prototype, 'changed');
 
   /**
@@ -280,11 +312,6 @@ namespace CodeEditor {
    */
   export
   interface IModel extends IDisposable {
-    /**
-     * A signal emitted when the value changes.
-     */
-    valueChanged: ISignal<IModel, IChangedArgs<string>>;
-
     /**
      * A signal emitted when a property changes.
      */
@@ -293,11 +320,11 @@ namespace CodeEditor {
     /**
      * The text stored in the model.
      */
-    value: string;  // TODO: this should be an iobservablestring.
+    value: ObservableString;
 
     /**
      * A mime type of the model.
-     * 
+     *
      * #### Notes
      * It is never `null`, the default mime type is `text/plain`.
      */
@@ -315,16 +342,28 @@ namespace CodeEditor {
 
     /**
      * Returns the content for the given line number.
+     *
+     * @param line - The line of interest.
+     *
+     * @returns The value of the line.
      */
     getLine(line: number): string;
 
     /**
      * Find an offset for the given position.
+     *
+     * @param position - The position of interest.
+     *
+     * @returns The offset at the position.
      */
     getOffsetAt(position: IPosition): number;
 
     /**
      * Find a position for the given offset.
+     *
+     * @param offset - The offset of interest.
+     *
+     * @returns The position at the offset.
      */
     getPositionAt(offset: number): IPosition;
 
@@ -353,37 +392,58 @@ namespace CodeEditor {
      * The uuid of this selection owner.
      */
     readonly uuid: string;
+
     /**
      * Returns the primary position of the cursor, never `null`.
      */
     getCursorPosition(): IPosition;
+
     /**
-     * Set the primary position of the cursor. This will remove any secondary cursors.
+     * Set the primary position of the cursor.
+     *
+     * @param position - The new primary position.
+     *
+     * #### Notes
+     * This will remove any secondary cursors.
      */
     setCursorPosition(position: IPosition): void;
+
     /**
      * Returns the primary selection, never `null`.
      */
     getSelection(): IRange;
+
     /**
-     * Set the primary selection. This will remove any secondary cursors.
+     * Set the primary selection.
+     *
+     * @param selection - The desired selection range.
+     *
+     * #### Notes
+     * This will remove any secondary cursors.
      */
     setSelection(selection: IRange): void;
+
     /**
      * Gets the selections for all the cursors, never `null` or empty.
      */
     getSelections(): IRange[];
+
     /**
      * Sets the selections for all the cursors.
+     *
+     * @param selections - The new selections.
+     *
+     * #### Notes
      * Cursors will be removed or added, as necessary.
-     * Passing an empty array resets a cursor position to the start of a document.
+     * Passing an empty array resets a cursor position to the start of a
+     * document.
      */
     setSelections(selections: IRange[]): void;
   }
 
   /**
    * A keydown handler type.
-   * 
+   *
    * #### Notes
    * Return `true` to prevent the default handling of the event by the
    * editor.
@@ -442,30 +502,40 @@ namespace CodeEditor {
     hasFocus(): boolean;
 
     /**
-     * Repaint editor. 
+     * Repaint the editor.
      */
     refresh(): void;
 
     /**
-     * Sets the size of the editor.
-     * 
+     * Set the size of the editor.
+     *
+     * @param size - The desired size.
+     *
      * #### Notes
-     * Sets null if the size is unknown.
+     * Use `null` if the size is unknown.
      */
     setSize(size: IDimension | null): void;
 
     /**
      * Reveals the given position in the editor.
+     *
+     * @param position - The desired position to reveal.
      */
     revealPosition(position: IPosition): void;
 
     /**
      * Reveals the given selection in the editor.
+     *
+     * @param position - The desired selection to reveal.
      */
     revealSelection(selection: IRange): void;
 
     /**
      * Get the window coordinates given a cursor position.
+     *
+     * @param position - The desired position.
+     *
+     * @returns The coordinates of the position.
      */
     getCoordinate(position: IPosition): ICoordinate;
   }
@@ -476,17 +546,17 @@ namespace CodeEditor {
   export
   interface IOptions {
     /**
-     * Whether line numbers should be displayed. Defaults to false.
+     * Whether line numbers should be displayed. Defaults to `false`.
      */
     lineNumbers?: boolean;
 
     /**
-     * Set to false for horizontal scrolling. Defaults to true.
+     * Set to false for horizontal scrolling. Defaults to `true`.
      */
     wordWrap?: boolean;
 
     /**
-     * Whether the editor is read-only. Defaults to false.
+     * Whether the editor is read-only. Defaults to `false`.
      */
     readOnly?: boolean;
 
@@ -495,358 +565,4 @@ namespace CodeEditor {
      */
     extra?: { [key: string]: any };
   }
-
-  /**
-   * The default implementation of the code editor model.
-   */
-  export
-  class Model implements IModel {
-    /**
-     * A signal emitted when a content of the model changed.
-     */
-    valueChanged: ISignal<this, IChangedArgs<string>>;
-
-    /**
-     * A signal emitted when a mimetype changes.
-     */
-    mimeTypeChanged: ISignal<this, IChangedArgs<string>>;
-
-    readonly selections = new Selections();
-
-    /**
-     * Whether the model is disposed.
-     */
-    get isDisposed(): boolean {
-      return this._isDisposed;
-    }
-
-    /**
-     * Dipose of the resources used by the model.
-     */
-    dispose(): void {
-      if (this._isDisposed) {
-        return;
-      }
-      this._isDisposed = true;
-      clearSignalData(this);
-    }
-
-    /**
-     * A mime type of the model.
-     */
-    get mimeType(): string {
-      return this._mimetype;
-    }
-    set mimeType(newValue: string) {
-      let oldValue = this._mimetype;
-      if (oldValue === newValue) {
-        return;
-      }
-      this._mimetype = newValue;
-      this.mimeTypeChanged.emit({
-        name: 'mimeType',
-        oldValue,
-        newValue
-      });
-    }
-
-    /**
-     * The text stored in the model.
-     */
-    get value(): string {
-      return this._value;
-    }
-    set value(newValue: string) {
-      let oldValue = this._value;
-      if (oldValue === newValue) {
-        return;
-      }
-      this._value = newValue;
-      this.valueChanged.emit({
-        name: 'value',
-        oldValue,
-        newValue
-      });
-    }
-
-    /**
-     * Get the number of lines in the model.
-     */
-    get lineCount(): number {
-      return this._value.split('\n').length;
-    }
-
-    /**
-     * Returns the content for the given line number.
-     */
-    getLine(line: number): string {
-      return this._value.split('\n')[line];
-    }
-
-    /**
-     * Find an offset for the given position.
-     */
-    getOffsetAt(position: CodeEditor.IPosition): number {
-      let lines = this._value.split('\n');
-      let before = lines.slice(0, position.line).join('\n').length;
-      return before + position.column;
-    }
-
-    /**
-     * Find a position fot the given offset.
-     */
-    getPositionAt(offset: number): CodeEditor.IPosition {
-      let text = this._value.slice(0, offset);
-      let lines = text.split('\n');
-      let column = lines[lines.length - 1].length;
-      return { line: lines.length - 1, column };
-    }
-
-    /**
-     * Undo one edit (if any undo events are stored).
-     */
-    undo(): void { /* no-op */ }
-
-    /**
-     * Redo one undone edit.
-     */
-    redo(): void { /* no-op */ }
-
-    /**
-     * Clear the undo history.
-     */
-    clearHistory(): void { /* no-op */ }
-
-    private _mimetype = '';
-    private _value = '';
-    private _isDisposed = false;
-  }
-}
-
-defineSignal(CodeEditor.Model.prototype, 'valueChanged');
-defineSignal(CodeEditor.Model.prototype, 'mimeTypeChanged');
-
-
-/**
- * An implementation of an editor for an html text area.
- */
-class TextAreaEditor extends Widget implements CodeEditor.IEditor {
-  readonly uuid = '1';
-  /**
-   * Construct a new text editor.
-   */
-  constructor(options: CodeEditor.IOptions, model: CodeEditor.IModel) {
-    super({ node: document.createElement('textarea') });
-    this._model = model;
-    let node = this.node as HTMLTextAreaElement;
-    node.readOnly = options.readOnly || false;
-    node.wrap = options.wordWrap ? 'hard' : 'soft';
-    model.selections.changed.connect(this.onModelSelectionsChanged, this);
-    model.valueChanged.connect(this.onModelValueChanged, this);
-    this.updateSelections();
-  }
-
-  get lineNumbers(): boolean {
-    return false;
-  }
-  set lineNumbers(value: boolean) {
-    /* no-op*/
-  }
-
-  /**
-   * Set to false for horizontal scrolling. Defaults to true.
-   */
-  get wordWrap(): boolean {
-    return (this.node as HTMLTextAreaElement).wrap === 'hard';
-  }
-  set wordWrap(value: boolean) {
-    (this.node as HTMLTextAreaElement).wrap = value ? 'hard' : 'soft';
-  }
-
-  /**
-   * Whether the editor is read-only.  Defaults to false.
-   */
-  get readOnly(): boolean {
-    return (this.node as HTMLTextAreaElement).readOnly;
-  }
-  set readOnly(value: boolean) {
-    (this.node as HTMLTextAreaElement).readOnly = value;
-  }
-
-  get model(): CodeEditor.IModel {
-    return this._model;
-  }
-
-  get onKeyDown(): CodeEditor.KeydownHandler | null {
-    return this._handler;
-  }
-  set onKeyDown(value: CodeEditor.KeydownHandler | null) {
-    this._handler = value;
-  }
-
-  get charWidth(): number {
-    // TODO css measurement
-    return -1;
-  }
-
-  get lineHeight(): number {
-    // TODO css measurement
-    return -1;
-  }
-
-  /**
-   * Brings browser focus to this editor text.
-   */
-  focus(): void {
-    this.node.focus();
-  }
-
-  /**
-   * Test whether the editor has keyboard focus.
-   */
-  hasFocus(): boolean {
-    return document.activeElement === this.node;
-  }
-
-  /**
-   * Repaint the editor.
-   */
-  refresh(): void { /* no-op */ }
-
-  /**
-   * Set the size of the editor in pixels.
-   */
-  setSize(dimension: CodeEditor.IDimension | null): void {
-    // override css here
-  }
-
-  /**
-   * Scroll the given cursor position into view.
-   */
-  revealPosition(pos: CodeEditor.IPosition): void {
-    // set node scroll position here.
-  }
-
-  /**
-   * Scroll the given cursor position into view.
-   */
-  revealSelection(selection: CodeEditor.IRange): void {
-    // set node scroll position here.
-  }
-
-  /**
-   * Get the window coordinates given a cursor position.
-   */
-  getCoordinate(position: CodeEditor.IPosition): CodeEditor.ICoordinate {
-    // more css measurements required
-    return void 0;
-  }
-
-  handleEvent(event: Event): void {
-    switch (event.type) {
-    case 'keydown':
-      this._evtKeydown(event as KeyboardEvent);
-      break;
-    case 'mouseup':
-      this._evtMouseUp(event as MouseEvent);
-      break;
-    case 'input':
-      this._evtInput(event);
-      break;
-    default:
-      break;
-    }
-  }
-
-  protected onAfterAttach(msg: Message): void {
-    super.onAfterAttach(msg);
-    this.node.addEventListener('keydown', this);
-    this.node.addEventListener('mouseup', this);
-    this.node.addEventListener('input', this);
-  }
-
-  protected onBeforeDetach(msg: Message): void {
-    this.node.removeEventListener('keydown', this);
-    this.node.removeEventListener('mouseup', this);
-    this.node.removeEventListener('input', this);
-  }
-
-  protected onModelValueChanged(sender: CodeEditor.IModel, args: IChangedArgs<string>): void {
-    if (this._changeGuard) {
-      return;
-    }
-    (this.node as HTMLTextAreaElement).value = args.newValue;
-  }
-
-  setCursorPosition(position: CodeEditor.IPosition): void {
-    this.setSelection({
-      start: position,
-      end: position
-    });
-  }
-
-  getCursorPosition(): CodeEditor.IPosition {
-    return this.getSelection().start;
-  }
-
-  setSelection(selection: CodeEditor.IRange | null): void {
-    const node = this.node as HTMLTextAreaElement;
-    if (selection) {
-      const start = this.model.getOffsetAt(selection.start);
-      const end = this.model.getOffsetAt(selection.end);
-      node.setSelectionRange(start, end);
-    } else {
-      node.setSelectionRange(0, 0);
-    }
-  }
-
-  getSelection(): CodeEditor.ITextSelection {
-    const node = this.node as HTMLTextAreaElement;
-    return {
-      uuid: this.uuid,
-      start: this.model.getPositionAt(node.selectionStart),
-      end: this.model.getPositionAt(node.selectionEnd)
-    };
-  }
-
-  getSelections(): CodeEditor.ITextSelection[] {
-    return [this.getSelection()];
-  }
-
-  setSelections(selections: CodeEditor.IRange[]): void {
-    this.setSelection(selections.length > 0 ? selections[0] : null);
-  }
-
-  protected onModelSelectionsChanged(sender: CodeEditor.ISelections, args: CodeEditor.ISelections.IChangedArgs) {
-    // display foreign cursors
-  }
-
-  private _evtKeydown(event: KeyboardEvent): void {
-    let handler = this._handler;
-    if (handler) {
-      handler(this, event);
-    }
-  }
-
-  private _evtMouseUp(event: MouseEvent): void {
-    this.updateSelections();
-  }
-
-  protected updateSelections() {
-    this._changeGuard = true;
-    const selections = this.getSelections();
-    this._model.selections.setSelections(this.uuid, selections);
-    this._changeGuard = false;
-  }
-
-  private _evtInput(event: Event): void {
-    let node = this.node as HTMLTextAreaElement;
-    this._changeGuard = true;
-    this.model.value = node.value;
-    this._changeGuard = false;
-  }
-
-  private _model: CodeEditor.IModel;
-  private _handler: CodeEditor.KeydownHandler | null = null;
-  private _changeGuard = false;
 }