mediawiki-extensions-Visual.../modules/ve-mw/ui/widgets/ve.ui.MWAceEditorWidget.js
Ed Sanders 35c44db988 AceEditorWidget: Allow users to force an Ace editor refresh
Ace is clever about not updating the rendering of elements
which aren't visible, so allow users to force an update, for
example if they changed the value while the whole widget
was hidden.

Change-Id: I7bbbffd17489bc80fe5fa80911f29d7223e125a3
2015-11-16 12:12:10 -08:00

214 lines
5.6 KiB
JavaScript

/*!
* VisualEditor UserInterface MWAceEditorWidget class.
*
* @copyright 2011-2015 VisualEditor Team and others; see http://ve.mit-license.org
*/
/* global ace, require */
/**
* Text input widget which use an Ace editor instance when available
*
* For the most part this can be treated just like a TextInputWidget with
* a few extra considerations:
*
* - For performance it is recommended to destroy the editor when
* you are finished with it, using #teardown. If you need to use
* the widget again let the editor can be restored with #setup.
* - After setting an initial value the undo stack can be reset
* using clearUndoStack so that you can't undo past the initial
* state.
*
* @class
* @extends ve.ui.WhitespacePreservingTextInputWidget
*
* @constructor
* @param {Object} [config] Configuration options
*/
ve.ui.MWAceEditorWidget = function VeUiMWAceEditorWidget( config ) {
// Configuration
config = config || {};
this.$ace = $( '<div dir="ltr">' );
this.editor = null;
// Initialise to a rejected promise for the setValue call in the parent constructor
this.loadingPromise = $.Deferred().reject().promise();
this.styleHeight = null;
// Parent constructor
ve.ui.MWAceEditorWidget.super.call( this, config );
// Clear the fake loading promise and setup properly
this.loadingPromise = null;
this.setup();
this.$element
.append( this.$ace )
.addClass( 've-ui-mwAceEditorWidget' );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWAceEditorWidget, ve.ui.WhitespacePreservingTextInputWidget );
/* Events */
/**
* The editor has resized
* @event resize
*/
/* Methods */
/**
* Setup the Ace editor instance
*/
ve.ui.MWAceEditorWidget.prototype.setup = function () {
if ( !this.loadingPromise ) {
this.loadingPromise = mw.loader.getState( 'ext.codeEditor.ace.modes' ) ?
mw.loader.using( 'ext.codeEditor.ace.modes' ).then( this.setupEditor.bind( this ) ) :
$.Deferred().reject().promise();
}
};
/**
* Destroy the Ace editor instance
*/
ve.ui.MWAceEditorWidget.prototype.teardown = function () {
var widget = this;
this.loadingPromise.done( function () {
widget.$input.removeClass( 'oo-ui-element-hidden' );
widget.editor.destroy();
widget.editor = null;
} ).always( function () {
widget.loadingPromise = null;
} );
};
/**
* Setup the Ace editor
*
* @fires resize
*/
ve.ui.MWAceEditorWidget.prototype.setupEditor = function () {
this.$input.addClass( 'oo-ui-element-hidden' );
this.editor = ace.edit( this.$ace[ 0 ] );
this.editor.setOptions( {
minLines: this.minRows || 3,
maxLines: this.autosize ? this.maxRows : this.minRows || 3
} );
this.editor.getSession().on( 'change', this.onEditorChange.bind( this ) );
this.editor.renderer.on( 'resize', this.onEditorResize.bind( this ) );
this.editor.resize();
};
/**
* @inheritdoc
*/
ve.ui.MWAceEditorWidget.prototype.setValue = function ( value ) {
var widget = this;
this.loadingPromise.done( function () {
var selectionState;
if ( value !== widget.editor.getValue() ) {
selectionState = widget.editor.session.selection.toJSON();
widget.editor.setValue( value );
widget.editor.session.selection.fromJSON( selectionState );
}
} ).fail( function () {
ve.ui.MWAceEditorWidget.super.prototype.setValue.call( widget, value );
} );
return this;
};
/**
* Handle change events from the Ace editor
*/
ve.ui.MWAceEditorWidget.prototype.onEditorChange = function () {
// Call setValue on the parent to keep the value property in sync with the editor
ve.ui.MWAceEditorWidget.super.prototype.setValue.call( this, this.editor.getValue() );
};
/**
* Handle resize events from the Ace editor
*
* @fires resize
*/
ve.ui.MWAceEditorWidget.prototype.onEditorResize = function () {
// On the first setup the editor doesn't resize until the end of the cycle
setTimeout( this.emit.bind( this, 'resize' ) );
};
/**
* Clear the editor's undo stack
*
* @chainable
*/
ve.ui.MWAceEditorWidget.prototype.clearUndoStack = function () {
var widget = this;
this.loadingPromise.done( function () {
widget.editor.session.setUndoManager(
new ace.UndoManager()
);
} );
return this;
};
/**
* Toggle the visibility of line numbers
*
* @param {boolean} visible Visible
* @chainable
*/
ve.ui.MWAceEditorWidget.prototype.toggleLineNumbers = function ( visible ) {
var widget = this;
this.loadingPromise.done( function () {
widget.editor.renderer.setOption( 'showLineNumbers', visible );
} );
return this;
};
/**
* Set the language mode of the editor (programming language)
*
* @param {string} lang Language
* @chainable
*/
ve.ui.MWAceEditorWidget.prototype.setLanguage = function ( lang ) {
var widget = this;
this.loadingPromise.done( function () {
widget.editor.getSession().setMode( 'ace/mode/' + ( require( 'ace/mode/' + lang ) ? lang : 'text' ) );
} );
return this;
};
/**
* Focus the editor
*/
ve.ui.MWAceEditorWidget.prototype.focus = function () {
var widget = this;
this.loadingPromise.done( function () {
widget.editor.focus();
} ).fail( function () {
ve.ui.MWAceEditorWidget.super.prototype.focus.call( widget );
} );
};
/**
* @inheritdoc
* @param {boolean} force Force a resize call on Ace editor
*/
ve.ui.MWAceEditorWidget.prototype.adjustSize = function ( force ) {
var widget = this;
// If the editor has loaded, resize events are emitted from #onEditorResize
// so do nothing here unless this is a user triggered resize, otherwise call the parent method.
if ( force ) {
this.loadingPromise.done( function () {
widget.editor.resize();
} );
}
this.loadingPromise.fail( function () {
// Parent method
ve.ui.MWAceEditorWidget.super.prototype.adjustSize.call( widget );
} );
};