mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2024-11-15 02:04:02 +00:00
094f20902c
While working on T270317, I realized the performance of the matchbrackets addon is not as good as described in T270237#6739993. The issue with my original benchmark was that I did it with a single pair of brackets with thousands of characters between. A paragraph with thousands of brackets behaves much worse. So bad that I feels painful when moving the cursor. Lowering the limit to something in the middle (between the original 1000 and my 10000) makes it behave much, much better on my machine. Bug: T270237 Bug: T270317 Change-Id: I31f850f4c7778d6b5ff1d0eb17fdaf0edf7ae019
311 lines
9.1 KiB
JavaScript
311 lines
9.1 KiB
JavaScript
( function () {
|
|
var useCodeMirror, codeMirror, api, originHooksTextarea, cmTextSelection,
|
|
$textbox1,
|
|
// Keep these modules in sync with CodeMirrorHooks.php
|
|
codeMirrorCoreModules = [
|
|
'ext.CodeMirror.lib',
|
|
'ext.CodeMirror.mode.mediawiki'
|
|
];
|
|
|
|
// Exit if WikiEditor is disabled
|
|
if ( !mw.loader.getState( 'ext.wikiEditor' ) ) {
|
|
return;
|
|
}
|
|
|
|
useCodeMirror = mw.user.options.get( 'usecodemirror' ) > 0;
|
|
api = new mw.Api();
|
|
|
|
originHooksTextarea = $.valHooks.textarea;
|
|
// define jQuery hook for searching and replacing text using JS if CodeMirror is enabled, see Bug: T108711
|
|
$.valHooks.textarea = {
|
|
get: function ( elem ) {
|
|
if ( elem.id === 'wpTextbox1' && codeMirror ) {
|
|
return codeMirror.doc.getValue();
|
|
} else if ( originHooksTextarea ) {
|
|
return originHooksTextarea.get( elem );
|
|
}
|
|
return elem.value;
|
|
},
|
|
set: function ( elem, value ) {
|
|
if ( elem.id === 'wpTextbox1' && codeMirror ) {
|
|
return codeMirror.doc.setValue( value );
|
|
} else if ( originHooksTextarea ) {
|
|
return originHooksTextarea.set( elem, value );
|
|
}
|
|
elem.value = value;
|
|
}
|
|
};
|
|
|
|
// jQuery.textSelection overrides for CodeMirror.
|
|
// See jQuery.textSelection.js for method documentation
|
|
cmTextSelection = {
|
|
getContents: function () {
|
|
return codeMirror.doc.getValue();
|
|
},
|
|
setContents: function ( content ) {
|
|
codeMirror.doc.setValue( content );
|
|
return this;
|
|
},
|
|
getSelection: function () {
|
|
return codeMirror.doc.getSelection();
|
|
},
|
|
setSelection: function ( options ) {
|
|
codeMirror.doc.setSelection( codeMirror.doc.posFromIndex( options.start ), codeMirror.doc.posFromIndex( options.end ) );
|
|
codeMirror.focus();
|
|
return this;
|
|
},
|
|
replaceSelection: function ( value ) {
|
|
codeMirror.doc.replaceSelection( value );
|
|
return this;
|
|
},
|
|
getCaretPosition: function ( options ) {
|
|
var caretPos = codeMirror.doc.indexFromPos( codeMirror.doc.getCursor( true ) ),
|
|
endPos = codeMirror.doc.indexFromPos( codeMirror.doc.getCursor( false ) );
|
|
if ( options.startAndEnd ) {
|
|
return [ caretPos, endPos ];
|
|
}
|
|
return caretPos;
|
|
},
|
|
scrollToCaretPosition: function () {
|
|
codeMirror.scrollIntoView( null );
|
|
return this;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Save CodeMirror enabled pref.
|
|
*
|
|
* @param {boolean} prefValue True, if CodeMirror should be enabled by default, otherwise false.
|
|
*/
|
|
function setCodeEditorPreference( prefValue ) {
|
|
useCodeMirror = prefValue; // Save state for function updateToolbarButton()
|
|
|
|
if ( mw.user.isAnon() ) { // Skip it for anon users
|
|
return;
|
|
}
|
|
api.saveOption( 'usecodemirror', prefValue ? 1 : 0 );
|
|
mw.user.options.set( 'usecodemirror', prefValue ? 1 : 0 );
|
|
}
|
|
|
|
/**
|
|
* Replaces the default textarea with CodeMirror
|
|
*/
|
|
function enableCodeMirror() {
|
|
var config = mw.config.get( 'extCodeMirrorConfig' );
|
|
|
|
mw.loader.using( codeMirrorCoreModules.concat( config.pluginModules ), function () {
|
|
var $codeMirror, cmOptions,
|
|
selectionStart = $textbox1.prop( 'selectionStart' ),
|
|
selectionEnd = $textbox1.prop( 'selectionEnd' ),
|
|
scrollTop = $textbox1.scrollTop();
|
|
|
|
// If CodeMirror is already loaded or wikEd gadget is enabled, abort. See T178348.
|
|
// FIXME: Would be good to replace the wikEd check with something more generic.
|
|
if ( codeMirror || mw.user.options.get( 'gadget-wikEd' ) > 0 ) {
|
|
return;
|
|
}
|
|
|
|
// T174055: Do not redefine the browser history navigation keys (T175378: for PC only)
|
|
CodeMirror.keyMap.pcDefault[ 'Alt-Left' ] = false;
|
|
CodeMirror.keyMap.pcDefault[ 'Alt-Right' ] = false;
|
|
|
|
cmOptions = {
|
|
mwConfig: config,
|
|
// styleActiveLine: true, // disabled since Bug: T162204, maybe should be optional
|
|
lineWrapping: true,
|
|
readOnly: $textbox1[ 0 ].readOnly,
|
|
// select mediawiki as text input mode
|
|
mode: 'text/mediawiki',
|
|
extraKeys: {
|
|
Tab: false,
|
|
'Shift-Tab': false,
|
|
// T174514: Move the cursor at the beginning/end of the current wrapped line
|
|
Home: 'goLineLeft',
|
|
End: 'goLineRight'
|
|
},
|
|
inputStyle: 'contenteditable',
|
|
spellcheck: true,
|
|
viewportMargin: Infinity
|
|
};
|
|
|
|
if ( mw.config.get( 'wgCodeMirrorEnableBracketMatching' ) ) {
|
|
cmOptions.matchBrackets = {
|
|
highlightNonMatching: false,
|
|
maxHighlightLineLength: 5000
|
|
};
|
|
}
|
|
|
|
codeMirror = CodeMirror.fromTextArea( $textbox1[ 0 ], cmOptions );
|
|
$codeMirror = $( codeMirror.getWrapperElement() );
|
|
|
|
// Allow textSelection() functions to work with CodeMirror editing field.
|
|
$codeMirror.textSelection( 'register', cmTextSelection );
|
|
// Also override textSelection() functions for the "real" hidden textarea to route to
|
|
// CodeMirror. We unregister this when switching to normal textarea mode.
|
|
$textbox1.textSelection( 'register', cmTextSelection );
|
|
|
|
// RL module jquery.ui
|
|
$codeMirror.resizable( {
|
|
handles: 'se',
|
|
resize: function ( event, ui ) {
|
|
ui.size.width = ui.originalSize.width;
|
|
}
|
|
} );
|
|
|
|
codeMirror.doc.setSelection( codeMirror.doc.posFromIndex( selectionEnd ), codeMirror.doc.posFromIndex( selectionStart ) );
|
|
codeMirror.scrollTo( null, scrollTop );
|
|
|
|
// HACK: <textarea> font size varies by browser (chrome/FF/IE)
|
|
$codeMirror.css( {
|
|
'font-size': $textbox1.css( 'font-size' ),
|
|
'line-height': $textbox1.css( 'line-height' )
|
|
} );
|
|
|
|
// use direction and language of the original textbox
|
|
$codeMirror.attr( {
|
|
dir: $textbox1.attr( 'dir' ),
|
|
lang: $textbox1.attr( 'lang' )
|
|
} );
|
|
|
|
// T194102: UniversalLanguageSelector integration is buggy, disabling it completely
|
|
$( codeMirror.getInputField() ).addClass( 'noime' );
|
|
|
|
// set the height of the textarea
|
|
codeMirror.setSize( null, $textbox1.height() );
|
|
} );
|
|
}
|
|
|
|
/**
|
|
* Updates CodeMirror button on the toolbar according to the current state (on/off)
|
|
*/
|
|
function updateToolbarButton() {
|
|
// eslint-disable-next-line no-jquery/no-global-selector
|
|
var $button = $( '#mw-editbutton-codemirror' );
|
|
|
|
$button.toggleClass( 'mw-editbutton-codemirror-active', !!useCodeMirror );
|
|
|
|
// WikiEditor2010 OOUI ToggleButtonWidget
|
|
if ( $button.data( 'setActive' ) ) {
|
|
$button.data( 'setActive' )( !!useCodeMirror );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enables or disables CodeMirror
|
|
*/
|
|
function switchCodeMirror() {
|
|
var selectionObj, selectionStart, selectionEnd, scrollTop, hasFocus, $codeMirror;
|
|
|
|
if ( codeMirror ) {
|
|
scrollTop = codeMirror.getScrollInfo().top;
|
|
selectionObj = codeMirror.doc.listSelections()[ 0 ];
|
|
selectionStart = codeMirror.doc.indexFromPos( selectionObj.head );
|
|
selectionEnd = codeMirror.doc.indexFromPos( selectionObj.anchor );
|
|
hasFocus = codeMirror.hasFocus();
|
|
$codeMirror = $( codeMirror.getWrapperElement() );
|
|
setCodeEditorPreference( false );
|
|
$codeMirror.textSelection( 'unregister' );
|
|
$textbox1.textSelection( 'unregister' );
|
|
codeMirror.toTextArea();
|
|
codeMirror = null;
|
|
if ( hasFocus ) {
|
|
$textbox1.trigger( 'focus' );
|
|
}
|
|
$textbox1.prop( 'selectionStart', selectionStart );
|
|
$textbox1.prop( 'selectionEnd', selectionEnd );
|
|
$textbox1.scrollTop( scrollTop );
|
|
} else {
|
|
enableCodeMirror();
|
|
setCodeEditorPreference( true );
|
|
}
|
|
updateToolbarButton();
|
|
|
|
/* eslint-disable camelcase */
|
|
mw.track( 'event.CodeMirrorUsage', {
|
|
editor: 'wikitext',
|
|
enabled: codeMirror !== null,
|
|
toggled: true,
|
|
session_token: mw.user.sessionId(),
|
|
user_id: mw.user.getId(),
|
|
// eslint-disable-next-line no-jquery/no-global-selector
|
|
edit_start_ts_ms: parseInt( $( 'input[name="wpStarttime"]' ).val() ) * 1000 || 0
|
|
} );
|
|
/* eslint-enable camelcase */
|
|
}
|
|
|
|
/**
|
|
* Adds the CodeMirror button to WikiEditor
|
|
*/
|
|
function addCodeMirrorToWikiEditor() {
|
|
var $codeMirrorButton,
|
|
context = $textbox1.data( 'wikiEditor-context' ),
|
|
toolbar = context && context.modules && context.modules.toolbar;
|
|
|
|
// Guard against something having removed WikiEditor (T271457)
|
|
if ( !toolbar ) {
|
|
return;
|
|
}
|
|
|
|
$textbox1.wikiEditor(
|
|
'addToToolbar',
|
|
{
|
|
section: 'main',
|
|
groups: {
|
|
codemirror: {
|
|
tools: {
|
|
CodeMirror: {
|
|
label: mw.msg( 'codemirror-toggle-label' ),
|
|
type: 'toggle',
|
|
oouiIcon: 'highlight',
|
|
action: {
|
|
type: 'callback',
|
|
execute: function () {
|
|
switchCodeMirror();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
$codeMirrorButton = toolbar.$toolbar.find( '.tool[rel=CodeMirror]' );
|
|
$codeMirrorButton
|
|
.attr( 'id', 'mw-editbutton-codemirror' );
|
|
|
|
if ( useCodeMirror ) {
|
|
enableCodeMirror();
|
|
}
|
|
updateToolbarButton();
|
|
|
|
/* eslint-disable camelcase */
|
|
mw.track( 'event.CodeMirrorUsage', {
|
|
editor: 'wikitext',
|
|
enabled: useCodeMirror,
|
|
toggled: false,
|
|
session_token: mw.user.sessionId(),
|
|
user_id: mw.user.getId(),
|
|
// eslint-disable-next-line no-jquery/no-global-selector
|
|
edit_start_ts_ms: parseInt( $( 'input[name="wpStarttime"]' ).val() ) * 1000 || 0
|
|
} );
|
|
/* eslint-enable camelcase */
|
|
}
|
|
|
|
$( function () {
|
|
// Add CodeMirror button to the enhanced editing toolbar.
|
|
mw.hook( 'wikiEditor.toolbarReady' ).add( function ( $textarea ) {
|
|
$textbox1 = $textarea;
|
|
addCodeMirrorToWikiEditor();
|
|
} );
|
|
} );
|
|
|
|
// Synchronize textarea with CodeMirror before leaving
|
|
window.addEventListener( 'beforeunload', function () {
|
|
if ( codeMirror ) {
|
|
codeMirror.save();
|
|
}
|
|
} );
|
|
|
|
}() );
|