The main class required to set up an Ace instance in the browser.
- -The main class required to set up an Ace instance in the browser.
- -Creates a new EditSession, and returns the associated Document.
Creates a new EditSession, and returns the associated Document.
| text | Document | String | Required. If |
| mode | TextMode | Required. The initial language mode to use for the document - |
Embeds the Ace editor into the DOM, at the element provided by el.
Embeds the Ace editor into the DOM, at the element provided by el.
| el | String | DOMElement | Required. Either the id of an element, or the element itself - |
Defines the floating pointer in the document. Whenever text is inserted or deleted before the cursor, the position of the cursor is updated.
- -Fires whenever the anchor position changes.
- -Fires whenever the anchor position changes.
-Both of these objects have a row and column property corresponding to the position.
Events that can trigger this function include setPosition().
| e | Object | Required. An object containing information about the anchor position. It has two properties: -
|
When called, the 'change' event listener is removed.
When called, the 'change' event listener is removed.
Returns the current document.
- -Returns the current document.
- -Returns an object identifying the row and column position of the current anchor.
Returns an object identifying the row and column position of the current anchor.
Sets the anchor position to the specified row and column. If noClip is true, the position is not clipped.
Sets the anchor position to the specified row and column. If noClip is true, the position is not clipped.
| row | Number | Required. The row index to move the anchor to - |
| column | Number | Required. The column index to move the anchor to - |
| noClip | Boolean | Required. Identifies if you want the position to be clipped - |
Tokenizes the current Document in the background, and caches the tokenized rows for future use.
If a certain row is changed, everything below that row is re-tokenized.
- -Fires whenever the background tokeniziers between a range of rows are going to be updated.
- -Fires whenever the background tokeniziers between a range of rows are going to be updated.
- -| e | Object | Required. An object containing two properties, |
Emits the 'update' event. firstRow and lastRow are used to define the boundaries of the region to be updated.
Stops tokenizing.
- -Stops tokenizing.
- -new CommandManager(platform, commands)
- -Contains the text of the document. Document can be attached to several EditSessions.
At its core, Documents are just an array of strings, with each row in the document matching up to the array index.
Fires whenever the document changes.
- -Fires whenever the document changes.
-Several methods trigger different "change" events. Below is a list of each action type, followed by each property that's also available:
"insertLines" (emitted by Document.insertLines())range: the Range of the change within the documentlines: the lines in the document that are changing"insertText" (emitted by Document.insertNewLine())range: the Range of the change within the documenttext: the text that's being added"removeLines" (emitted by Document.insertLines())range: the Range of the change within the documentlines: the lines in the document that were removednl: the new line character (as defined by Document.getNewLineCharacter())"removeText" (emitted by Document.removeInLine() and Document.removeNewLine())range: the Range of the change within the documenttext: the text that's being removed| e | Object | Required. Contains at least one property called |
Applies all the changes previously accumulated. These can be either 'includeText', 'insertLines', 'removeText', and 'removeLines'.
Applies all the changes previously accumulated. These can be either 'includeText', 'insertLines', 'removeText', and 'removeLines'.
| deltas | Object | Required. - |
Returns all lines in the document as string array.
- -Returns all lines in the document as string array. Warning: The caller should not modify this array!
- -Returns the number of rows in the document.
- -Returns the number of rows in the document.
- -Returns an array of strings of the rows between firstRow and lastRow. This function is inclusive of lastRow.
Returns the newline character that's being used, depending on the value of newLineMode.
Returns the newline character that's being used, depending on the value of newLineMode.
Returns the type of newlines being used; either windows, unix, or auto
Returns the type of newlines being used; either windows, unix, or auto
Given a range within the document, this function returns all the text within that range as a single string.
- -Given a range within the document, this function returns all the text within that range as a single string.
- -| range | Range | Required. The range to work with - |
Returns all the lines in the document as a single string, split by the new line character.
- -Returns all the lines in the document as a single string, split by the new line character.
- -Converts an index position in a document to a {row, column} object.
Converts an index position in a document to a {row, column} object.
Index refers to the "absolute position" of a character in the document. For example:
-var x = 0; // 10 characters, plus one for newline
-var y = -1;
-Here, y is an index 15: 11 characters for the first row, and 5 characters until y in the second.
| index | Number | Required. An index to convert - |
| startRow | Number | Required. =0 The row from which to start the conversion - |
Inserts text into the position at the current row. This method also triggers the 'change' event.
Inserts the elements in lines into the document, starting at the row index given by row. This method also triggers the 'change' event.
Inserts a new line into the document at the current row's position. This method also triggers the 'change' event.
Inserts a new line into the document at the current row's position. This method also triggers the 'change' event.
| position | Object | Required. The position to insert at - |
Converts the {row, column} position in a document to the character's index.
Converts the {row, column} position in a document to the character's index.
Index refers to the "absolute position" of a character in the document. For example:
-var x = 0; // 10 characters, plus one for newline
-var y = -1;
-Here, y is an index 15: 11 characters for the first row, and 5 characters until y in the second.
| pos | Object | Required. The |
| startRow | Number | Required. =0 The row from which to start the conversion - |
Removes the specified columns from the row. This method also triggers the 'change' event.
Removes the new line between row and the row immediately following it. This method also triggers the 'change' event.
Removes the new line between row and the row immediately following it. This method also triggers the 'change' event.
| row | Number | Required. The row to check - |
Reverts any changes previously applied. These can be either 'includeText', 'insertLines', 'removeText', and 'removeLines'.
Reverts any changes previously applied. These can be either 'includeText', 'insertLines', 'removeText', and 'removeLines'.
| deltas | Object | Required. - |
Sets up a new EditSession and associates it with the given Document and TextMode.
Sets up a new EditSession and associates it with the given Document and TextMode.
| text | Document | String | Required. If |
| mode | TextMode | Required. The initial language mode to use for the document - |
Emitted when an annotation changes, like through EditSession.setAnnotations().
Emitted when an annotation changes, like through EditSession.setAnnotations().
Emitted when a back marker changes.
- -Emitted when a back marker changes.
- -Emitted when the gutter changes, either by setting or removing breakpoints, or when the gutter decorations change.
- -Emitted when the gutter changes, either by setting or removing breakpoints, or when the gutter decorations change.
- -Emitted when a code fold is added or removed.
- -Emitted when a code fold is added or removed.
- -Emitted when a front marker changes.
- -Emitted when a front marker changes.
- -Emitted when the current mode changes.
- -Emitted when the current mode changes.
- -Emitted when the ability to overwrite text changes, via EditSession.setOverwrite().
Emitted when the ability to overwrite text changes, via EditSession.setOverwrite().
Emitted when the tab size changes, via EditSession.setTabSize().
Emitted when the tab size changes, via EditSession.setTabSize().
Emitted when the wrapping limit changes.
- -Emitted when the wrapping limit changes.
- -Emitted when the wrap mode changes.
- -Emitted when the wrap mode changes.
- -Emitted when a background tokenizer asynchronously processes new rows.
- -Emitted when a background tokenizer asynchronously processes new rows.
- -| e | Object | Required. An object containing one property, |
Adds a new marker to the given Range. If inFront is true, a front marker is defined, and the 'changeFrontMarker' event fires; otherwise, the 'changeBackMarker' event fires.
Adds a new marker to the given Range. If inFront is true, a front marker is defined, and the 'changeFrontMarker' event fires; otherwise, the 'changeBackMarker' event fires.
| range | Range | Required. Define the range of the marker - |
| clazz | String | Required. Set the CSS class for the marker - |
| type | Function | String | Required. Identify the type of the marker - |
| inFront | Boolean | Required. Set to |
Clears all the annotations for this session. This function also triggers the 'changeAnnotation' event.
Clears all the annotations for this session. This function also triggers the 'changeAnnotation' event.
Removes a breakpoint on the row number given by rows. This function also emits the 'changeBreakpoint' event.
Removes a breakpoint on the row number given by rows. This function also emits the 'changeBreakpoint' event.
| row | Number | Required. A row index - |
Removes all breakpoints on the rows. This function also emites the 'changeBreakpoint' event.
Removes all breakpoints on the rows. This function also emites the 'changeBreakpoint' event.
Converts document coordinates to screen coordinates. This takes into account code folding, word wrap, tab size, and any other visual modifications.
- -Returns the annotations for the EditSession.
Returns the annotations for the EditSession.
Returns an array of numbers, indicating which rows have breakpoints.
- -Returns an array of numbers, indicating which rows have breakpoints.
- -Returns the Document associated with this session.
Returns the Document associated with this session.
Returns the number of rows in the document.
- -Returns the number of rows in the document.
- -Returns an array of strings of the rows between firstRow and lastRow. This function is inclusive of lastRow.
Returns an array containing the IDs of all the markers, either front or back.
- -Returns an array containing the IDs of all the markers, either front or back.
- -| inFront | Boolean | Required. If |
Returns the current text mode.
- -Returns the current text mode.
- -Returns the current new line mode.
- -Returns the current new line mode.
- -Returns true if overwrites are enabled; false otherwise.
Returns true if overwrites are enabled; false otherwise.
Returns the position (on screen) for the last character in the provided screen row.
- -Returns the position (on screen) for the last character in the provided screen row.
- -| screenRow | Number | Required. The screen row to check - |
Returns the length of the screen.
- -Returns the length of the screen.
- -The distance to the next tab stop at the specified screen column.
- -The distance to the next tab stop at the specified screen column.
- -| screenColumn | Number | Required. The screen column to check - |
Returns the width of the screen.
- -Returns the width of the screen.
- -Returns the value of the distance between the left of the editor and the leftmost part of the visible content.
- -Returns the value of the distance between the left of the editor and the leftmost part of the visible content.
- -Returns the value of the distance between the top of the editor and the topmost part of the visible content.
- -Returns the value of the distance between the top of the editor and the topmost part of the visible content.
- -Returns selection object.
- -Returns selection object.
- -Returns the current tab size.
- -Returns the current tab size.
- -Returns the current value for tabs. If the user is using soft tabs, this will be a series of spaces (defined by getTabSize()); otherwise it's simply '\t'.
Returns the current value for tabs. If the user is using soft tabs, this will be a series of spaces (defined by getTabSize()); otherwise it's simply '\t'.
Given a range within the document, this function returns all the text within that range as a single string.
- -Given a range within the document, this function returns all the text within that range as a single string.
- -| range | Range | Required. The range to work with - |
Returns an object indicating the token at the current row. The object has two properties: index and start.
Starts tokenizing at the row indicated. Returns a list of objects of the tokenized rows.
- -Starts tokenizing at the row indicated. Returns a list of objects of the tokenized rows.
- -| row | Number | Required. The row to start at - |
Returns the current undo manager.
- -Returns the current undo manager.
- -Returns true if soft tabs are being used, false otherwise.
Returns true if soft tabs are being used, false otherwise.
Returns true if workers are being used.
Returns true if workers are being used.
Returns true if wrap mode is being used; false otherwise.
Returns true if wrap mode is being used; false otherwise.
Given a starting row and column, this method returns the Range of the first word boundary it finds.
Returns the value of wrap limit.
- -Returns the value of wrap limit.
- -Returns an object that defines the minimum and maximum of the wrap limit; it looks something like this:
- -Returns an object that defines the minimum and maximum of the wrap limit; it looks something like this:
-{ min: wrapLimitRange_min, max: wrapLimitRange_max }
- -Indents all the rows, from startRow to endRow (inclusive), by prefixing each row with the token in indentString.
Indents all the rows, from startRow to endRow (inclusive), by prefixing each row with the token in indentString.
If indentString contains the '\t' character, it's replaced by whatever is defined by getTabString().
| startRow | Number | Required. Starting row - |
| endRow | Number | Required. Ending row - |
| indentString | String | Required. The indent token - |
Shifts all the lines in the document down one, starting from firstRow and ending at lastRow.
Shifts all the lines in the document up one, starting from firstRow and ending at lastRow.
Moves a range of text from the given range to the given position. toPosition is an object that looks like this:
{ row: newRowLocation, column: newColumnLocation }
-
- Moves a range of text from the given range to the given position. toPosition is an object that looks like this:
{ row: newRowLocation, column: newColumnLocation }
-
- | fromRange | Range | Required. The range of text you want moved within the document - |
| toPosition | Object | Required. The location (row and column) where you want to move the text to - |
Reloads all the tokens on the current session. This function calls BackgroundTokenizer.start () to all the rows; it also emits the 'tokenizerUpdate' event.
Reloads all the tokens on the current session. This function calls BackgroundTokenizer.start () to all the rows; it also emits the 'tokenizerUpdate' event.
| e | Object | Required. - |
Removes the marker with the specified ID. If this marker was in front, the 'changeFrontMarker' event is emitted. If the marker was in the back, the 'changeBackMarker' event is emitted.
Removes the marker with the specified ID. If this marker was in front, the 'changeFrontMarker' event is emitted. If the marker was in the back, the 'changeBackMarker' event is emitted.
| markerId | Number | Required. A number representing a marker - |
Converts characters coordinates on the screen to characters coordinates within the document. This takes into account code folding, word wrap, tab size, and any other visual modifications.
- -Converts characters coordinates on the screen to characters coordinates within the document. This takes into account code folding, word wrap, tab size, and any other visual modifications.
- -| screenRow | Number | Required. The screen row to check - |
| screenColumn | Number | Required. The screen column to check - |
Sets annotations for the EditSession. This functions emits the 'changeAnnotation' event.
Sets annotations for the EditSession. This functions emits the 'changeAnnotation' event.
| annotations | Array | Required. A list of annotations - |
Sets a breakpoint on the row number given by rows. This function also emites the 'changeBreakpoint' event.
Sets a breakpoint on every row number given by rows. This function also emites the 'changeBreakpoint' event.
Sets a breakpoint on every row number given by rows. This function also emites the 'changeBreakpoint' event.
| rows | Array | Required. An array of row indices - |
Sets the EditSession to point to a new Document. If a BackgroundTokenizer exists, it also points to doc.
Sets the EditSession to point to a new Document. If a BackgroundTokenizer exists, it also points to doc.
| doc | Document | Required. The new |
Pass in true to enable overwrites in your session, or false to disable.
Pass in true to enable overwrites in your session, or false to disable.
If overwrites is enabled, any text you enter will type over any text after it. If the value of overwrite changes, this function also emits the changeOverwrite event.
| overwrite | Boolean | Required. Defines wheter or not to set overwrites - |
Sets the value of the distance between the left of the editor and the leftmost part of the visible content.
- -Sets the value of the distance between the left of the editor and the leftmost part of the visible content.
- -| scrollLeft | Object | Required. - |
This function sets the scroll top value. It also emits the 'changeScrollTop' event.
This function sets the scroll top value. It also emits the 'changeScrollTop' event.
| scrollTop | Number | Required. The new scroll top value - |
Set the number of spaces that define a soft tab; for example, passing in 4 transforms the soft tabs to be equivalent to four spaces. This function also emits the changeTabSize event.
Set the number of spaces that define a soft tab; for example, passing in 4 transforms the soft tabs to be equivalent to four spaces. This function also emits the changeTabSize event.
| tabSize | Number | Required. The new tab size - |
Sets the undo manager.
- -Enables or disables highlighting of the range where an undo occured.
- -Enables or disables highlighting of the range where an undo occured.
- -| enable | Boolean | Required. If |
Pass true to enable the use of soft tabs. Soft tabs means you're using spaces instead of the tab character ('\t').
Pass true to enable the use of soft tabs. Soft tabs means you're using spaces instead of the tab character ('\t').
| useSoftTabs | Boolean | Required. Value indicating whether or not to use soft tabs - |
Sets whether or not line wrapping is enabled. If useWrapMode is different than the current value, the 'changeWrapMode' event is emitted.
Sets whether or not line wrapping is enabled. If useWrapMode is different than the current value, the 'changeWrapMode' event is emitted.
| useWrapMode | Boolean | Required. Enable (or disable) wrap mode - |
Sets the boundaries of wrap. Either value can be null to have an unconstrained wrap, or, they can be the same number to pin the limit. If the wrap limits for min or max are different, this method also emits the 'changeWrapMode' event.
Sets the boundaries of wrap. Either value can be null to have an unconstrained wrap, or, they can be the same number to pin the limit. If the wrap limits for min or max are different, this method also emits the 'changeWrapMode' event.
| min | Number | Required. The minimum wrap value (the left side wrap) - |
| max | Number | Required. The maximum wrap value (the right side wrap) - |
Sets the value of overwrite to the opposite of whatever it currently is.
- -Sets the value of overwrite to the opposite of whatever it currently is.
- -The main entry point into the Ace functionality.
-The Editor manages the EditSession (which manages Documents), as well as the VirtualRenderer, which draws everything to the screen.
Event sessions dealing with the mouse and keyboard are bubbled up from Document to the Editor, which decides what to do with them.
Creates a new Editor object.
Creates a new Editor object.
| renderer | VirtualRenderer | Required. Associated |
| session | EditSession | Required. The |
Emitted once the editor has been blurred.
- -Emitted once the editor has been blurred.
- -Emitted when the selection style changes, via Editor.setSelectionStyle().
Emitted when the selection style changes, via Editor.setSelectionStyle().
| data | Object | Required. Contains one property, |
Emitted whenever the EditSession changes.
Emitted whenever the EditSession changes.
| e | Object | Required. An object with two properties, |
Emitted once the editor comes into focus.
- -Emitted once the editor comes into focus.
- -Emitted when text is pasted.
- -Emitted when text is pasted.
- -| e | Object | Required. An object which contains one property, |
Aligns the cursors or selected text.
- -Aligns the cursors or selected text.
- -Outdents the current line.
- -Outdents the current line.
- -Blurs the current textInput.
Blurs the current textInput.
Attempts to center the current selection on the screen.
- -Attempts to center the current selection on the screen.
- -Empties the selection (by de-selecting it). This function also emits the 'changeSelection' event.
Empties the selection (by de-selecting it). This function also emits the 'changeSelection' event.
Copies all the selected lines down one row.
- -Copies all the selected lines down one row.
- -Copies all the selected lines up one row.
- -Copies all the selected lines up one row.
- -Cleans up the entire editor.
- -Cleans up the entire editor.
- -Removes all the selections except the last added one.
- -Removes all the selections except the last added one.
- -Attempts to find needle within the document. For more information on options, see Search.
Performs another search for needle in the document. For more information on options, see Search.
Brings the current textInput into focus.
Brings the current textInput into focus.
Returns true if the behaviors are currently enabled. "Behaviors" in this case is the auto-pairing of special characters, like quotation marks, parenthesis, or brackets.
Returns true if the behaviors are currently enabled. "Behaviors" in this case is the auto-pairing of special characters, like quotation marks, parenthesis, or brackets.
Returns the string of text currently highlighted.
- -Returns the string of text currently highlighted.
- -Gets the current position of the cursor.
- -Gets the current position of the cursor.
- -Returns the screen position of the cursor.
- -Returns the screen position of the cursor.
- -Returns the current mouse drag delay.
- -Returns the current mouse drag delay.
- -Returns the index of the first visible row.
- -Returns the index of the first visible row.
- -Returns true if current lines are always highlighted.
Returns true if current lines are always highlighted.
Returns true if currently highlighted words are to be highlighted.
Returns true if currently highlighted words are to be highlighted.
Returns the keyboard handler, such as "vim" or "windows".
- -Returns the keyboard handler, such as "vim" or "windows".
- -Returns the index of the last visible row.
- -Returns the index of the last visible row.
- -Works like EditSession.getTokenAt(), except it returns a number.
Works like EditSession.getTokenAt(), except it returns a number.
| row | Object | Required. - |
| column | Object | Required. - |
Returns true if overwrites are enabled; false otherwise.
Returns true if overwrites are enabled; false otherwise.
Returns the column number of where the print margin is.
- -Returns the column number of where the print margin is.
- -Returns true if the editor is set to read-only mode.
Returns true if the editor is set to read-only mode.
Returns the value indicating how fast the mouse scroll speed is (in milliseconds).
- -Returns the value indicating how fast the mouse scroll speed is (in milliseconds).
- -Returns selection object.
- -Returns selection object.
- -Returns the current selection style.
- -Returns the current selection style.
- -Returns the current session being used.
- -Returns the current session being used.
- -Returns true if the fold widgets are shown.
Returns true if the fold widgets are shown.
Returns true if invisible characters are being shown.
Returns true if invisible characters are being shown.
Returns true if the print margin is being shown.
Returns true if the print margin is being shown.
Returns the path of the current theme.
- -Returns the path of the current theme.
- -Returns the current session's content.
- -Returns the current session's content.
- -Returns true if the wrapping behaviors are currently enabled.
Returns true if the wrapping behaviors are currently enabled.
Moves the cursor to the specified line number, and also into the indicated column.
- -Shifts the document to wherever "page down" is, as well as moving the cursor position.
- -Shifts the document to wherever "page down" is, as well as moving the cursor position.
- -Shifts the document to wherever "page up" is, as well as moving the cursor position.
- -Shifts the document to wherever "page up" is, as well as moving the cursor position.
- -Indents the current line.
- -Indents the current line.
- -Returns true if the current textInput is in focus.
Returns true if the current textInput is in focus.
If the character before the cursor is a number, this functions changes its value by amount.
If the character before the cursor is a number, this functions changes its value by amount.
| amount | Number | Required. The value to change the numeral by (can be negative to decrease value) - |
Moves the cursor to the specified row and column. Note that this does not de-select the current selection.
- -Shifts all the selected lines down one row.
- -Shifts all the selected lines down one row.
- -Shifts all the selected lines up one row.
- -Shifts all the selected lines up one row.
- -Moves the cursor to the end of the current file. Note that this does de-select the current selection.
- -Moves the cursor to the start of the current file. Note that this does de-select the current selection.
- -Moves the cursor to the end of the current line. Note that this does de-select the current selection.
- -Moves the cursor to the start of the current line. Note that this does de-select the current selection.
- -Moves the cursor to the word immediately to the left of the current position. Note that this does de-select the current selection.
- -Moves the cursor to the word immediately to the right of the current position. Note that this does de-select the current selection.
- -Called whenever a text "copy" happens.
- -Called whenever a text "copy" happens.
- -Emitted when the selection changes.
- -Emitted when the selection changes.
- -Called whenever a text "cut" happens.
- -Called whenever a text "cut" happens.
- -Perform a redo operation on the document, reimplementing the last change.
- -Perform a redo operation on the document, reimplementing the last change.
- -Removes words of text from the editor. A "word" is defined as a string of characters bookended by whitespace.
- -Removes words of text from the editor. A "word" is defined as a string of characters bookended by whitespace.
- -| dir | String | Required. The direction of the deletion to occur, either "left" or "right" - |
Removes all the lines in the current selection
- -Removes all the lines in the current selection
- -Removes the selection marker.
- -Removes the selection marker.
- -| The | Range | Required. selection range added with |
Removes all the words to the right of the current selection, until the end of the line.
- -Removes all the words to the right of the current selection, until the end of the line.
- -Removes all the words to the left of the current selection, until the start of the line.
- -Removes all the words to the left of the current selection, until the start of the line.
- -Removes the word directly to the left of the current selection.
- -Removes the word directly to the left of the current selection.
- -Removes the word directly to the right of the current selection.
- -Removes the word directly to the right of the current selection.
- -Scrolls the document to wherever "page down" is, without changing the cursor position.
- -Scrolls the document to wherever "page down" is, without changing the cursor position.
- -Scrolls the document to wherever "page up" is, without changing the cursor position.
- -Scrolls the document to wherever "page up" is, without changing the cursor position.
- -Scrolls to a line. If center is true, it puts the line in middle of screen (or attempts to).
Scrolls to a line. If center is true, it puts the line in middle of screen (or attempts to).
| line | Number | Required. The line to scroll to - |
| center | Boolean | Required. If |
| animate | Boolean | Required. If |
| callback | Function | Required. Function to be called when the animation has finished - |
Selects all the text in editor.
- -Selects all the text in editor.
- -Finds the next occurrence of text in an active selection and adds it to the selections.
- -Selects the text from the current position of the document until where a "page down" finishes.
- -Selects the text from the current position of the document until where a "page down" finishes.
- -Selects the text from the current position of the document until where a "page up" finishes.
- -Selects the text from the current position of the document until where a "page up" finishes.
- -Specifies whether to use behaviors or not. "Behaviors" in this case is the auto-pairing of special characters, like quotation marks, parenthesis, or brackets.
- -Specifies whether to use behaviors or not. "Behaviors" in this case is the auto-pairing of special characters, like quotation marks, parenthesis, or brackets.
- -| enabled | Boolean | Required. Enables or disables behaviors - |
Determines whether or not the current line should be highlighted.
- -Determines whether or not the current line should be highlighted.
- -| shouldHighlight | Boolean | Required. Set to |
Determines if the currently selected word should be highlighted.
- -Determines if the currently selected word should be highlighted.
- -| shouldHighlight | Boolean | Required. Set to |
Pass in true to enable overwrites in your session, or false to disable. If overwrites is enabled, any text you enter will type over any text after it. If the value of overwrite changes, this function also emits the changeOverwrite event.
Pass in true to enable overwrites in your session, or false to disable. If overwrites is enabled, any text you enter will type over any text after it. If the value of overwrite changes, this function also emits the changeOverwrite event.
| overwrite | Boolean | Required. Defines whether or not to set overwrites - |
If readOnly is true, then the editor is set to read-only mode, and none of the content can change.
If readOnly is true, then the editor is set to read-only mode, and none of the content can change.
| readOnly | Boolean | Required. Specifies whether the editor can be modified or not - |
Indicates how selections should occur.
- -Indicates how selections should occur.
-By default, selections are set to "line". There are no other styles at the moment, -although this code change in the future.
-This function also emits the 'changeSelectionStyle' event.
| style | String | Required. The new selection style - |
Sets a new editsession to use. This method also emits the 'changeSession' event.
Sets a new editsession to use. This method also emits the 'changeSession' event.
| session | EditSession | Required. The new session to use - |
If showInvisibles is set to true, invisible characters—like spaces or new lines—are show in the editor.
If showInvisibles is set to true, invisible characters—like spaces or new lines—are show in the editor.
| showInvisibles | Boolean | Required. Specifies whether or not to show invisible characters - |
If showPrintMargin is set to true, the print margin is shown in the editor.
If showPrintMargin is set to true, the print margin is shown in the editor.
| showPrintMargin | Boolean | Required. Specifies whether or not to show the print margin - |
Sets a new theme for the editor. theme should exist, and be a directory path, like ace/theme/textmate.
Sets a new theme for the editor. theme should exist, and be a directory path, like ace/theme/textmate.
| theme | String | Required. The path to a theme - |
Specifies whether to use wrapping behaviors or not, i.e. automatically wrapping the selection with characters such as brackets -when such a character is typed in.
- -Specifies whether to use wrapping behaviors or not, i.e. automatically wrapping the selection with characters such as brackets -when such a character is typed in.
- -| enabled | Boolean | Required. Enables or disables wrapping behaviors - |
Splits the line at the current selection (by inserting an '\n').
Splits the line at the current selection (by inserting an '\n').
Given the currently selected range, this function either comments all the lines, or uncomments all of them.
- -Given the currently selected range, this function either comments all the lines, or uncomments all of them.
- -Sets the value of overwrite to the opposite of whatever it currently is.
- -Sets the value of overwrite to the opposite of whatever it currently is.
- -Converts the current selection entirely into lowercase.
- -Converts the current selection entirely into lowercase.
- -Converts the current selection entirely into uppercase.
- -Converts the current selection entirely into uppercase.
- -Transposes current line.
- -Transposes current line.
- -Perform an undo operation on the document, reverting the last change.
- -Perform an undo operation on the document, reverting the last change.
- -Updates the cursor and marker layers.
- -Updates the cursor and marker layers.
- -Welcome to the Ace API Reference Guide. Ace is a standalone code editor written in JavaScript that you can embed onto any website. We're used in a bunch of places already, like GitHub, Google, and Facebook.
-On the left, you'll find a list of all of our currently documented classes. There are plenty more to do, but these represent the "core" set. For more information on how to work with Ace, check out the main Ace website.
- - - - - - - -PlaceHolder.cancel()
- -PlaceHolder.cancel()
-TODO
- -PlaceHolder.detach()
- -PlaceHolder.detach()
-TODO
- -PlaceHolder.hideOtherMarkers()
- -PlaceHolder.hideOtherMarkers()
-Hides all over markers in the EditSession that are not the currently selected one.
PlaceHolder.setup()
- -PlaceHolder.setup()
-TODO
- -PlaceHolder.showOtherMarkers()
- -PlaceHolder.showOtherMarkers()
-TODO
- -This object is used in various places to indicate a region within the editor. To better visualize how this works, imagine a rectangle. Each quadrant of the rectangle is analogous to a range, as ranges contain a starting row and starting column, and an ending row, and ending column.
- -Creates a new Range object with the given starting and ending row and column points.
Returns the part of the current Range that occurs within the boundaries of firstRow and lastRow as a new Range object.
Returns a duplicate of the calling range.
- -Returns a duplicate of the calling range.
- -Returns a range containing the starting and ending rows of the original range, but with a column value of 0.
Returns a range containing the starting and ending rows of the original range, but with a column value of 0.
Checks the row and column points of p with the row and column points of the calling range.
Checks the row and column points of p with the row and column points of the calling range.
| p | Range | Required. A point to compare with - |
Returns true if the row and column provided are within the given range. This can better be expressed as returning true if:
this.start.row <= row <= this.end.row &&
- this.start.column <= column <= this.end.column
-
- Returns true if the row and column provided are within the given range. This can better be expressed as returning true if:
this.start.row <= row <= this.end.row &&
- this.start.column <= column <= this.end.column
-
- | row | Number | Required. A row to check for - |
| column | Number | Required. A column to check for - |
Checks the start and end points of range and compares them to the calling range. Returns true if the range is contained within the caller's range.
Checks the start and end points of range and compares them to the calling range. Returns true if the range is contained within the caller's range.
| range | Range | Required. A range to compare with - |
Changes the row and column points for the calling range for both the starting and ending points.
- -Returns true if the caller's ending row point is the same as row, and if the caller's ending column is the same as column.
Returns true if and only if the starting row and column, and ending row and column, are equivalent to those given by range.
Returns true if and only if the starting row and column, and ending row and column, are equivalent to those given by range.
| range | Range | Required. A range to check against - |
Returns true if the range spans across multiple lines.
Returns true if the range spans across multiple lines.
Returns true if the caller's starting row point is the same as row, and if the caller's starting column is the same as column.
Given the current Range, this function converts those starting and ending points into screen positions, and then returns a new Range object.
Given the current Range, this function converts those starting and ending points into screen positions, and then returns a new Range object.
| session | EditSession | Required. The |
Returns a string containing the range's row and column information, given like this:
- [start.row/start.column] -> [end.row/end.column]
-
- Returns a string containing the range's row and column information, given like this:
- [start.row/start.column] -> [end.row/end.column]
-
- Batches changes (that force something to be redrawn) in the background.
- -
-// elements on the page and add the "prettyprint" style. If at least one
-// prettyprint element was found, call the Google Prettify prettyPrint() API.
-//http://sstatic.net/so/js/master.js?v=6523
-function styleCode()
-{
- if (typeof disableStyleCode != "undefined")
- {
- return;
- }
-
- var a = false;
-
- $("pre code").parent().each(function()
- {
- if (!$(this).hasClass("prettyprint"))
- {
- $(this).addClass("prettyprint");
- a = true
- }
- });
-
- if (a) { prettyPrint() }
-}
\ No newline at end of file
diff --git a/api/resources/javascripts/prettify.js b/api/resources/javascripts/prettify.js
deleted file mode 100644
index eef5ad7e6a0..00000000000
--- a/api/resources/javascripts/prettify.js
+++ /dev/null
@@ -1,28 +0,0 @@
-var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
-(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
-[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
-l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
-q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
-q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
-"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
-a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
-for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
-"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
-H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
-J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
-I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]+/],["dec",/^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api/search.html b/api/search.html
deleted file mode 100644
index 4b3d4ddff41..00000000000
--- a/api/search.html
+++ /dev/null
@@ -1,295 +0,0 @@
-
-
-
-
-
-
- Search
-
-
-
-
-
-
- A class designed to handle all sorts of text searches within a Document.
-
-
-
-
- Constructors
-
-
-
-
-
- -
-
- - new Search()
-
-
-
-
-
-
- Creates a new Search object. The following search options are available:
-
-
- Creates a new Search object. The following search options are available:
-
-needle: The string or regular expression you're looking for
-backwards: Whether to search backwards from where cursor currently is. Defaults to false.
-wrap: Whether to wrap the search back to the beginning when it hits the end. Defaults to false.
-caseSensitive: Whether the search ought to be case-sensitive. Defaults to false.
-wholeWord: Whether the search matches only on whole words. Defaults to false.
-range: The Range to search within. Set this to null for the whole document
-regExp: Whether the search is a regular expression or not. Defaults to false.
-start: The starting Range or cursor position to begin the search
-skipCurrent: Whether or not to include the current line in the search. Default to false.
-
-
-
-
-
-
-
- Methods
-
-
-
-
-
- -
-
- - Search.find(EditSession session)
-
-
-
- - Range
-
-
-
-
-
-
-
-
- Searches for options.needle. If found, this method returns the Range where the text first occurs. If options.backwards is true, the search goes backwards in the session.
-
-
- Searches for options.needle. If found, this method returns the Range where the text first occurs. If options.backwards is true, the search goes backwards in the session.
-
- Arguments
session EditSession Required. The session to search with
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Search.findAll(EditSession session)
-
-
-
- - Range
-
-
-
-
-
-
-
-
- Searches for all occurances options.needle. If found, this method returns an array of Ranges where the text first occurs. If options.backwards is true, the search goes backwards in the session.
-
-
- Searches for all occurances options.needle. If found, this method returns an array of Ranges where the text first occurs. If options.backwards is true, the search goes backwards in the session.
-
- Arguments
session EditSession Required. The session to search with
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Search.getOptions()
-
-
-
- - Object
-
-
-
-
-
-
-
-
- Returns an object containing all the search options.
-
-
- Returns an object containing all the search options.
-
-
-
-
-
-
-
-
-
-
-
- Searches for options.needle in input, and, if found, replaces it with replacement.
-
-
- Searches for options.needle in input, and, if found, replaces it with replacement.
-
- Arguments
input String Required. The text to search in
-replacement String Required. The replacing text
-
-- (String): If
options.regExp is true, this function returns input with the replacement already made. Otherwise, this function just returns replacement.
-If options.needle was not found, this function returns null.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Search.setOptions()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/api/selection.html b/api/selection.html
deleted file mode 100644
index 4da8f771b3b..00000000000
--- a/api/selection.html
+++ /dev/null
@@ -1,1781 +0,0 @@
-
-
-
-
-
-
- Selection
-
-
-
-
-
-
- Contains the cursor position and the text selection of an edit session.
-The row/columns used in the selection are in document coordinates representing the coordinates as they appear in the document before applying soft wrap and folding.
-
-
-
-
- Constructors
-
-
-
-
-
- -
-
- - new Selection(EditSession session)
-
-
-
-
-
-
- Creates a new Selection object.
-
-
-
-
-
-
-
- Events
-
-
-
-
-
- -
-
- - Selection.on("changeCursor", function())
-
-
-
-
-
-
- Emitted when the cursor position changes.
-
-
- Emitted when the cursor position changes.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.on("changeSelection", function())
-
-
-
-
-
-
- Emitted when the cursor selection changes.
-
-
- Emitted when the cursor selection changes.
-
-
-
-
-
-
- Methods
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.clearSelection()
-
-
-
-
-
-
- Empties the selection (by de-selecting it). This function also emits the 'changeSelection' event.
-
-
- Empties the selection (by de-selecting it). This function also emits the 'changeSelection' event.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.detach()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.fromOrientedRange()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.getAllRanges()
-
-
-
- - Array
-
-
-
-
-
-
-
-
- Returns a concatenation of all the ranges.
-
-
- Returns a concatenation of all the ranges.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.getCursor()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Gets the current position of the cursor.
-
-
- Gets the current position of the cursor.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.getLineRange()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.getSelectionAnchor()
-
-
-
- - Object
-
-
-
-
-
-
-
-
- Returns an object containing the row and column of the calling selection anchor.
-
-
- Returns an object containing the row and column of the calling selection anchor.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.getSelectionLead()
-
-
-
- - Object
-
-
-
-
-
-
-
-
- Returns an object containing the row and column of the calling selection lead.
-
-
- Returns an object containing the row and column of the calling selection lead.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.isBackwards()
-
-
-
- - Boolean
-
-
-
-
-
-
-
-
- Returns true if the selection is going backwards in the document.
-
-
- Returns true if the selection is going backwards in the document.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.isEmpty()
-
-
-
- - Boolean
-
-
-
-
-
-
-
-
- Returns true if the selection is empty.
-
-
- Returns true if the selection is empty.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.isMultiLine()
-
-
-
- - Boolean
-
-
-
-
-
-
-
-
- Returns true if the selection is a multi-line.
-
-
- Returns true if the selection is a multi-line.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.mergeOverlappingRanges()
-
-
-
-
-
-
- Merges overlapping ranges ensuring consistency after changes
-
-
- Merges overlapping ranges ensuring consistency after changes
-
-
-
-
-
-
-
-
-
-
-
- Moves the cursor to position indicated by the parameters. Negative numbers move the cursor backwards in the document.
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorDown()
-
-
-
-
-
-
- Moves the cursor down one row.
-
-
- Moves the cursor down one row.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorFileEnd()
-
-
-
-
-
-
- Moves the cursor to the end of the file.
-
-
- Moves the cursor to the end of the file.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorFileStart()
-
-
-
-
-
-
- Moves the cursor to the start of the file.
-
-
- Moves the cursor to the start of the file.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorLeft()
-
-
-
-
-
-
- Moves the cursor left one column.
-
-
- Moves the cursor left one column.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorLineEnd()
-
-
-
-
-
-
- Moves the cursor to the end of the line.
-
-
- Moves the cursor to the end of the line.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorLineStart()
-
-
-
-
-
-
- Moves the cursor to the start of the line.
-
-
- Moves the cursor to the start of the line.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorLongWordLeft()
-
-
-
-
-
-
- Moves the cursor to the word on the left.
-
-
- Moves the cursor to the word on the left.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorLongWordRight()
-
-
-
-
-
-
- Moves the cursor to the word on the right.
-
-
- Moves the cursor to the word on the right.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorRight()
-
-
-
-
-
-
- Moves the cursor right one column.
-
-
- Moves the cursor right one column.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorShortWordLeft()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorShortWordRight()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Moves the cursor to the row and column provided. If preventUpdateDesiredColumn is true, then the cursor stays in the same column position as its original point.
-
-
- Moves the cursor to the row and column provided. If preventUpdateDesiredColumn is true, then the cursor stays in the same column position as its original point.
-
- Arguments
row Number Required. The row to move to
-column Number Required. The column to move to
-keepDesiredColumn Boolean Required. If true, the cursor move does not respect the previous column
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Moves the cursor to the screen position indicated by row and column. If preventUpdateDesiredColumn is true, then the cursor stays in the same column position as its original point.
-
-
- Moves the cursor to the screen position indicated by row and column. If preventUpdateDesiredColumn is true, then the cursor stays in the same column position as its original point.
-
- Arguments
row Number Required. The row to move to
-column Number Required. The column to move to
-keepDesiredColumn Boolean Required. If true, the cursor move does not respect the previous column
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorUp()
-
-
-
-
-
-
- Moves the cursor up one row.
-
-
- Moves the cursor up one row.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorWordLeft()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.moveCursorWordRight()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Gets list of ranges composing rectangular block on the screen
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectAll()
-
-
-
-
-
-
- Selects all the text in the document.
-
-
- Selects all the text in the document.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectAWord()
-
-
-
-
-
-
- Selects a word, including its right whitespace.
-
-
- Selects a word, including its right whitespace.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectDown()
-
-
-
-
-
-
- Moves the selection down one row.
-
-
- Moves the selection down one row.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectFileEnd()
-
-
-
-
-
-
- Moves the selection to the end of the file.
-
-
- Moves the selection to the end of the file.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectFileStart()
-
-
-
-
-
-
- Moves the selection to the start of the file.
-
-
- Moves the selection to the start of the file.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectLeft()
-
-
-
-
-
-
- Moves the selection left one column.
-
-
- Moves the selection left one column.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectLine()
-
-
-
-
-
-
- Selects the entire line.
-
-
- Selects the entire line.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectLineEnd()
-
-
-
-
-
-
- Moves the selection to the end of the current line.
-
-
- Moves the selection to the end of the current line.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectLineStart()
-
-
-
-
-
-
- Moves the selection to the beginning of the current line.
-
-
- Moves the selection to the beginning of the current line.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectRight()
-
-
-
-
-
-
- Moves the selection right one column.
-
-
- Moves the selection right one column.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectUp()
-
-
-
-
-
-
- Moves the selection up one row.
-
-
- Moves the selection up one row.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectWord()
-
-
-
-
-
-
- Selects an entire word boundary.
-
-
- Selects an entire word boundary.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectWordLeft()
-
-
-
-
-
-
- Moves the selection to the first word on the left.
-
-
- Moves the selection to the first word on the left.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.selectWordRight()
-
-
-
-
-
-
- Moves the selection to the first word on the right.
-
-
- Moves the selection to the first word on the right.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.shiftSelection(Number columns)
-
-
-
-
-
-
- Shifts the selection up (or down, if isBackwards() is true) the given number of columns.
-
-
- Shifts the selection up (or down, if isBackwards() is true) the given number of columns.
-
- Arguments
columns Number Required. The number of columns to shift by
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.splitIntoLines()
-
-
-
-
-
-
- Splits all the ranges into lines.
-
-
- Splits all the ranges into lines.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.toggleBlockSelection()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.toOrientedRange()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Selection.toSingleRange()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/api/split.html b/api/split.html
deleted file mode 100644
index fd75edceb1c..00000000000
--- a/api/split.html
+++ /dev/null
@@ -1,640 +0,0 @@
-
-
-
-
-
-
- Split
-
-
-
-
-
-
- Methods
-
-
-
-
-
- -
-
- - Split.blur()
-
-
-
-
-
-
- Blurs the current editor.
-
-
- Blurs the current editor.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.execute()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.focus()
-
-
-
-
-
-
- Focuses the current editor.
-
-
- Focuses the current editor.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.getCurrentEditor()
-
-
-
- - Editor
-
-
-
-
-
-
-
-
- Returns the current editor.
-
-
- Returns the current editor.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.getOrientation()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Returns the orientation.
-
-
- Returns the orientation.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.getSplits()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Returns the number of splits.
-
-
- Returns the number of splits.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.hasRedo()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.hasUndo()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.redo()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.reset()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.resize()
-
-
-
-
-
-
- Resizes the editor.
-
-
- Resizes the editor.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.setSession(EditSession session, Number idx)
-
-
-
-
-
-
-
-
-
- Arguments
session EditSession Required. The new edit session
-idx Number Required. The editor's index you're interested in
-Sets a new EditSession for the indicated editor.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.setSplits()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.undo()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - Split.UndoManagerProxy()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api/token_iterator.html b/api/token_iterator.html
deleted file mode 100644
index 0252712fdf2..00000000000
--- a/api/token_iterator.html
+++ /dev/null
@@ -1,248 +0,0 @@
-
-
-
-
-
-
- TokenIterator
-
-
-
-
-
-
- This class provides an essay way to treat the document as a stream of tokens, and provides methods to iterate over these tokens.
-
-
-
-
- Constructors
-
-
-
-
-
- -
-
- - new TokenIterator(EditSession session, Number initialRow, Number initialColumn)
-
-
-
-
-
-
- Creates a new token iterator object. The initial token index is set to the provided row and column coordinates.
-
-
- Creates a new token iterator object. The initial token index is set to the provided row and column coordinates.
-
- Arguments
session EditSession Required. The session to associate with
-initialRow Number Required. The row to start the tokenizing at
-initialColumn Number Required. The column to start the tokenizing at
-
-
-
-
-
-
- Methods
-
-
-
-
-
- -
-
- - TokenIterator.getCurrentToken()
-
-
-
- - String
-
-
-
-
-
-
-
-
- Returns the current tokenized string.
-
-
- Returns the current tokenized string.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - TokenIterator.getCurrentTokenColumn()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Returns the current column.
-
-
- Returns the current column.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - TokenIterator.getCurrentTokenRow()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Returns the current row.
-
-
- Returns the current row.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - TokenIterator.stepBackward()
-
-
-
- - String
-
-
-
-
-
-
-
-
- Tokenizes all the items from the current point to the row prior in the document.
-
-
- Tokenizes all the items from the current point to the row prior in the document.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - TokenIterator.stepForward()
-
-
-
- - String
-
-
-
-
-
-
-
-
- Tokenizes all the items from the current point until the next row in the document. If the current point is at the end of the file, this function returns null. Otherwise, it returns the tokenized string.
-
-
- Tokenizes all the items from the current point until the next row in the document. If the current point is at the end of the file, this function returns null. Otherwise, it returns the tokenized string.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/api/tokenizer.html b/api/tokenizer.html
deleted file mode 100644
index b45bd58da21..00000000000
--- a/api/tokenizer.html
+++ /dev/null
@@ -1,122 +0,0 @@
-
-
-
-
-
-
- Tokenizer
-
-
-
-
-
-
- This class takes a set of highlighting rules, and creates a tokenizer out of them. For more information, see the wiki on extending highlighters.
-
-
-
-
- Constructors
-
-
-
-
-
-
-
-
- Methods
-
-
-
-
-
- Returns an object containing two properties: tokens, which contains all the tokens; and state, the current state.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api/undomanager.html b/api/undomanager.html
deleted file mode 100644
index ea43a7fbc58..00000000000
--- a/api/undomanager.html
+++ /dev/null
@@ -1,271 +0,0 @@
-
-
-
-
-
-
- UndoManager
-
-
-
-
-
-
- This object maintains the undo stack for an EditSession.
-
-
-
-
- Constructors
-
-
-
-
-
- -
-
- - new UndoManager()
-
-
-
-
-
-
- Resets the current undo state and creates a new UndoManager.
-
-
- Resets the current undo state and creates a new UndoManager.
-
-
-
-
-
-
- Methods
-
-
-
-
-
-
-
-
- -
-
- - UndoManager.hasRedo()
-
-
-
- - Boolean
-
-
-
-
-
-
-
-
- Returns true if there are redo operations left to perform.
-
-
- Returns true if there are redo operations left to perform.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - UndoManager.hasUndo()
-
-
-
- - Boolean
-
-
-
-
-
-
-
-
- Returns true if there are undo operations left to perform.
-
-
- Returns true if there are undo operations left to perform.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - UndoManager.redo(Boolean dontSelect)
-
-
-
-
-
-
- Perform a redo operation on the document, reimplementing the last change.
-
-
- Perform a redo operation on the document, reimplementing the last change.
-
- Arguments
dontSelect Boolean Required. If true, doesn't select the range of where the change occurred
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - UndoManager.reset()
-
-
-
-
-
-
- Destroys the stack of undo and redo redo operations.
-
-
- Destroys the stack of undo and redo redo operations.
-
-
-
-
-
-
-
-
-
-
-
- Perform an undo operation on the document, reverting the last change.
-
-
- Perform an undo operation on the document, reverting the last change.
-
- Arguments
dontSelect Boolean Required. If true, doesn't select the range of where the change occurred
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/api/virtual_renderer.html b/api/virtual_renderer.html
deleted file mode 100644
index abdfecc8756..00000000000
--- a/api/virtual_renderer.html
+++ /dev/null
@@ -1,2190 +0,0 @@
-
-
-
-
-
-
- VirtualRenderer
-
-
-
-
-
-
- The class that is responsible for drawing everything you see on the screen!
-
-
-
-
- Constructors
-
-
-
-
-
- -
-
- - new VirtualRenderer(DOMElement container, String theme)
-
-
-
-
-
-
- Constructs a new VirtualRenderer within the container specified, applying the given theme.
-
-
- Constructs a new VirtualRenderer within the container specified, applying the given theme.
-
- Arguments
container DOMElement Required. The root element of the editor
-theme String Required. The starting theme
-
-
-
-
-
-
- Methods
-
-
-
-
-
- -
-
- - VirtualRenderer._loadTheme()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Deprecated; (moved to EditSession)
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.adjustWrapLimit()
-
-
-
-
-
-
- Adjusts the wrap limit, which is the number of characters that can fit within the width of the edit area on screen.
-
-
- Adjusts the wrap limit, which is the number of characters that can fit within the width of the edit area on screen.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.alignCursor()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.animateScrolling()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.destroy()
-
-
-
-
-
-
- Destroys the text and cursor layers for this renderer.
-
-
- Destroys the text and cursor layers for this renderer.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getAnimatedScroll()
-
-
-
- - Boolean
-
-
-
-
-
-
-
-
- Returns whether an animated scroll happens or not.
-
-
- Returns whether an animated scroll happens or not.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getContainerElement()
-
-
-
- - DOMElement
-
-
-
-
-
-
-
-
- Returns the root element containing this renderer.
-
-
- Returns the root element containing this renderer.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getDisplayIndentGuides()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getFadeFoldWidgets()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getFirstFullyVisibleRow()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Returns the index of the first fully visible row. "Fully" here means that the characters in the row are not truncated; that the top and the bottom of the row are on the screen.
-
-
- Returns the index of the first fully visible row. "Fully" here means that the characters in the row are not truncated; that the top and the bottom of the row are on the screen.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getFirstVisibleRow()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Returns the index of the first visible row.
-
-
- Returns the index of the first visible row.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getHighlightGutterLine()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getHScrollBarAlwaysVisible()
-
-
-
- - Boolean
-
-
-
-
-
-
-
-
- Returns whether the horizontal scrollbar is set to be always visible.
-
-
- Returns whether the horizontal scrollbar is set to be always visible.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getLastFullyVisibleRow()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Returns the index of the last fully visible row. "Fully" here means that the characters in the row are not truncated; that the top and the bottom of the row are on the screen.
-
-
- Returns the index of the last fully visible row. "Fully" here means that the characters in the row are not truncated; that the top and the bottom of the row are on the screen.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getLastVisibleRow()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Returns the index of the last visible row.
-
-
- Returns the index of the last visible row.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getMouseEventTarget()
-
-
-
- - DOMElement
-
-
-
-
-
-
-
-
- Returns the element that the mouse events are attached to
-
-
- Returns the element that the mouse events are attached to
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getPrintMarginColumn()
-
-
-
- - Boolean
-
-
-
-
-
-
-
-
- Returns whether the print margin column is being shown or not.
-
-
- Returns whether the print margin column is being shown or not.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getScrollBottomRow()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Returns the last visible row, regardless of whether it's fully visible or not.
-
-
- Returns the last visible row, regardless of whether it's fully visible or not.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getScrollLeft()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Returns the value of the distance between the left of the editor and the leftmost part of the visible content.
-
-
- Returns the value of the distance between the left of the editor and the leftmost part of the visible content.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getScrollTop()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Returns the value of the distance between the top of the editor and the topmost part of the visible content.
-
-
- Returns the value of the distance between the top of the editor and the topmost part of the visible content.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getScrollTopRow()
-
-
-
- - Number
-
-
-
-
-
-
-
-
- Returns the first visible row, regardless of whether it's fully visible or not.
-
-
- Returns the first visible row, regardless of whether it's fully visible or not.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getShowGutter()
-
-
-
- - Boolean
-
-
-
-
-
-
-
-
- Returns true if the gutter is being shown.
-
-
- Returns true if the gutter is being shown.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getShowInvisibles()
-
-
-
- - Boolean
-
-
-
-
-
-
-
-
- Returns whether invisible characters are being shown or not.
-
-
- Returns whether invisible characters are being shown or not.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getShowPrintMargin()
-
-
-
- - Boolean
-
-
-
-
-
-
-
-
- Returns whether the print margin is being shown or not.
-
-
- Returns whether the print margin is being shown or not.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getTextAreaContainer()
-
-
-
- - DOMElement
-
-
-
-
-
-
-
-
- Returns the element to which the hidden text area is added.
-
-
- Returns the element to which the hidden text area is added.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.getTheme()
-
-
-
- - String
-
-
-
-
-
-
-
-
- Returns the path of the current theme.
-
-
- Returns the path of the current theme.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.hideComposition()
-
-
-
-
-
-
- Hides the current composition.
-
-
- Hides the current composition.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.hideCursor()
-
-
-
-
-
-
- Hides the cursor icon.
-
-
- Hides the cursor icon.
-
-
-
-
-
-
-
-
-
-
-
- Returns true if you can still scroll by either parameter; in other words, you haven't reached the end of the file or line.
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.onChangeTabSize()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.onGutterResize()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Triggers a resize of the editor.
-
-
- Triggers a resize of the editor.
-
- Arguments
force Boolean Required. If true, recomputes the size, even if the height and width haven't changed
-gutterWidth Number Required. The width of the gutter in pixels
-width Number Required. The width of the editor in pixels
-height Number Required. The height of the editor, in pixels
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.pixelToScreenCoordinates()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Deprecated; (moved to EditSession)
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.screenToTextCoordinates()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.scrollSelectionIntoView()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Gracefully scrolls the editor to the row indicated.
-
-
- Gracefully scrolls the editor to the row indicated.
-
- Arguments
line Number Required. A line number
-center Boolean Required. If true, centers the editor the to indicated line
-animate Boolean Required. If true animates scrolling
-callback Function Required. Function to be called after the animation has finished
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.setAnimatedScroll(Boolean shouldAnimate)
-
-
-
-
-
-
- Identifies whether you want to have an animated scroll or not.
-
-
- Identifies whether you want to have an animated scroll or not.
-
- Arguments
shouldAnimate Boolean Required. Set to true to show animated scrolls
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.setDisplayIndentGuides()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.setFadeFoldWidgets()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.setHighlightGutterLine()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.setHScrollBarAlwaysVisible(Boolean alwaysVisible)
-
-
-
-
-
-
- Identifies whether you want to show the horizontal scrollbar or not.
-
-
- Identifies whether you want to show the horizontal scrollbar or not.
-
- Arguments
alwaysVisible Boolean Required. Set to true to make the horizontal scroll bar visible
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.setPrintMarginColumn(Boolean showPrintMargin)
-
-
-
-
-
-
- Identifies whether you want to show the print margin column or not.
-
-
- Identifies whether you want to show the print margin column or not.
-
- Arguments
showPrintMargin Boolean Required. Set to true to show the print margin column
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.setSession(Object session)
-
-
-
-
-
-
- Associates the renderer with an EditSession.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.setShowInvisibles(Boolean showInvisibles)
-
-
-
-
-
-
- Identifies whether you want to show invisible characters or not.
-
-
- Identifies whether you want to show invisible characters or not.
-
- Arguments
showInvisibles Boolean Required. Set to true to show invisibles
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.setShowPrintMargin(Boolean showPrintMargin)
-
-
-
-
-
-
- Identifies whether you want to show the print margin or not.
-
-
- Identifies whether you want to show the print margin or not.
-
- Arguments
showPrintMargin Boolean Required. Set to true to show the print margin
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.setStyle()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.setTheme(String theme)
-
-
-
-
-
-
- Sets a new theme for the editor. theme should exist, and be a directory path, like ace/theme/textmate.
-
-
- Sets a new theme for the editor. theme should exist, and be a directory path, like ace/theme/textmate.
-
- Arguments
theme String Required. The path to a theme
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.showCursor()
-
-
-
-
-
-
- Shows the cursor icon.
-
-
- Shows the cursor icon.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.updateBackMarkers()
-
-
-
-
-
-
- Schedules an update to all the back markers in the document.
-
-
- Schedules an update to all the back markers in the document.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.updateCharacterSize()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.updateCursor()
-
-
-
-
-
-
- Updates the cursor icon.
-
-
- Updates the cursor icon.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.updateFontSize()
-
-
-
-
-
-
- Updates the font size.
-
-
- Updates the font size.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.updateFrontMarkers()
-
-
-
-
-
-
- Schedules an update to all the front markers in the document.
-
-
- Schedules an update to all the front markers in the document.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.updateText()
-
-
-
-
-
-
- Triggers a full update of the text, for all the rows.
-
-
- Triggers a full update of the text, for all the rows.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.visualizeBlur()
-
-
-
-
-
-
- Blurs the current container.
-
-
- Blurs the current container.
-
-
-
-
-
-
-
-
-
-
-
- -
-
- - VirtualRenderer.visualizeFocus()
-
-
-
-
-
-
- Focuses the current container.
-
-
- Focuses the current container.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/build b/build
index c915483e909..56a46777e92 160000
--- a/build
+++ b/build
@@ -1 +1 @@
-Subproject commit c915483e909bc1b3061cabaaf932f796c723226b
+Subproject commit 56a46777e92abd648dce1c29069c13a74480f9a5
diff --git a/demo/autoresize.html b/demo/autoresize.html
index 7b7fbf48b74..00f8f1b7fb6 100644
--- a/demo/autoresize.html
+++ b/demo/autoresize.html
@@ -9,12 +9,13 @@
.ace_editor {
border: 1px solid lightgray;
margin: auto;
- height: 200px;
width: 80%;
}
.scrollmargin {
height: 80px;
- text-align: center;
+ }
+ .scrollmargin2 {
+ height: 180px;
}
@@ -26,16 +27,19 @@
+
+
+
diff --git a/demo/code_lens.html b/demo/code_lens.html
new file mode 100644
index 00000000000..2e721f967cd
--- /dev/null
+++ b/demo/code_lens.html
@@ -0,0 +1,124 @@
+
+
+
+
+ ACE Code Lens demo
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/csp.html b/demo/csp.html
new file mode 100644
index 00000000000..a03f0fcae37
--- /dev/null
+++ b/demo/csp.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+ Editor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/diff/examples/editor.16.js b/demo/diff/examples/editor.16.js
new file mode 100644
index 00000000000..8fc5808b14d
--- /dev/null
+++ b/demo/diff/examples/editor.16.js
@@ -0,0 +1,2948 @@
+"use strict";
+
+var oop = require("./lib/oop");
+var dom = require("./lib/dom");
+var lang = require("./lib/lang");
+var useragent = require("./lib/useragent");
+var TextInput = require("./keyboard/textinput").TextInput;
+var MouseHandler = require("./mouse/mouse_handler").MouseHandler;
+var FoldHandler = require("./mouse/fold_handler").FoldHandler;
+var KeyBinding = require("./keyboard/keybinding").KeyBinding;
+var EditSession = require("./edit_session").EditSession;
+var Search = require("./search").Search;
+var Range = require("./range").Range;
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+var CommandManager = require("./commands/command_manager").CommandManager;
+var defaultCommands = require("./commands/default_commands").commands;
+var config = require("./config");
+var TokenIterator = require("./token_iterator").TokenIterator;
+var LineWidgets = require("./line_widgets").LineWidgets;
+
+var clipboard = require("./clipboard");
+
+/**
+ * The main entry point into the Ace functionality.
+ *
+ * The `Editor` manages the [[EditSession]] (which manages [[Document]]s), as well as the [[VirtualRenderer]], which draws everything to the screen.
+ *
+ * Event sessions dealing with the mouse and keyboard are bubbled up from `Document` to the `Editor`, which decides what to do with them.
+ * @class Editor
+ **/
+
+/**
+ * Creates a new `Editor` object.
+ *
+ * @param {VirtualRenderer} renderer Associated `VirtualRenderer` that draws everything
+ * @param {EditSession} session The `EditSession` to refer to
+ *
+ *
+ * @constructor
+ **/
+var Editor = function(renderer, session, options) {
+ this.$toDestroy = [];
+ var container = renderer.getContainerElement();
+ this.container = container;
+ this.renderer = renderer;
+ this.id = "editor" + (++Editor.$uid);
+
+ this.commands = new CommandManager(useragent.isMac ? "mac" : "win", defaultCommands);
+ if (typeof document == "object") {
+ this.textInput = new TextInput(renderer.getTextAreaContainer(), this);
+ this.renderer.textarea = this.textInput.getElement();
+ // TODO detect touch event support
+ this.$mouseHandler = new MouseHandler(this);
+ new FoldHandler(this);
+ }
+
+ this.keyBinding = new KeyBinding(this);
+
+ this.$search = new Search().set({
+ wrap: true
+ });
+
+ this.$historyTracker = this.$historyTracker.bind(this);
+ this.commands.on("exec", this.$historyTracker);
+
+ this.$initOperationListeners();
+
+ this._$emitInputEvent = lang.delayedCall(function() {
+ this._signal("input", {});
+ if (this.session && !this.session.destroyed)
+ this.session.bgTokenizer.scheduleStart();
+ }.bind(this));
+
+ this.on("change", function(_, _self) {
+ _self._$emitInputEvent.schedule(31);
+ });
+
+ this.setSession(session || options && options.session || new EditSession(""));
+ config.resetOptions(this);
+ if (options)
+ this.setOptions(options);
+ config._signal("editor", this);
+};
+
+Editor.$uid = 0;
+
+(function(){
+
+ oop.implement(this, EventEmitter);
+
+ this.$initOperationListeners = function() {
+ this.commands.on("exec", this.startOperation.bind(this), true);
+ this.commands.on("afterExec", this.endOperation.bind(this), true);
+
+ this.$opResetTimer = lang.delayedCall(this.endOperation.bind(this, true));
+
+ // todo: add before change events?
+ this.on("change", function() {
+ if (!this.curOp) {
+ this.startOperation();
+ this.curOp.selectionBefore = this.$lastSel;
+ }
+ this.curOp.docChanged = true;
+ }.bind(this), true);
+
+ this.on("changeSelection", function() {
+ if (!this.curOp) {
+ this.startOperation();
+ this.curOp.selectionBefore = this.$lastSel;
+ }
+ this.curOp.selectionChanged = true;
+ }.bind(this), true);
+ };
+
+ this.curOp = null;
+ this.prevOp = {};
+ this.startOperation = function(commandEvent) {
+ if (this.curOp) {
+ if (!commandEvent || this.curOp.command)
+ return;
+ this.prevOp = this.curOp;
+ }
+ if (!commandEvent) {
+ this.previousCommand = null;
+ commandEvent = {};
+ }
+
+ this.$opResetTimer.schedule();
+ this.curOp = this.session.curOp = {
+ command: commandEvent.command || {},
+ args: commandEvent.args,
+ scrollTop: this.renderer.scrollTop
+ };
+ this.curOp.selectionBefore = this.selection.toJSON();
+ };
+
+ this.endOperation = function(e) {
+ if (this.curOp && this.session) {
+ if (e && e.returnValue === false || !this.session)
+ return (this.curOp = null);
+ if (e == true && this.curOp.command && this.curOp.command.name == "mouse")
+ return;
+ this._signal("beforeEndOperation");
+ if (!this.curOp) return;
+ var command = this.curOp.command;
+ var scrollIntoView = command && command.scrollIntoView;
+ if (scrollIntoView) {
+ switch (scrollIntoView) {
+ case "center-animate":
+ scrollIntoView = "animate";
+ /* fall through */
+ case "center":
+ this.renderer.scrollCursorIntoView(null, 0.5);
+ break;
+ case "animate":
+ case "cursor":
+ this.renderer.scrollCursorIntoView();
+ break;
+ case "selectionPart":
+ var range = this.selection.getRange();
+ var config = this.renderer.layerConfig;
+ if (range.start.row >= config.lastRow || range.end.row <= config.firstRow) {
+ this.renderer.scrollSelectionIntoView(this.selection.anchor, this.selection.lead);
+ }
+ break;
+ default:
+ break;
+ }
+ if (scrollIntoView == "animate")
+ this.renderer.animateScrolling(this.curOp.scrollTop);
+ }
+ var sel = this.selection.toJSON();
+ this.curOp.selectionAfter = sel;
+ this.$lastSel = this.selection.toJSON();
+
+ // console.log(this.$lastSel+" endOP")
+ this.session.getUndoManager().addSelection(sel);
+ this.prevOp = this.curOp;
+ this.curOp = null;
+ }
+ };
+
+ // TODO use property on commands instead of this
+ this.$mergeableCommands = ["backspace", "del", "insertstring"];
+ this.$historyTracker = function(e) {
+ if (!this.$mergeUndoDeltas)
+ return;
+
+ var prev = this.prevOp;
+ var mergeableCommands = this.$mergeableCommands;
+ // previous command was the same
+ var shouldMerge = prev.command && (e.command.name == prev.command.name);
+ if (e.command.name == "insertstring") {
+ var text = e.args;
+ if (this.mergeNextCommand === undefined)
+ this.mergeNextCommand = true;
+
+ shouldMerge = shouldMerge
+ && this.mergeNextCommand // previous command allows to coalesce with
+ && (!/\s/.test(text) || /\s/.test(prev.args)); // previous insertion was of same type
+
+ this.mergeNextCommand = true;
+ } else {
+ shouldMerge = shouldMerge
+ && mergeableCommands.indexOf(e.command.name) !== -1; // the command is mergeable
+ }
+
+ if (
+ this.$mergeUndoDeltas != "always"
+ && Date.now() - this.sequenceStartTime > 2000
+ ) {
+ shouldMerge = false; // the sequence is too long
+ }
+
+ if (shouldMerge)
+ this.session.mergeUndoDeltas = true;
+ else if (mergeableCommands.indexOf(e.command.name) !== -1)
+ this.sequenceStartTime = Date.now();
+ };
+
+ /**
+ * Sets a new key handler, such as "vim" or "windows".
+ * @param {String} keyboardHandler The new key handler
+ *
+ **/
+ this.setKeyboardHandler = function(keyboardHandler, cb) {
+ if (keyboardHandler && typeof keyboardHandler === "string" && keyboardHandler != "ace") {
+ this.$keybindingId = keyboardHandler;
+ var _self = this;
+ config.loadModule(["keybinding", keyboardHandler], function(module) {
+ if (_self.$keybindingId == keyboardHandler)
+ _self.keyBinding.setKeyboardHandler(module && module.handler);
+ cb && cb();
+ });
+ } else {
+ this.$keybindingId = null;
+ this.keyBinding.setKeyboardHandler(keyboardHandler);
+ cb && cb();
+ }
+ };
+
+ /**
+ * Returns the keyboard handler, such as "vim" or "windows".
+ *
+ * @returns {String}
+ *
+ **/
+ this.getKeyboardHandler = function() {
+ return this.keyBinding.getKeyboardHandler();
+ };
+
+
+ /**
+ * Emitted whenever the [[EditSession]] changes.
+ * @event changeSession
+ * @param {Object} e An object with two properties, `oldSession` and `session`, that represent the old and new [[EditSession]]s.
+ *
+ **/
+ /**
+ * Sets a new editsession to use. This method also emits the `'changeSession'` event.
+ * @param {EditSession} session The new session to use
+ *
+ **/
+ this.setSession = function(session) {
+ if (this.session == session)
+ return;
+
+ // make sure operationEnd events are not emitted to wrong session
+ if (this.curOp) this.endOperation();
+ this.curOp = {};
+
+ var oldSession = this.session;
+ if (oldSession) {
+ this.session.off("change", this.$onDocumentChange);
+ this.session.off("changeMode", this.$onChangeMode);
+ this.session.off("tokenizerUpdate", this.$onTokenizerUpdate);
+ this.session.off("changeTabSize", this.$onChangeTabSize);
+ this.session.off("changeWrapLimit", this.$onChangeWrapLimit);
+ this.session.off("changeWrapMode", this.$onChangeWrapMode);
+ this.session.off("changeFold", this.$onChangeFold);
+ this.session.off("changeFrontMarker", this.$onChangeFrontMarker);
+ this.session.off("changeBackMarker", this.$onChangeBackMarker);
+ this.session.off("changeBreakpoint", this.$onChangeBreakpoint);
+ this.session.off("changeAnnotation", this.$onChangeAnnotation);
+ this.session.off("changeOverwrite", this.$onCursorChange);
+ this.session.off("changeScrollTop", this.$onScrollTopChange);
+ this.session.off("changeScrollLeft", this.$onScrollLeftChange);
+
+ var selection = this.session.getSelection();
+ selection.off("changeCursor", this.$onCursorChange);
+ selection.off("changeSelection", this.$onSelectionChange);
+ }
+
+ this.session = session;
+ if (session) {
+ this.$onDocumentChange = this.onDocumentChange.bind(this);
+ session.on("change", this.$onDocumentChange);
+ this.renderer.setSession(session);
+
+ this.$onChangeMode = this.onChangeMode.bind(this);
+ session.on("changeMode", this.$onChangeMode);
+
+ this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this);
+ session.on("tokenizerUpdate", this.$onTokenizerUpdate);
+
+ this.$onChangeTabSize = this.renderer.onChangeTabSize.bind(this.renderer);
+ session.on("changeTabSize", this.$onChangeTabSize);
+
+ this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this);
+ session.on("changeWrapLimit", this.$onChangeWrapLimit);
+
+ this.$onChangeWrapMode = this.onChangeWrapMode.bind(this);
+ session.on("changeWrapMode", this.$onChangeWrapMode);
+
+ this.$onChangeFold = this.onChangeFold.bind(this);
+ session.on("changeFold", this.$onChangeFold);
+
+ this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this);
+ this.session.on("changeFrontMarker", this.$onChangeFrontMarker);
+
+ this.$onChangeBackMarker = this.onChangeBackMarker.bind(this);
+ this.session.on("changeBackMarker", this.$onChangeBackMarker);
+
+ this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this);
+ this.session.on("changeBreakpoint", this.$onChangeBreakpoint);
+
+ this.$onChangeAnnotation = this.onChangeAnnotation.bind(this);
+ this.session.on("changeAnnotation", this.$onChangeAnnotation);
+
+ this.$onCursorChange = this.onCursorChange.bind(this);
+ this.session.on("changeOverwrite", this.$onCursorChange);
+
+ this.$onScrollTopChange = this.onScrollTopChange.bind(this);
+ this.session.on("changeScrollTop", this.$onScrollTopChange);
+
+ this.$onScrollLeftChange = this.onScrollLeftChange.bind(this);
+ this.session.on("changeScrollLeft", this.$onScrollLeftChange);
+
+ this.selection = session.getSelection();
+ this.selection.on("changeCursor", this.$onCursorChange);
+
+ this.$onSelectionChange = this.onSelectionChange.bind(this);
+ this.selection.on("changeSelection", this.$onSelectionChange);
+
+ this.onChangeMode();
+
+ this.onCursorChange();
+
+ this.onScrollTopChange();
+ this.onScrollLeftChange();
+ this.onSelectionChange();
+ this.onChangeFrontMarker();
+ this.onChangeBackMarker();
+ this.onChangeBreakpoint();
+ this.onChangeAnnotation();
+ this.session.getUseWrapMode() && this.renderer.adjustWrapLimit();
+ this.renderer.updateFull();
+ } else {
+ this.selection = null;
+ this.renderer.setSession(session);
+ }
+
+ this._signal("changeSession", {
+ session: session,
+ oldSession: oldSession
+ });
+
+ this.curOp = null;
+
+ oldSession && oldSession._signal("changeEditor", {oldEditor: this});
+ session && session._signal("changeEditor", {editor: this});
+
+ if (session && !session.destroyed)
+ session.bgTokenizer.scheduleStart();
+ };
+
+ /**
+ * Returns the current session being used.
+ * @returns {EditSession}
+ **/
+ this.getSession = function() {
+ return this.session;
+ };
+
+ /**
+ * Sets the current document to `val`.
+ * @param {String} val The new value to set for the document
+ * @param {Number} cursorPos Where to set the new value. `undefined` or 0 is selectAll, -1 is at the document start, and 1 is at the end
+ *
+ * @returns {String} The current document value
+ * @related Document.setValue
+ **/
+ this.setValue = function(val, cursorPos) {
+ this.session.doc.setValue(val);
+
+ if (!cursorPos)
+ this.selectAll();
+ else if (cursorPos == 1)
+ this.navigateFileEnd();
+ else if (cursorPos == -1)
+ this.navigateFileStart();
+
+ return val;
+ };
+
+ /**
+ * Returns the current session's content.
+ *
+ * @returns {String}
+ * @related EditSession.getValue
+ **/
+ this.getValue = function() {
+ return this.session.getValue();
+ };
+
+ /**
+ *
+ * Returns the currently highlighted selection.
+ * @returns {Selection} The selection object
+ **/
+ this.getSelection = function() {
+ return this.selection;
+ };
+
+ /**
+ * {:VirtualRenderer.onResize}
+ * @param {Boolean} force If `true`, recomputes the size, even if the height and width haven't changed
+ *
+ *
+ * @related VirtualRenderer.onResize
+ **/
+ this.resize = function(force) {
+ this.renderer.onResize(force);
+ };
+
+ /**
+ * {:VirtualRenderer.setTheme}
+ * @param {String} theme The path to a theme
+ * @param {Function} cb optional callback called when theme is loaded
+ **/
+ this.setTheme = function(theme, cb) {
+ this.renderer.setTheme(theme, cb);
+ };
+
+ /**
+ * {:VirtualRenderer.getTheme}
+ *
+ * @returns {String} The set theme
+ * @related VirtualRenderer.getTheme
+ **/
+ this.getTheme = function() {
+ return this.renderer.getTheme();
+ };
+
+ /**
+ * {:VirtualRenderer.setStyle}
+ * @param {String} style A class name
+ *
+ *
+ * @related VirtualRenderer.setStyle
+ **/
+ this.setStyle = function(style) {
+ this.renderer.setStyle(style);
+ };
+
+ /**
+ * {:VirtualRenderer.unsetStyle}
+ * @related VirtualRenderer.unsetStyle
+ **/
+ this.unsetStyle = function(style) {
+ this.renderer.unsetStyle(style);
+ };
+
+ /**
+ * Gets the current font size of the editor text.
+ */
+ this.getFontSize = function () {
+ return this.getOption("fontSize") ||
+ dom.computedStyle(this.container).fontSize;
+ };
+
+ /**
+ * Set a new font size (in pixels) for the editor text.
+ * @param {String} size A font size ( _e.g._ "12px")
+ *
+ *
+ **/
+ this.setFontSize = function(size) {
+ this.setOption("fontSize", size);
+ };
+
+ this.$highlightBrackets = function() {
+ if (this.$highlightPending) {
+ return;
+ }
+
+ // perform highlight async to not block the browser during navigation
+ var self = this;
+ this.$highlightPending = true;
+ setTimeout(function () {
+ self.$highlightPending = false;
+ var session = self.session;
+ if (!session || session.destroyed) return;
+ if (session.$bracketHighlight) {
+ session.$bracketHighlight.markerIds.forEach(function(id) {
+ session.removeMarker(id);
+ });
+ session.$bracketHighlight = null;
+ }
+ var pos = self.getCursorPosition();
+ var handler = self.getKeyboardHandler();
+ var isBackwards = handler && handler.$getDirectionForHighlight && handler.$getDirectionForHighlight(self);
+ var ranges = session.getMatchingBracketRanges(pos, isBackwards);
+
+ if (!ranges) {
+ var iterator = new TokenIterator(session, pos.row, pos.column);
+ var token = iterator.getCurrentToken();
+
+ if (token && /\b(?:tag-open|tag-name)/.test(token.type)) {
+ var tagNamesRanges = session.getMatchingTags(pos);
+ if (tagNamesRanges) ranges = [tagNamesRanges.openTagName, tagNamesRanges.closeTagName];
+ }
+ }
+ if (!ranges && session.$mode.getMatching)
+ ranges = session.$mode.getMatching(self.session);
+ if (!ranges) {
+ if (self.getHighlightIndentGuides()) self.renderer.$textLayer.$highlightIndentGuide();
+ return;
+ }
+
+ var markerType = "ace_bracket";
+ if (!Array.isArray(ranges)) {
+ ranges = [ranges];
+ } else if (ranges.length == 1) {
+ markerType = "ace_error_bracket";
+ }
+
+ // show adjacent ranges as one
+ if (ranges.length == 2) {
+ if (Range.comparePoints(ranges[0].end, ranges[1].start) == 0)
+ ranges = [Range.fromPoints(ranges[0].start, ranges[1].end)];
+ else if (Range.comparePoints(ranges[0].start, ranges[1].end) == 0)
+ ranges = [Range.fromPoints(ranges[1].start, ranges[0].end)];
+ }
+
+ session.$bracketHighlight = {
+ ranges: ranges,
+ markerIds: ranges.map(function(range) {
+ return session.addMarker(range, markerType, "text");
+ })
+ };
+ if (self.getHighlightIndentGuides()) self.renderer.$textLayer.$highlightIndentGuide();
+ }, 50);
+ };
+
+ /**
+ *
+ * Brings the current `textInput` into focus.
+ **/
+ this.focus = function() {
+ this.textInput.focus();
+ };
+
+ /**
+ * Returns `true` if the current `textInput` is in focus.
+ * @return {Boolean}
+ **/
+ this.isFocused = function() {
+ return this.textInput.isFocused();
+ };
+
+ /**
+ *
+ * Blurs the current `textInput`.
+ **/
+ this.blur = function() {
+ this.textInput.blur();
+ };
+
+ /**
+ * Emitted once the editor comes into focus.
+ * @event focus
+ *
+ *
+ **/
+ this.onFocus = function(e) {
+ if (this.$isFocused)
+ return;
+ this.$isFocused = true;
+ this.renderer.showCursor();
+ this.renderer.visualizeFocus();
+ this._emit("focus", e);
+ };
+
+ /**
+ * Emitted once the editor has been blurred.
+ * @event blur
+ *
+ *
+ **/
+ this.onBlur = function(e) {
+ if (!this.$isFocused)
+ return;
+ this.$isFocused = false;
+ this.renderer.hideCursor();
+ this.renderer.visualizeBlur();
+ this._emit("blur", e);
+ };
+
+ this.$cursorChange = function() {
+ this.renderer.updateCursor();
+ this.$highlightBrackets();
+ this.$updateHighlightActiveLine();
+ };
+
+ /**
+ * Emitted whenever the document is changed.
+ * @event change
+ * @param {Object} e Contains a single property, `data`, which has the delta of changes
+ *
+ *
+ *
+ **/
+ this.onDocumentChange = function(delta) {
+ // Rerender and emit "change" event.
+ var wrap = this.session.$useWrapMode;
+ var lastRow = (delta.start.row == delta.end.row ? delta.end.row : Infinity);
+ this.renderer.updateLines(delta.start.row, lastRow, wrap);
+
+ this._signal("change", delta);
+
+ // Update cursor because tab characters can influence the cursor position.
+ this.$cursorChange();
+ };
+
+ this.onTokenizerUpdate = function(e) {
+ var rows = e.data;
+ this.renderer.updateLines(rows.first, rows.last);
+ };
+
+
+ this.onScrollTopChange = function() {
+ this.renderer.scrollToY(this.session.getScrollTop());
+ };
+
+ this.onScrollLeftChange = function() {
+ this.renderer.scrollToX(this.session.getScrollLeft());
+ };
+
+ /**
+ * Emitted when the selection changes.
+ *
+ **/
+ this.onCursorChange = function() {
+ this.$cursorChange();
+ this._signal("changeSelection");
+ };
+
+ this.$updateHighlightActiveLine = function() {
+ var session = this.getSession();
+
+ var highlight;
+ if (this.$highlightActiveLine) {
+ if (this.$selectionStyle != "line" || !this.selection.isMultiLine())
+ highlight = this.getCursorPosition();
+ if (this.renderer.theme && this.renderer.theme.$selectionColorConflict && !this.selection.isEmpty())
+ highlight = false;
+ if (this.renderer.$maxLines && this.session.getLength() === 1 && !(this.renderer.$minLines > 1))
+ highlight = false;
+ }
+
+ if (session.$highlightLineMarker && !highlight) {
+ session.removeMarker(session.$highlightLineMarker.id);
+ session.$highlightLineMarker = null;
+ } else if (!session.$highlightLineMarker && highlight) {
+ var range = new Range(highlight.row, highlight.column, highlight.row, Infinity);
+ range.id = session.addMarker(range, "ace_active-line", "screenLine");
+ session.$highlightLineMarker = range;
+ } else if (highlight) {
+ session.$highlightLineMarker.start.row = highlight.row;
+ session.$highlightLineMarker.end.row = highlight.row;
+ session.$highlightLineMarker.start.column = highlight.column;
+ session._signal("changeBackMarker");
+ }
+ };
+
+ this.onSelectionChange = function(e) {
+ var session = this.session;
+
+ if (session.$selectionMarker) {
+ session.removeMarker(session.$selectionMarker);
+ }
+ session.$selectionMarker = null;
+
+ if (!this.selection.isEmpty()) {
+ var range = this.selection.getRange();
+ var style = this.getSelectionStyle();
+ session.$selectionMarker = session.addMarker(range, "ace_selection", style);
+ } else {
+ this.$updateHighlightActiveLine();
+ }
+
+ var re = this.$highlightSelectedWord && this.$getSelectionHighLightRegexp();
+ this.session.highlight(re);
+
+ this._signal("changeSelection");
+ };
+
+ this.$getSelectionHighLightRegexp = function() {
+ var session = this.session;
+
+ var selection = this.getSelectionRange();
+ if (selection.isEmpty() || selection.isMultiLine())
+ return;
+
+ var startColumn = selection.start.column;
+ var endColumn = selection.end.column;
+ var line = session.getLine(selection.start.row);
+
+ var needle = line.substring(startColumn, endColumn);
+ // maximum allowed size for regular expressions in 32000,
+ // but getting close to it has significant impact on the performance
+ if (needle.length > 5000 || !/[\w\d]/.test(needle))
+ return;
+
+ var re = this.$search.$assembleRegExp({
+ wholeWord: true,
+ caseSensitive: true,
+ needle: needle
+ });
+
+ var wordWithBoundary = line.substring(startColumn - 1, endColumn + 1);
+ if (!re.test(wordWithBoundary))
+ return;
+
+ return re;
+ };
+
+
+ this.onChangeFrontMarker = function() {
+ this.renderer.updateFrontMarkers();
+ };
+
+ this.onChangeBackMarker = function() {
+ this.renderer.updateBackMarkers();
+ };
+
+
+ this.onChangeBreakpoint = function() {
+ this.renderer.updateBreakpoints();
+ };
+
+ this.onChangeAnnotation = function() {
+ this.renderer.setAnnotations(this.session.getAnnotations());
+ };
+
+
+ this.onChangeMode = function(e) {
+ this.renderer.updateText();
+ this._emit("changeMode", e);
+ };
+
+
+ this.onChangeWrapLimit = function() {
+ this.renderer.updateFull();
+ };
+
+ this.onChangeWrapMode = function() {
+ this.renderer.onResize(true);
+ };
+
+
+ this.onChangeFold = function() {
+ // Update the active line marker as due to folding changes the current
+ // line range on the screen might have changed.
+ this.$updateHighlightActiveLine();
+ // TODO: This might be too much updating. Okay for now.
+ this.renderer.updateFull();
+ };
+
+
+ /**
+ * Returns the string of text currently highlighted.
+ * @returns {String}
+ **/
+ this.getSelectedText = function() {
+ return this.session.getTextRange(this.getSelectionRange());
+ };
+
+ /**
+ * Emitted when text is copied.
+ * @event copy
+ * @param {String} text The copied text
+ *
+ **/
+ /**
+ * Returns the string of text currently highlighted.
+ * @returns {String}
+ **/
+ this.getCopyText = function() {
+ var text = this.getSelectedText();
+ var nl = this.session.doc.getNewLineCharacter();
+ var copyLine= false;
+ if (!text && this.$copyWithEmptySelection) {
+ copyLine = true;
+ var ranges = this.selection.getAllRanges();
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ if (i && ranges[i - 1].start.row == range.start.row)
+ continue;
+ text += this.session.getLine(range.start.row) + nl;
+ }
+ }
+ var e = {text: text};
+ this._signal("copy", e);
+ clipboard.lineMode = copyLine ? e.text : false;
+ return e.text;
+ };
+
+ /**
+ * Called whenever a text "copy" happens.
+ **/
+ this.onCopy = function() {
+ this.commands.exec("copy", this);
+ };
+
+ /**
+ * Called whenever a text "cut" happens.
+ **/
+ this.onCut = function() {
+ this.commands.exec("cut", this);
+ };
+
+ /**
+ * Emitted when text is pasted.
+ * @event paste
+ * @param {Object} an object which contains one property, `text`, that represents the text to be pasted. Editing this property will alter the text that is pasted.
+ *
+ *
+ **/
+ /**
+ * Called whenever a text "paste" happens.
+ * @param {String} text The pasted text
+ *
+ *
+ **/
+ this.onPaste = function(text, event) {
+ var e = {text: text, event: event};
+ this.commands.exec("paste", this, e);
+ };
+
+ this.$handlePaste = function(e) {
+ if (typeof e == "string")
+ e = {text: e};
+ this._signal("paste", e);
+ var text = e.text;
+
+ var lineMode = text === clipboard.lineMode;
+ var session = this.session;
+ if (!this.inMultiSelectMode || this.inVirtualSelectionMode) {
+ if (lineMode)
+ session.insert({ row: this.selection.lead.row, column: 0 }, text);
+ else
+ this.insert(text);
+ } else if (lineMode) {
+ this.selection.rangeList.ranges.forEach(function(range) {
+ session.insert({ row: range.start.row, column: 0 }, text);
+ });
+ } else {
+ var lines = text.split(/\r\n|\r|\n/);
+ var ranges = this.selection.rangeList.ranges;
+
+ var isFullLine = lines.length == 2 && (!lines[0] || !lines[1]);
+ if (lines.length != ranges.length || isFullLine)
+ return this.commands.exec("insertstring", this, text);
+
+ for (var i = ranges.length; i--;) {
+ var range = ranges[i];
+ if (!range.isEmpty())
+ session.remove(range);
+
+ session.insert(range.start, lines[i]);
+ }
+ }
+ };
+
+ this.execCommand = function(command, args) {
+ return this.commands.exec(command, this, args);
+ };
+
+ /**
+ * Inserts `text` into wherever the cursor is pointing.
+ * @param {String} text The new text to add
+ *
+ **/
+ this.insert = function(text, pasted) {
+ var session = this.session;
+ var mode = session.getMode();
+ var cursor = this.getCursorPosition();
+
+ if (this.getBehavioursEnabled() && !pasted) {
+ // Get a transform if the current mode wants one.
+ var transform = mode.transformAction(session.getState(cursor.row), 'insertion', this, session, text);
+ if (transform) {
+ if (text !== transform.text) {
+ // keep automatic insertion in a separate delta, unless it is in multiselect mode
+ if (!this.inVirtualSelectionMode) {
+ this.session.mergeUndoDeltas = false;
+ this.mergeNextCommand = false;
+ }
+ }
+ text = transform.text;
+
+ }
+ }
+
+ if (text == "\t")
+ text = this.session.getTabString();
+
+ // remove selected text
+ if (!this.selection.isEmpty()) {
+ var range = this.getSelectionRange();
+ cursor = this.session.remove(range);
+ this.clearSelection();
+ }
+ else if (this.session.getOverwrite() && text.indexOf("\n") == -1) {
+ var range = new Range.fromPoints(cursor, cursor);
+ range.end.column += text.length;
+ this.session.remove(range);
+ }
+
+ if (text == "\n" || text == "\r\n") {
+ var line = session.getLine(cursor.row);
+ if (cursor.column > line.search(/\S|$/)) {
+ var d = line.substr(cursor.column).search(/\S|$/);
+ session.doc.removeInLine(cursor.row, cursor.column, cursor.column + d);
+ }
+ }
+ this.clearSelection();
+
+ var start = cursor.column;
+ var lineState = session.getState(cursor.row);
+ var line = session.getLine(cursor.row);
+ var shouldOutdent = mode.checkOutdent(lineState, line, text);
+ session.insert(cursor, text);
+
+ if (transform && transform.selection) {
+ if (transform.selection.length == 2) { // Transform relative to the current column
+ this.selection.setSelectionRange(
+ new Range(cursor.row, start + transform.selection[0],
+ cursor.row, start + transform.selection[1]));
+ } else { // Transform relative to the current row.
+ this.selection.setSelectionRange(
+ new Range(cursor.row + transform.selection[0],
+ transform.selection[1],
+ cursor.row + transform.selection[2],
+ transform.selection[3]));
+ }
+ }
+ if (this.$enableAutoIndent) {
+ if (session.getDocument().isNewLine(text)) {
+ var lineIndent = mode.getNextLineIndent(lineState, line.slice(0, cursor.column), session.getTabString());
+
+ session.insert({row: cursor.row+1, column: 0}, lineIndent);
+ }
+ if (shouldOutdent)
+ mode.autoOutdent(lineState, session, cursor.row);
+ }
+ };
+
+ this.autoIndent = function () {
+ var session = this.session;
+ var mode = session.getMode();
+
+ var startRow, endRow;
+ if (this.selection.isEmpty()) {
+ startRow = 0;
+ endRow = session.doc.getLength() - 1;
+ } else {
+ var selectedRange = this.getSelectionRange();
+
+ startRow = selectedRange.start.row;
+ endRow = selectedRange.end.row;
+ }
+
+ var prevLineState = "";
+ var prevLine = "";
+ var lineIndent = "";
+ var line, currIndent, range;
+ var tab = session.getTabString();
+
+ for (var row = startRow; row <= endRow; row++) {
+ if (row > 0) {
+ prevLineState = session.getState(row - 1);
+ prevLine = session.getLine(row - 1);
+ lineIndent = mode.getNextLineIndent(prevLineState, prevLine, tab);
+ }
+
+ line = session.getLine(row);
+ currIndent = mode.$getIndent(line);
+ if (lineIndent !== currIndent) {
+ if (currIndent.length > 0) {
+ range = new Range(row, 0, row, currIndent.length);
+ session.remove(range);
+ }
+ if (lineIndent.length > 0) {
+ session.insert({row: row, column: 0}, lineIndent);
+ }
+ }
+
+ mode.autoOutdent(prevLineState, session, row);
+ }
+ };
+
+
+ this.onTextInput = function(text, composition) {
+ if (!composition)
+ return this.keyBinding.onTextInput(text);
+
+ this.startOperation({command: { name: "insertstring" }});
+ var applyComposition = this.applyComposition.bind(this, text, composition);
+ if (this.selection.rangeCount)
+ this.forEachSelection(applyComposition);
+ else
+ applyComposition();
+ this.endOperation();
+ };
+
+ this.applyComposition = function(text, composition) {
+ if (composition.extendLeft || composition.extendRight) {
+ var r = this.selection.getRange();
+ r.start.column -= composition.extendLeft;
+ r.end.column += composition.extendRight;
+ if (r.start.column < 0) {
+ r.start.row--;
+ r.start.column += this.session.getLine(r.start.row).length + 1;
+ }
+ this.selection.setRange(r);
+ if (!text && !r.isEmpty())
+ this.remove();
+ }
+ if (text || !this.selection.isEmpty())
+ this.insert(text, true);
+ if (composition.restoreStart || composition.restoreEnd) {
+ var r = this.selection.getRange();
+ r.start.column -= composition.restoreStart;
+ r.end.column -= composition.restoreEnd;
+ this.selection.setRange(r);
+ }
+ };
+
+ this.onCommandKey = function(e, hashId, keyCode) {
+ return this.keyBinding.onCommandKey(e, hashId, keyCode);
+ };
+
+ /**
+ * Pass in `true` to enable overwrites in your session, or `false` to disable. If overwrites is enabled, any text you enter will type over any text after it. If the value of `overwrite` changes, this function also emits the `changeOverwrite` event.
+ * @param {Boolean} overwrite Defines whether or not to set overwrites
+ *
+ *
+ * @related EditSession.setOverwrite
+ **/
+ this.setOverwrite = function(overwrite) {
+ this.session.setOverwrite(overwrite);
+ };
+
+ /**
+ * Returns `true` if overwrites are enabled; `false` otherwise.
+ * @returns {Boolean}
+ * @related EditSession.getOverwrite
+ **/
+ this.getOverwrite = function() {
+ return this.session.getOverwrite();
+ };
+
+ /**
+ * Sets the value of overwrite to the opposite of whatever it currently is.
+ * @related EditSession.toggleOverwrite
+ **/
+ this.toggleOverwrite = function() {
+ this.session.toggleOverwrite();
+ };
+
+ /**
+ * Sets how fast the mouse scrolling should do.
+ * @param {Number} speed A value indicating the new speed (in milliseconds)
+ **/
+ this.setScrollSpeed = function(speed) {
+ this.setOption("scrollSpeed", speed);
+ };
+
+ /**
+ * Returns the value indicating how fast the mouse scroll speed is (in milliseconds).
+ * @returns {Number}
+ **/
+ this.getScrollSpeed = function() {
+ return this.getOption("scrollSpeed");
+ };
+
+ /**
+ * Sets the delay (in milliseconds) of the mouse drag.
+ * @param {Number} dragDelay A value indicating the new delay
+ **/
+ this.setDragDelay = function(dragDelay) {
+ this.setOption("dragDelay", dragDelay);
+ };
+
+ /**
+ * Returns the current mouse drag delay.
+ * @returns {Number}
+ **/
+ this.getDragDelay = function() {
+ return this.getOption("dragDelay");
+ };
+
+ /**
+ * Emitted when the selection style changes, via [[Editor.setSelectionStyle]].
+ * @event changeSelectionStyle
+ * @param {Object} data Contains one property, `data`, which indicates the new selection style
+ **/
+ /**
+ * Draw selection markers spanning whole line, or only over selected text. Default value is "line"
+ * @param {String} style The new selection style "line"|"text"
+ *
+ **/
+ this.setSelectionStyle = function(val) {
+ this.setOption("selectionStyle", val);
+ };
+
+ /**
+ * Returns the current selection style.
+ * @returns {String}
+ **/
+ this.getSelectionStyle = function() {
+ return this.getOption("selectionStyle");
+ };
+
+ /**
+ * Determines whether or not the current line should be highlighted.
+ * @param {Boolean} shouldHighlight Set to `true` to highlight the current line
+ **/
+ this.setHighlightActiveLine = function(shouldHighlight) {
+ this.setOption("highlightActiveLine", shouldHighlight);
+ };
+ /**
+ * Returns `true` if current lines are always highlighted.
+ * @return {Boolean}
+ **/
+ this.getHighlightActiveLine = function() {
+ return this.getOption("highlightActiveLine");
+ };
+ this.setHighlightGutterLine = function(shouldHighlight) {
+ this.setOption("highlightGutterLine", shouldHighlight);
+ };
+
+ this.getHighlightGutterLine = function() {
+ return this.getOption("highlightGutterLine");
+ };
+
+ /**
+ * Determines if the currently selected word should be highlighted.
+ * @param {Boolean} shouldHighlight Set to `true` to highlight the currently selected word
+ *
+ **/
+ this.setHighlightSelectedWord = function(shouldHighlight) {
+ this.setOption("highlightSelectedWord", shouldHighlight);
+ };
+
+ /**
+ * Returns `true` if currently highlighted words are to be highlighted.
+ * @returns {Boolean}
+ **/
+ this.getHighlightSelectedWord = function() {
+ return this.$highlightSelectedWord;
+ };
+
+ this.setAnimatedScroll = function(shouldAnimate){
+ this.renderer.setAnimatedScroll(shouldAnimate);
+ };
+
+ this.getAnimatedScroll = function(){
+ return this.renderer.getAnimatedScroll();
+ };
+
+ /**
+ * If `showInvisibles` is set to `true`, invisible characters—like spaces or new lines—are show in the editor.
+ * @param {Boolean} showInvisibles Specifies whether or not to show invisible characters
+ *
+ **/
+ this.setShowInvisibles = function(showInvisibles) {
+ this.renderer.setShowInvisibles(showInvisibles);
+ };
+
+ /**
+ * Returns `true` if invisible characters are being shown.
+ * @returns {Boolean}
+ **/
+ this.getShowInvisibles = function() {
+ return this.renderer.getShowInvisibles();
+ };
+
+ this.setDisplayIndentGuides = function(display) {
+ this.renderer.setDisplayIndentGuides(display);
+ };
+
+ this.getDisplayIndentGuides = function() {
+ return this.renderer.getDisplayIndentGuides();
+ };
+
+ this.setHighlightIndentGuides = function(highlight) {
+ this.renderer.setHighlightIndentGuides(highlight);
+ };
+
+ this.getHighlightIndentGuides = function() {
+ return this.renderer.getHighlightIndentGuides();
+ };
+
+ /**
+ * If `showPrintMargin` is set to `true`, the print margin is shown in the editor.
+ * @param {Boolean} showPrintMargin Specifies whether or not to show the print margin
+ *
+ **/
+ this.setShowPrintMargin = function(showPrintMargin) {
+ this.renderer.setShowPrintMargin(showPrintMargin);
+ };
+
+ /**
+ * Returns `true` if the print margin is being shown.
+ * @returns {Boolean}
+ **/
+ this.getShowPrintMargin = function() {
+ return this.renderer.getShowPrintMargin();
+ };
+
+ /**
+ * Sets the column defining where the print margin should be.
+ * @param {Number} showPrintMargin Specifies the new print margin
+ *
+ **/
+ this.setPrintMarginColumn = function(showPrintMargin) {
+ this.renderer.setPrintMarginColumn(showPrintMargin);
+ };
+
+ /**
+ * Returns the column number of where the print margin is.
+ * @returns {Number}
+ **/
+ this.getPrintMarginColumn = function() {
+ return this.renderer.getPrintMarginColumn();
+ };
+
+ /**
+ * If `readOnly` is true, then the editor is set to read-only mode, and none of the content can change.
+ * @param {Boolean} readOnly Specifies whether the editor can be modified or not
+ *
+ **/
+ this.setReadOnly = function(readOnly) {
+ this.setOption("readOnly", readOnly);
+ };
+
+ /**
+ * Returns `true` if the editor is set to read-only mode.
+ * @returns {Boolean}
+ **/
+ this.getReadOnly = function() {
+ return this.getOption("readOnly");
+ };
+
+ /**
+ * Specifies whether to use behaviors or not. ["Behaviors" in this case is the auto-pairing of special characters, like quotation marks, parenthesis, or brackets.]{: #BehaviorsDef}
+ * @param {Boolean} enabled Enables or disables behaviors
+ *
+ **/
+ this.setBehavioursEnabled = function (enabled) {
+ this.setOption("behavioursEnabled", enabled);
+ };
+
+ /**
+ * Returns `true` if the behaviors are currently enabled. {:BehaviorsDef}
+ *
+ * @returns {Boolean}
+ **/
+ this.getBehavioursEnabled = function () {
+ return this.getOption("behavioursEnabled");
+ };
+
+ /**
+ * Specifies whether to use wrapping behaviors or not, i.e. automatically wrapping the selection with characters such as brackets
+ * when such a character is typed in.
+ * @param {Boolean} enabled Enables or disables wrapping behaviors
+ *
+ **/
+ this.setWrapBehavioursEnabled = function (enabled) {
+ this.setOption("wrapBehavioursEnabled", enabled);
+ };
+
+ /**
+ * Returns `true` if the wrapping behaviors are currently enabled.
+ **/
+ this.getWrapBehavioursEnabled = function () {
+ return this.getOption("wrapBehavioursEnabled");
+ };
+
+ /**
+ * Indicates whether the fold widgets should be shown or not.
+ * @param {Boolean} show Specifies whether the fold widgets are shown
+ **/
+ this.setShowFoldWidgets = function(show) {
+ this.setOption("showFoldWidgets", show);
+
+ };
+ /**
+ * Returns `true` if the fold widgets are shown.
+ * @return {Boolean}
+ **/
+ this.getShowFoldWidgets = function() {
+ return this.getOption("showFoldWidgets");
+ };
+
+ this.setFadeFoldWidgets = function(fade) {
+ this.setOption("fadeFoldWidgets", fade);
+ };
+
+ this.getFadeFoldWidgets = function() {
+ return this.getOption("fadeFoldWidgets");
+ };
+
+ /**
+ * Removes the current selection or one character.
+ * @param {String} dir The direction of the deletion to occur, either "left" or "right"
+ *
+ **/
+ this.remove = function(dir) {
+ if (this.selection.isEmpty()){
+ if (dir == "left")
+ this.selection.selectLeft();
+ else
+ this.selection.selectRight();
+ }
+
+ var range = this.getSelectionRange();
+ if (this.getBehavioursEnabled()) {
+ var session = this.session;
+ var state = session.getState(range.start.row);
+ var new_range = session.getMode().transformAction(state, 'deletion', this, session, range);
+
+ if (range.end.column === 0) {
+ var text = session.getTextRange(range);
+ if (text[text.length - 1] == "\n") {
+ var line = session.getLine(range.end.row);
+ if (/^\s+$/.test(line)) {
+ range.end.column = line.length;
+ }
+ }
+ }
+ if (new_range)
+ range = new_range;
+ }
+
+ this.session.remove(range);
+ this.clearSelection();
+ };
+
+ /**
+ * Removes the word directly to the right of the current selection.
+ **/
+ this.removeWordRight = function() {
+ if (this.selection.isEmpty())
+ this.selection.selectWordRight();
+
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ };
+
+ /**
+ * Removes the word directly to the left of the current selection.
+ **/
+ this.removeWordLeft = function() {
+ if (this.selection.isEmpty())
+ this.selection.selectWordLeft();
+
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ };
+
+ /**
+ * Removes all the words to the left of the current selection, until the start of the line.
+ **/
+ this.removeToLineStart = function() {
+ if (this.selection.isEmpty())
+ this.selection.selectLineStart();
+ if (this.selection.isEmpty())
+ this.selection.selectLeft();
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ };
+
+ /**
+ * Removes all the words to the right of the current selection, until the end of the line.
+ **/
+ this.removeToLineEnd = function() {
+ if (this.selection.isEmpty())
+ this.selection.selectLineEnd();
+
+ var range = this.getSelectionRange();
+ if (range.start.column == range.end.column && range.start.row == range.end.row) {
+ range.end.column = 0;
+ range.end.row++;
+ }
+
+ this.session.remove(range);
+ this.clearSelection();
+ };
+
+ /**
+ * Splits the line at the current selection (by inserting an `'\n'`).
+ **/
+ this.splitLine = function() {
+ if (!this.selection.isEmpty()) {
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ }
+
+ var cursor = this.getCursorPosition();
+ this.insert("\n");
+ this.moveCursorToPosition(cursor);
+ };
+
+ /**
+ * Set the "ghost" text in provided position. "Ghost" text is a kind of
+ * preview text inside the editor which can be used to preview some code
+ * inline in the editor such as, for example, code completions.
+ *
+ * @param {String} text Text to be inserted as "ghost" text
+ * @param {object} position Position to insert text to
+ */
+ this.setGhostText = function(text, position) {
+ if (!this.session.widgetManager) {
+ this.session.widgetManager = new LineWidgets(this.session);
+ this.session.widgetManager.attach(this);
+ }
+ this.renderer.setGhostText(text, position);
+ };
+
+ /**
+ * Removes "ghost" text currently displayed in the editor.
+ */
+ this.removeGhostText = function() {
+ if (!this.session.widgetManager) return;
+
+ this.renderer.removeGhostText();
+ };
+
+ /**
+ * Transposes current line.
+ **/
+ this.transposeLetters = function() {
+ if (!this.selection.isEmpty()) {
+ return;
+ }
+
+ var cursor = this.getCursorPosition();
+ var column = cursor.column;
+ if (column === 0)
+ return;
+
+ var line = this.session.getLine(cursor.row);
+ var swap, range;
+ if (column < line.length) {
+ swap = line.charAt(column) + line.charAt(column-1);
+ range = new Range(cursor.row, column-1, cursor.row, column+1);
+ }
+ else {
+ swap = line.charAt(column-1) + line.charAt(column-2);
+ range = new Range(cursor.row, column-2, cursor.row, column);
+ }
+ this.session.replace(range, swap);
+ this.session.selection.moveToPosition(range.end);
+ };
+
+ /**
+ * Converts the current selection entirely into lowercase.
+ **/
+ this.toLowerCase = function() {
+ var originalRange = this.getSelectionRange();
+ if (this.selection.isEmpty()) {
+ this.selection.selectWord();
+ }
+
+ var range = this.getSelectionRange();
+ var text = this.session.getTextRange(range);
+ this.session.replace(range, text.toLowerCase());
+ this.selection.setSelectionRange(originalRange);
+ };
+
+ /**
+ * Converts the current selection entirely into uppercase.
+ **/
+ this.toUpperCase = function() {
+ var originalRange = this.getSelectionRange();
+ if (this.selection.isEmpty()) {
+ this.selection.selectWord();
+ }
+
+ var range = this.getSelectionRange();
+ var text = this.session.getTextRange(range);
+ this.session.replace(range, text.toUpperCase());
+ this.selection.setSelectionRange(originalRange);
+ };
+
+ /**
+ * Inserts an indentation into the current cursor position or indents the selected lines.
+ *
+ * @related EditSession.indentRows
+ **/
+ this.indent = function() {
+ var session = this.session;
+ var range = this.getSelectionRange();
+
+ if (range.start.row < range.end.row) {
+ var rows = this.$getSelectedRows();
+ session.indentRows(rows.first, rows.last, "\t");
+ return;
+ } else if (range.start.column < range.end.column) {
+ var text = session.getTextRange(range);
+ if (!/^\s+$/.test(text)) {
+ var rows = this.$getSelectedRows();
+ session.indentRows(rows.first, rows.last, "\t");
+ return;
+ }
+ }
+
+ var line = session.getLine(range.start.row);
+ var position = range.start;
+ var size = session.getTabSize();
+ var column = session.documentToScreenColumn(position.row, position.column);
+
+ if (this.session.getUseSoftTabs()) {
+ var count = (size - column % size);
+ var indentString = lang.stringRepeat(" ", count);
+ } else {
+ var count = column % size;
+ while (line[range.start.column - 1] == " " && count) {
+ range.start.column--;
+ count--;
+ }
+ this.selection.setSelectionRange(range);
+ indentString = "\t";
+ }
+ return this.insert(indentString);
+ };
+
+ /**
+ * Indents the current line.
+ * @related EditSession.indentRows
+ **/
+ this.blockIndent = function() {
+ var rows = this.$getSelectedRows();
+ this.session.indentRows(rows.first, rows.last, "\t");
+ };
+
+ /**
+ * Outdents the current line.
+ * @related EditSession.outdentRows
+ **/
+ this.blockOutdent = function() {
+ var selection = this.session.getSelection();
+ this.session.outdentRows(selection.getRange());
+ };
+
+ // TODO: move out of core when we have good mechanism for managing extensions
+ this.sortLines = function() {
+ var rows = this.$getSelectedRows();
+ var session = this.session;
+
+ var lines = [];
+ for (var i = rows.first; i <= rows.last; i++)
+ lines.push(session.getLine(i));
+
+ lines.sort(function(a, b) {
+ if (a.toLowerCase() < b.toLowerCase()) return -1;
+ if (a.toLowerCase() > b.toLowerCase()) return 1;
+ return 0;
+ });
+
+ var deleteRange = new Range(0, 0, 0, 0);
+ for (var i = rows.first; i <= rows.last; i++) {
+ var line = session.getLine(i);
+ deleteRange.start.row = i;
+ deleteRange.end.row = i;
+ deleteRange.end.column = line.length;
+ session.replace(deleteRange, lines[i-rows.first]);
+ }
+ };
+
+ /**
+ * Given the currently selected range, this function either comments all the lines, or uncomments all of them.
+ **/
+ this.toggleCommentLines = function() {
+ var state = this.session.getState(this.getCursorPosition().row);
+ var rows = this.$getSelectedRows();
+ this.session.getMode().toggleCommentLines(state, this.session, rows.first, rows.last);
+ };
+
+ this.toggleBlockComment = function() {
+ var cursor = this.getCursorPosition();
+ var state = this.session.getState(cursor.row);
+ var range = this.getSelectionRange();
+ this.session.getMode().toggleBlockComment(state, this.session, range, cursor);
+ };
+
+ /**
+ * Works like [[EditSession.getTokenAt]], except it returns a number.
+ * @returns {Number}
+ **/
+ this.getNumberAt = function(row, column) {
+ var _numberRx = /[\-]?[0-9]+(?:\.[0-9]+)?/g;
+ _numberRx.lastIndex = 0;
+
+ var s = this.session.getLine(row);
+ while (_numberRx.lastIndex < column) {
+ var m = _numberRx.exec(s);
+ if(m.index <= column && m.index+m[0].length >= column){
+ var number = {
+ value: m[0],
+ start: m.index,
+ end: m.index+m[0].length
+ };
+ return number;
+ }
+ }
+ return null;
+ };
+
+ /**
+ * If the character before the cursor is a number, this functions changes its value by `amount`.
+ * @param {Number} amount The value to change the numeral by (can be negative to decrease value)
+ *
+ **/
+ this.modifyNumber = function(amount) {
+ var row = this.selection.getCursor().row;
+ var column = this.selection.getCursor().column;
+
+ // get the char before the cursor
+ var charRange = new Range(row, column-1, row, column);
+
+ var c = this.session.getTextRange(charRange);
+ // if the char is a digit
+ if (!isNaN(parseFloat(c)) && isFinite(c)) {
+ // get the whole number the digit is part of
+ var nr = this.getNumberAt(row, column);
+ // if number found
+ if (nr) {
+ var fp = nr.value.indexOf(".") >= 0 ? nr.start + nr.value.indexOf(".") + 1 : nr.end;
+ var decimals = nr.start + nr.value.length - fp;
+
+ var t = parseFloat(nr.value);
+ t *= Math.pow(10, decimals);
+
+
+ if(fp !== nr.end && column < fp){
+ amount *= Math.pow(10, nr.end - column - 1);
+ } else {
+ amount *= Math.pow(10, nr.end - column);
+ }
+
+ t += amount;
+ t /= Math.pow(10, decimals);
+ var nnr = t.toFixed(decimals);
+
+ //update number
+ var replaceRange = new Range(row, nr.start, row, nr.end);
+ this.session.replace(replaceRange, nnr);
+
+ //reposition the cursor
+ this.moveCursorTo(row, Math.max(nr.start +1, column + nnr.length - nr.value.length));
+
+ }
+ } else {
+ this.toggleWord();
+ }
+ };
+
+ this.$toggleWordPairs = [
+ ["first", "last"],
+ ["true", "false"],
+ ["yes", "no"],
+ ["width", "height"],
+ ["top", "bottom"],
+ ["right", "left"],
+ ["on", "off"],
+ ["x", "y"],
+ ["get", "set"],
+ ["max", "min"],
+ ["horizontal", "vertical"],
+ ["show", "hide"],
+ ["add", "remove"],
+ ["up", "down"],
+ ["before", "after"],
+ ["even", "odd"],
+ ["in", "out"],
+ ["inside", "outside"],
+ ["next", "previous"],
+ ["increase", "decrease"],
+ ["attach", "detach"],
+ ["&&", "||"],
+ ["==", "!="]
+ ];
+
+ this.toggleWord = function () {
+ var row = this.selection.getCursor().row;
+ var column = this.selection.getCursor().column;
+ this.selection.selectWord();
+ var currentState = this.getSelectedText();
+ var currWordStart = this.selection.getWordRange().start.column;
+ var wordParts = currentState.replace(/([a-z]+|[A-Z]+)(?=[A-Z_]|$)/g, '$1 ').split(/\s/);
+ var delta = column - currWordStart - 1;
+ if (delta < 0) delta = 0;
+ var curLength = 0, itLength = 0;
+ var that = this;
+ if (currentState.match(/[A-Za-z0-9_]+/)) {
+ wordParts.forEach(function (item, i) {
+ itLength = curLength + item.length;
+ if (delta >= curLength && delta <= itLength) {
+ currentState = item;
+ that.selection.clearSelection();
+ that.moveCursorTo(row, curLength + currWordStart);
+ that.selection.selectTo(row, itLength + currWordStart);
+ }
+ curLength = itLength;
+ });
+ }
+
+ var wordPairs = this.$toggleWordPairs;
+ var reg;
+ for (var i = 0; i < wordPairs.length; i++) {
+ var item = wordPairs[i];
+ for (var j = 0; j <= 1; j++) {
+ var negate = +!j;
+ var firstCondition = currentState.match(new RegExp('^\\s?_?(' + lang.escapeRegExp(item[j]) + ')\\s?$', 'i'));
+ if (firstCondition) {
+ var secondCondition = currentState.match(new RegExp('([_]|^|\\s)(' + lang.escapeRegExp(firstCondition[1]) + ')($|\\s)', 'g'));
+ if (secondCondition) {
+ reg = currentState.replace(new RegExp(lang.escapeRegExp(item[j]), 'i'), function (result) {
+ var res = item[negate];
+ if (result.toUpperCase() == result) {
+ res = res.toUpperCase();
+ } else if (result.charAt(0).toUpperCase() == result.charAt(0)) {
+ res = res.substr(0, 0) + item[negate].charAt(0).toUpperCase() + res.substr(1);
+ }
+ return res;
+ });
+ this.insert(reg);
+ reg = "";
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Finds link at defined {row} and {column}
+ * @returns {String}
+ **/
+ this.findLinkAt = function (row, column) {
+ var line = this.session.getLine(row);
+ var wordParts = line.split(/((?:https?|ftp):\/\/[\S]+)/);
+ var columnPosition = column;
+ if (columnPosition < 0) columnPosition = 0;
+ var previousPosition = 0, currentPosition = 0, match;
+ for (let item of wordParts) {
+ currentPosition = previousPosition + item.length;
+ if (columnPosition >= previousPosition && columnPosition <= currentPosition) {
+ if (item.match(/((?:https?|ftp):\/\/[\S]+)/)) {
+ match = item.replace(/[\s:.,'";}\]]+$/, "");
+ break;
+ }
+ }
+ previousPosition = currentPosition;
+ }
+ return match;
+ };
+
+ /**
+ * Open valid url under cursor in another tab
+ * @returns {Boolean}
+ **/
+ this.openLink = function () {
+ var cursor = this.selection.getCursor();
+ var url = this.findLinkAt(cursor.row, cursor.column);
+ if (url)
+ window.open(url, '_blank');
+ return url != null;
+ };
+
+ /**
+ * Removes all the lines in the current selection
+ * @related EditSession.remove
+ **/
+ this.removeLines = function() {
+ var rows = this.$getSelectedRows();
+ this.session.removeFullLines(rows.first, rows.last);
+ this.clearSelection();
+ };
+
+ this.duplicateSelection = function() {
+ var sel = this.selection;
+ var doc = this.session;
+ var range = sel.getRange();
+ var reverse = sel.isBackwards();
+ if (range.isEmpty()) {
+ var row = range.start.row;
+ doc.duplicateLines(row, row);
+ } else {
+ var point = reverse ? range.start : range.end;
+ var endPoint = doc.insert(point, doc.getTextRange(range), false);
+ range.start = point;
+ range.end = endPoint;
+
+ sel.setSelectionRange(range, reverse);
+ }
+ };
+
+ /**
+ * Shifts all the selected lines down one row.
+ *
+ * @returns {Number} On success, it returns -1.
+ * @related EditSession.moveLinesUp
+ **/
+ this.moveLinesDown = function() {
+ this.$moveLines(1, false);
+ };
+
+ /**
+ * Shifts all the selected lines up one row.
+ * @returns {Number} On success, it returns -1.
+ * @related EditSession.moveLinesDown
+ **/
+ this.moveLinesUp = function() {
+ this.$moveLines(-1, false);
+ };
+
+ /**
+ * Moves a range of text from the given range to the given position. `toPosition` is an object that looks like this:
+ * ```json
+ * { row: newRowLocation, column: newColumnLocation }
+ * ```
+ * @param {Range} fromRange The range of text you want moved within the document
+ * @param {Object} toPosition The location (row and column) where you want to move the text to
+ *
+ * @returns {Range} The new range where the text was moved to.
+ * @related EditSession.moveText
+ **/
+ this.moveText = function(range, toPosition, copy) {
+ return this.session.moveText(range, toPosition, copy);
+ };
+
+ /**
+ * Copies all the selected lines up one row.
+ * @returns {Number} On success, returns 0.
+ *
+ **/
+ this.copyLinesUp = function() {
+ this.$moveLines(-1, true);
+ };
+
+ /**
+ * Copies all the selected lines down one row.
+ * @returns {Number} On success, returns the number of new rows added; in other words, `lastRow - firstRow + 1`.
+ * @related EditSession.duplicateLines
+ *
+ **/
+ this.copyLinesDown = function() {
+ this.$moveLines(1, true);
+ };
+
+ /**
+ * for internal use
+ * @ignore
+ *
+ **/
+ this.$moveLines = function(dir, copy) {
+ var rows, moved;
+ var selection = this.selection;
+ if (!selection.inMultiSelectMode || this.inVirtualSelectionMode) {
+ var range = selection.toOrientedRange();
+ rows = this.$getSelectedRows(range);
+ moved = this.session.$moveLines(rows.first, rows.last, copy ? 0 : dir);
+ if (copy && dir == -1) moved = 0;
+ range.moveBy(moved, 0);
+ selection.fromOrientedRange(range);
+ } else {
+ var ranges = selection.rangeList.ranges;
+ selection.rangeList.detach(this.session);
+ this.inVirtualSelectionMode = true;
+
+ var diff = 0;
+ var totalDiff = 0;
+ var l = ranges.length;
+ for (var i = 0; i < l; i++) {
+ var rangeIndex = i;
+ ranges[i].moveBy(diff, 0);
+ rows = this.$getSelectedRows(ranges[i]);
+ var first = rows.first;
+ var last = rows.last;
+ while (++i < l) {
+ if (totalDiff) ranges[i].moveBy(totalDiff, 0);
+ var subRows = this.$getSelectedRows(ranges[i]);
+ if (copy && subRows.first != last)
+ break;
+ else if (!copy && subRows.first > last + 1)
+ break;
+ last = subRows.last;
+ }
+ i--;
+ diff = this.session.$moveLines(first, last, copy ? 0 : dir);
+ if (copy && dir == -1) rangeIndex = i + 1;
+ while (rangeIndex <= i) {
+ ranges[rangeIndex].moveBy(diff, 0);
+ rangeIndex++;
+ }
+ if (!copy) diff = 0;
+ totalDiff += diff;
+ }
+
+ selection.fromOrientedRange(selection.ranges[0]);
+ selection.rangeList.attach(this.session);
+ this.inVirtualSelectionMode = false;
+ }
+ };
+
+ /**
+ * Returns an object indicating the currently selected rows. The object looks like this:
+ *
+ * ```json
+ * { first: range.start.row, last: range.end.row }
+ * ```
+ *
+ * @returns {Object}
+ **/
+ this.$getSelectedRows = function(range) {
+ range = (range || this.getSelectionRange()).collapseRows();
+
+ return {
+ first: this.session.getRowFoldStart(range.start.row),
+ last: this.session.getRowFoldEnd(range.end.row)
+ };
+ };
+
+ this.onCompositionStart = function(compositionState) {
+ this.renderer.showComposition(compositionState);
+ };
+
+ this.onCompositionUpdate = function(text) {
+ this.renderer.setCompositionText(text);
+ };
+
+ this.onCompositionEnd = function() {
+ this.renderer.hideComposition();
+ };
+
+ /**
+ * {:VirtualRenderer.getFirstVisibleRow}
+ *
+ * @returns {Number}
+ * @related VirtualRenderer.getFirstVisibleRow
+ **/
+ this.getFirstVisibleRow = function() {
+ return this.renderer.getFirstVisibleRow();
+ };
+
+ /**
+ * {:VirtualRenderer.getLastVisibleRow}
+ *
+ * @returns {Number}
+ * @related VirtualRenderer.getLastVisibleRow
+ **/
+ this.getLastVisibleRow = function() {
+ return this.renderer.getLastVisibleRow();
+ };
+
+ /**
+ * Indicates if the row is currently visible on the screen.
+ * @param {Number} row The row to check
+ *
+ * @returns {Boolean}
+ **/
+ this.isRowVisible = function(row) {
+ return (row >= this.getFirstVisibleRow() && row <= this.getLastVisibleRow());
+ };
+
+ /**
+ * Indicates if the entire row is currently visible on the screen.
+ * @param {Number} row The row to check
+ *
+ *
+ * @returns {Boolean}
+ **/
+ this.isRowFullyVisible = function(row) {
+ return (row >= this.renderer.getFirstFullyVisibleRow() && row <= this.renderer.getLastFullyVisibleRow());
+ };
+
+ /**
+ * Returns the number of currently visible rows.
+ * @returns {Number}
+ **/
+ this.$getVisibleRowCount = function() {
+ return this.renderer.getScrollBottomRow() - this.renderer.getScrollTopRow() + 1;
+ };
+
+ this.$moveByPage = function(dir, select) {
+ var renderer = this.renderer;
+ var config = this.renderer.layerConfig;
+ var rows = dir * Math.floor(config.height / config.lineHeight);
+
+ if (select === true) {
+ this.selection.$moveSelection(function(){
+ this.moveCursorBy(rows, 0);
+ });
+ } else if (select === false) {
+ this.selection.moveCursorBy(rows, 0);
+ this.selection.clearSelection();
+ }
+
+ var scrollTop = renderer.scrollTop;
+
+ renderer.scrollBy(0, rows * config.lineHeight);
+ if (select != null)
+ renderer.scrollCursorIntoView(null, 0.5);
+
+ renderer.animateScrolling(scrollTop);
+ };
+
+ /**
+ * Selects the text from the current position of the document until where a "page down" finishes.
+ **/
+ this.selectPageDown = function() {
+ this.$moveByPage(1, true);
+ };
+
+ /**
+ * Selects the text from the current position of the document until where a "page up" finishes.
+ **/
+ this.selectPageUp = function() {
+ this.$moveByPage(-1, true);
+ };
+
+ /**
+ * Shifts the document to wherever "page down" is, as well as moving the cursor position.
+ **/
+ this.gotoPageDown = function() {
+ this.$moveByPage(1, false);
+ };
+
+ /**
+ * Shifts the document to wherever "page up" is, as well as moving the cursor position.
+ **/
+ this.gotoPageUp = function() {
+ this.$moveByPage(-1, false);
+ };
+
+ /**
+ * Scrolls the document to wherever "page down" is, without changing the cursor position.
+ **/
+ this.scrollPageDown = function() {
+ this.$moveByPage(1);
+ };
+
+ /**
+ * Scrolls the document to wherever "page up" is, without changing the cursor position.
+ **/
+ this.scrollPageUp = function() {
+ this.$moveByPage(-1);
+ };
+
+ /**
+ * Moves the editor to the specified row.
+ * @related VirtualRenderer.scrollToRow
+ **/
+ this.scrollToRow = function(row) {
+ this.renderer.scrollToRow(row);
+ };
+
+ /**
+ * Scrolls to a line. If `center` is `true`, it puts the line in middle of screen (or attempts to).
+ * @param {Number} line The line to scroll to
+ * @param {Boolean} center If `true`
+ * @param {Boolean} animate If `true` animates scrolling
+ * @param {Function} callback Function to be called when the animation has finished
+ *
+ *
+ * @related VirtualRenderer.scrollToLine
+ **/
+ this.scrollToLine = function(line, center, animate, callback) {
+ this.renderer.scrollToLine(line, center, animate, callback);
+ };
+
+ /**
+ * Attempts to center the current selection on the screen.
+ **/
+ this.centerSelection = function() {
+ var range = this.getSelectionRange();
+ var pos = {
+ row: Math.floor(range.start.row + (range.end.row - range.start.row) / 2),
+ column: Math.floor(range.start.column + (range.end.column - range.start.column) / 2)
+ };
+ this.renderer.alignCursor(pos, 0.5);
+ };
+
+ /**
+ * Gets the current position of the cursor.
+ * @returns {Object} An object that looks something like this:
+ *
+ * ```json
+ * { row: currRow, column: currCol }
+ * ```
+ *
+ * @related Selection.getCursor
+ **/
+ this.getCursorPosition = function() {
+ return this.selection.getCursor();
+ };
+
+ /**
+ * Returns the screen position of the cursor.
+ * @returns {Number}
+ * @related EditSession.documentToScreenPosition
+ **/
+ this.getCursorPositionScreen = function() {
+ return this.session.documentToScreenPosition(this.getCursorPosition());
+ };
+
+ /**
+ * {:Selection.getRange}
+ * @returns {Range}
+ * @related Selection.getRange
+ **/
+ this.getSelectionRange = function() {
+ return this.selection.getRange();
+ };
+
+
+ /**
+ * Selects all the text in editor.
+ * @related Selection.selectAll
+ **/
+ this.selectAll = function() {
+ this.selection.selectAll();
+ };
+
+ /**
+ * {:Selection.clearSelection}
+ * @related Selection.clearSelection
+ **/
+ this.clearSelection = function() {
+ this.selection.clearSelection();
+ };
+
+ /**
+ * Moves the cursor to the specified row and column. Note that this does not de-select the current selection.
+ * @param {Number} row The new row number
+ * @param {Number} column The new column number
+ *
+ *
+ * @related Selection.moveCursorTo
+ **/
+ this.moveCursorTo = function(row, column) {
+ this.selection.moveCursorTo(row, column);
+ };
+
+ /**
+ * Moves the cursor to the position indicated by `pos.row` and `pos.column`.
+ * @param {Object} pos An object with two properties, row and column
+ *
+ *
+ * @related Selection.moveCursorToPosition
+ **/
+ this.moveCursorToPosition = function(pos) {
+ this.selection.moveCursorToPosition(pos);
+ };
+
+ /**
+ * Moves the cursor's row and column to the next matching bracket or HTML tag.
+ *
+ **/
+ this.jumpToMatching = function (select, expand) {
+ var cursor = this.getCursorPosition();
+ var iterator = new TokenIterator(this.session, cursor.row, cursor.column);
+ var prevToken = iterator.getCurrentToken();
+ var tokenCount = 0;
+ if (prevToken && prevToken.type.indexOf('tag-name') !== -1) {
+ prevToken = iterator.stepBackward();
+ }
+ var token = prevToken || iterator.stepForward();
+
+ if (!token) return;
+
+ //get next closing tag or bracket
+ var matchType;
+ var found = false;
+ var depth = {};
+ var i = cursor.column - token.start;
+ var bracketType;
+ var brackets = {
+ ")": "(",
+ "(": "(",
+ "]": "[",
+ "[": "[",
+ "{": "{",
+ "}": "{"
+ };
+
+ do {
+ if (token.value.match(/[{}()\[\]]/g)) {
+ for (; i < token.value.length && !found; i++) {
+ if (!brackets[token.value[i]]) {
+ continue;
+ }
+
+ bracketType = brackets[token.value[i]] + '.' + token.type.replace("rparen", "lparen");
+
+ if (isNaN(depth[bracketType])) {
+ depth[bracketType] = 0;
+ }
+
+ switch (token.value[i]) {
+ case '(':
+ case '[':
+ case '{':
+ depth[bracketType]++;
+ break;
+ case ')':
+ case ']':
+ case '}':
+ depth[bracketType]--;
+
+ if (depth[bracketType] === -1) {
+ matchType = 'bracket';
+ found = true;
+ }
+ break;
+ }
+ }
+ }
+ else if (token.type.indexOf('tag-name') !== -1) {
+ if (isNaN(depth[token.value])) {
+ depth[token.value] = 0;
+ }
+
+ if (prevToken.value === '<' && tokenCount > 1) {
+ depth[token.value]++;
+ }
+ else if (prevToken.value === '') {
+ depth[token.value]--;
+ }
+
+ if (depth[token.value] === -1) {
+ matchType = 'tag';
+ found = true;
+ }
+ }
+
+ if (!found) {
+ prevToken = token;
+ tokenCount++;
+ token = iterator.stepForward();
+ i = 0;
+ }
+ } while (token && !found);
+
+ //no match found
+ if (!matchType) return;
+
+ var range, pos;
+ if (matchType === 'bracket') {
+ range = this.session.getBracketRange(cursor);
+ if (!range) {
+ range = new Range(iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() + i - 1,
+ iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() + i - 1
+ );
+ pos = range.start;
+ if (expand || pos.row === cursor.row && Math.abs(pos.column - cursor.column)
+ < 2) range = this.session.getBracketRange(pos);
+ }
+ }
+ else if (matchType === 'tag') {
+ if (!token || token.type.indexOf('tag-name') === -1) return;
+ range = new Range(iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() - 2,
+ iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() - 2
+ );
+
+ //find matching tag
+ if (range.compare(cursor.row, cursor.column) === 0) {
+ var tagsRanges = this.session.getMatchingTags(cursor);
+ if (tagsRanges) {
+ if (tagsRanges.openTag.contains(cursor.row, cursor.column)) {
+ range = tagsRanges.closeTag;
+ pos = range.start;
+ }
+ else {
+ range = tagsRanges.openTag;
+ if (tagsRanges.closeTag.start.row === cursor.row && tagsRanges.closeTag.start.column
+ === cursor.column) pos = range.end; else pos = range.start;
+ }
+ }
+ }
+
+ //we found it
+ pos = pos || range.start;
+ }
+
+ pos = range && range.cursor || pos;
+ if (pos) {
+ if (select) {
+ if (range && expand) {
+ this.selection.setRange(range);
+ }
+ else if (range && range.isEqual(this.getSelectionRange())) {
+ this.clearSelection();
+ }
+ else {
+ this.selection.selectTo(pos.row, pos.column);
+ }
+ }
+ else {
+ this.selection.moveTo(pos.row, pos.column);
+ }
+ }
+ };
+
+ /**
+ * Moves the cursor to the specified line number, and also into the indicated column.
+ * @param {Number} lineNumber The line number to go to
+ * @param {Number} column A column number to go to
+ * @param {Boolean} animate If `true` animates scolling
+ *
+ **/
+ this.gotoLine = function(lineNumber, column, animate) {
+ this.selection.clearSelection();
+ this.session.unfold({row: lineNumber - 1, column: column || 0});
+
+ // todo: find a way to automatically exit multiselect mode
+ this.exitMultiSelectMode && this.exitMultiSelectMode();
+ this.moveCursorTo(lineNumber - 1, column || 0);
+
+ if (!this.isRowFullyVisible(lineNumber - 1))
+ this.scrollToLine(lineNumber - 1, true, animate);
+ };
+
+ /**
+ * Moves the cursor to the specified row and column. Note that this does de-select the current selection.
+ * @param {Number} row The new row number
+ * @param {Number} column The new column number
+ *
+ *
+ * @related Editor.moveCursorTo
+ **/
+ this.navigateTo = function(row, column) {
+ this.selection.moveTo(row, column);
+ };
+
+ /**
+ * Moves the cursor up in the document the specified number of times. Note that this does de-select the current selection.
+ * @param {Number} times The number of times to change navigation
+ *
+ *
+ **/
+ this.navigateUp = function(times) {
+ if (this.selection.isMultiLine() && !this.selection.isBackwards()) {
+ var selectionStart = this.selection.anchor.getPosition();
+ return this.moveCursorToPosition(selectionStart);
+ }
+ this.selection.clearSelection();
+ this.selection.moveCursorBy(-times || -1, 0);
+ };
+
+ /**
+ * Moves the cursor down in the document the specified number of times. Note that this does de-select the current selection.
+ * @param {Number} times The number of times to change navigation
+ *
+ *
+ **/
+ this.navigateDown = function(times) {
+ if (this.selection.isMultiLine() && this.selection.isBackwards()) {
+ var selectionEnd = this.selection.anchor.getPosition();
+ return this.moveCursorToPosition(selectionEnd);
+ }
+ this.selection.clearSelection();
+ this.selection.moveCursorBy(times || 1, 0);
+ };
+
+ /**
+ * Moves the cursor left in the document the specified number of times. Note that this does de-select the current selection.
+ * @param {Number} times The number of times to change navigation
+ *
+ *
+ **/
+ this.navigateLeft = function(times) {
+ if (!this.selection.isEmpty()) {
+ var selectionStart = this.getSelectionRange().start;
+ this.moveCursorToPosition(selectionStart);
+ }
+ else {
+ times = times || 1;
+ while (times--) {
+ this.selection.moveCursorLeft();
+ }
+ }
+ this.clearSelection();
+ };
+
+ /**
+ * Moves the cursor right in the document the specified number of times. Note that this does de-select the current selection.
+ * @param {Number} times The number of times to change navigation
+ *
+ *
+ **/
+ this.navigateRight = function(times) {
+ if (!this.selection.isEmpty()) {
+ var selectionEnd = this.getSelectionRange().end;
+ this.moveCursorToPosition(selectionEnd);
+ }
+ else {
+ times = times || 1;
+ while (times--) {
+ this.selection.moveCursorRight();
+ }
+ }
+ this.clearSelection();
+ };
+
+ /**
+ *
+ * Moves the cursor to the start of the current line. Note that this does de-select the current selection.
+ **/
+ this.navigateLineStart = function() {
+ this.selection.moveCursorLineStart();
+ this.clearSelection();
+ };
+
+ /**
+ *
+ * Moves the cursor to the end of the current line. Note that this does de-select the current selection.
+ **/
+ this.navigateLineEnd = function() {
+ this.selection.moveCursorLineEnd();
+ this.clearSelection();
+ };
+
+ /**
+ *
+ * Moves the cursor to the end of the current file. Note that this does de-select the current selection.
+ **/
+ this.navigateFileEnd = function() {
+ this.selection.moveCursorFileEnd();
+ this.clearSelection();
+ };
+
+ /**
+ *
+ * Moves the cursor to the start of the current file. Note that this does de-select the current selection.
+ **/
+ this.navigateFileStart = function() {
+ this.selection.moveCursorFileStart();
+ this.clearSelection();
+ };
+
+ /**
+ *
+ * Moves the cursor to the word immediately to the right of the current position. Note that this does de-select the current selection.
+ **/
+ this.navigateWordRight = function() {
+ this.selection.moveCursorWordRight();
+ this.clearSelection();
+ };
+
+ /**
+ *
+ * Moves the cursor to the word immediately to the left of the current position. Note that this does de-select the current selection.
+ **/
+ this.navigateWordLeft = function() {
+ this.selection.moveCursorWordLeft();
+ this.clearSelection();
+ };
+
+ /**
+ * Replaces the first occurrence of `options.needle` with the value in `replacement`.
+ * @param {String} replacement The text to replace with
+ * @param {Object} options The [[Search `Search`]] options to use
+ *
+ *
+ **/
+ this.replace = function(replacement, options) {
+ if (options)
+ this.$search.set(options);
+
+ var range = this.$search.find(this.session);
+ var replaced = 0;
+ if (!range)
+ return replaced;
+
+ if (this.$tryReplace(range, replacement)) {
+ replaced = 1;
+ }
+
+ this.selection.setSelectionRange(range);
+ this.renderer.scrollSelectionIntoView(range.start, range.end);
+
+ return replaced;
+ };
+
+ /**
+ * Replaces all occurrences of `options.needle` with the value in `replacement`.
+ * @param {String} replacement The text to replace with
+ * @param {Object} options The [[Search `Search`]] options to use
+ *
+ *
+ **/
+ this.replaceAll = function(replacement, options) {
+ if (options) {
+ this.$search.set(options);
+ }
+
+ var ranges = this.$search.findAll(this.session);
+ var replaced = 0;
+ if (!ranges.length)
+ return replaced;
+
+ var selection = this.getSelectionRange();
+ this.selection.moveTo(0, 0);
+
+ for (var i = ranges.length - 1; i >= 0; --i) {
+ if(this.$tryReplace(ranges[i], replacement)) {
+ replaced++;
+ }
+ }
+
+ this.selection.setSelectionRange(selection);
+
+ return replaced;
+ };
+
+ this.$tryReplace = function(range, replacement) {
+ var input = this.session.getTextRange(range);
+ replacement = this.$search.replace(input, replacement);
+ if (replacement !== null) {
+ range.end = this.session.replace(range, replacement);
+ return range;
+ } else {
+ return null;
+ }
+ };
+
+ /**
+ * {:Search.getOptions} For more information on `options`, see [[Search `Search`]].
+ * @related Search.getOptions
+ * @returns {Object}
+ **/
+ this.getLastSearchOptions = function() {
+ return this.$search.getOptions();
+ };
+
+ /**
+ * Attempts to find `needle` within the document. For more information on `options`, see [[Search `Search`]].
+ * @param {String} needle The text to search for (optional)
+ * @param {Object} options An object defining various search properties
+ * @param {Boolean} animate If `true` animate scrolling
+ *
+ *
+ * @related Search.find
+ **/
+ this.find = function(needle, options, animate) {
+ if (!options)
+ options = {};
+
+ if (typeof needle == "string" || needle instanceof RegExp)
+ options.needle = needle;
+ else if (typeof needle == "object")
+ oop.mixin(options, needle);
+
+ var range = this.selection.getRange();
+ if (options.needle == null) {
+ needle = this.session.getTextRange(range)
+ || this.$search.$options.needle;
+ if (!needle) {
+ range = this.session.getWordRange(range.start.row, range.start.column);
+ needle = this.session.getTextRange(range);
+ }
+ this.$search.set({needle: needle});
+ }
+
+ this.$search.set(options);
+ if (!options.start)
+ this.$search.set({start: range});
+
+ var newRange = this.$search.find(this.session);
+ if (options.preventScroll)
+ return newRange;
+ if (newRange) {
+ this.revealRange(newRange, animate);
+ return newRange;
+ }
+ // clear selection if nothing is found
+ if (options.backwards)
+ range.start = range.end;
+ else
+ range.end = range.start;
+ this.selection.setRange(range);
+ };
+
+ /**
+ * Performs another search for `needle` in the document. For more information on `options`, see [[Search `Search`]].
+ * @param {Object} options search options
+ * @param {Boolean} animate If `true` animate scrolling
+ *
+ *
+ * @related Editor.find
+ **/
+ this.findNext = function(options, animate) {
+ this.find({skipCurrent: true, backwards: false}, options, animate);
+ };
+
+ /**
+ * Performs a search for `needle` backwards. For more information on `options`, see [[Search `Search`]].
+ * @param {Object} options search options
+ * @param {Boolean} animate If `true` animate scrolling
+ *
+ *
+ * @related Editor.find
+ **/
+ this.findPrevious = function(options, animate) {
+ this.find(options, {skipCurrent: true, backwards: true}, animate);
+ };
+
+ this.revealRange = function(range, animate) {
+ this.session.unfold(range);
+ this.selection.setSelectionRange(range);
+
+ var scrollTop = this.renderer.scrollTop;
+ this.renderer.scrollSelectionIntoView(range.start, range.end, 0.5);
+ if (animate !== false)
+ this.renderer.animateScrolling(scrollTop);
+ };
+
+ /**
+ * {:UndoManager.undo}
+ * @related UndoManager.undo
+ **/
+ this.undo = function() {
+ this.session.getUndoManager().undo(this.session);
+ this.renderer.scrollCursorIntoView(null, 0.5);
+ };
+
+ /**
+ * {:UndoManager.redo}
+ * @related UndoManager.redo
+ **/
+ this.redo = function() {
+ this.session.getUndoManager().redo(this.session);
+ this.renderer.scrollCursorIntoView(null, 0.5);
+ };
+
+ /**
+ *
+ * Cleans up the entire editor.
+ **/
+ this.destroy = function() {
+ if (this.$toDestroy) {
+ this.$toDestroy.forEach(function(el) {
+ el.destroy();
+ });
+ this.$toDestroy = null;
+ }
+ if (this.$mouseHandler)
+ this.$mouseHandler.destroy();
+ this.renderer.destroy();
+ this._signal("destroy", this);
+ if (this.session)
+ this.session.destroy();
+ if (this._$emitInputEvent)
+ this._$emitInputEvent.cancel();
+ this.removeAllListeners();
+ };
+
+ /**
+ * Enables automatic scrolling of the cursor into view when editor itself is inside scrollable element
+ * @param {Boolean} enable default true
+ **/
+ this.setAutoScrollEditorIntoView = function(enable) {
+ if (!enable)
+ return;
+ var rect;
+ var self = this;
+ var shouldScroll = false;
+ if (!this.$scrollAnchor)
+ this.$scrollAnchor = document.createElement("div");
+ var scrollAnchor = this.$scrollAnchor;
+ scrollAnchor.style.cssText = "position:absolute";
+ this.container.insertBefore(scrollAnchor, this.container.firstChild);
+ var onChangeSelection = this.on("changeSelection", function() {
+ shouldScroll = true;
+ });
+ // needed to not trigger sync reflow
+ var onBeforeRender = this.renderer.on("beforeRender", function() {
+ if (shouldScroll)
+ rect = self.renderer.container.getBoundingClientRect();
+ });
+ var onAfterRender = this.renderer.on("afterRender", function() {
+ if (shouldScroll && rect && (self.isFocused()
+ || self.searchBox && self.searchBox.isFocused())
+ ) {
+ var renderer = self.renderer;
+ var pos = renderer.$cursorLayer.$pixelPos;
+ var config = renderer.layerConfig;
+ var top = pos.top - config.offset;
+ if (pos.top >= 0 && top + rect.top < 0) {
+ shouldScroll = true;
+ } else if (pos.top < config.height &&
+ pos.top + rect.top + config.lineHeight > window.innerHeight) {
+ shouldScroll = false;
+ } else {
+ shouldScroll = null;
+ }
+ if (shouldScroll != null) {
+ scrollAnchor.style.top = top + "px";
+ scrollAnchor.style.left = pos.left + "px";
+ scrollAnchor.style.height = config.lineHeight + "px";
+ scrollAnchor.scrollIntoView(shouldScroll);
+ }
+ shouldScroll = rect = null;
+ }
+ });
+ this.setAutoScrollEditorIntoView = function(enable) {
+ if (enable)
+ return;
+ delete this.setAutoScrollEditorIntoView;
+ this.off("changeSelection", onChangeSelection);
+ this.renderer.off("afterRender", onAfterRender);
+ this.renderer.off("beforeRender", onBeforeRender);
+ };
+ };
+
+
+ this.$resetCursorStyle = function() {
+ var style = this.$cursorStyle || "ace";
+ var cursorLayer = this.renderer.$cursorLayer;
+ if (!cursorLayer)
+ return;
+ cursorLayer.setSmoothBlinking(/smooth/.test(style));
+ cursorLayer.isBlinking = !this.$readOnly && style != "wide";
+ dom.setCssClass(cursorLayer.element, "ace_slim-cursors", /slim/.test(style));
+ };
+
+ /**
+ * opens a prompt displaying message
+ **/
+ this.prompt = function(message, options, callback) {
+ var editor = this;
+ config.loadModule("ace/ext/prompt", function (module) {
+ module.prompt(editor, message, options, callback);
+ });
+ };
+
+}).call(Editor.prototype);
+
+
+
+config.defineOptions(Editor.prototype, "editor", {
+ selectionStyle: {
+ set: function(style) {
+ this.onSelectionChange();
+ this._signal("changeSelectionStyle", {data: style});
+ },
+ initialValue: "line"
+ },
+ highlightActiveLine: {
+ set: function() {this.$updateHighlightActiveLine();},
+ initialValue: true
+ },
+ highlightSelectedWord: {
+ set: function(shouldHighlight) {this.$onSelectionChange();},
+ initialValue: true
+ },
+ readOnly: {
+ set: function(readOnly) {
+ this.textInput.setReadOnly(readOnly);
+ this.$resetCursorStyle();
+ },
+ initialValue: false
+ },
+ copyWithEmptySelection: {
+ set: function(value) {
+ this.textInput.setCopyWithEmptySelection(value);
+ },
+ initialValue: false
+ },
+ cursorStyle: {
+ set: function(val) { this.$resetCursorStyle(); },
+ values: ["ace", "slim", "smooth", "wide"],
+ initialValue: "ace"
+ },
+ mergeUndoDeltas: {
+ values: [false, true, "always"],
+ initialValue: true
+ },
+ behavioursEnabled: {initialValue: true},
+ wrapBehavioursEnabled: {initialValue: true},
+ enableAutoIndent: {initialValue: true},
+ autoScrollEditorIntoView: {
+ set: function(val) {this.setAutoScrollEditorIntoView(val);}
+ },
+ keyboardHandler: {
+ set: function(val) { this.setKeyboardHandler(val); },
+ get: function() { return this.$keybindingId; },
+ handlesSet: true
+ },
+ value: {
+ set: function(val) { this.session.setValue(val); },
+ get: function() { return this.getValue(); },
+ handlesSet: true,
+ hidden: true
+ },
+ session: {
+ set: function(val) { this.setSession(val); },
+ get: function() { return this.session; },
+ handlesSet: true,
+ hidden: true
+ },
+
+ showLineNumbers: {
+ set: function(show) {
+ this.renderer.$gutterLayer.setShowLineNumbers(show);
+ this.renderer.$loop.schedule(this.renderer.CHANGE_GUTTER);
+ if (show && this.$relativeLineNumbers)
+ relativeNumberRenderer.attach(this);
+ else
+ relativeNumberRenderer.detach(this);
+ },
+ initialValue: true
+ },
+ relativeLineNumbers: {
+ set: function(value) {
+ if (this.$showLineNumbers && value)
+ relativeNumberRenderer.attach(this);
+ else
+ relativeNumberRenderer.detach(this);
+ }
+ },
+ placeholder: {
+ set: function(message) {
+ if (!this.$updatePlaceholder) {
+ this.$updatePlaceholder = function() {
+ var value = this.session && (this.renderer.$composition || this.getValue());
+ if (value && this.renderer.placeholderNode) {
+ this.renderer.off("afterRender", this.$updatePlaceholder);
+ dom.removeCssClass(this.container, "ace_hasPlaceholder");
+ this.renderer.placeholderNode.remove();
+ this.renderer.placeholderNode = null;
+ } else if (!value && !this.renderer.placeholderNode) {
+ this.renderer.on("afterRender", this.$updatePlaceholder);
+ dom.addCssClass(this.container, "ace_hasPlaceholder");
+ var el = dom.createElement("div");
+ el.className = "ace_placeholder";
+ el.textContent = this.$placeholder || "";
+ this.renderer.placeholderNode = el;
+ this.renderer.content.appendChild(this.renderer.placeholderNode);
+ } else if (!value && this.renderer.placeholderNode) {
+ this.renderer.placeholderNode.textContent = this.$placeholder || "";
+ }
+ }.bind(this);
+ this.on("input", this.$updatePlaceholder);
+ }
+ this.$updatePlaceholder();
+ }
+ },
+ customScrollbar: "renderer",
+ hScrollBarAlwaysVisible: "renderer",
+ vScrollBarAlwaysVisible: "renderer",
+ highlightGutterLine: "renderer",
+ animatedScroll: "renderer",
+ showInvisibles: "renderer",
+ showPrintMargin: "renderer",
+ printMarginColumn: "renderer",
+ printMargin: "renderer",
+ fadeFoldWidgets: "renderer",
+ showFoldWidgets: "renderer",
+ displayIndentGuides: "renderer",
+ highlightIndentGuides: "renderer",
+ showGutter: "renderer",
+ fontSize: "renderer",
+ fontFamily: "renderer",
+ maxLines: "renderer",
+ minLines: "renderer",
+ scrollPastEnd: "renderer",
+ fixedWidthGutter: "renderer",
+ theme: "renderer",
+ hasCssTransforms: "renderer",
+ maxPixelHeight: "renderer",
+ useTextareaForIME: "renderer",
+
+ scrollSpeed: "$mouseHandler",
+ dragDelay: "$mouseHandler",
+ dragEnabled: "$mouseHandler",
+ focusTimeout: "$mouseHandler",
+ tooltipFollowsMouse: "$mouseHandler",
+
+ firstLineNumber: "session",
+ overwrite: "session",
+ newLineMode: "session",
+ useWorker: "session",
+ useSoftTabs: "session",
+ navigateWithinSoftTabs: "session",
+ tabSize: "session",
+ wrap: "session",
+ indentedSoftWrap: "session",
+ foldStyle: "session",
+ mode: "session"
+});
+
+
+var relativeNumberRenderer = {
+ getText: function(session, row) {
+ return (Math.abs(session.selection.lead.row - row) || (row + 1 + (row < 9 ? "\xb7" : ""))) + "";
+ },
+ getWidth: function(session, lastLineNumber, config) {
+ return Math.max(
+ lastLineNumber.toString().length,
+ (config.lastRow + 1).toString().length,
+ 2
+ ) * config.characterWidth;
+ },
+ update: function(e, editor) {
+ editor.renderer.$loop.schedule(editor.renderer.CHANGE_GUTTER);
+ },
+ attach: function(editor) {
+ editor.renderer.$gutterLayer.$renderer = this;
+ editor.on("changeSelection", this.update);
+ this.update(null, editor);
+ },
+ detach: function(editor) {
+ if (editor.renderer.$gutterLayer.$renderer == this)
+ editor.renderer.$gutterLayer.$renderer = null;
+ editor.off("changeSelection", this.update);
+ this.update(null, editor);
+ }
+};
+
+exports.Editor = Editor;
diff --git a/demo/diff/examples/editor.17.js b/demo/diff/examples/editor.17.js
new file mode 100644
index 00000000000..474e8448b24
--- /dev/null
+++ b/demo/diff/examples/editor.17.js
@@ -0,0 +1,2983 @@
+"use strict";
+
+var oop = require("./lib/oop");
+var dom = require("./lib/dom");
+var lang = require("./lib/lang");
+var useragent = require("./lib/useragent");
+var TextInput = require("./keyboard/textinput").TextInput;
+var MouseHandler = require("./mouse/mouse_handler").MouseHandler;
+var FoldHandler = require("./mouse/fold_handler").FoldHandler;
+var KeyBinding = require("./keyboard/keybinding").KeyBinding;
+var EditSession = require("./edit_session").EditSession;
+var Search = require("./search").Search;
+var Range = require("./range").Range;
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+var CommandManager = require("./commands/command_manager").CommandManager;
+var defaultCommands = require("./commands/default_commands").commands;
+var config = require("./config");
+var TokenIterator = require("./token_iterator").TokenIterator;
+var LineWidgets = require("./line_widgets").LineWidgets;
+
+var clipboard = require("./clipboard");
+var keys = require('./lib/keys');
+
+/**
+ * The main entry point into the Ace functionality.
+ *
+ * The `Editor` manages the [[EditSession]] (which manages [[Document]]s), as well as the [[VirtualRenderer]], which draws everything to the screen.
+ *
+ * Event sessions dealing with the mouse and keyboard are bubbled up from `Document` to the `Editor`, which decides what to do with them.
+ **/
+class Editor {
+ /**
+ * Creates a new `Editor` object.
+ *
+ * @param {VirtualRenderer} renderer Associated `VirtualRenderer` that draws everything
+ * @param {EditSession} session The `EditSession` to refer to
+ **/
+ constructor(renderer, session, options) {
+ this.$toDestroy = [];
+ var container = renderer.getContainerElement();
+ this.container = container;
+ this.renderer = renderer;
+ this.id = "editor" + (++Editor.$uid);
+
+ this.commands = new CommandManager(useragent.isMac ? "mac" : "win", defaultCommands);
+ if (typeof document == "object") {
+ this.textInput = new TextInput(renderer.getTextAreaContainer(), this);
+ this.renderer.textarea = this.textInput.getElement();
+ // TODO detect touch event support
+ this.$mouseHandler = new MouseHandler(this);
+ new FoldHandler(this);
+ }
+
+ this.keyBinding = new KeyBinding(this);
+
+ this.$search = new Search().set({
+ wrap: true
+ });
+
+ this.$historyTracker = this.$historyTracker.bind(this);
+ this.commands.on("exec", this.$historyTracker);
+
+ this.$initOperationListeners();
+
+ this._$emitInputEvent = lang.delayedCall(function() {
+ this._signal("input", {});
+ if (this.session && !this.session.destroyed)
+ this.session.bgTokenizer.scheduleStart();
+ }.bind(this));
+
+ this.on("change", function(_, _self) {
+ _self._$emitInputEvent.schedule(31);
+ });
+
+ this.setSession(session || options && options.session || new EditSession(""));
+ config.resetOptions(this);
+ if (options)
+ this.setOptions(options);
+ config._signal("editor", this);
+ }
+
+ $initOperationListeners() {
+ this.commands.on("exec", this.startOperation.bind(this), true);
+ this.commands.on("afterExec", this.endOperation.bind(this), true);
+
+ this.$opResetTimer = lang.delayedCall(this.endOperation.bind(this, true));
+
+ // todo: add before change events?
+ this.on("change", function() {
+ if (!this.curOp) {
+ this.startOperation();
+ this.curOp.selectionBefore = this.$lastSel;
+ }
+ this.curOp.docChanged = true;
+ }.bind(this), true);
+
+ this.on("changeSelection", function() {
+ if (!this.curOp) {
+ this.startOperation();
+ this.curOp.selectionBefore = this.$lastSel;
+ }
+ this.curOp.selectionChanged = true;
+ }.bind(this), true);
+ }
+
+ startOperation(commandEvent) {
+ if (this.curOp) {
+ if (!commandEvent || this.curOp.command)
+ return;
+ this.prevOp = this.curOp;
+ }
+ if (!commandEvent) {
+ this.previousCommand = null;
+ commandEvent = {};
+ }
+
+ this.$opResetTimer.schedule();
+ this.curOp = this.session.curOp = {
+ command: commandEvent.command || {},
+ args: commandEvent.args,
+ scrollTop: this.renderer.scrollTop
+ };
+ this.curOp.selectionBefore = this.selection.toJSON();
+ }
+
+ endOperation(e) {
+ if (this.curOp && this.session) {
+ if (e && e.returnValue === false || !this.session)
+ return (this.curOp = null);
+ if (e == true && this.curOp.command && this.curOp.command.name == "mouse")
+ return;
+ this._signal("beforeEndOperation");
+ if (!this.curOp) return;
+ var command = this.curOp.command;
+ var scrollIntoView = command && command.scrollIntoView;
+ if (scrollIntoView) {
+ switch (scrollIntoView) {
+ case "center-animate":
+ scrollIntoView = "animate";
+ /* fall through */
+ case "center":
+ this.renderer.scrollCursorIntoView(null, 0.5);
+ break;
+ case "animate":
+ case "cursor":
+ this.renderer.scrollCursorIntoView();
+ break;
+ case "selectionPart":
+ var range = this.selection.getRange();
+ var config = this.renderer.layerConfig;
+ if (range.start.row >= config.lastRow || range.end.row <= config.firstRow) {
+ this.renderer.scrollSelectionIntoView(this.selection.anchor, this.selection.lead);
+ }
+ break;
+ default:
+ break;
+ }
+ if (scrollIntoView == "animate")
+ this.renderer.animateScrolling(this.curOp.scrollTop);
+ }
+ var sel = this.selection.toJSON();
+ this.curOp.selectionAfter = sel;
+ this.$lastSel = this.selection.toJSON();
+
+ // console.log(this.$lastSel+" endOP")
+ this.session.getUndoManager().addSelection(sel);
+ this.prevOp = this.curOp;
+ this.curOp = null;
+ }
+ }
+
+ $historyTracker(e) {
+ if (!this.$mergeUndoDeltas)
+ return;
+
+ var prev = this.prevOp;
+ var mergeableCommands = this.$mergeableCommands;
+ // previous command was the same
+ var shouldMerge = prev.command && (e.command.name == prev.command.name);
+ if (e.command.name == "insertstring") {
+ var text = e.args;
+ if (this.mergeNextCommand === undefined)
+ this.mergeNextCommand = true;
+
+ shouldMerge = shouldMerge
+ && this.mergeNextCommand // previous command allows to coalesce with
+ && (!/\s/.test(text) || /\s/.test(prev.args)); // previous insertion was of same type
+
+ this.mergeNextCommand = true;
+ } else {
+ shouldMerge = shouldMerge
+ && mergeableCommands.indexOf(e.command.name) !== -1; // the command is mergeable
+ }
+
+ if (
+ this.$mergeUndoDeltas != "always"
+ && Date.now() - this.sequenceStartTime > 2000
+ ) {
+ shouldMerge = false; // the sequence is too long
+ }
+
+ if (shouldMerge)
+ this.session.mergeUndoDeltas = true;
+ else if (mergeableCommands.indexOf(e.command.name) !== -1)
+ this.sequenceStartTime = Date.now();
+ }
+
+ /**
+ * Sets a new key handler, such as "vim" or "windows".
+ * @param {String} keyboardHandler The new key handler
+ *
+ **/
+ setKeyboardHandler(keyboardHandler, cb) {
+ if (keyboardHandler && typeof keyboardHandler === "string" && keyboardHandler != "ace") {
+ this.$keybindingId = keyboardHandler;
+ var _self = this;
+ config.loadModule(["keybinding", keyboardHandler], function(module) {
+ if (_self.$keybindingId == keyboardHandler)
+ _self.keyBinding.setKeyboardHandler(module && module.handler);
+ cb && cb();
+ });
+ } else {
+ this.$keybindingId = null;
+ this.keyBinding.setKeyboardHandler(keyboardHandler);
+ cb && cb();
+ }
+ }
+
+ /**
+ * Returns the keyboard handler, such as "vim" or "windows".
+ *
+ * @returns {String}
+ *
+ **/
+ getKeyboardHandler() {
+ return this.keyBinding.getKeyboardHandler();
+ }
+
+
+ /**
+ * Emitted whenever the [[EditSession]] changes.
+ * @event changeSession
+ * @param {Object} e An object with two properties, `oldSession` and `session`, that represent the old and new [[EditSession]]s.
+ *
+ **/
+ /**
+ * Sets a new editsession to use. This method also emits the `'changeSession'` event.
+ * @param {EditSession} session The new session to use
+ *
+ **/
+ setSession(session) {
+ if (this.session == session)
+ return;
+
+ // make sure operationEnd events are not emitted to wrong session
+ if (this.curOp) this.endOperation();
+ this.curOp = {};
+
+ var oldSession = this.session;
+ if (oldSession) {
+ this.session.off("change", this.$onDocumentChange);
+ this.session.off("changeMode", this.$onChangeMode);
+ this.session.off("tokenizerUpdate", this.$onTokenizerUpdate);
+ this.session.off("changeTabSize", this.$onChangeTabSize);
+ this.session.off("changeWrapLimit", this.$onChangeWrapLimit);
+ this.session.off("changeWrapMode", this.$onChangeWrapMode);
+ this.session.off("changeFold", this.$onChangeFold);
+ this.session.off("changeFrontMarker", this.$onChangeFrontMarker);
+ this.session.off("changeBackMarker", this.$onChangeBackMarker);
+ this.session.off("changeBreakpoint", this.$onChangeBreakpoint);
+ this.session.off("changeAnnotation", this.$onChangeAnnotation);
+ this.session.off("changeOverwrite", this.$onCursorChange);
+ this.session.off("changeScrollTop", this.$onScrollTopChange);
+ this.session.off("changeScrollLeft", this.$onScrollLeftChange);
+
+ var selection = this.session.getSelection();
+ selection.off("changeCursor", this.$onCursorChange);
+ selection.off("changeSelection", this.$onSelectionChange);
+ }
+
+ this.session = session;
+ if (session) {
+ this.$onDocumentChange = this.onDocumentChange.bind(this);
+ session.on("change", this.$onDocumentChange);
+ this.renderer.setSession(session);
+
+ this.$onChangeMode = this.onChangeMode.bind(this);
+ session.on("changeMode", this.$onChangeMode);
+
+ this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this);
+ session.on("tokenizerUpdate", this.$onTokenizerUpdate);
+
+ this.$onChangeTabSize = this.renderer.onChangeTabSize.bind(this.renderer);
+ session.on("changeTabSize", this.$onChangeTabSize);
+
+ this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this);
+ session.on("changeWrapLimit", this.$onChangeWrapLimit);
+
+ this.$onChangeWrapMode = this.onChangeWrapMode.bind(this);
+ session.on("changeWrapMode", this.$onChangeWrapMode);
+
+ this.$onChangeFold = this.onChangeFold.bind(this);
+ session.on("changeFold", this.$onChangeFold);
+
+ this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this);
+ this.session.on("changeFrontMarker", this.$onChangeFrontMarker);
+
+ this.$onChangeBackMarker = this.onChangeBackMarker.bind(this);
+ this.session.on("changeBackMarker", this.$onChangeBackMarker);
+
+ this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this);
+ this.session.on("changeBreakpoint", this.$onChangeBreakpoint);
+
+ this.$onChangeAnnotation = this.onChangeAnnotation.bind(this);
+ this.session.on("changeAnnotation", this.$onChangeAnnotation);
+
+ this.$onCursorChange = this.onCursorChange.bind(this);
+ this.session.on("changeOverwrite", this.$onCursorChange);
+
+ this.$onScrollTopChange = this.onScrollTopChange.bind(this);
+ this.session.on("changeScrollTop", this.$onScrollTopChange);
+
+ this.$onScrollLeftChange = this.onScrollLeftChange.bind(this);
+ this.session.on("changeScrollLeft", this.$onScrollLeftChange);
+
+ this.selection = session.getSelection();
+ this.selection.on("changeCursor", this.$onCursorChange);
+
+ this.$onSelectionChange = this.onSelectionChange.bind(this);
+ this.selection.on("changeSelection", this.$onSelectionChange);
+
+ this.onChangeMode();
+
+ this.onCursorChange();
+
+ this.onScrollTopChange();
+ this.onScrollLeftChange();
+ this.onSelectionChange();
+ this.onChangeFrontMarker();
+ this.onChangeBackMarker();
+ this.onChangeBreakpoint();
+ this.onChangeAnnotation();
+ this.session.getUseWrapMode() && this.renderer.adjustWrapLimit();
+ this.renderer.updateFull();
+ } else {
+ this.selection = null;
+ this.renderer.setSession(session);
+ }
+
+ this._signal("changeSession", {
+ session: session,
+ oldSession: oldSession
+ });
+
+ this.curOp = null;
+
+ oldSession && oldSession._signal("changeEditor", {oldEditor: this});
+ session && session._signal("changeEditor", {editor: this});
+
+ if (session && !session.destroyed)
+ session.bgTokenizer.scheduleStart();
+ }
+
+ /**
+ * Returns the current session being used.
+ * @returns {EditSession}
+ **/
+ getSession() {
+ return this.session;
+ }
+
+ /**
+ * Sets the current document to `val`.
+ * @param {String} val The new value to set for the document
+ * @param {Number} cursorPos Where to set the new value. `undefined` or 0 is selectAll, -1 is at the document start, and 1 is at the end
+ *
+ * @returns {String} The current document value
+ * @related Document.setValue
+ **/
+ setValue(val, cursorPos) {
+ this.session.doc.setValue(val);
+
+ if (!cursorPos)
+ this.selectAll();
+ else if (cursorPos == 1)
+ this.navigateFileEnd();
+ else if (cursorPos == -1)
+ this.navigateFileStart();
+
+ return val;
+ }
+
+ /**
+ * Returns the current session's content.
+ *
+ * @returns {String}
+ * @related EditSession.getValue
+ **/
+ getValue() {
+ return this.session.getValue();
+ }
+
+ /**
+ *
+ * Returns the currently highlighted selection.
+ * @returns {Selection} The selection object
+ **/
+ getSelection() {
+ return this.selection;
+ }
+
+ /**
+ * {:VirtualRenderer.onResize}
+ * @param {Boolean} force If `true`, recomputes the size, even if the height and width haven't changed
+ *
+ *
+ * @related VirtualRenderer.onResize
+ **/
+ resize(force) {
+ this.renderer.onResize(force);
+ }
+
+ /**
+ * {:VirtualRenderer.setTheme}
+ * @param {String} theme The path to a theme
+ * @param {Function} cb optional callback called when theme is loaded
+ **/
+ setTheme(theme, cb) {
+ this.renderer.setTheme(theme, cb);
+ }
+
+ /**
+ * {:VirtualRenderer.getTheme}
+ *
+ * @returns {String} The set theme
+ * @related VirtualRenderer.getTheme
+ **/
+ getTheme() {
+ return this.renderer.getTheme();
+ }
+
+ /**
+ * {:VirtualRenderer.setStyle}
+ * @param {String} style A class name
+ *
+ *
+ * @related VirtualRenderer.setStyle
+ **/
+ setStyle(style) {
+ this.renderer.setStyle(style);
+ }
+
+ /**
+ * {:VirtualRenderer.unsetStyle}
+ * @related VirtualRenderer.unsetStyle
+ **/
+ unsetStyle(style) {
+ this.renderer.unsetStyle(style);
+ }
+
+ /**
+ * Gets the current font size of the editor text.
+ */
+ getFontSize() {
+ return this.getOption("fontSize") ||
+ dom.computedStyle(this.container).fontSize;
+ }
+
+ /**
+ * Set a new font size (in pixels) for the editor text.
+ * @param {String} size A font size ( _e.g._ "12px")
+ *
+ *
+ **/
+ setFontSize(size) {
+ this.setOption("fontSize", size);
+ }
+
+ $highlightBrackets() {
+ if (this.$highlightPending) {
+ return;
+ }
+
+ // perform highlight async to not block the browser during navigation
+ var self = this;
+ this.$highlightPending = true;
+ setTimeout(function () {
+ self.$highlightPending = false;
+ var session = self.session;
+ if (!session || session.destroyed) return;
+ if (session.$bracketHighlight) {
+ session.$bracketHighlight.markerIds.forEach(function(id) {
+ session.removeMarker(id);
+ });
+ session.$bracketHighlight = null;
+ }
+ var pos = self.getCursorPosition();
+ var handler = self.getKeyboardHandler();
+ var isBackwards = handler && handler.$getDirectionForHighlight && handler.$getDirectionForHighlight(self);
+ var ranges = session.getMatchingBracketRanges(pos, isBackwards);
+
+ if (!ranges) {
+ var iterator = new TokenIterator(session, pos.row, pos.column);
+ var token = iterator.getCurrentToken();
+
+ if (token && /\b(?:tag-open|tag-name)/.test(token.type)) {
+ var tagNamesRanges = session.getMatchingTags(pos);
+ if (tagNamesRanges) ranges = [tagNamesRanges.openTagName, tagNamesRanges.closeTagName];
+ }
+ }
+ if (!ranges && session.$mode.getMatching)
+ ranges = session.$mode.getMatching(self.session);
+ if (!ranges) {
+ if (self.getHighlightIndentGuides()) self.renderer.$textLayer.$highlightIndentGuide();
+ return;
+ }
+
+ var markerType = "ace_bracket";
+ if (!Array.isArray(ranges)) {
+ ranges = [ranges];
+ } else if (ranges.length == 1) {
+ markerType = "ace_error_bracket";
+ }
+
+ // show adjacent ranges as one
+ if (ranges.length == 2) {
+ if (Range.comparePoints(ranges[0].end, ranges[1].start) == 0)
+ ranges = [Range.fromPoints(ranges[0].start, ranges[1].end)];
+ else if (Range.comparePoints(ranges[0].start, ranges[1].end) == 0)
+ ranges = [Range.fromPoints(ranges[1].start, ranges[0].end)];
+ }
+
+ session.$bracketHighlight = {
+ ranges: ranges,
+ markerIds: ranges.map(function(range) {
+ return session.addMarker(range, markerType, "text");
+ })
+ };
+ if (self.getHighlightIndentGuides()) self.renderer.$textLayer.$highlightIndentGuide();
+ }, 50);
+ }
+
+ /**
+ *
+ * Brings the current `textInput` into focus.
+ **/
+ focus() {
+ this.textInput.focus();
+ }
+
+ /**
+ * Returns `true` if the current `textInput` is in focus.
+ * @return {Boolean}
+ **/
+ isFocused() {
+ return this.textInput.isFocused();
+ }
+
+ /**
+ *
+ * Blurs the current `textInput`.
+ **/
+ blur() {
+ this.textInput.blur();
+ }
+
+ /**
+ * Emitted once the editor comes into focus.
+ * @event focus
+ *
+ *
+ **/
+ onFocus(e) {
+ if (this.$isFocused)
+ return;
+ this.$isFocused = true;
+ this.renderer.showCursor();
+ this.renderer.visualizeFocus();
+ this._emit("focus", e);
+ }
+
+ /**
+ * Emitted once the editor has been blurred.
+ * @event blur
+ *
+ *
+ **/
+ onBlur(e) {
+ if (!this.$isFocused)
+ return;
+ this.$isFocused = false;
+ this.renderer.hideCursor();
+ this.renderer.visualizeBlur();
+ this._emit("blur", e);
+ }
+
+ $cursorChange() {
+ this.renderer.updateCursor();
+ this.$highlightBrackets();
+ this.$updateHighlightActiveLine();
+ }
+
+ /**
+ * Emitted whenever the document is changed.
+ * @event change
+ * @param {Object} delta Contains a single property, `data`, which has the delta of changes
+ *
+ *
+ *
+ **/
+ onDocumentChange(delta) {
+ // Rerender and emit "change" event.
+ var wrap = this.session.$useWrapMode;
+ var lastRow = (delta.start.row == delta.end.row ? delta.end.row : Infinity);
+ this.renderer.updateLines(delta.start.row, lastRow, wrap);
+
+ this._signal("change", delta);
+
+ // Update cursor because tab characters can influence the cursor position.
+ this.$cursorChange();
+ }
+
+ onTokenizerUpdate(e) {
+ var rows = e.data;
+ this.renderer.updateLines(rows.first, rows.last);
+ }
+
+
+ onScrollTopChange() {
+ this.renderer.scrollToY(this.session.getScrollTop());
+ }
+
+ onScrollLeftChange() {
+ this.renderer.scrollToX(this.session.getScrollLeft());
+ }
+
+ /**
+ * Emitted when the selection changes.
+ *
+ **/
+ onCursorChange() {
+ this.$cursorChange();
+ this._signal("changeSelection");
+ }
+
+ $updateHighlightActiveLine() {
+ var session = this.getSession();
+
+ var highlight;
+ if (this.$highlightActiveLine) {
+ if (this.$selectionStyle != "line" || !this.selection.isMultiLine())
+ highlight = this.getCursorPosition();
+ if (this.renderer.theme && this.renderer.theme.$selectionColorConflict && !this.selection.isEmpty())
+ highlight = false;
+ if (this.renderer.$maxLines && this.session.getLength() === 1 && !(this.renderer.$minLines > 1))
+ highlight = false;
+ }
+
+ if (session.$highlightLineMarker && !highlight) {
+ session.removeMarker(session.$highlightLineMarker.id);
+ session.$highlightLineMarker = null;
+ } else if (!session.$highlightLineMarker && highlight) {
+ var range = new Range(highlight.row, highlight.column, highlight.row, Infinity);
+ range.id = session.addMarker(range, "ace_active-line", "screenLine");
+ session.$highlightLineMarker = range;
+ } else if (highlight) {
+ session.$highlightLineMarker.start.row = highlight.row;
+ session.$highlightLineMarker.end.row = highlight.row;
+ session.$highlightLineMarker.start.column = highlight.column;
+ session._signal("changeBackMarker");
+ }
+ }
+
+ onSelectionChange(e) {
+ var session = this.session;
+
+ if (session.$selectionMarker) {
+ session.removeMarker(session.$selectionMarker);
+ }
+ session.$selectionMarker = null;
+
+ if (!this.selection.isEmpty()) {
+ var range = this.selection.getRange();
+ var style = this.getSelectionStyle();
+ session.$selectionMarker = session.addMarker(range, "ace_selection", style);
+ } else {
+ this.$updateHighlightActiveLine();
+ }
+
+ var re = this.$highlightSelectedWord && this.$getSelectionHighLightRegexp();
+ this.session.highlight(re);
+
+ this._signal("changeSelection");
+ }
+
+ $getSelectionHighLightRegexp() {
+ var session = this.session;
+
+ var selection = this.getSelectionRange();
+ if (selection.isEmpty() || selection.isMultiLine())
+ return;
+
+ var startColumn = selection.start.column;
+ var endColumn = selection.end.column;
+ var line = session.getLine(selection.start.row);
+
+ var needle = line.substring(startColumn, endColumn);
+ // maximum allowed size for regular expressions in 32000,
+ // but getting close to it has significant impact on the performance
+ if (needle.length > 5000 || !/[\w\d]/.test(needle))
+ return;
+
+ var re = this.$search.$assembleRegExp({
+ wholeWord: true,
+ caseSensitive: true,
+ needle: needle
+ });
+
+ var wordWithBoundary = line.substring(startColumn - 1, endColumn + 1);
+ if (!re.test(wordWithBoundary))
+ return;
+
+ return re;
+ }
+
+
+ onChangeFrontMarker() {
+ this.renderer.updateFrontMarkers();
+ }
+
+ onChangeBackMarker() {
+ this.renderer.updateBackMarkers();
+ }
+
+
+ onChangeBreakpoint() {
+ this.renderer.updateBreakpoints();
+ }
+
+ onChangeAnnotation() {
+ this.renderer.setAnnotations(this.session.getAnnotations());
+ }
+
+
+ onChangeMode (e) {
+ this.renderer.updateText();
+ this._emit("changeMode", e);
+ }
+
+
+ onChangeWrapLimit() {
+ this.renderer.updateFull();
+ }
+
+ onChangeWrapMode() {
+ this.renderer.onResize(true);
+ }
+
+
+ onChangeFold() {
+ // Update the active line marker as due to folding changes the current
+ // line range on the screen might have changed.
+ this.$updateHighlightActiveLine();
+ // TODO: This might be too much updating. Okay for now.
+ this.renderer.updateFull();
+ }
+
+
+ /**
+ * Returns the string of text currently highlighted.
+ * @returns {String}
+ **/
+ getSelectedText() {
+ return this.session.getTextRange(this.getSelectionRange());
+ }
+
+ /**
+ * Emitted when text is copied.
+ * @event copy
+ * @param {String} text The copied text
+ *
+ **/
+ /**
+ * Returns the string of text currently highlighted.
+ * @returns {String}
+ **/
+ getCopyText () {
+ var text = this.getSelectedText();
+ var nl = this.session.doc.getNewLineCharacter();
+ var copyLine= false;
+ if (!text && this.$copyWithEmptySelection) {
+ copyLine = true;
+ var ranges = this.selection.getAllRanges();
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ if (i && ranges[i - 1].start.row == range.start.row)
+ continue;
+ text += this.session.getLine(range.start.row) + nl;
+ }
+ }
+ var e = {text: text};
+ this._signal("copy", e);
+ clipboard.lineMode = copyLine ? e.text : false;
+ return e.text;
+ }
+
+ /**
+ * Called whenever a text "copy" happens.
+ **/
+ onCopy() {
+ this.commands.exec("copy", this);
+ }
+
+ /**
+ * Called whenever a text "cut" happens.
+ **/
+ onCut() {
+ this.commands.exec("cut", this);
+ }
+
+ /**
+ * Emitted when text is pasted.
+ * @event paste
+ * @param {Object} an object which contains one property, `text`, that represents the text to be pasted. Editing this property will alter the text that is pasted.
+ *
+ *
+ **/
+ /**
+ * Called whenever a text "paste" happens.
+ * @param {String} text The pasted text
+ *
+ *
+ **/
+ onPaste(text, event) {
+ var e = {text: text, event: event};
+ this.commands.exec("paste", this, e);
+ }
+
+ $handlePaste(e) {
+ if (typeof e == "string")
+ e = {text: e};
+ this._signal("paste", e);
+ var text = e.text;
+
+ var lineMode = text === clipboard.lineMode;
+ var session = this.session;
+ if (!this.inMultiSelectMode || this.inVirtualSelectionMode) {
+ if (lineMode)
+ session.insert({ row: this.selection.lead.row, column: 0 }, text);
+ else
+ this.insert(text);
+ } else if (lineMode) {
+ this.selection.rangeList.ranges.forEach(function(range) {
+ session.insert({ row: range.start.row, column: 0 }, text);
+ });
+ } else {
+ var lines = text.split(/\r\n|\r|\n/);
+ var ranges = this.selection.rangeList.ranges;
+
+ var isFullLine = lines.length == 2 && (!lines[0] || !lines[1]);
+ if (lines.length != ranges.length || isFullLine)
+ return this.commands.exec("insertstring", this, text);
+
+ for (var i = ranges.length; i--;) {
+ var range = ranges[i];
+ if (!range.isEmpty())
+ session.remove(range);
+
+ session.insert(range.start, lines[i]);
+ }
+ }
+ }
+
+ execCommand(command, args) {
+ return this.commands.exec(command, this, args);
+ }
+
+ /**
+ * Inserts `text` into wherever the cursor is pointing.
+ * @param {String} text The new text to add
+ *
+ **/
+ insert(text, pasted) {
+ var session = this.session;
+ var mode = session.getMode();
+ var cursor = this.getCursorPosition();
+
+ if (this.getBehavioursEnabled() && !pasted) {
+ // Get a transform if the current mode wants one.
+ var transform = mode.transformAction(session.getState(cursor.row), 'insertion', this, session, text);
+ if (transform) {
+ if (text !== transform.text) {
+ // keep automatic insertion in a separate delta, unless it is in multiselect mode
+ if (!this.inVirtualSelectionMode) {
+ this.session.mergeUndoDeltas = false;
+ this.mergeNextCommand = false;
+ }
+ }
+ text = transform.text;
+
+ }
+ }
+
+ if (text == "\t")
+ text = this.session.getTabString();
+
+ // remove selected text
+ if (!this.selection.isEmpty()) {
+ var range = this.getSelectionRange();
+ cursor = this.session.remove(range);
+ this.clearSelection();
+ }
+ else if (this.session.getOverwrite() && text.indexOf("\n") == -1) {
+ var range = new Range.fromPoints(cursor, cursor);
+ range.end.column += text.length;
+ this.session.remove(range);
+ }
+
+ if (text == "\n" || text == "\r\n") {
+ var line = session.getLine(cursor.row);
+ if (cursor.column > line.search(/\S|$/)) {
+ var d = line.substr(cursor.column).search(/\S|$/);
+ session.doc.removeInLine(cursor.row, cursor.column, cursor.column + d);
+ }
+ }
+ this.clearSelection();
+
+ var start = cursor.column;
+ var lineState = session.getState(cursor.row);
+ var line = session.getLine(cursor.row);
+ var shouldOutdent = mode.checkOutdent(lineState, line, text);
+ session.insert(cursor, text);
+
+ if (transform && transform.selection) {
+ if (transform.selection.length == 2) { // Transform relative to the current column
+ this.selection.setSelectionRange(
+ new Range(cursor.row, start + transform.selection[0],
+ cursor.row, start + transform.selection[1]));
+ } else { // Transform relative to the current row.
+ this.selection.setSelectionRange(
+ new Range(cursor.row + transform.selection[0],
+ transform.selection[1],
+ cursor.row + transform.selection[2],
+ transform.selection[3]));
+ }
+ }
+ if (this.$enableAutoIndent) {
+ if (session.getDocument().isNewLine(text)) {
+ var lineIndent = mode.getNextLineIndent(lineState, line.slice(0, cursor.column), session.getTabString());
+
+ session.insert({row: cursor.row+1, column: 0}, lineIndent);
+ }
+ if (shouldOutdent)
+ mode.autoOutdent(lineState, session, cursor.row);
+ }
+ }
+
+ autoIndent() {
+ var session = this.session;
+ var mode = session.getMode();
+
+ var startRow, endRow;
+ if (this.selection.isEmpty()) {
+ startRow = 0;
+ endRow = session.doc.getLength() - 1;
+ } else {
+ var selectedRange = this.getSelectionRange();
+
+ startRow = selectedRange.start.row;
+ endRow = selectedRange.end.row;
+ }
+
+ var prevLineState = "";
+ var prevLine = "";
+ var lineIndent = "";
+ var line, currIndent, range;
+ var tab = session.getTabString();
+
+ for (var row = startRow; row <= endRow; row++) {
+ if (row > 0) {
+ prevLineState = session.getState(row - 1);
+ prevLine = session.getLine(row - 1);
+ lineIndent = mode.getNextLineIndent(prevLineState, prevLine, tab);
+ }
+
+ line = session.getLine(row);
+ currIndent = mode.$getIndent(line);
+ if (lineIndent !== currIndent) {
+ if (currIndent.length > 0) {
+ range = new Range(row, 0, row, currIndent.length);
+ session.remove(range);
+ }
+ if (lineIndent.length > 0) {
+ session.insert({row: row, column: 0}, lineIndent);
+ }
+ }
+
+ mode.autoOutdent(prevLineState, session, row);
+ }
+ }
+
+
+ onTextInput(text, composition) {
+ if (!composition)
+ return this.keyBinding.onTextInput(text);
+
+ this.startOperation({command: { name: "insertstring" }});
+ var applyComposition = this.applyComposition.bind(this, text, composition);
+ if (this.selection.rangeCount)
+ this.forEachSelection(applyComposition);
+ else
+ applyComposition();
+ this.endOperation();
+ }
+
+ applyComposition(text, composition) {
+ if (composition.extendLeft || composition.extendRight) {
+ var r = this.selection.getRange();
+ r.start.column -= composition.extendLeft;
+ r.end.column += composition.extendRight;
+ if (r.start.column < 0) {
+ r.start.row--;
+ r.start.column += this.session.getLine(r.start.row).length + 1;
+ }
+ this.selection.setRange(r);
+ if (!text && !r.isEmpty())
+ this.remove();
+ }
+ if (text || !this.selection.isEmpty())
+ this.insert(text, true);
+ if (composition.restoreStart || composition.restoreEnd) {
+ var r = this.selection.getRange();
+ r.start.column -= composition.restoreStart;
+ r.end.column -= composition.restoreEnd;
+ this.selection.setRange(r);
+ }
+ }
+
+ onCommandKey(e, hashId, keyCode) {
+ return this.keyBinding.onCommandKey(e, hashId, keyCode);
+ }
+
+ /**
+ * Pass in `true` to enable overwrites in your session, or `false` to disable. If overwrites is enabled, any text you enter will type over any text after it. If the value of `overwrite` changes, this function also emits the `changeOverwrite` event.
+ * @param {Boolean} overwrite Defines whether or not to set overwrites
+ *
+ *
+ * @related EditSession.setOverwrite
+ **/
+ setOverwrite(overwrite) {
+ this.session.setOverwrite(overwrite);
+ }
+
+ /**
+ * Returns `true` if overwrites are enabled; `false` otherwise.
+ * @returns {Boolean}
+ * @related EditSession.getOverwrite
+ **/
+ getOverwrite() {
+ return this.session.getOverwrite();
+ }
+
+ /**
+ * Sets the value of overwrite to the opposite of whatever it currently is.
+ * @related EditSession.toggleOverwrite
+ **/
+ toggleOverwrite() {
+ this.session.toggleOverwrite();
+ }
+
+ /**
+ * Sets how fast the mouse scrolling should do.
+ * @param {Number} speed A value indicating the new speed (in milliseconds)
+ **/
+ setScrollSpeed(speed) {
+ this.setOption("scrollSpeed", speed);
+ }
+
+ /**
+ * Returns the value indicating how fast the mouse scroll speed is (in milliseconds).
+ * @returns {Number}
+ **/
+ getScrollSpeed() {
+ return this.getOption("scrollSpeed");
+ }
+
+ /**
+ * Sets the delay (in milliseconds) of the mouse drag.
+ * @param {Number} dragDelay A value indicating the new delay
+ **/
+ setDragDelay(dragDelay) {
+ this.setOption("dragDelay", dragDelay);
+ }
+
+ /**
+ * Returns the current mouse drag delay.
+ * @returns {Number}
+ **/
+ getDragDelay() {
+ return this.getOption("dragDelay");
+ }
+
+ /**
+ * Emitted when the selection style changes, via [[Editor.setSelectionStyle]].
+ * @event changeSelectionStyle
+ * @param {Object} data Contains one property, `data`, which indicates the new selection style
+ **/
+ /**
+ * Draw selection markers spanning whole line, or only over selected text. Default value is "line"
+ * @param {String} val The new selection style "line"|"text"
+ *
+ **/
+ setSelectionStyle(val) {
+ this.setOption("selectionStyle", val);
+ }
+
+ /**
+ * Returns the current selection style.
+ * @returns {String}
+ **/
+ getSelectionStyle() {
+ return this.getOption("selectionStyle");
+ }
+
+ /**
+ * Determines whether or not the current line should be highlighted.
+ * @param {Boolean} shouldHighlight Set to `true` to highlight the current line
+ **/
+ setHighlightActiveLine(shouldHighlight) {
+ this.setOption("highlightActiveLine", shouldHighlight);
+ }
+ /**
+ * Returns `true` if current lines are always highlighted.
+ * @return {Boolean}
+ **/
+ getHighlightActiveLine() {
+ return this.getOption("highlightActiveLine");
+ }
+ setHighlightGutterLine(shouldHighlight) {
+ this.setOption("highlightGutterLine", shouldHighlight);
+ }
+
+ getHighlightGutterLine() {
+ return this.getOption("highlightGutterLine");
+ }
+
+ /**
+ * Determines if the currently selected word should be highlighted.
+ * @param {Boolean} shouldHighlight Set to `true` to highlight the currently selected word
+ *
+ **/
+ setHighlightSelectedWord(shouldHighlight) {
+ this.setOption("highlightSelectedWord", shouldHighlight);
+ }
+
+ /**
+ * Returns `true` if currently highlighted words are to be highlighted.
+ * @returns {Boolean}
+ **/
+ getHighlightSelectedWord() {
+ return this.$highlightSelectedWord;
+ }
+
+ setAnimatedScroll(shouldAnimate){
+ this.renderer.setAnimatedScroll(shouldAnimate);
+ }
+
+ getAnimatedScroll(){
+ return this.renderer.getAnimatedScroll();
+ }
+
+ /**
+ * If `showInvisibles` is set to `true`, invisible characters—like spaces or new lines—are show in the editor.
+ * @param {Boolean} showInvisibles Specifies whether or not to show invisible characters
+ *
+ **/
+ setShowInvisibles(showInvisibles) {
+ this.renderer.setShowInvisibles(showInvisibles);
+ }
+
+ /**
+ * Returns `true` if invisible characters are being shown.
+ * @returns {Boolean}
+ **/
+ getShowInvisibles() {
+ return this.renderer.getShowInvisibles();
+ }
+
+ setDisplayIndentGuides(display) {
+ this.renderer.setDisplayIndentGuides(display);
+ }
+
+ getDisplayIndentGuides() {
+ return this.renderer.getDisplayIndentGuides();
+ }
+
+ setHighlightIndentGuides(highlight) {
+ this.renderer.setHighlightIndentGuides(highlight);
+ }
+
+ getHighlightIndentGuides() {
+ return this.renderer.getHighlightIndentGuides();
+ }
+
+ /**
+ * If `showPrintMargin` is set to `true`, the print margin is shown in the editor.
+ * @param {Boolean} showPrintMargin Specifies whether or not to show the print margin
+ *
+ **/
+ setShowPrintMargin(showPrintMargin) {
+ this.renderer.setShowPrintMargin(showPrintMargin);
+ }
+
+ /**
+ * Returns `true` if the print margin is being shown.
+ * @returns {Boolean}
+ **/
+ getShowPrintMargin() {
+ return this.renderer.getShowPrintMargin();
+ }
+
+ /**
+ * Sets the column defining where the print margin should be.
+ * @param {Number} showPrintMargin Specifies the new print margin
+ *
+ **/
+ setPrintMarginColumn(showPrintMargin) {
+ this.renderer.setPrintMarginColumn(showPrintMargin);
+ }
+
+ /**
+ * Returns the column number of where the print margin is.
+ * @returns {Number}
+ **/
+ getPrintMarginColumn() {
+ return this.renderer.getPrintMarginColumn();
+ }
+
+ /**
+ * If `readOnly` is true, then the editor is set to read-only mode, and none of the content can change.
+ * @param {Boolean} readOnly Specifies whether the editor can be modified or not
+ *
+ **/
+ setReadOnly(readOnly) {
+ this.setOption("readOnly", readOnly);
+ }
+
+ /**
+ * Returns `true` if the editor is set to read-only mode.
+ * @returns {Boolean}
+ **/
+ getReadOnly() {
+ return this.getOption("readOnly");
+ }
+
+ /**
+ * Specifies whether to use behaviors or not. ["Behaviors" in this case is the auto-pairing of special characters, like quotation marks, parenthesis, or brackets.]{: #BehaviorsDef}
+ * @param {Boolean} enabled Enables or disables behaviors
+ *
+ **/
+ setBehavioursEnabled(enabled) {
+ this.setOption("behavioursEnabled", enabled);
+ }
+
+ /**
+ * Returns `true` if the behaviors are currently enabled. {:BehaviorsDef}
+ *
+ * @returns {Boolean}
+ **/
+ getBehavioursEnabled() {
+ return this.getOption("behavioursEnabled");
+ }
+
+ /**
+ * Specifies whether to use wrapping behaviors or not, i.e. automatically wrapping the selection with characters such as brackets
+ * when such a character is typed in.
+ * @param {Boolean} enabled Enables or disables wrapping behaviors
+ *
+ **/
+ setWrapBehavioursEnabled(enabled) {
+ this.setOption("wrapBehavioursEnabled", enabled);
+ }
+
+ /**
+ * Returns `true` if the wrapping behaviors are currently enabled.
+ **/
+ getWrapBehavioursEnabled() {
+ return this.getOption("wrapBehavioursEnabled");
+ }
+
+ /**
+ * Indicates whether the fold widgets should be shown or not.
+ * @param {Boolean} show Specifies whether the fold widgets are shown
+ **/
+ setShowFoldWidgets(show) {
+ this.setOption("showFoldWidgets", show);
+
+ }
+ /**
+ * Returns `true` if the fold widgets are shown.
+ * @return {Boolean}
+ **/
+ getShowFoldWidgets() {
+ return this.getOption("showFoldWidgets");
+ }
+
+ setFadeFoldWidgets(fade) {
+ this.setOption("fadeFoldWidgets", fade);
+ }
+
+ getFadeFoldWidgets() {
+ return this.getOption("fadeFoldWidgets");
+ }
+
+ /**
+ * Removes the current selection or one character.
+ * @param {String} dir The direction of the deletion to occur, either "left" or "right"
+ *
+ **/
+ remove(dir) {
+ if (this.selection.isEmpty()){
+ if (dir == "left")
+ this.selection.selectLeft();
+ else
+ this.selection.selectRight();
+ }
+
+ var range = this.getSelectionRange();
+ if (this.getBehavioursEnabled()) {
+ var session = this.session;
+ var state = session.getState(range.start.row);
+ var new_range = session.getMode().transformAction(state, 'deletion', this, session, range);
+
+ if (range.end.column === 0) {
+ var text = session.getTextRange(range);
+ if (text[text.length - 1] == "\n") {
+ var line = session.getLine(range.end.row);
+ if (/^\s+$/.test(line)) {
+ range.end.column = line.length;
+ }
+ }
+ }
+ if (new_range)
+ range = new_range;
+ }
+
+ this.session.remove(range);
+ this.clearSelection();
+ }
+
+ /**
+ * Removes the word directly to the right of the current selection.
+ **/
+ removeWordRight() {
+ if (this.selection.isEmpty())
+ this.selection.selectWordRight();
+
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ }
+
+ /**
+ * Removes the word directly to the left of the current selection.
+ **/
+ removeWordLeft() {
+ if (this.selection.isEmpty())
+ this.selection.selectWordLeft();
+
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ }
+
+ /**
+ * Removes all the words to the left of the current selection, until the start of the line.
+ **/
+ removeToLineStart() {
+ if (this.selection.isEmpty())
+ this.selection.selectLineStart();
+ if (this.selection.isEmpty())
+ this.selection.selectLeft();
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ }
+
+ /**
+ * Removes all the words to the right of the current selection, until the end of the line.
+ **/
+ removeToLineEnd() {
+ if (this.selection.isEmpty())
+ this.selection.selectLineEnd();
+
+ var range = this.getSelectionRange();
+ if (range.start.column == range.end.column && range.start.row == range.end.row) {
+ range.end.column = 0;
+ range.end.row++;
+ }
+
+ this.session.remove(range);
+ this.clearSelection();
+ }
+
+ /**
+ * Splits the line at the current selection (by inserting an `'\n'`).
+ **/
+ splitLine() {
+ if (!this.selection.isEmpty()) {
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ }
+
+ var cursor = this.getCursorPosition();
+ this.insert("\n");
+ this.moveCursorToPosition(cursor);
+ }
+
+ /**
+ * Set the "ghost" text in provided position. "Ghost" text is a kind of
+ * preview text inside the editor which can be used to preview some code
+ * inline in the editor such as, for example, code completions.
+ *
+ * @param {String} text Text to be inserted as "ghost" text
+ * @param {object} position Position to insert text to
+ */
+ setGhostText(text, position) {
+ if (!this.session.widgetManager) {
+ this.session.widgetManager = new LineWidgets(this.session);
+ this.session.widgetManager.attach(this);
+ }
+ this.renderer.setGhostText(text, position);
+ }
+
+ /**
+ * Removes "ghost" text currently displayed in the editor.
+ */
+ removeGhostText() {
+ if (!this.session.widgetManager) return;
+
+ this.renderer.removeGhostText();
+ }
+
+ /**
+ * Transposes current line.
+ **/
+ transposeLetters() {
+ if (!this.selection.isEmpty()) {
+ return;
+ }
+
+ var cursor = this.getCursorPosition();
+ var column = cursor.column;
+ if (column === 0)
+ return;
+
+ var line = this.session.getLine(cursor.row);
+ var swap, range;
+ if (column < line.length) {
+ swap = line.charAt(column) + line.charAt(column-1);
+ range = new Range(cursor.row, column-1, cursor.row, column+1);
+ }
+ else {
+ swap = line.charAt(column-1) + line.charAt(column-2);
+ range = new Range(cursor.row, column-2, cursor.row, column);
+ }
+ this.session.replace(range, swap);
+ this.session.selection.moveToPosition(range.end);
+ }
+
+ /**
+ * Converts the current selection entirely into lowercase.
+ **/
+ toLowerCase() {
+ var originalRange = this.getSelectionRange();
+ if (this.selection.isEmpty()) {
+ this.selection.selectWord();
+ }
+
+ var range = this.getSelectionRange();
+ var text = this.session.getTextRange(range);
+ this.session.replace(range, text.toLowerCase());
+ this.selection.setSelectionRange(originalRange);
+ }
+
+ /**
+ * Converts the current selection entirely into uppercase.
+ **/
+ toUpperCase() {
+ var originalRange = this.getSelectionRange();
+ if (this.selection.isEmpty()) {
+ this.selection.selectWord();
+ }
+
+ var range = this.getSelectionRange();
+ var text = this.session.getTextRange(range);
+ this.session.replace(range, text.toUpperCase());
+ this.selection.setSelectionRange(originalRange);
+ }
+
+ /**
+ * Inserts an indentation into the current cursor position or indents the selected lines.
+ *
+ * @related EditSession.indentRows
+ **/
+ indent() {
+ var session = this.session;
+ var range = this.getSelectionRange();
+
+ if (range.start.row < range.end.row) {
+ var rows = this.$getSelectedRows();
+ session.indentRows(rows.first, rows.last, "\t");
+ return;
+ } else if (range.start.column < range.end.column) {
+ var text = session.getTextRange(range);
+ if (!/^\s+$/.test(text)) {
+ var rows = this.$getSelectedRows();
+ session.indentRows(rows.first, rows.last, "\t");
+ return;
+ }
+ }
+
+ var line = session.getLine(range.start.row);
+ var position = range.start;
+ var size = session.getTabSize();
+ var column = session.documentToScreenColumn(position.row, position.column);
+
+ if (this.session.getUseSoftTabs()) {
+ var count = (size - column % size);
+ var indentString = lang.stringRepeat(" ", count);
+ } else {
+ var count = column % size;
+ while (line[range.start.column - 1] == " " && count) {
+ range.start.column--;
+ count--;
+ }
+ this.selection.setSelectionRange(range);
+ indentString = "\t";
+ }
+ return this.insert(indentString);
+ }
+
+ /**
+ * Indents the current line.
+ * @related EditSession.indentRows
+ **/
+ blockIndent() {
+ var rows = this.$getSelectedRows();
+ this.session.indentRows(rows.first, rows.last, "\t");
+ }
+
+ /**
+ * Outdents the current line.
+ * @related EditSession.outdentRows
+ **/
+ blockOutdent() {
+ var selection = this.session.getSelection();
+ this.session.outdentRows(selection.getRange());
+ }
+
+ // TODO: move out of core when we have good mechanism for managing extensions
+ sortLines() {
+ var rows = this.$getSelectedRows();
+ var session = this.session;
+
+ var lines = [];
+ for (var i = rows.first; i <= rows.last; i++)
+ lines.push(session.getLine(i));
+
+ lines.sort(function(a, b) {
+ if (a.toLowerCase() < b.toLowerCase()) return -1;
+ if (a.toLowerCase() > b.toLowerCase()) return 1;
+ return 0;
+ });
+
+ var deleteRange = new Range(0, 0, 0, 0);
+ for (var i = rows.first; i <= rows.last; i++) {
+ var line = session.getLine(i);
+ deleteRange.start.row = i;
+ deleteRange.end.row = i;
+ deleteRange.end.column = line.length;
+ session.replace(deleteRange, lines[i-rows.first]);
+ }
+ }
+
+ /**
+ * Given the currently selected range, this function either comments all the lines, or uncomments all of them.
+ **/
+ toggleCommentLines() {
+ var state = this.session.getState(this.getCursorPosition().row);
+ var rows = this.$getSelectedRows();
+ this.session.getMode().toggleCommentLines(state, this.session, rows.first, rows.last);
+ }
+
+ toggleBlockComment() {
+ var cursor = this.getCursorPosition();
+ var state = this.session.getState(cursor.row);
+ var range = this.getSelectionRange();
+ this.session.getMode().toggleBlockComment(state, this.session, range, cursor);
+ }
+
+ /**
+ * Works like [[EditSession.getTokenAt]], except it returns a number.
+ * @returns {Number}
+ **/
+ getNumberAt(row, column) {
+ var _numberRx = /[\-]?[0-9]+(?:\.[0-9]+)?/g;
+ _numberRx.lastIndex = 0;
+
+ var s = this.session.getLine(row);
+ while (_numberRx.lastIndex < column) {
+ var m = _numberRx.exec(s);
+ if(m.index <= column && m.index+m[0].length >= column){
+ var number = {
+ value: m[0],
+ start: m.index,
+ end: m.index+m[0].length
+ };
+ return number;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * If the character before the cursor is a number, this functions changes its value by `amount`.
+ * @param {Number} amount The value to change the numeral by (can be negative to decrease value)
+ *
+ **/
+ modifyNumber(amount) {
+ var row = this.selection.getCursor().row;
+ var column = this.selection.getCursor().column;
+
+ // get the char before the cursor
+ var charRange = new Range(row, column-1, row, column);
+
+ var c = this.session.getTextRange(charRange);
+ // if the char is a digit
+ if (!isNaN(parseFloat(c)) && isFinite(c)) {
+ // get the whole number the digit is part of
+ var nr = this.getNumberAt(row, column);
+ // if number found
+ if (nr) {
+ var fp = nr.value.indexOf(".") >= 0 ? nr.start + nr.value.indexOf(".") + 1 : nr.end;
+ var decimals = nr.start + nr.value.length - fp;
+
+ var t = parseFloat(nr.value);
+ t *= Math.pow(10, decimals);
+
+
+ if(fp !== nr.end && column < fp){
+ amount *= Math.pow(10, nr.end - column - 1);
+ } else {
+ amount *= Math.pow(10, nr.end - column);
+ }
+
+ t += amount;
+ t /= Math.pow(10, decimals);
+ var nnr = t.toFixed(decimals);
+
+ //update number
+ var replaceRange = new Range(row, nr.start, row, nr.end);
+ this.session.replace(replaceRange, nnr);
+
+ //reposition the cursor
+ this.moveCursorTo(row, Math.max(nr.start +1, column + nnr.length - nr.value.length));
+
+ }
+ } else {
+ this.toggleWord();
+ }
+ }
+
+ toggleWord() {
+ var row = this.selection.getCursor().row;
+ var column = this.selection.getCursor().column;
+ this.selection.selectWord();
+ var currentState = this.getSelectedText();
+ var currWordStart = this.selection.getWordRange().start.column;
+ var wordParts = currentState.replace(/([a-z]+|[A-Z]+)(?=[A-Z_]|$)/g, '$1 ').split(/\s/);
+ var delta = column - currWordStart - 1;
+ if (delta < 0) delta = 0;
+ var curLength = 0, itLength = 0;
+ var that = this;
+ if (currentState.match(/[A-Za-z0-9_]+/)) {
+ wordParts.forEach(function (item, i) {
+ itLength = curLength + item.length;
+ if (delta >= curLength && delta <= itLength) {
+ currentState = item;
+ that.selection.clearSelection();
+ that.moveCursorTo(row, curLength + currWordStart);
+ that.selection.selectTo(row, itLength + currWordStart);
+ }
+ curLength = itLength;
+ });
+ }
+
+ var wordPairs = this.$toggleWordPairs;
+ var reg;
+ for (var i = 0; i < wordPairs.length; i++) {
+ var item = wordPairs[i];
+ for (var j = 0; j <= 1; j++) {
+ var negate = +!j;
+ var firstCondition = currentState.match(new RegExp('^\\s?_?(' + lang.escapeRegExp(item[j]) + ')\\s?$', 'i'));
+ if (firstCondition) {
+ var secondCondition = currentState.match(new RegExp('([_]|^|\\s)(' + lang.escapeRegExp(firstCondition[1]) + ')($|\\s)', 'g'));
+ if (secondCondition) {
+ reg = currentState.replace(new RegExp(lang.escapeRegExp(item[j]), 'i'), function (result) {
+ var res = item[negate];
+ if (result.toUpperCase() == result) {
+ res = res.toUpperCase();
+ } else if (result.charAt(0).toUpperCase() == result.charAt(0)) {
+ res = res.substr(0, 0) + item[negate].charAt(0).toUpperCase() + res.substr(1);
+ }
+ return res;
+ });
+ this.insert(reg);
+ reg = "";
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Finds link at defined {row} and {column}
+ * @returns {String}
+ **/
+ findLinkAt(row, column) {
+ var line = this.session.getLine(row);
+ var wordParts = line.split(/((?:https?|ftp):\/\/[\S]+)/);
+ var columnPosition = column;
+ if (columnPosition < 0) columnPosition = 0;
+ var previousPosition = 0, currentPosition = 0, match;
+ for (let item of wordParts) {
+ currentPosition = previousPosition + item.length;
+ if (columnPosition >= previousPosition && columnPosition <= currentPosition) {
+ if (item.match(/((?:https?|ftp):\/\/[\S]+)/)) {
+ match = item.replace(/[\s:.,'";}\]]+$/, "");
+ break;
+ }
+ }
+ previousPosition = currentPosition;
+ }
+ return match;
+ }
+
+ /**
+ * Open valid url under cursor in another tab
+ * @returns {Boolean}
+ **/
+ openLink() {
+ var cursor = this.selection.getCursor();
+ var url = this.findLinkAt(cursor.row, cursor.column);
+ if (url)
+ window.open(url, '_blank');
+ return url != null;
+ }
+
+ /**
+ * Removes all the lines in the current selection
+ * @related EditSession.remove
+ **/
+ removeLines() {
+ var rows = this.$getSelectedRows();
+ this.session.removeFullLines(rows.first, rows.last);
+ this.clearSelection();
+ }
+
+ duplicateSelection() {
+ var sel = this.selection;
+ var doc = this.session;
+ var range = sel.getRange();
+ var reverse = sel.isBackwards();
+ if (range.isEmpty()) {
+ var row = range.start.row;
+ doc.duplicateLines(row, row);
+ } else {
+ var point = reverse ? range.start : range.end;
+ var endPoint = doc.insert(point, doc.getTextRange(range), false);
+ range.start = point;
+ range.end = endPoint;
+
+ sel.setSelectionRange(range, reverse);
+ }
+ }
+
+ /**
+ * Shifts all the selected lines down one row.
+ *
+ * @returns {Number} On success, it returns -1.
+ * @related EditSession.moveLinesUp
+ **/
+ moveLinesDown() {
+ this.$moveLines(1, false);
+ }
+
+ /**
+ * Shifts all the selected lines up one row.
+ * @returns {Number} On success, it returns -1.
+ * @related EditSession.moveLinesDown
+ **/
+ moveLinesUp() {
+ this.$moveLines(-1, false);
+ }
+
+ /**
+ * Moves a range of text from the given range to the given position. `toPosition` is an object that looks like this:
+ * ```json
+ * { row: newRowLocation, column: newColumnLocation }
+ * ```
+ * @param {Range} range The range of text you want moved within the document
+ * @param {Object} toPosition The location (row and column) where you want to move the text to
+ *
+ * @returns {Range} The new range where the text was moved to.
+ * @related EditSession.moveText
+ **/
+ moveText(range, toPosition, copy) {
+ return this.session.moveText(range, toPosition, copy);
+ }
+
+ /**
+ * Copies all the selected lines up one row.
+ * @returns {Number} On success, returns 0.
+ *
+ **/
+ copyLinesUp() {
+ this.$moveLines(-1, true);
+ }
+
+ /**
+ * Copies all the selected lines down one row.
+ * @returns {Number} On success, returns the number of new rows added; in other words, `lastRow - firstRow + 1`.
+ * @related EditSession.duplicateLines
+ *
+ **/
+ copyLinesDown() {
+ this.$moveLines(1, true);
+ }
+
+ /**
+ * for internal use
+ * @ignore
+ *
+ **/
+ $moveLines(dir, copy) {
+ var rows, moved;
+ var selection = this.selection;
+ if (!selection.inMultiSelectMode || this.inVirtualSelectionMode) {
+ var range = selection.toOrientedRange();
+ rows = this.$getSelectedRows(range);
+ moved = this.session.$moveLines(rows.first, rows.last, copy ? 0 : dir);
+ if (copy && dir == -1) moved = 0;
+ range.moveBy(moved, 0);
+ selection.fromOrientedRange(range);
+ } else {
+ var ranges = selection.rangeList.ranges;
+ selection.rangeList.detach(this.session);
+ this.inVirtualSelectionMode = true;
+
+ var diff = 0;
+ var totalDiff = 0;
+ var l = ranges.length;
+ for (var i = 0; i < l; i++) {
+ var rangeIndex = i;
+ ranges[i].moveBy(diff, 0);
+ rows = this.$getSelectedRows(ranges[i]);
+ var first = rows.first;
+ var last = rows.last;
+ while (++i < l) {
+ if (totalDiff) ranges[i].moveBy(totalDiff, 0);
+ var subRows = this.$getSelectedRows(ranges[i]);
+ if (copy && subRows.first != last)
+ break;
+ else if (!copy && subRows.first > last + 1)
+ break;
+ last = subRows.last;
+ }
+ i--;
+ diff = this.session.$moveLines(first, last, copy ? 0 : dir);
+ if (copy && dir == -1) rangeIndex = i + 1;
+ while (rangeIndex <= i) {
+ ranges[rangeIndex].moveBy(diff, 0);
+ rangeIndex++;
+ }
+ if (!copy) diff = 0;
+ totalDiff += diff;
+ }
+
+ selection.fromOrientedRange(selection.ranges[0]);
+ selection.rangeList.attach(this.session);
+ this.inVirtualSelectionMode = false;
+ }
+ }
+
+ /**
+ * Returns an object indicating the currently selected rows. The object looks like this:
+ *
+ * ```json
+ * { first: range.start.row, last: range.end.row }
+ * ```
+ *
+ * @returns {Object}
+ **/
+ $getSelectedRows(range) {
+ range = (range || this.getSelectionRange()).collapseRows();
+
+ return {
+ first: this.session.getRowFoldStart(range.start.row),
+ last: this.session.getRowFoldEnd(range.end.row)
+ };
+ }
+
+ onCompositionStart(compositionState) {
+ this.renderer.showComposition(compositionState);
+ }
+
+ onCompositionUpdate(text) {
+ this.renderer.setCompositionText(text);
+ }
+
+ onCompositionEnd() {
+ this.renderer.hideComposition();
+ }
+
+ /**
+ * {:VirtualRenderer.getFirstVisibleRow}
+ *
+ * @returns {Number}
+ * @related VirtualRenderer.getFirstVisibleRow
+ **/
+ getFirstVisibleRow() {
+ return this.renderer.getFirstVisibleRow();
+ }
+
+ /**
+ * {:VirtualRenderer.getLastVisibleRow}
+ *
+ * @returns {Number}
+ * @related VirtualRenderer.getLastVisibleRow
+ **/
+ getLastVisibleRow() {
+ return this.renderer.getLastVisibleRow();
+ }
+
+ /**
+ * Indicates if the row is currently visible on the screen.
+ * @param {Number} row The row to check
+ *
+ * @returns {Boolean}
+ **/
+ isRowVisible(row) {
+ return (row >= this.getFirstVisibleRow() && row <= this.getLastVisibleRow());
+ }
+
+ /**
+ * Indicates if the entire row is currently visible on the screen.
+ * @param {Number} row The row to check
+ *
+ *
+ * @returns {Boolean}
+ **/
+ isRowFullyVisible(row) {
+ return (row >= this.renderer.getFirstFullyVisibleRow() && row <= this.renderer.getLastFullyVisibleRow());
+ }
+
+ /**
+ * Returns the number of currently visible rows.
+ * @returns {Number}
+ **/
+ $getVisibleRowCount() {
+ return this.renderer.getScrollBottomRow() - this.renderer.getScrollTopRow() + 1;
+ }
+
+ $moveByPage(dir, select) {
+ var renderer = this.renderer;
+ var config = this.renderer.layerConfig;
+ var rows = dir * Math.floor(config.height / config.lineHeight);
+
+ if (select === true) {
+ this.selection.$moveSelection(function(){
+ this.moveCursorBy(rows, 0);
+ });
+ } else if (select === false) {
+ this.selection.moveCursorBy(rows, 0);
+ this.selection.clearSelection();
+ }
+
+ var scrollTop = renderer.scrollTop;
+
+ renderer.scrollBy(0, rows * config.lineHeight);
+ if (select != null)
+ renderer.scrollCursorIntoView(null, 0.5);
+
+ renderer.animateScrolling(scrollTop);
+ }
+
+ /**
+ * Selects the text from the current position of the document until where a "page down" finishes.
+ **/
+ selectPageDown() {
+ this.$moveByPage(1, true);
+ }
+
+ /**
+ * Selects the text from the current position of the document until where a "page up" finishes.
+ **/
+ selectPageUp() {
+ this.$moveByPage(-1, true);
+ }
+
+ /**
+ * Shifts the document to wherever "page down" is, as well as moving the cursor position.
+ **/
+ gotoPageDown() {
+ this.$moveByPage(1, false);
+ }
+
+ /**
+ * Shifts the document to wherever "page up" is, as well as moving the cursor position.
+ **/
+ gotoPageUp() {
+ this.$moveByPage(-1, false);
+ }
+
+ /**
+ * Scrolls the document to wherever "page down" is, without changing the cursor position.
+ **/
+ scrollPageDown() {
+ this.$moveByPage(1);
+ }
+
+ /**
+ * Scrolls the document to wherever "page up" is, without changing the cursor position.
+ **/
+ scrollPageUp() {
+ this.$moveByPage(-1);
+ }
+
+ /**
+ * Moves the editor to the specified row.
+ * @related VirtualRenderer.scrollToRow
+ **/
+ scrollToRow(row) {
+ this.renderer.scrollToRow(row);
+ }
+
+ /**
+ * Scrolls to a line. If `center` is `true`, it puts the line in middle of screen (or attempts to).
+ * @param {Number} line The line to scroll to
+ * @param {Boolean} center If `true`
+ * @param {Boolean} animate If `true` animates scrolling
+ * @param {Function} callback Function to be called when the animation has finished
+ *
+ *
+ * @related VirtualRenderer.scrollToLine
+ **/
+ scrollToLine(line, center, animate, callback) {
+ this.renderer.scrollToLine(line, center, animate, callback);
+ }
+
+ /**
+ * Attempts to center the current selection on the screen.
+ **/
+ centerSelection() {
+ var range = this.getSelectionRange();
+ var pos = {
+ row: Math.floor(range.start.row + (range.end.row - range.start.row) / 2),
+ column: Math.floor(range.start.column + (range.end.column - range.start.column) / 2)
+ };
+ this.renderer.alignCursor(pos, 0.5);
+ }
+
+ /**
+ * Gets the current position of the cursor.
+ * @returns {Object} An object that looks something like this:
+ *
+ * ```json
+ * { row: currRow, column: currCol }
+ * ```
+ *
+ * @related Selection.getCursor
+ **/
+ getCursorPosition() {
+ return this.selection.getCursor();
+ }
+
+ /**
+ * Returns the screen position of the cursor.
+ * @returns {Position}
+ * @related EditSession.documentToScreenPosition
+ **/
+ getCursorPositionScreen() {
+ return this.session.documentToScreenPosition(this.getCursorPosition());
+ }
+
+ /**
+ * {:Selection.getRange}
+ * @returns {Range}
+ * @related Selection.getRange
+ **/
+ getSelectionRange() {
+ return this.selection.getRange();
+ }
+
+ /**
+ * Selects all the text in editor.
+ * @related Selection.selectAll
+ **/
+ selectAll() {
+ this.selection.selectAll();
+ }
+
+ /**
+ * {:Selection.clearSelection}
+ * @related Selection.clearSelection
+ **/
+ clearSelection() {
+ this.selection.clearSelection();
+ }
+
+ /**
+ * Moves the cursor to the specified row and column. Note that this does not de-select the current selection.
+ * @param {Number} row The new row number
+ * @param {Number} column The new column number
+ * @related Selection.moveCursorTo
+ **/
+ moveCursorTo(row, column) {
+ this.selection.moveCursorTo(row, column);
+ }
+
+ /**
+ * Moves the cursor to the position indicated by `pos.row` and `pos.column`.
+ * @param {Position} pos An object with two properties, row and column
+ * @related Selection.moveCursorToPosition
+ **/
+ moveCursorToPosition(pos) {
+ this.selection.moveCursorToPosition(pos);
+ }
+
+ /**
+ * Moves the cursor's row and column to the next matching bracket or HTML tag.
+ *
+ **/
+ jumpToMatching(select, expand) {
+ var cursor = this.getCursorPosition();
+ var iterator = new TokenIterator(this.session, cursor.row, cursor.column);
+ var prevToken = iterator.getCurrentToken();
+ var tokenCount = 0;
+ if (prevToken && prevToken.type.indexOf('tag-name') !== -1) {
+ prevToken = iterator.stepBackward();
+ }
+ var token = prevToken || iterator.stepForward();
+
+ if (!token) return;
+
+ //get next closing tag or bracket
+ var matchType;
+ var found = false;
+ var depth = {};
+ var i = cursor.column - token.start;
+ var bracketType;
+ var brackets = {
+ ")": "(",
+ "(": "(",
+ "]": "[",
+ "[": "[",
+ "{": "{",
+ "}": "{"
+ };
+
+ do {
+ if (token.value.match(/[{}()\[\]]/g)) {
+ for (; i < token.value.length && !found; i++) {
+ if (!brackets[token.value[i]]) {
+ continue;
+ }
+
+ bracketType = brackets[token.value[i]] + '.' + token.type.replace("rparen", "lparen");
+
+ if (isNaN(depth[bracketType])) {
+ depth[bracketType] = 0;
+ }
+
+ switch (token.value[i]) {
+ case '(':
+ case '[':
+ case '{':
+ depth[bracketType]++;
+ break;
+ case ')':
+ case ']':
+ case '}':
+ depth[bracketType]--;
+
+ if (depth[bracketType] === -1) {
+ matchType = 'bracket';
+ found = true;
+ }
+ break;
+ }
+ }
+ }
+ else if (token.type.indexOf('tag-name') !== -1) {
+ if (isNaN(depth[token.value])) {
+ depth[token.value] = 0;
+ }
+
+ if (prevToken.value === '<' && tokenCount > 1) {
+ depth[token.value]++;
+ }
+ else if (prevToken.value === '') {
+ depth[token.value]--;
+ }
+
+ if (depth[token.value] === -1) {
+ matchType = 'tag';
+ found = true;
+ }
+ }
+
+ if (!found) {
+ prevToken = token;
+ tokenCount++;
+ token = iterator.stepForward();
+ i = 0;
+ }
+ } while (token && !found);
+
+ //no match found
+ if (!matchType) return;
+
+ var range, pos;
+ if (matchType === 'bracket') {
+ range = this.session.getBracketRange(cursor);
+ if (!range) {
+ range = new Range(iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() + i - 1,
+ iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() + i - 1
+ );
+ pos = range.start;
+ if (expand || pos.row === cursor.row && Math.abs(pos.column - cursor.column)
+ < 2) range = this.session.getBracketRange(pos);
+ }
+ }
+ else if (matchType === 'tag') {
+ if (!token || token.type.indexOf('tag-name') === -1) return;
+ range = new Range(iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() - 2,
+ iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() - 2
+ );
+
+ //find matching tag
+ if (range.compare(cursor.row, cursor.column) === 0) {
+ var tagsRanges = this.session.getMatchingTags(cursor);
+ if (tagsRanges) {
+ if (tagsRanges.openTag.contains(cursor.row, cursor.column)) {
+ range = tagsRanges.closeTag;
+ pos = range.start;
+ }
+ else {
+ range = tagsRanges.openTag;
+ if (tagsRanges.closeTag.start.row === cursor.row && tagsRanges.closeTag.start.column
+ === cursor.column) pos = range.end; else pos = range.start;
+ }
+ }
+ }
+
+ //we found it
+ pos = pos || range.start;
+ }
+
+ pos = range && range.cursor || pos;
+ if (pos) {
+ if (select) {
+ if (range && expand) {
+ this.selection.setRange(range);
+ }
+ else if (range && range.isEqual(this.getSelectionRange())) {
+ this.clearSelection();
+ }
+ else {
+ this.selection.selectTo(pos.row, pos.column);
+ }
+ }
+ else {
+ this.selection.moveTo(pos.row, pos.column);
+ }
+ }
+ }
+
+ /**
+ * Moves the cursor to the specified line number, and also into the indicated column.
+ * @param {Number} lineNumber The line number to go to
+ * @param {Number} column A column number to go to
+ * @param {Boolean} animate If `true` animates scolling
+ *
+ **/
+ gotoLine(lineNumber, column, animate) {
+ this.selection.clearSelection();
+ this.session.unfold({row: lineNumber - 1, column: column || 0});
+
+ // todo: find a way to automatically exit multiselect mode
+ this.exitMultiSelectMode && this.exitMultiSelectMode();
+ this.moveCursorTo(lineNumber - 1, column || 0);
+
+ if (!this.isRowFullyVisible(lineNumber - 1))
+ this.scrollToLine(lineNumber - 1, true, animate);
+ }
+
+ /**
+ * Moves the cursor to the specified row and column. Note that this does de-select the current selection.
+ * @param {Number} row The new row number
+ * @param {Number} column The new column number
+ *
+ *
+ * @related Editor.moveCursorTo
+ **/
+ navigateTo(row, column) {
+ this.selection.moveTo(row, column);
+ }
+
+ /**
+ * Moves the cursor up in the document the specified number of times. Note that this does de-select the current selection.
+ * @param {Number} times The number of times to change navigation
+ *
+ *
+ **/
+ navigateUp(times) {
+ if (this.selection.isMultiLine() && !this.selection.isBackwards()) {
+ var selectionStart = this.selection.anchor.getPosition();
+ return this.moveCursorToPosition(selectionStart);
+ }
+ this.selection.clearSelection();
+ this.selection.moveCursorBy(-times || -1, 0);
+ }
+
+ /**
+ * Moves the cursor down in the document the specified number of times. Note that this does de-select the current selection.
+ * @param {Number} times The number of times to change navigation
+ *
+ *
+ **/
+ navigateDown(times) {
+ if (this.selection.isMultiLine() && this.selection.isBackwards()) {
+ var selectionEnd = this.selection.anchor.getPosition();
+ return this.moveCursorToPosition(selectionEnd);
+ }
+ this.selection.clearSelection();
+ this.selection.moveCursorBy(times || 1, 0);
+ }
+
+ /**
+ * Moves the cursor left in the document the specified number of times. Note that this does de-select the current selection.
+ * @param {Number} times The number of times to change navigation
+ *
+ *
+ **/
+ navigateLeft(times) {
+ if (!this.selection.isEmpty()) {
+ var selectionStart = this.getSelectionRange().start;
+ this.moveCursorToPosition(selectionStart);
+ }
+ else {
+ times = times || 1;
+ while (times--) {
+ this.selection.moveCursorLeft();
+ }
+ }
+ this.clearSelection();
+ }
+
+ /**
+ * Moves the cursor right in the document the specified number of times. Note that this does de-select the current selection.
+ * @param {Number} times The number of times to change navigation
+ *
+ *
+ **/
+ navigateRight(times) {
+ if (!this.selection.isEmpty()) {
+ var selectionEnd = this.getSelectionRange().end;
+ this.moveCursorToPosition(selectionEnd);
+ }
+ else {
+ times = times || 1;
+ while (times--) {
+ this.selection.moveCursorRight();
+ }
+ }
+ this.clearSelection();
+ }
+
+ /**
+ *
+ * Moves the cursor to the start of the current line. Note that this does de-select the current selection.
+ **/
+ navigateLineStart() {
+ this.selection.moveCursorLineStart();
+ this.clearSelection();
+ }
+
+ /**
+ *
+ * Moves the cursor to the end of the current line. Note that this does de-select the current selection.
+ **/
+ navigateLineEnd() {
+ this.selection.moveCursorLineEnd();
+ this.clearSelection();
+ }
+
+ /**
+ *
+ * Moves the cursor to the end of the current file. Note that this does de-select the current selection.
+ **/
+ navigateFileEnd() {
+ this.selection.moveCursorFileEnd();
+ this.clearSelection();
+ }
+
+ /**
+ *
+ * Moves the cursor to the start of the current file. Note that this does de-select the current selection.
+ **/
+ navigateFileStart() {
+ this.selection.moveCursorFileStart();
+ this.clearSelection();
+ }
+
+ /**
+ *
+ * Moves the cursor to the word immediately to the right of the current position. Note that this does de-select the current selection.
+ **/
+ navigateWordRight() {
+ this.selection.moveCursorWordRight();
+ this.clearSelection();
+ }
+
+ /**
+ *
+ * Moves the cursor to the word immediately to the left of the current position. Note that this does de-select the current selection.
+ **/
+ navigateWordLeft() {
+ this.selection.moveCursorWordLeft();
+ this.clearSelection();
+ }
+
+ /**
+ * Replaces the first occurrence of `options.needle` with the value in `replacement`.
+ * @param {String} replacement The text to replace with
+ * @param {Object} options The [[Search `Search`]] options to use
+ *
+ *
+ **/
+ replace(replacement, options) {
+ if (options)
+ this.$search.set(options);
+
+ var range = this.$search.find(this.session);
+ var replaced = 0;
+ if (!range)
+ return replaced;
+
+ if (this.$tryReplace(range, replacement)) {
+ replaced = 1;
+ }
+
+ this.selection.setSelectionRange(range);
+ this.renderer.scrollSelectionIntoView(range.start, range.end);
+
+ return replaced;
+ }
+
+ /**
+ * Replaces all occurrences of `options.needle` with the value in `replacement`.
+ * @param {String} replacement The text to replace with
+ * @param {Object} options The [[Search `Search`]] options to use
+ *
+ *
+ **/
+ replaceAll(replacement, options) {
+ if (options) {
+ this.$search.set(options);
+ }
+
+ var ranges = this.$search.findAll(this.session);
+ var replaced = 0;
+ if (!ranges.length)
+ return replaced;
+
+ var selection = this.getSelectionRange();
+ this.selection.moveTo(0, 0);
+
+ for (var i = ranges.length - 1; i >= 0; --i) {
+ if(this.$tryReplace(ranges[i], replacement)) {
+ replaced++;
+ }
+ }
+
+ this.selection.setSelectionRange(selection);
+
+ return replaced;
+ }
+
+ $tryReplace(range, replacement) {
+ var input = this.session.getTextRange(range);
+ replacement = this.$search.replace(input, replacement);
+ if (replacement !== null) {
+ range.end = this.session.replace(range, replacement);
+ return range;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * {:Search.getOptions} For more information on `options`, see [[Search `Search`]].
+ * @related Search.getOptions
+ * @returns {Object}
+ **/
+ getLastSearchOptions() {
+ return this.$search.getOptions();
+ }
+
+ /**
+ * Attempts to find `needle` within the document. For more information on `options`, see [[Search `Search`]].
+ * @param {String|RegExp|Object} needle The text to search for (optional)
+ * @param {Object} options An object defining various search properties
+ * @param {Boolean} animate If `true` animate scrolling
+ * @related Search.find
+ **/
+ find(needle, options, animate) {
+ if (!options)
+ options = {};
+
+ if (typeof needle == "string" || needle instanceof RegExp)
+ options.needle = needle;
+ else if (typeof needle == "object")
+ oop.mixin(options, needle);
+
+ var range = this.selection.getRange();
+ if (options.needle == null) {
+ needle = this.session.getTextRange(range)
+ || this.$search.$options.needle;
+ if (!needle) {
+ range = this.session.getWordRange(range.start.row, range.start.column);
+ needle = this.session.getTextRange(range);
+ }
+ this.$search.set({needle: needle});
+ }
+
+ this.$search.set(options);
+ if (!options.start)
+ this.$search.set({start: range});
+
+ var newRange = this.$search.find(this.session);
+ if (options.preventScroll)
+ return newRange;
+ if (newRange) {
+ this.revealRange(newRange, animate);
+ return newRange;
+ }
+ // clear selection if nothing is found
+ if (options.backwards)
+ range.start = range.end;
+ else
+ range.end = range.start;
+ this.selection.setRange(range);
+ }
+
+ /**
+ * Performs another search for `needle` in the document. For more information on `options`, see [[Search `Search`]].
+ * @param {Object} options search options
+ * @param {Boolean} animate If `true` animate scrolling
+ *
+ *
+ * @related Editor.find
+ **/
+ findNext(options, animate) {
+ this.find({skipCurrent: true, backwards: false}, options, animate);
+ }
+
+ /**
+ * Performs a search for `needle` backwards. For more information on `options`, see [[Search `Search`]].
+ * @param {Object} options search options
+ * @param {Boolean} animate If `true` animate scrolling
+ *
+ *
+ * @related Editor.find
+ **/
+ findPrevious(options, animate) {
+ this.find(options, {skipCurrent: true, backwards: true}, animate);
+ }
+
+ revealRange(range, animate) {
+ this.session.unfold(range);
+ this.selection.setSelectionRange(range);
+
+ var scrollTop = this.renderer.scrollTop;
+ this.renderer.scrollSelectionIntoView(range.start, range.end, 0.5);
+ if (animate !== false)
+ this.renderer.animateScrolling(scrollTop);
+ }
+
+ /**
+ * {:UndoManager.undo}
+ * @related UndoManager.undo
+ **/
+ undo() {
+ this.session.getUndoManager().undo(this.session);
+ this.renderer.scrollCursorIntoView(null, 0.5);
+ }
+
+ /**
+ * {:UndoManager.redo}
+ * @related UndoManager.redo
+ **/
+ redo() {
+ this.session.getUndoManager().redo(this.session);
+ this.renderer.scrollCursorIntoView(null, 0.5);
+ }
+
+ /**
+ *
+ * Cleans up the entire editor.
+ **/
+ destroy() {
+ if (this.$toDestroy) {
+ this.$toDestroy.forEach(function(el) {
+ el.destroy();
+ });
+ this.$toDestroy = null;
+ }
+ if (this.$mouseHandler)
+ this.$mouseHandler.destroy();
+ this.renderer.destroy();
+ this._signal("destroy", this);
+ if (this.session)
+ this.session.destroy();
+ if (this._$emitInputEvent)
+ this._$emitInputEvent.cancel();
+ this.removeAllListeners();
+ }
+
+ /**
+ * Enables automatic scrolling of the cursor into view when editor itself is inside scrollable element
+ * @param {Boolean} enable default true
+ **/
+ setAutoScrollEditorIntoView(enable) {
+ if (!enable)
+ return;
+ var rect;
+ var self = this;
+ var shouldScroll = false;
+ if (!this.$scrollAnchor)
+ this.$scrollAnchor = document.createElement("div");
+ var scrollAnchor = this.$scrollAnchor;
+ scrollAnchor.style.cssText = "position:absolute";
+ this.container.insertBefore(scrollAnchor, this.container.firstChild);
+ var onChangeSelection = this.on("changeSelection", function() {
+ shouldScroll = true;
+ });
+ // needed to not trigger sync reflow
+ var onBeforeRender = this.renderer.on("beforeRender", function() {
+ if (shouldScroll)
+ rect = self.renderer.container.getBoundingClientRect();
+ });
+ var onAfterRender = this.renderer.on("afterRender", function() {
+ if (shouldScroll && rect && (self.isFocused()
+ || self.searchBox && self.searchBox.isFocused())
+ ) {
+ var renderer = self.renderer;
+ var pos = renderer.$cursorLayer.$pixelPos;
+ var config = renderer.layerConfig;
+ var top = pos.top - config.offset;
+ if (pos.top >= 0 && top + rect.top < 0) {
+ shouldScroll = true;
+ } else if (pos.top < config.height &&
+ pos.top + rect.top + config.lineHeight > window.innerHeight) {
+ shouldScroll = false;
+ } else {
+ shouldScroll = null;
+ }
+ if (shouldScroll != null) {
+ scrollAnchor.style.top = top + "px";
+ scrollAnchor.style.left = pos.left + "px";
+ scrollAnchor.style.height = config.lineHeight + "px";
+ scrollAnchor.scrollIntoView(shouldScroll);
+ }
+ shouldScroll = rect = null;
+ }
+ });
+ this.setAutoScrollEditorIntoView = function(enable) {
+ if (enable)
+ return;
+ delete this.setAutoScrollEditorIntoView;
+ this.off("changeSelection", onChangeSelection);
+ this.renderer.off("afterRender", onAfterRender);
+ this.renderer.off("beforeRender", onBeforeRender);
+ };
+ }
+
+
+ $resetCursorStyle() {
+ var style = this.$cursorStyle || "ace";
+ var cursorLayer = this.renderer.$cursorLayer;
+ if (!cursorLayer)
+ return;
+ cursorLayer.setSmoothBlinking(/smooth/.test(style));
+ cursorLayer.isBlinking = !this.$readOnly && style != "wide";
+ dom.setCssClass(cursorLayer.element, "ace_slim-cursors", /slim/.test(style));
+ }
+
+ /**
+ * opens a prompt displaying message
+ **/
+ prompt(message, options, callback) {
+ var editor = this;
+ config.loadModule("ace/ext/prompt", function (module) {
+ module.prompt(editor, message, options, callback);
+ });
+ }
+
+}
+
+Editor.$uid = 0;
+Editor.prototype.curOp = null;
+Editor.prototype.prevOp = {};
+// TODO use property on commands instead of this
+Editor.prototype.$mergeableCommands = ["backspace", "del", "insertstring"];
+Editor.prototype.$toggleWordPairs = [
+ ["first", "last"],
+ ["true", "false"],
+ ["yes", "no"],
+ ["width", "height"],
+ ["top", "bottom"],
+ ["right", "left"],
+ ["on", "off"],
+ ["x", "y"],
+ ["get", "set"],
+ ["max", "min"],
+ ["horizontal", "vertical"],
+ ["show", "hide"],
+ ["add", "remove"],
+ ["up", "down"],
+ ["before", "after"],
+ ["even", "odd"],
+ ["in", "out"],
+ ["inside", "outside"],
+ ["next", "previous"],
+ ["increase", "decrease"],
+ ["attach", "detach"],
+ ["&&", "||"],
+ ["==", "!="]
+];
+
+oop.implement(Editor.prototype, EventEmitter);
+
+
+config.defineOptions(Editor.prototype, "editor", {
+ selectionStyle: {
+ set: function(style) {
+ this.onSelectionChange();
+ this._signal("changeSelectionStyle", {data: style});
+ },
+ initialValue: "line"
+ },
+ highlightActiveLine: {
+ set: function() {this.$updateHighlightActiveLine();},
+ initialValue: true
+ },
+ highlightSelectedWord: {
+ set: function(shouldHighlight) {this.$onSelectionChange();},
+ initialValue: true
+ },
+ readOnly: {
+ set: function(readOnly) {
+ this.textInput.setReadOnly(readOnly);
+ this.$resetCursorStyle();
+ },
+ initialValue: false
+ },
+ copyWithEmptySelection: {
+ set: function(value) {
+ this.textInput.setCopyWithEmptySelection(value);
+ },
+ initialValue: false
+ },
+ cursorStyle: {
+ set: function(val) { this.$resetCursorStyle(); },
+ values: ["ace", "slim", "smooth", "wide"],
+ initialValue: "ace"
+ },
+ mergeUndoDeltas: {
+ values: [false, true, "always"],
+ initialValue: true
+ },
+ behavioursEnabled: {initialValue: true},
+ wrapBehavioursEnabled: {initialValue: true},
+ enableAutoIndent: {initialValue: true},
+ autoScrollEditorIntoView: {
+ set: function(val) {this.setAutoScrollEditorIntoView(val);}
+ },
+ keyboardHandler: {
+ set: function(val) { this.setKeyboardHandler(val); },
+ get: function() { return this.$keybindingId; },
+ handlesSet: true
+ },
+ value: {
+ set: function(val) { this.session.setValue(val); },
+ get: function() { return this.getValue(); },
+ handlesSet: true,
+ hidden: true
+ },
+ session: {
+ set: function(val) { this.setSession(val); },
+ get: function() { return this.session; },
+ handlesSet: true,
+ hidden: true
+ },
+
+ showLineNumbers: {
+ set: function(show) {
+ this.renderer.$gutterLayer.setShowLineNumbers(show);
+ this.renderer.$loop.schedule(this.renderer.CHANGE_GUTTER);
+ if (show && this.$relativeLineNumbers)
+ relativeNumberRenderer.attach(this);
+ else
+ relativeNumberRenderer.detach(this);
+ },
+ initialValue: true
+ },
+ relativeLineNumbers: {
+ set: function(value) {
+ if (this.$showLineNumbers && value)
+ relativeNumberRenderer.attach(this);
+ else
+ relativeNumberRenderer.detach(this);
+ }
+ },
+ placeholder: {
+ set: function(message) {
+ if (!this.$updatePlaceholder) {
+ this.$updatePlaceholder = function() {
+ var value = this.session && (this.renderer.$composition || this.getValue());
+ if (value && this.renderer.placeholderNode) {
+ this.renderer.off("afterRender", this.$updatePlaceholder);
+ dom.removeCssClass(this.container, "ace_hasPlaceholder");
+ this.renderer.placeholderNode.remove();
+ this.renderer.placeholderNode = null;
+ } else if (!value && !this.renderer.placeholderNode) {
+ this.renderer.on("afterRender", this.$updatePlaceholder);
+ dom.addCssClass(this.container, "ace_hasPlaceholder");
+ var el = dom.createElement("div");
+ el.className = "ace_placeholder";
+ el.textContent = this.$placeholder || "";
+ this.renderer.placeholderNode = el;
+ this.renderer.content.appendChild(this.renderer.placeholderNode);
+ } else if (!value && this.renderer.placeholderNode) {
+ this.renderer.placeholderNode.textContent = this.$placeholder || "";
+ }
+ }.bind(this);
+ this.on("input", this.$updatePlaceholder);
+ }
+ this.$updatePlaceholder();
+ }
+ },
+ enableKeyboardAccessibility: {
+ set: function(value) {
+ var blurCommand = {
+ name: "blurTextInput",
+ description: "Set focus to the editor content div to allow tabbing through the page",
+ bindKey: "Esc",
+ exec: function(editor) {
+ editor.blur();
+ editor.renderer.content.focus();
+ },
+ readOnly: true
+ };
+
+ var focusOnEnterKeyup = function (e) {
+ if (e.target == this.renderer.content && e.keyCode === keys['enter']){
+ e.stopPropagation();
+ e.preventDefault();
+ this.focus();
+ }
+ };
+
+ var keyboardFocusClassName = "ace_keyboard-focus";
+
+ // Prevent focus to be captured when tabbing through the page. When focus is set to the content div,
+ // press Enter key to give focus to Ace and press Esc to again allow to tab through the page.
+ if (value){
+ this.textInput.getElement().setAttribute("tabindex", -1);
+ this.renderer.content.setAttribute("tabindex", 0);
+ this.renderer.content.classList.add(keyboardFocusClassName);
+ this.renderer.content.setAttribute("aria-label",
+ "Editor, press Enter key to start editing, press Escape key to exit"
+ );
+
+ this.renderer.content.addEventListener("keyup", focusOnEnterKeyup.bind(this));
+ this.commands.addCommand(blurCommand);
+ } else {
+ this.textInput.getElement().setAttribute("tabindex", 0);
+ this.renderer.content.setAttribute("tabindex", -1);
+ this.renderer.content.classList.remove(keyboardFocusClassName);
+ this.renderer.content.setAttribute("aria-label", "");
+
+ this.renderer.content.removeEventListener("keyup", focusOnEnterKeyup.bind(this));
+ this.commands.removeCommand(blurCommand);
+ }
+ },
+ initialValue: false
+ },
+ customScrollbar: "renderer",
+ hScrollBarAlwaysVisible: "renderer",
+ vScrollBarAlwaysVisible: "renderer",
+ highlightGutterLine: "renderer",
+ animatedScroll: "renderer",
+ showInvisibles: "renderer",
+ showPrintMargin: "renderer",
+ printMarginColumn: "renderer",
+ printMargin: "renderer",
+ fadeFoldWidgets: "renderer",
+ showFoldWidgets: "renderer",
+ displayIndentGuides: "renderer",
+ highlightIndentGuides: "renderer",
+ showGutter: "renderer",
+ fontSize: "renderer",
+ fontFamily: "renderer",
+ maxLines: "renderer",
+ minLines: "renderer",
+ scrollPastEnd: "renderer",
+ fixedWidthGutter: "renderer",
+ theme: "renderer",
+ hasCssTransforms: "renderer",
+ maxPixelHeight: "renderer",
+ useTextareaForIME: "renderer",
+ useResizeObserver: "renderer",
+ useSvgGutterIcons: "renderer",
+
+ scrollSpeed: "$mouseHandler",
+ dragDelay: "$mouseHandler",
+ dragEnabled: "$mouseHandler",
+ focusTimeout: "$mouseHandler",
+ tooltipFollowsMouse: "$mouseHandler",
+
+ firstLineNumber: "session",
+ overwrite: "session",
+ newLineMode: "session",
+ useWorker: "session",
+ useSoftTabs: "session",
+ navigateWithinSoftTabs: "session",
+ tabSize: "session",
+ wrap: "session",
+ indentedSoftWrap: "session",
+ foldStyle: "session",
+ mode: "session"
+});
+
+
+var relativeNumberRenderer = {
+ getText: function(session, row) {
+ return (Math.abs(session.selection.lead.row - row) || (row + 1 + (row < 9 ? "\xb7" : ""))) + "";
+ },
+ getWidth: function(session, lastLineNumber, config) {
+ return Math.max(
+ lastLineNumber.toString().length,
+ (config.lastRow + 1).toString().length,
+ 2
+ ) * config.characterWidth;
+ },
+ update: function(e, editor) {
+ editor.renderer.$loop.schedule(editor.renderer.CHANGE_GUTTER);
+ },
+ attach: function(editor) {
+ editor.renderer.$gutterLayer.$renderer = this;
+ editor.on("changeSelection", this.update);
+ this.update(null, editor);
+ },
+ detach: function(editor) {
+ if (editor.renderer.$gutterLayer.$renderer == this)
+ editor.renderer.$gutterLayer.$renderer = null;
+ editor.off("changeSelection", this.update);
+ this.update(null, editor);
+ }
+};
+
+exports.Editor = Editor;
diff --git a/demo/diff/examples/editor.40.js b/demo/diff/examples/editor.40.js
new file mode 100644
index 00000000000..099fca450b8
--- /dev/null
+++ b/demo/diff/examples/editor.40.js
@@ -0,0 +1,3159 @@
+"use strict";
+
+/**
+ * @typedef {import("./virtual_renderer").VirtualRenderer} VirtualRenderer
+ * @typedef {import("./selection").Selection} Selection
+ * @typedef {import("../ace-internal").Ace.Point} Point
+ * @typedef {import("../ace-internal").Ace.SearchOptions} SearchOptions
+ */
+
+var oop = require("./lib/oop");
+var dom = require("./lib/dom");
+var lang = require("./lib/lang");
+var useragent = require("./lib/useragent");
+var TextInput = require("./keyboard/textinput").TextInput;
+var MouseHandler = require("./mouse/mouse_handler").MouseHandler;
+var FoldHandler = require("./mouse/fold_handler").FoldHandler;
+var KeyBinding = require("./keyboard/keybinding").KeyBinding;
+var EditSession = require("./edit_session").EditSession;
+var Search = require("./search").Search;
+var Range = require("./range").Range;
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+var CommandManager = require("./commands/command_manager").CommandManager;
+var defaultCommands = require("./commands/default_commands").commands;
+var config = require("./config");
+var TokenIterator = require("./token_iterator").TokenIterator;
+var GutterKeyboardHandler = require("./keyboard/gutter_handler").GutterKeyboardHandler;
+var nls = require("./config").nls;
+
+var clipboard = require("./clipboard");
+var keys = require('./lib/keys');
+
+var event = require("./lib/event");
+var HoverTooltip = require("./tooltip").HoverTooltip;
+
+/**
+ * The main entry point into the Ace functionality.
+ *
+ * The `Editor` manages the [[EditSession]] (which manages [[Document]]s), as well as the [[VirtualRenderer]], which draws everything to the screen.
+ *
+ * Event sessions dealing with the mouse and keyboard are bubbled up from `Document` to the `Editor`, which decides what to do with them.
+ **/
+class Editor {
+ /**
+ * Creates a new `Editor` object.
+ *
+ * @param {VirtualRenderer} renderer Associated `VirtualRenderer` that draws everything
+ * @param {EditSession} [session] The `EditSession` to refer to
+ * @param {Partial} [options] The default options
+ **/
+ constructor(renderer, session, options) {
+ /**@type{EditSession}*/this.session;
+ this.$toDestroy = [];
+
+ var container = renderer.getContainerElement();
+ /**@type {HTMLElement & {env?:any, value?:any}}*/
+ this.container = container;
+ /**@type {VirtualRenderer}*/
+ this.renderer = renderer;
+ /**@type {string}*/
+ this.id = "editor" + (++Editor.$uid);
+ this.commands = new CommandManager(useragent.isMac ? "mac" : "win", defaultCommands);
+ if (typeof document == "object") {
+ this.textInput = new TextInput(renderer.getTextAreaContainer(), this);
+ this.renderer.textarea = this.textInput.getElement();
+ // TODO detect touch event support
+ /**@type {MouseHandler}*/
+ this.$mouseHandler = new MouseHandler(this);
+ new FoldHandler(this);
+ }
+ /**@type {KeyBinding}*/
+ this.keyBinding = new KeyBinding(this);
+ /**@type {Search}*/
+ this.$search = new Search().set({
+ wrap: true
+ });
+
+ this.$historyTracker = this.$historyTracker.bind(this);
+ this.commands.on("exec", this.$historyTracker);
+
+ this.$initOperationListeners();
+
+ this._$emitInputEvent = lang.delayedCall(function() {
+ this._signal("input", {});
+ if (this.session && !this.session.destroyed)
+ this.session.bgTokenizer.scheduleStart();
+ }.bind(this));
+
+ this.on("change", function(_, _self) {
+ _self._$emitInputEvent.schedule(31);
+ });
+
+ this.setSession(session || options && options.session || new EditSession(""));
+ config.resetOptions(this);
+ if (options)
+ this.setOptions(options);
+ config._signal("editor", this);
+ }
+
+ $initOperationListeners() {
+ this.commands.on("exec", this.startOperation.bind(this), true);
+ this.commands.on("afterExec", this.endOperation.bind(this), true);
+ }
+
+ startOperation(commandEvent) {
+ this.session.startOperation(commandEvent);
+ }
+
+ /**
+ * @arg e
+ */
+ endOperation(e) {
+ this.session.endOperation(e);
+ }
+
+ onStartOperation(commandEvent) {
+ this.curOp = this.session.curOp;
+ this.curOp.scrollTop = this.renderer.scrollTop;
+ this.prevOp = this.session.prevOp;
+
+ if (!commandEvent) {
+ this.previousCommand = null;
+ }
+ }
+
+ /**
+ * @arg e
+ */
+ onEndOperation(e) {
+ if (this.curOp && this.session) {
+ if (e && e.returnValue === false) {
+ this.curOp = null;
+ return;
+ }
+
+ this._signal("beforeEndOperation");
+ if (!this.curOp) return;
+
+ var command = this.curOp.command;
+ var scrollIntoView = command && command.scrollIntoView;
+ if (scrollIntoView) {
+ switch (scrollIntoView) {
+ case "center-animate":
+ scrollIntoView = "animate";
+ /* fall through */
+ case "center":
+ this.renderer.scrollCursorIntoView(null, 0.5);
+ break;
+ case "animate":
+ case "cursor":
+ this.renderer.scrollCursorIntoView();
+ break;
+ case "selectionPart":
+ var range = this.selection.getRange();
+ var config = this.renderer.layerConfig;
+ if (range.start.row >= config.lastRow || range.end.row <= config.firstRow) {
+ this.renderer.scrollSelectionIntoView(this.selection.anchor, this.selection.lead);
+ }
+ break;
+ default:
+ break;
+ }
+ if (scrollIntoView == "animate")
+ this.renderer.animateScrolling(this.curOp.scrollTop);
+ }
+
+ this.$lastSel = this.session.selection.toJSON();
+ this.prevOp = this.curOp;
+ this.curOp = null;
+ }
+ }
+
+ /**
+ * @param e
+ */
+ $historyTracker(e) {
+ if (!this.$mergeUndoDeltas)
+ return;
+
+ var prev = this.prevOp;
+ var mergeableCommands = this.$mergeableCommands;
+ // previous command was the same
+ var shouldMerge = prev.command && (e.command.name == prev.command.name);
+ if (e.command.name == "insertstring") {
+ var text = e.args;
+ if (this.mergeNextCommand === undefined)
+ this.mergeNextCommand = true;
+
+ shouldMerge = shouldMerge
+ && this.mergeNextCommand // previous command allows to coalesce with
+ && (!/\s/.test(text) || /\s/.test(prev.args)); // previous insertion was of same type
+
+ this.mergeNextCommand = true;
+ } else {
+ shouldMerge = shouldMerge
+ && mergeableCommands.indexOf(e.command.name) !== -1; // the command is mergeable
+ }
+
+ if (
+ this.$mergeUndoDeltas != "always"
+ && Date.now() - this.sequenceStartTime > 2000
+ ) {
+ shouldMerge = false; // the sequence is too long
+ }
+
+ if (shouldMerge)
+ this.session.mergeUndoDeltas = true;
+ else if (mergeableCommands.indexOf(e.command.name) !== -1)
+ this.sequenceStartTime = Date.now();
+ }
+
+ /**
+ * Sets a new key handler, such as "vim" or "windows".
+ * @param {String | import("../ace-internal").Ace.KeyboardHandler | null} keyboardHandler The new key handler
+ * @param {() => void} [cb]
+ **/
+ setKeyboardHandler(keyboardHandler, cb) {
+ if (keyboardHandler && typeof keyboardHandler === "string" && keyboardHandler != "ace") {
+ this.$keybindingId = keyboardHandler;
+ var _self = this;
+ config.loadModule(["keybinding", keyboardHandler], function(module) {
+ if (_self.$keybindingId == keyboardHandler)
+ _self.keyBinding.setKeyboardHandler(module && module.handler);
+ cb && cb();
+ });
+ } else {
+ this.$keybindingId = null;
+ // @ts-ignore
+ this.keyBinding.setKeyboardHandler(keyboardHandler);
+ cb && cb();
+ }
+ }
+
+ /**
+ * Returns the keyboard handler, such as "vim" or "windows".
+ * @returns {Object}
+ **/
+ getKeyboardHandler() {
+ return this.keyBinding.getKeyboardHandler();
+ }
+
+
+
+ /**
+ * Sets a new editsession to use. This method also emits the `'changeSession'` event.
+ * @param {EditSession} [session] The new session to use
+ **/
+ setSession(session) {
+ if (this.session == session)
+ return;
+
+ // make sure operationEnd events are not emitted to wrong session
+ if (this.curOp) this.endOperation();
+ this.curOp = {};
+
+ var oldSession = this.session;
+ if (oldSession) {
+ this.session.off("change", this.$onDocumentChange);
+ this.session.off("changeMode", this.$onChangeMode);
+ this.session.off("tokenizerUpdate", this.$onTokenizerUpdate);
+ this.session.off("changeTabSize", this.$onChangeTabSize);
+ this.session.off("changeWrapLimit", this.$onChangeWrapLimit);
+ this.session.off("changeWrapMode", this.$onChangeWrapMode);
+ this.session.off("changeFold", this.$onChangeFold);
+ this.session.off("changeFrontMarker", this.$onChangeFrontMarker);
+ this.session.off("changeBackMarker", this.$onChangeBackMarker);
+ this.session.off("changeBreakpoint", this.$onChangeBreakpoint);
+ this.session.off("changeAnnotation", this.$onChangeAnnotation);
+ this.session.off("changeOverwrite", this.$onCursorChange);
+ this.session.off("changeScrollTop", this.$onScrollTopChange);
+ this.session.off("changeScrollLeft", this.$onScrollLeftChange);
+ this.session.off("startOperation", this.$onStartOperation);
+ this.session.off("endOperation", this.$onEndOperation);
+
+ var selection = this.session.getSelection();
+ selection.off("changeCursor", this.$onCursorChange);
+ selection.off("changeSelection", this.$onSelectionChange);
+ }
+
+ this.session = session;
+ if (session) {
+ this.$onDocumentChange = this.onDocumentChange.bind(this);
+ session.on("change", this.$onDocumentChange);
+ this.renderer.setSession(session);
+
+ this.$onChangeMode = this.onChangeMode.bind(this);
+ session.on("changeMode", this.$onChangeMode);
+
+ this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this);
+ session.on("tokenizerUpdate", this.$onTokenizerUpdate);
+
+ this.$onChangeTabSize = this.renderer.onChangeTabSize.bind(this.renderer);
+ session.on("changeTabSize", this.$onChangeTabSize);
+
+ this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this);
+ session.on("changeWrapLimit", this.$onChangeWrapLimit);
+
+ this.$onChangeWrapMode = this.onChangeWrapMode.bind(this);
+ session.on("changeWrapMode", this.$onChangeWrapMode);
+
+ this.$onChangeFold = this.onChangeFold.bind(this);
+ session.on("changeFold", this.$onChangeFold);
+
+ this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this);
+ this.session.on("changeFrontMarker", this.$onChangeFrontMarker);
+
+ this.$onChangeBackMarker = this.onChangeBackMarker.bind(this);
+ this.session.on("changeBackMarker", this.$onChangeBackMarker);
+
+ this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this);
+ this.session.on("changeBreakpoint", this.$onChangeBreakpoint);
+
+ this.$onChangeAnnotation = this.onChangeAnnotation.bind(this);
+ this.session.on("changeAnnotation", this.$onChangeAnnotation);
+
+ this.$onCursorChange = this.onCursorChange.bind(this);
+ this.session.on("changeOverwrite", this.$onCursorChange);
+
+ this.$onScrollTopChange = this.onScrollTopChange.bind(this);
+ this.session.on("changeScrollTop", this.$onScrollTopChange);
+
+ this.$onScrollLeftChange = this.onScrollLeftChange.bind(this);
+ this.session.on("changeScrollLeft", this.$onScrollLeftChange);
+
+ this.selection = session.getSelection();
+ this.selection.on("changeCursor", this.$onCursorChange);
+
+ this.$onSelectionChange = this.onSelectionChange.bind(this);
+ this.selection.on("changeSelection", this.$onSelectionChange);
+
+ this.$onStartOperation = this.onStartOperation.bind(this);
+ this.session.on("startOperation", this.$onStartOperation);
+ this.$onEndOperation = this.onEndOperation.bind(this);
+ this.session.on("endOperation", this.$onEndOperation);
+
+ this.onChangeMode();
+
+ this.onCursorChange();
+
+ this.onScrollTopChange();
+ this.onScrollLeftChange();
+ this.onSelectionChange();
+ this.onChangeFrontMarker();
+ this.onChangeBackMarker();
+ this.onChangeBreakpoint();
+ this.onChangeAnnotation();
+ this.session.getUseWrapMode() && this.renderer.adjustWrapLimit();
+ this.renderer.updateFull();
+ } else {
+ this.selection = null;
+ this.renderer.setSession(session);
+ }
+
+ this._signal("changeSession", {
+ session: session,
+ oldSession: oldSession
+ });
+
+ this.curOp = null;
+
+ oldSession && oldSession._signal("changeEditor", {oldEditor: this});
+ if (oldSession) oldSession.$editor = null;
+ session && session._signal("changeEditor", {editor: this});
+ if (session) session.$editor = this;
+
+ if (session && !session.destroyed)
+ session.bgTokenizer.scheduleStart();
+ }
+
+ /**
+ * Returns the current session being used.
+ * @returns {EditSession}
+ **/
+ getSession() {
+ return this.session;
+ }
+
+ /**
+ * Sets the current document to `val`.
+ * @param {String} val The new value to set for the document
+ * @param {Number} [cursorPos] Where to set the new value. `undefined` or 0 is selectAll, -1 is at the document start, and 1 is at the end
+ *
+ * @returns {String} The current document value
+ * @related Document.setValue
+ **/
+ setValue(val, cursorPos) {
+ this.session.doc.setValue(val);
+
+ if (!cursorPos)
+ this.selectAll();
+ else if (cursorPos == 1)
+ this.navigateFileEnd();
+ else if (cursorPos == -1)
+ this.navigateFileStart();
+
+ return val;
+ }
+
+ /**
+ * Returns the current session's content.
+ *
+ * @returns {String}
+ * @related EditSession.getValue
+ **/
+ getValue() {
+ return this.session.getValue();
+ }
+
+ /**
+ *
+ * Returns the currently highlighted selection.
+ * @returns {Selection} The selection object
+ **/
+ getSelection() {
+ return this.selection;
+ }
+
+ /**
+ * {:VirtualRenderer.onResize}
+ * @param {Boolean} [force] If `true`, recomputes the size, even if the height and width haven't changed
+ * @related VirtualRenderer.onResize
+ **/
+ resize(force) {
+ this.renderer.onResize(force);
+ }
+
+ /**
+ * {:VirtualRenderer.setTheme}
+ * @param {string | import("../ace-internal").Ace.Theme} theme The path to a theme
+ * @param {() => void} [cb] optional callback called when theme is loaded
+ **/
+ setTheme(theme, cb) {
+ this.renderer.setTheme(theme, cb);
+ }
+
+ /**
+ * {:VirtualRenderer.getTheme}
+ *
+ * @returns {String} The set theme
+ * @related VirtualRenderer.getTheme
+ **/
+ getTheme() {
+ return this.renderer.getTheme();
+ }
+
+ /**
+ * {:VirtualRenderer.setStyle}
+ * @param {String} style A class name
+ * @related VirtualRenderer.setStyle
+ **/
+ setStyle(style) {
+ this.renderer.setStyle(style);
+ }
+
+ /**
+ * {:VirtualRenderer.unsetStyle}
+ * @related VirtualRenderer.unsetStyle
+ * @param {string} style
+ */
+ unsetStyle(style) {
+ this.renderer.unsetStyle(style);
+ }
+
+ /**
+ * Gets the current font size of the editor text.
+ * @return {string | number}
+ */
+ getFontSize() {
+ return this.getOption("fontSize") ||
+ dom.computedStyle(this.container).fontSize;
+ }
+
+ /**
+ * Set a new font size (in pixels) for the editor text.
+ * @param {String | number} size A font size ( _e.g._ "12px")
+ **/
+ setFontSize(size) {
+ this.setOption("fontSize", size);
+ }
+
+ $highlightBrackets() {
+ if (this.$highlightPending) {
+ return;
+ }
+
+ // perform highlight async to not block the browser during navigation
+ var self = this;
+ this.$highlightPending = true;
+ setTimeout(function () {
+ self.$highlightPending = false;
+ var session = self.session;
+ if (!session || session.destroyed) return;
+ if (session.$bracketHighlight) {
+ session.$bracketHighlight.markerIds.forEach(function(id) {
+ session.removeMarker(id);
+ });
+ session.$bracketHighlight = null;
+ }
+ var pos = self.getCursorPosition();
+ var handler = self.getKeyboardHandler();
+ var isBackwards = handler && handler.$getDirectionForHighlight && handler.$getDirectionForHighlight(self);
+ var ranges = session.getMatchingBracketRanges(pos, isBackwards);
+
+ if (!ranges) {
+ var iterator = new TokenIterator(session, pos.row, pos.column);
+ var token = iterator.getCurrentToken();
+
+ if (token && /\b(?:tag-open|tag-name)/.test(token.type)) {
+ var tagNamesRanges = session.getMatchingTags(pos);
+ if (tagNamesRanges) {
+ ranges = [
+ tagNamesRanges.openTagName.isEmpty() ? tagNamesRanges.openTag : tagNamesRanges.openTagName,
+ tagNamesRanges.closeTagName.isEmpty() ? tagNamesRanges.closeTag : tagNamesRanges.closeTagName
+ ];
+ }
+ }
+ }
+ if (!ranges && session.$mode.getMatching)
+ ranges = session.$mode.getMatching(self.session);
+ if (!ranges) {
+ if (self.getHighlightIndentGuides()) self.renderer.$textLayer.$highlightIndentGuide();
+ return;
+ }
+
+ var markerType = "ace_bracket";
+ if (!Array.isArray(ranges)) {
+ ranges = [ranges];
+ } else if (ranges.length == 1) {
+ markerType = "ace_error_bracket";
+ }
+
+ // show adjacent ranges as one
+ if (ranges.length == 2) {
+ if (Range.comparePoints(ranges[0].end, ranges[1].start) == 0)
+ ranges = [Range.fromPoints(ranges[0].start, ranges[1].end)];
+ else if (Range.comparePoints(ranges[0].start, ranges[1].end) == 0)
+ ranges = [Range.fromPoints(ranges[1].start, ranges[0].end)];
+ }
+
+ session.$bracketHighlight = {
+ ranges: ranges,
+ markerIds: ranges.map(function(range) {
+ return session.addMarker(range, markerType, "text");
+ })
+ };
+ if (self.getHighlightIndentGuides()) self.renderer.$textLayer.$highlightIndentGuide();
+ }, 50);
+ }
+
+ /**
+ *
+ * Brings the current `textInput` into focus.
+ **/
+ focus() {
+ this.textInput.focus();
+ }
+
+ /**
+ * Returns `true` if the current `textInput` is in focus.
+ * @return {Boolean}
+ **/
+ isFocused() {
+ return this.textInput.isFocused();
+ }
+
+ /**
+ *
+ * Blurs the current `textInput`.
+ **/
+ blur() {
+ this.textInput.blur();
+ }
+
+ /**
+ * Emitted once the editor comes into focus.
+ * @internal
+ **/
+ onFocus(e) {
+ if (this.$isFocused)
+ return;
+ this.$isFocused = true;
+ this.renderer.showCursor();
+ this.renderer.visualizeFocus();
+ this._emit("focus", e);
+ }
+
+ /**
+ * Emitted once the editor has been blurred.
+ * @internal
+ **/
+ onBlur(e) {
+ if (!this.$isFocused)
+ return;
+ this.$isFocused = false;
+ this.renderer.hideCursor();
+ this.renderer.visualizeBlur();
+ this._emit("blur", e);
+ }
+
+ /**
+ */
+ $cursorChange() {
+ this.renderer.updateCursor();
+ this.$highlightBrackets();
+ this.$updateHighlightActiveLine();
+ }
+
+ /**
+ * Emitted whenever the document is changed.
+ * @param {import("../ace-internal").Ace.Delta} delta Contains a single property, `data`, which has the delta of changes
+ * @internal
+ **/
+ onDocumentChange(delta) {
+ // Rerender and emit "change" event.
+ var wrap = this.session.$useWrapMode;
+ var lastRow = (delta.start.row == delta.end.row ? delta.end.row : Infinity);
+ this.renderer.updateLines(delta.start.row, lastRow, wrap);
+
+ this._signal("change", delta);
+
+ // Update cursor because tab characters can influence the cursor position.
+ this.$cursorChange();
+ }
+
+ /**
+ * @internal
+ */
+ onTokenizerUpdate(e) {
+ var rows = e.data;
+ this.renderer.updateLines(rows.first, rows.last);
+ }
+
+ /**
+ * @internal
+ */
+ onScrollTopChange() {
+ this.renderer.scrollToY(this.session.getScrollTop());
+ }
+
+ /**
+ * @internal
+ */
+ onScrollLeftChange() {
+ this.renderer.scrollToX(this.session.getScrollLeft());
+ }
+
+ /**
+ * Emitted when the selection changes.
+ * @internal
+ **/
+ onCursorChange() {
+ this.$cursorChange();
+ this._signal("changeSelection");
+ }
+
+ /**
+ */
+ $updateHighlightActiveLine() {
+ var session = this.getSession();
+ /**@type {Point|false}*/
+ var highlight;
+ if (this.$highlightActiveLine) {
+ if (this.$selectionStyle != "line" || !this.selection.isMultiLine())
+ highlight = this.getCursorPosition();
+ if (this.renderer.theme && this.renderer.theme.$selectionColorConflict && !this.selection.isEmpty())
+ highlight = false;
+ if (this.renderer.$maxLines && this.session.getLength() === 1 && !(this.renderer.$minLines > 1))
+ highlight = false;
+ }
+
+ if (session.$highlightLineMarker && !highlight) {
+ session.removeMarker(session.$highlightLineMarker.id);
+ session.$highlightLineMarker = null;
+ } else if (!session.$highlightLineMarker && highlight) {
+ var range = new Range(highlight.row, highlight.column, highlight.row, Infinity);
+ range.id = session.addMarker(range, "ace_active-line", "screenLine");
+ session.$highlightLineMarker = range;
+ } else if (highlight) {
+ session.$highlightLineMarker.start.row = highlight.row;
+ session.$highlightLineMarker.end.row = highlight.row;
+ session.$highlightLineMarker.start.column = highlight.column;
+ session._signal("changeBackMarker");
+ }
+ }
+
+ /**
+ * @param e
+ * @internal
+ */
+ onSelectionChange(e) {
+ var session = this.session;
+
+ if (session.$selectionMarker) {
+ session.removeMarker(session.$selectionMarker);
+ }
+ session.$selectionMarker = null;
+
+ if (!this.selection.isEmpty()) {
+ var range = this.selection.getRange();
+ var style = this.getSelectionStyle();
+ session.$selectionMarker = session.addMarker(range, "ace_selection", style);
+ } else {
+ this.$updateHighlightActiveLine();
+ }
+
+ var re = this.$highlightSelectedWord && this.$getSelectionHighLightRegexp();
+ this.session.highlight(re);
+
+ this._signal("changeSelection");
+ }
+
+ $getSelectionHighLightRegexp() {
+ var session = this.session;
+
+ var selection = this.getSelectionRange();
+ if (selection.isEmpty() || selection.isMultiLine())
+ return;
+
+ var startColumn = selection.start.column;
+ var endColumn = selection.end.column;
+ var line = session.getLine(selection.start.row);
+
+ var needle = line.substring(startColumn, endColumn);
+ // maximum allowed size for regular expressions in 32000,
+ // but getting close to it has significant impact on the performance
+ if (needle.length > 5000 || !/[\w\d]/.test(needle))
+ return;
+
+ var re = this.$search.$assembleRegExp({
+ wholeWord: true,
+ caseSensitive: true,
+ needle: needle
+ });
+
+ var wordWithBoundary = line.substring(startColumn - 1, endColumn + 1);
+ if (!re.test(wordWithBoundary))
+ return;
+
+ return re;
+ }
+
+ /**
+ * @internal
+ */
+ onChangeFrontMarker() {
+ this.renderer.updateFrontMarkers();
+ }
+
+ /**
+ * @internal
+ */
+ onChangeBackMarker() {
+ this.renderer.updateBackMarkers();
+ }
+
+ /**
+ * @internal
+ */
+ onChangeBreakpoint() {
+ this.renderer.updateBreakpoints();
+ }
+
+ /**
+ * @internal
+ */
+ onChangeAnnotation() {
+ this.renderer.setAnnotations(this.session.getAnnotations());
+ }
+
+ /**
+ * @param e
+ * @internal
+ */
+ onChangeMode (e) {
+ this.renderer.updateText();
+ this._emit("changeMode", e);
+ }
+
+ /**
+ * @internal
+ */
+ onChangeWrapLimit() {
+ this.renderer.updateFull();
+ }
+
+ /**
+ * @internal
+ */
+ onChangeWrapMode() {
+ this.renderer.onResize(true);
+ }
+
+
+ /**
+ * @internal
+ */
+ onChangeFold() {
+ // Update the active line marker as due to folding changes the current
+ // line range on the screen might have changed.
+ this.$updateHighlightActiveLine();
+ // TODO: This might be too much updating. Okay for now.
+ this.renderer.updateFull();
+ }
+
+
+ /**
+ * Returns the string of text currently highlighted.
+ * @returns {String}
+ **/
+ getSelectedText() {
+ return this.session.getTextRange(this.getSelectionRange());
+ }
+
+
+ /**
+ * Returns the string of text currently highlighted.
+ * @returns {String}
+ **/
+ getCopyText () {
+ var text = this.getSelectedText();
+ var nl = this.session.doc.getNewLineCharacter();
+ var copyLine= false;
+ if (!text && this.$copyWithEmptySelection) {
+ copyLine = true;
+ var ranges = this.selection.getAllRanges();
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ if (i && ranges[i - 1].start.row == range.start.row)
+ continue;
+ text += this.session.getLine(range.start.row) + nl;
+ }
+ }
+ var e = {text: text};
+ this._signal("copy", e);
+ clipboard.lineMode = copyLine ? e.text : false;
+ return e.text;
+ }
+
+ /**
+ * Called whenever a text "copy" happens.
+ * @internal
+ **/
+ onCopy() {
+ this.commands.exec("copy", this);
+ }
+
+ /**
+ * Called whenever a text "cut" happens.
+ * @internal
+ **/
+ onCut() {
+ this.commands.exec("cut", this);
+ }
+
+
+ /**
+ * Called whenever a text "paste" happens.
+ * @param {String} text The pasted text
+ * @param {ClipboardEvent} [event]
+ * @internal
+ **/
+ onPaste(text, event) {
+ var e = {text: text, event: event};
+ this.commands.exec("paste", this, e);
+ }
+
+ /**
+ *
+ * @param {string | {text: string, event?: ClipboardEvent}} e
+ * @returns {boolean}
+ */
+ $handlePaste(e) {
+ if (typeof e == "string")
+ e = {text: e};
+ this._signal("paste", e);
+ var text = e.text;
+
+ var lineMode = text === clipboard.lineMode;
+ var session = this.session;
+ if (!this.inMultiSelectMode || this.inVirtualSelectionMode) {
+ if (lineMode)
+ session.insert({ row: this.selection.lead.row, column: 0 }, text);
+ else
+ this.insert(text);
+ } else if (lineMode) {
+ this.selection.rangeList.ranges.forEach(function(range) {
+ session.insert({ row: range.start.row, column: 0 }, text);
+ });
+ } else {
+ var lines = text.split(/\r\n|\r|\n/);
+ var ranges = this.selection.rangeList.ranges;
+
+ var isFullLine = lines.length == 2 && (!lines[0] || !lines[1]);
+ if (lines.length != ranges.length || isFullLine)
+ return this.commands.exec("insertstring", this, text);
+
+ for (var i = ranges.length; i--;) {
+ var range = ranges[i];
+ if (!range.isEmpty())
+ session.remove(range);
+
+ session.insert(range.start, lines[i]);
+ }
+ }
+ }
+
+ /**
+ *
+ * @param {string | string[]} command
+ * @param [args]
+ * @return {boolean}
+ */
+ execCommand(command, args) {
+ return this.commands.exec(command, this, args);
+ }
+
+ /**
+ * Inserts `text` into wherever the cursor is pointing.
+ * @param {String} text The new text to add
+ * @param {boolean} [pasted]
+ **/
+ insert(text, pasted) {
+ var session = this.session;
+ var mode = session.getMode();
+ var cursor = this.getCursorPosition();
+
+ if (this.getBehavioursEnabled() && !pasted) {
+ // Get a transform if the current mode wants one.
+ var transform = mode.transformAction(session.getState(cursor.row), 'insertion', this, session, text);
+ if (transform) {
+ if (text !== transform.text) {
+ // keep automatic insertion in a separate delta, unless it is in multiselect mode
+ if (!this.inVirtualSelectionMode) {
+ this.session.mergeUndoDeltas = false;
+ this.mergeNextCommand = false;
+ }
+ }
+ text = transform.text;
+
+ }
+ }
+
+ if (text == "\t")
+ text = this.session.getTabString();
+
+ // remove selected text
+ if (!this.selection.isEmpty()) {
+ var range = this.getSelectionRange();
+ cursor = this.session.remove(range);
+ this.clearSelection();
+ }
+ else if (this.session.getOverwrite() && text.indexOf("\n") == -1) {
+ var range = Range.fromPoints(cursor, cursor);
+ range.end.column += text.length;
+ this.session.remove(range);
+ }
+
+ if (text == "\n" || text == "\r\n") {
+ var line = session.getLine(cursor.row);
+ if (cursor.column > line.search(/\S|$/)) {
+ var d = line.substr(cursor.column).search(/\S|$/);
+ session.doc.removeInLine(cursor.row, cursor.column, cursor.column + d);
+ }
+ }
+ this.clearSelection();
+
+ var start = cursor.column;
+ var lineState = session.getState(cursor.row);
+ var line = session.getLine(cursor.row);
+ var shouldOutdent = mode.checkOutdent(lineState, line, text);
+ session.insert(cursor, text);
+
+ if (transform && transform.selection) {
+ if (transform.selection.length == 2) { // Transform relative to the current column
+ this.selection.setSelectionRange(
+ new Range(cursor.row, start + transform.selection[0],
+ cursor.row, start + transform.selection[1]));
+ } else { // Transform relative to the current row.
+ this.selection.setSelectionRange(
+ new Range(cursor.row + transform.selection[0],
+ transform.selection[1],
+ cursor.row + transform.selection[2],
+ transform.selection[3]));
+ }
+ }
+ if (this.$enableAutoIndent) {
+ if (session.getDocument().isNewLine(text)) {
+ var lineIndent = mode.getNextLineIndent(lineState, line.slice(0, cursor.column), session.getTabString());
+
+ session.insert({row: cursor.row+1, column: 0}, lineIndent);
+ }
+ if (shouldOutdent)
+ mode.autoOutdent(lineState, session, cursor.row);
+ }
+ }
+
+ autoIndent() {
+ var session = this.session;
+ var mode = session.getMode();
+
+ var ranges = this.selection.isEmpty()
+ ? [new Range(0, 0, session.doc.getLength() - 1, 0)]
+ : this.selection.getAllRanges();
+
+ /**@type{string|string[]}*/
+ var prevLineState = "";
+ var prevLine = "";
+ var lineIndent = "";
+ var tab = session.getTabString();
+ for (var i = 0; i < ranges.length; i++) {
+ var startRow = ranges[i].start.row;
+ var endRow = ranges[i].end.row;
+
+ for (var row = startRow; row <= endRow; row++) {
+ if (row > 0) {
+ prevLineState = session.getState(row - 1);
+ prevLine = session.getLine(row - 1);
+ lineIndent = mode.getNextLineIndent(prevLineState, prevLine, tab);
+ }
+
+ var line = session.getLine(row);
+ var currIndent = mode.$getIndent(line);
+ if (lineIndent !== currIndent) {
+ if (currIndent.length > 0) {
+ var range = new Range(row, 0, row, currIndent.length);
+ session.remove(range);
+ }
+ if (lineIndent.length > 0) {
+ session.insert({row: row, column: 0}, lineIndent);
+ }
+ }
+
+ mode.autoOutdent(prevLineState, session, row);
+ }
+ }
+ }
+
+ /**
+ *
+ * @param text
+ * @param composition
+ * @returns {*}
+ * @internal
+ */
+ onTextInput(text, composition) {
+ if (!composition)
+ return this.keyBinding.onTextInput(text);
+
+ this.startOperation({command: { name: "insertstring" }});
+ var applyComposition = this.applyComposition.bind(this, text, composition);
+ if (this.selection.rangeCount)
+ this.forEachSelection(applyComposition);
+ else
+ applyComposition();
+ this.endOperation();
+ }
+
+ /**
+ * @param {string} [text]
+ * @param {any} [composition]
+ */
+ applyComposition(text, composition) {
+ if (composition.extendLeft || composition.extendRight) {
+ var r = this.selection.getRange();
+ r.start.column -= composition.extendLeft;
+ r.end.column += composition.extendRight;
+ if (r.start.column < 0) {
+ r.start.row--;
+ r.start.column += this.session.getLine(r.start.row).length + 1;
+ }
+ this.selection.setRange(r);
+ if (!text && !r.isEmpty())
+ this.remove();
+ }
+ if (text || !this.selection.isEmpty())
+ this.insert(text, true);
+ if (composition.restoreStart || composition.restoreEnd) {
+ var r = this.selection.getRange();
+ r.start.column -= composition.restoreStart;
+ r.end.column -= composition.restoreEnd;
+ this.selection.setRange(r);
+ }
+ }
+
+ /**
+ * @internal
+ */
+ onCommandKey(e, hashId, keyCode) {
+ return this.keyBinding.onCommandKey(e, hashId, keyCode);
+ }
+
+ /**
+ * Pass in `true` to enable overwrites in your session, or `false` to disable. If overwrites is enabled, any text you enter will type over any text after it. If the value of `overwrite` changes, this function also emits the `changeOverwrite` event.
+ * @param {Boolean} overwrite Defines whether or not to set overwrites
+ * @related EditSession.setOverwrite
+ **/
+ setOverwrite(overwrite) {
+ this.session.setOverwrite(overwrite);
+ }
+
+ /**
+ * Returns `true` if overwrites are enabled; `false` otherwise.
+ * @returns {Boolean}
+ * @related EditSession.getOverwrite
+ **/
+ getOverwrite() {
+ return this.session.getOverwrite();
+ }
+
+ /**
+ * Sets the value of overwrite to the opposite of whatever it currently is.
+ * @related EditSession.toggleOverwrite
+ **/
+ toggleOverwrite() {
+ this.session.toggleOverwrite();
+ }
+
+ /**
+ * Sets how fast the mouse scrolling should do.
+ * @param {Number} speed A value indicating the new speed (in milliseconds)
+ **/
+ setScrollSpeed(speed) {
+ this.setOption("scrollSpeed", speed);
+ }
+
+ /**
+ * Returns the value indicating how fast the mouse scroll speed is (in milliseconds).
+ * @returns {Number}
+ **/
+ getScrollSpeed() {
+ return this.getOption("scrollSpeed");
+ }
+
+ /**
+ * Sets the delay (in milliseconds) of the mouse drag.
+ * @param {Number} dragDelay A value indicating the new delay
+ **/
+ setDragDelay(dragDelay) {
+ this.setOption("dragDelay", dragDelay);
+ }
+
+ /**
+ * Returns the current mouse drag delay.
+ * @returns {Number}
+ **/
+ getDragDelay() {
+ return this.getOption("dragDelay");
+ }
+
+
+ /**
+ * Draw selection markers spanning whole line, or only over selected text. Default value is "line"
+ * @param {"fullLine" | "screenLine" | "text" | "line"} val The new selection style "line"|"text"
+ **/
+ setSelectionStyle(val) {
+ this.setOption("selectionStyle", val);
+ }
+
+ /**
+ * Returns the current selection style.
+ * @returns {import("../ace-internal").Ace.EditorOptions["selectionStyle"]}
+ **/
+ getSelectionStyle() {
+ return this.getOption("selectionStyle");
+ }
+
+ /**
+ * Determines whether or not the current line should be highlighted.
+ * @param {Boolean} shouldHighlight Set to `true` to highlight the current line
+ **/
+ setHighlightActiveLine(shouldHighlight) {
+ this.setOption("highlightActiveLine", shouldHighlight);
+ }
+ /**
+ * Returns `true` if current lines are always highlighted.
+ * @return {Boolean}
+ **/
+ getHighlightActiveLine() {
+ return this.getOption("highlightActiveLine");
+ }
+
+ /**
+ * @param {boolean} shouldHighlight
+ */
+ setHighlightGutterLine(shouldHighlight) {
+ this.setOption("highlightGutterLine", shouldHighlight);
+ }
+
+ /**
+ * @returns {Boolean}
+ */
+ getHighlightGutterLine() {
+ return this.getOption("highlightGutterLine");
+ }
+
+ /**
+ * Determines if the currently selected word should be highlighted.
+ * @param {Boolean} shouldHighlight Set to `true` to highlight the currently selected word
+ **/
+ setHighlightSelectedWord(shouldHighlight) {
+ this.setOption("highlightSelectedWord", shouldHighlight);
+ }
+
+ /**
+ * Returns `true` if currently highlighted words are to be highlighted.
+ * @returns {Boolean}
+ **/
+ getHighlightSelectedWord() {
+ return this.$highlightSelectedWord;
+ }
+
+ /**
+ * @param {boolean} shouldAnimate
+ */
+ setAnimatedScroll(shouldAnimate){
+ this.renderer.setAnimatedScroll(shouldAnimate);
+ }
+
+ /**
+ * @return {boolean}
+ */
+ getAnimatedScroll(){
+ return this.renderer.getAnimatedScroll();
+ }
+
+ /**
+ * If `showInvisibles` is set to `true`, invisible characters—like spaces or new lines—are show in the editor.
+ * @param {Boolean} showInvisibles Specifies whether or not to show invisible characters
+ **/
+ setShowInvisibles(showInvisibles) {
+ this.renderer.setShowInvisibles(showInvisibles);
+ }
+
+ /**
+ * Returns `true` if invisible characters are being shown.
+ * @returns {Boolean}
+ **/
+ getShowInvisibles() {
+ return this.renderer.getShowInvisibles();
+ }
+
+ /**
+ * @param {boolean} display
+ */
+ setDisplayIndentGuides(display) {
+ this.renderer.setDisplayIndentGuides(display);
+ }
+
+ /**
+ * @return {boolean}
+ */
+ getDisplayIndentGuides() {
+ return this.renderer.getDisplayIndentGuides();
+ }
+
+ /**
+ * @param {boolean} highlight
+ */
+ setHighlightIndentGuides(highlight) {
+ this.renderer.setHighlightIndentGuides(highlight);
+ }
+
+ /**
+ * @return {boolean}
+ */
+ getHighlightIndentGuides() {
+ return this.renderer.getHighlightIndentGuides();
+ }
+
+ /**
+ * If `showPrintMargin` is set to `true`, the print margin is shown in the editor.
+ * @param {Boolean} showPrintMargin Specifies whether or not to show the print margin
+ *
+ **/
+ setShowPrintMargin(showPrintMargin) {
+ this.renderer.setShowPrintMargin(showPrintMargin);
+ }
+
+ /**
+ * Returns `true` if the print margin is being shown.
+ * @returns {Boolean}
+ **/
+ getShowPrintMargin() {
+ return this.renderer.getShowPrintMargin();
+ }
+
+ /**
+ * Sets the column defining where the print margin should be.
+ * @param {Number} showPrintMargin Specifies the new print margin
+ *
+ **/
+ setPrintMarginColumn(showPrintMargin) {
+ this.renderer.setPrintMarginColumn(showPrintMargin);
+ }
+
+ /**
+ * Returns the column number of where the print margin is.
+ * @returns {Number}
+ **/
+ getPrintMarginColumn() {
+ return this.renderer.getPrintMarginColumn();
+ }
+
+ /**
+ * If `readOnly` is true, then the editor is set to read-only mode, and none of the content can change.
+ * @param {Boolean} readOnly Specifies whether the editor can be modified or not
+ **/
+ setReadOnly(readOnly) {
+ this.setOption("readOnly", readOnly);
+ }
+
+ /**
+ * Returns `true` if the editor is set to read-only mode.
+ * @returns {Boolean}
+ **/
+ getReadOnly() {
+ return this.getOption("readOnly");
+ }
+
+ /**
+ * Specifies whether to use behaviors or not. ["Behaviors" in this case is the auto-pairing of special characters, like quotation marks, parenthesis, or brackets.]{: #BehaviorsDef}
+ * @param {Boolean} enabled Enables or disables behaviors
+ **/
+ setBehavioursEnabled(enabled) {
+ this.setOption("behavioursEnabled", enabled);
+ }
+
+ /**
+ * Returns `true` if the behaviors are currently enabled. {:BehaviorsDef}
+ * @returns {Boolean}
+ **/
+ getBehavioursEnabled() {
+ return this.getOption("behavioursEnabled");
+ }
+
+ /**
+ * Specifies whether to use wrapping behaviors or not, i.e. automatically wrapping the selection with characters such as brackets
+ * when such a character is typed in.
+ * @param {Boolean} enabled Enables or disables wrapping behaviors
+ **/
+ setWrapBehavioursEnabled(enabled) {
+ this.setOption("wrapBehavioursEnabled", enabled);
+ }
+
+ /**
+ * Returns `true` if the wrapping behaviors are currently enabled.
+ * @returns {boolean}
+ **/
+ getWrapBehavioursEnabled() {
+ return this.getOption("wrapBehavioursEnabled");
+ }
+
+ /**
+ * Indicates whether the fold widgets should be shown or not.
+ * @param {Boolean} show Specifies whether the fold widgets are shown
+ **/
+ setShowFoldWidgets(show) {
+ this.setOption("showFoldWidgets", show);
+
+ }
+ /**
+ * Returns `true` if the fold widgets are shown.
+ * @return {Boolean}
+ **/
+ getShowFoldWidgets() {
+ return this.getOption("showFoldWidgets");
+ }
+
+ /**
+ * @param {boolean} fade
+ */
+ setFadeFoldWidgets(fade) {
+ this.setOption("fadeFoldWidgets", fade);
+ }
+
+ /**
+ * @returns {boolean}
+ */
+ getFadeFoldWidgets() {
+ return this.getOption("fadeFoldWidgets");
+ }
+
+ /**
+ * Removes the current selection or one character.
+ * @param {'left' | 'right'} [dir] The direction of the deletion to occur, either "left" or "right"
+ **/
+ remove(dir) {
+ if (this.selection.isEmpty()){
+ if (dir == "left")
+ this.selection.selectLeft();
+ else
+ this.selection.selectRight();
+ }
+
+ var range = this.getSelectionRange();
+ if (this.getBehavioursEnabled()) {
+ var session = this.session;
+ var state = session.getState(range.start.row);
+ var new_range = session.getMode().transformAction(state, 'deletion', this, session, range);
+
+ if (range.end.column === 0) {
+ var text = session.getTextRange(range);
+ if (text[text.length - 1] == "\n") {
+ var line = session.getLine(range.end.row);
+ if (/^\s+$/.test(line)) {
+ range.end.column = line.length;
+ }
+ }
+ }
+ if (new_range)
+ // @ts-expect-error TODO: possible bug, new_range could be not a Range
+ range = new_range;
+ }
+
+ this.session.remove(range);
+ this.clearSelection();
+ }
+
+ /**
+ * Removes the word directly to the right of the current selection.
+ **/
+ removeWordRight() {
+ if (this.selection.isEmpty())
+ this.selection.selectWordRight();
+
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ }
+
+ /**
+ * Removes the word directly to the left of the current selection.
+ **/
+ removeWordLeft() {
+ if (this.selection.isEmpty())
+ this.selection.selectWordLeft();
+
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ }
+
+ /**
+ * Removes all the words to the left of the current selection, until the start of the line.
+ **/
+ removeToLineStart() {
+ if (this.selection.isEmpty())
+ this.selection.selectLineStart();
+ if (this.selection.isEmpty())
+ this.selection.selectLeft();
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ }
+
+ /**
+ * Removes all the words to the right of the current selection, until the end of the line.
+ **/
+ removeToLineEnd() {
+ if (this.selection.isEmpty())
+ this.selection.selectLineEnd();
+
+ var range = this.getSelectionRange();
+ if (range.start.column == range.end.column && range.start.row == range.end.row) {
+ range.end.column = 0;
+ range.end.row++;
+ }
+
+ this.session.remove(range);
+ this.clearSelection();
+ }
+
+ /**
+ * Splits the line at the current selection (by inserting an `'\n'`).
+ **/
+ splitLine() {
+ if (!this.selection.isEmpty()) {
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ }
+
+ var cursor = this.getCursorPosition();
+ this.insert("\n");
+ this.moveCursorToPosition(cursor);
+ }
+
+ /**
+ * Set the "ghost" text in provided position. "Ghost" text is a kind of
+ * preview text inside the editor which can be used to preview some code
+ * inline in the editor such as, for example, code completions.
+ *
+ * @param {String} text Text to be inserted as "ghost" text
+ * @param {Point} [position] Position to insert text to
+ */
+ setGhostText(text, position) {
+ this.renderer.setGhostText(text, position);
+ }
+
+ /**
+ * Removes "ghost" text currently displayed in the editor.
+ */
+ removeGhostText() {
+ this.renderer.removeGhostText();
+ }
+
+ /**
+ * Transposes current line.
+ **/
+ transposeLetters() {
+ if (!this.selection.isEmpty()) {
+ return;
+ }
+
+ var cursor = this.getCursorPosition();
+ var column = cursor.column;
+ if (column === 0)
+ return;
+
+ var line = this.session.getLine(cursor.row);
+ var swap, range;
+ if (column < line.length) {
+ swap = line.charAt(column) + line.charAt(column-1);
+ range = new Range(cursor.row, column-1, cursor.row, column+1);
+ }
+ else {
+ swap = line.charAt(column-1) + line.charAt(column-2);
+ range = new Range(cursor.row, column-2, cursor.row, column);
+ }
+ this.session.replace(range, swap);
+ this.session.selection.moveToPosition(range.end);
+ }
+
+ /**
+ * Converts the current selection entirely into lowercase.
+ **/
+ toLowerCase() {
+ var originalRange = this.getSelectionRange();
+ if (this.selection.isEmpty()) {
+ this.selection.selectWord();
+ }
+
+ var range = this.getSelectionRange();
+ var text = this.session.getTextRange(range);
+ this.session.replace(range, text.toLowerCase());
+ this.selection.setSelectionRange(originalRange);
+ }
+
+ /**
+ * Converts the current selection entirely into uppercase.
+ **/
+ toUpperCase() {
+ var originalRange = this.getSelectionRange();
+ if (this.selection.isEmpty()) {
+ this.selection.selectWord();
+ }
+
+ var range = this.getSelectionRange();
+ var text = this.session.getTextRange(range);
+ this.session.replace(range, text.toUpperCase());
+ this.selection.setSelectionRange(originalRange);
+ }
+
+ /**
+ * Inserts an indentation into the current cursor position or indents the selected lines.
+ *
+ * @related EditSession.indentRows
+ **/
+ indent() {
+ var session = this.session;
+ var range = this.getSelectionRange();
+
+ if (range.start.row < range.end.row) {
+ var rows = this.$getSelectedRows();
+ session.indentRows(rows.first, rows.last, "\t");
+ return;
+ } else if (range.start.column < range.end.column) {
+ var text = session.getTextRange(range);
+ if (!/^\s+$/.test(text)) {
+ var rows = this.$getSelectedRows();
+ session.indentRows(rows.first, rows.last, "\t");
+ return;
+ }
+ }
+
+ var line = session.getLine(range.start.row);
+ var position = range.start;
+ var size = session.getTabSize();
+ var column = session.documentToScreenColumn(position.row, position.column);
+
+ if (this.session.getUseSoftTabs()) {
+ var count = (size - column % size);
+ var indentString = lang.stringRepeat(" ", count);
+ } else {
+ var count = column % size;
+ while (line[range.start.column - 1] == " " && count) {
+ range.start.column--;
+ count--;
+ }
+ this.selection.setSelectionRange(range);
+ indentString = "\t";
+ }
+ return this.insert(indentString);
+ }
+
+ /**
+ * Indents the current line.
+ * @related EditSession.indentRows
+ **/
+ blockIndent() {
+ var rows = this.$getSelectedRows();
+ this.session.indentRows(rows.first, rows.last, "\t");
+ }
+
+ /**
+ * Outdents the current line.
+ * @related EditSession.outdentRows
+ **/
+ blockOutdent() {
+ var selection = this.session.getSelection();
+ this.session.outdentRows(selection.getRange());
+ }
+
+ // TODO: move out of core when we have good mechanism for managing extensions
+ sortLines() {
+ var rows = this.$getSelectedRows();
+ var session = this.session;
+
+ var lines = [];
+ for (var i = rows.first; i <= rows.last; i++)
+ lines.push(session.getLine(i));
+
+ lines.sort(function(a, b) {
+ if (a.toLowerCase() < b.toLowerCase()) return -1;
+ if (a.toLowerCase() > b.toLowerCase()) return 1;
+ return 0;
+ });
+
+ var deleteRange = new Range(0, 0, 0, 0);
+ for (var i = rows.first; i <= rows.last; i++) {
+ var line = session.getLine(i);
+ deleteRange.start.row = i;
+ deleteRange.end.row = i;
+ deleteRange.end.column = line.length;
+ session.replace(deleteRange, lines[i-rows.first]);
+ }
+ }
+
+ /**
+ * Given the currently selected range, this function either comments all the lines, or uncomments all of them.
+ **/
+ toggleCommentLines() {
+ var state = this.session.getState(this.getCursorPosition().row);
+ var rows = this.$getSelectedRows();
+ this.session.getMode().toggleCommentLines(state, this.session, rows.first, rows.last);
+ }
+
+ toggleBlockComment() {
+ var cursor = this.getCursorPosition();
+ var state = this.session.getState(cursor.row);
+ var range = this.getSelectionRange();
+ this.session.getMode().toggleBlockComment(state, this.session, range, cursor);
+ }
+
+ /**
+ * Works like [[EditSession.getTokenAt]], except it returns a number.
+ * @returns {any}
+ **/
+ getNumberAt(row, column) {
+ var _numberRx = /[\-]?[0-9]+(?:\.[0-9]+)?/g;
+ _numberRx.lastIndex = 0;
+
+ var s = this.session.getLine(row);
+ while (_numberRx.lastIndex < column) {
+ var m = _numberRx.exec(s);
+ if(m.index <= column && m.index+m[0].length >= column){
+ var number = {
+ value: m[0],
+ start: m.index,
+ end: m.index+m[0].length
+ };
+ return number;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * If the character before the cursor is a number, this functions changes its value by `amount`.
+ * @param {Number} amount The value to change the numeral by (can be negative to decrease value)
+ **/
+ modifyNumber(amount) {
+ var row = this.selection.getCursor().row;
+ var column = this.selection.getCursor().column;
+
+ // get the char before the cursor
+ var charRange = new Range(row, column-1, row, column);
+
+ var c = this.session.getTextRange(charRange);
+ // if the char is a digit
+ // @ts-ignore
+ if (!isNaN(parseFloat(c)) && isFinite(c)) {
+ // get the whole number the digit is part of
+ var nr = this.getNumberAt(row, column);
+ // if number found
+ if (nr) {
+ var fp = nr.value.indexOf(".") >= 0 ? nr.start + nr.value.indexOf(".") + 1 : nr.end;
+ var decimals = nr.start + nr.value.length - fp;
+
+ var t = parseFloat(nr.value);
+ t *= Math.pow(10, decimals);
+
+
+ if(fp !== nr.end && column < fp){
+ amount *= Math.pow(10, nr.end - column - 1);
+ } else {
+ amount *= Math.pow(10, nr.end - column);
+ }
+
+ t += amount;
+ t /= Math.pow(10, decimals);
+ var nnr = t.toFixed(decimals);
+
+ //update number
+ var replaceRange = new Range(row, nr.start, row, nr.end);
+ this.session.replace(replaceRange, nnr);
+
+ //reposition the cursor
+ this.moveCursorTo(row, Math.max(nr.start +1, column + nnr.length - nr.value.length));
+
+ }
+ } else {
+ this.toggleWord();
+ }
+ }
+
+ /**
+ */
+ toggleWord() {
+ var row = this.selection.getCursor().row;
+ var column = this.selection.getCursor().column;
+ this.selection.selectWord();
+ var currentState = this.getSelectedText();
+ var currWordStart = this.selection.getWordRange().start.column;
+ var wordParts = currentState.replace(/([a-z]+|[A-Z]+)(?=[A-Z_]|$)/g, '$1 ').split(/\s/);
+ var delta = column - currWordStart - 1;
+ if (delta < 0) delta = 0;
+ var curLength = 0, itLength = 0;
+ var that = this;
+ if (currentState.match(/[A-Za-z0-9_]+/)) {
+ wordParts.forEach(function (item, i) {
+ itLength = curLength + item.length;
+ if (delta >= curLength && delta <= itLength) {
+ currentState = item;
+ that.selection.clearSelection();
+ that.moveCursorTo(row, curLength + currWordStart);
+ that.selection.selectTo(row, itLength + currWordStart);
+ }
+ curLength = itLength;
+ });
+ }
+
+ var wordPairs = this.$toggleWordPairs;
+ var reg;
+ for (var i = 0; i < wordPairs.length; i++) {
+ var item = wordPairs[i];
+ for (var j = 0; j <= 1; j++) {
+ var negate = +!j;
+ var firstCondition = currentState.match(new RegExp('^\\s?_?(' + lang.escapeRegExp(item[j]) + ')\\s?$', 'i'));
+ if (firstCondition) {
+ var secondCondition = currentState.match(new RegExp('([_]|^|\\s)(' + lang.escapeRegExp(firstCondition[1]) + ')($|\\s)', 'g'));
+ if (secondCondition) {
+ reg = currentState.replace(new RegExp(lang.escapeRegExp(item[j]), 'i'), function (result) {
+ var res = item[negate];
+ if (result.toUpperCase() == result) {
+ res = res.toUpperCase();
+ } else if (result.charAt(0).toUpperCase() == result.charAt(0)) {
+ res = res.substr(0, 0) + item[negate].charAt(0).toUpperCase() + res.substr(1);
+ }
+ return res;
+ });
+ this.insert(reg);
+ reg = "";
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Finds link at defined {row} and {column}
+ * @returns {String}
+ **/
+ findLinkAt(row, column) {
+ var line = this.session.getLine(row);
+ var wordParts = line.split(/((?:https?|ftp):\/\/[\S]+)/);
+ var columnPosition = column;
+ if (columnPosition < 0) columnPosition = 0;
+ var previousPosition = 0, currentPosition = 0, match;
+ for (let item of wordParts) {
+ currentPosition = previousPosition + item.length;
+ if (columnPosition >= previousPosition && columnPosition <= currentPosition) {
+ if (item.match(/((?:https?|ftp):\/\/[\S]+)/)) {
+ match = item.replace(/[\s:.,'";}\]]+$/, "");
+ break;
+ }
+ }
+ previousPosition = currentPosition;
+ }
+ return match;
+ }
+
+ /**
+ * Open valid url under cursor in another tab
+ * @returns {Boolean}
+ **/
+ openLink() {
+ var cursor = this.selection.getCursor();
+ var url = this.findLinkAt(cursor.row, cursor.column);
+ if (url)
+ window.open(url, '_blank');
+ return url != null;
+ }
+
+ /**
+ * Removes all the lines in the current selection
+ * @related EditSession.remove
+ **/
+ removeLines() {
+ var rows = this.$getSelectedRows();
+ this.session.removeFullLines(rows.first, rows.last);
+ this.clearSelection();
+ }
+
+ duplicateSelection() {
+ var sel = this.selection;
+ var doc = this.session;
+ var range = sel.getRange();
+ var reverse = sel.isBackwards();
+ if (range.isEmpty()) {
+ var row = range.start.row;
+ doc.duplicateLines(row, row);
+ } else {
+ var point = reverse ? range.start : range.end;
+ var endPoint = doc.insert(point, doc.getTextRange(range));
+ range.start = point;
+ range.end = endPoint;
+
+ sel.setSelectionRange(range, reverse);
+ }
+ }
+
+ /**
+ * Shifts all the selected lines down one row.
+ *
+ * @related EditSession.moveLinesUp
+ **/
+ moveLinesDown() {
+ this.$moveLines(1, false);
+ }
+
+ /**
+ * Shifts all the selected lines up one row.
+ * @related EditSession.moveLinesDown
+ **/
+ moveLinesUp() {
+ this.$moveLines(-1, false);
+ }
+
+ /**
+ * Moves a range of text from the given range to the given position. `toPosition` is an object that looks like this:
+ * ```json
+ * { row: newRowLocation, column: newColumnLocation }
+ * ```
+ * @param {Range} range The range of text you want moved within the document
+ * @param {Point} toPosition The location (row and column) where you want to move the text to
+ * @param {boolean} [copy]
+ *
+ * @returns {Range} The new range where the text was moved to.
+ * @related EditSession.moveText
+ **/
+ moveText(range, toPosition, copy) {
+ return this.session.moveText(range, toPosition, copy);
+ }
+
+ /**
+ * Copies all the selected lines up one row.
+ *
+ **/
+ copyLinesUp() {
+ this.$moveLines(-1, true);
+ }
+
+ /**
+ * Copies all the selected lines down one row.
+ * @related EditSession.duplicateLines
+ *
+ **/
+ copyLinesDown() {
+ this.$moveLines(1, true);
+ }
+
+ /**
+ * for internal use
+ * @ignore
+ *
+ **/
+ $moveLines(dir, copy) {
+ var rows, moved;
+ var selection = this.selection;
+ if (!selection.inMultiSelectMode || this.inVirtualSelectionMode) {
+ var range = selection.toOrientedRange();
+ rows = this.$getSelectedRows(range);
+ moved = this.session.$moveLines(rows.first, rows.last, copy ? 0 : dir);
+ if (copy && dir == -1) moved = 0;
+ range.moveBy(moved, 0);
+ selection.fromOrientedRange(range);
+ } else {
+ var ranges = selection.rangeList.ranges;
+ // @ts-expect-error TODO: possible bug, no args in parameters
+ selection.rangeList.detach(this.session);
+ this.inVirtualSelectionMode = true;
+
+ var diff = 0;
+ var totalDiff = 0;
+ var l = ranges.length;
+ for (var i = 0; i < l; i++) {
+ var rangeIndex = i;
+ ranges[i].moveBy(diff, 0);
+ rows = this.$getSelectedRows(ranges[i]);
+ var first = rows.first;
+ var last = rows.last;
+ while (++i < l) {
+ if (totalDiff) ranges[i].moveBy(totalDiff, 0);
+ var subRows = this.$getSelectedRows(ranges[i]);
+ if (copy && subRows.first != last)
+ break;
+ else if (!copy && subRows.first > last + 1)
+ break;
+ last = subRows.last;
+ }
+ i--;
+ diff = this.session.$moveLines(first, last, copy ? 0 : dir);
+ if (copy && dir == -1) rangeIndex = i + 1;
+ while (rangeIndex <= i) {
+ ranges[rangeIndex].moveBy(diff, 0);
+ rangeIndex++;
+ }
+ if (!copy) diff = 0;
+ totalDiff += diff;
+ }
+
+ selection.fromOrientedRange(selection.ranges[0]);
+ selection.rangeList.attach(this.session);
+ this.inVirtualSelectionMode = false;
+ }
+ }
+
+ /**
+ * Returns an object indicating the currently selected rows. The object looks like this:
+ *
+ * ```json
+ * { first: range.start.row, last: range.end.row }
+ * ```
+ *
+ * @returns {Object}
+ **/
+ $getSelectedRows(range) {
+ range = (range || this.getSelectionRange()).collapseRows();
+
+ return {
+ first: this.session.getRowFoldStart(range.start.row),
+ last: this.session.getRowFoldEnd(range.end.row)
+ };
+ }
+
+ /**
+ * @internal
+ */
+ onCompositionStart(compositionState) {
+ this.renderer.showComposition(compositionState);
+ }
+
+ /**
+ * @internal
+ */
+ onCompositionUpdate(text) {
+ this.renderer.setCompositionText(text);
+ }
+
+ /**
+ * @internal
+ */
+ onCompositionEnd() {
+ this.renderer.hideComposition();
+ }
+
+ /**
+ * {:VirtualRenderer.getFirstVisibleRow}
+ *
+ * @returns {Number}
+ * @related VirtualRenderer.getFirstVisibleRow
+ **/
+ getFirstVisibleRow() {
+ return this.renderer.getFirstVisibleRow();
+ }
+
+ /**
+ * {:VirtualRenderer.getLastVisibleRow}
+ *
+ * @returns {Number}
+ * @related VirtualRenderer.getLastVisibleRow
+ **/
+ getLastVisibleRow() {
+ return this.renderer.getLastVisibleRow();
+ }
+
+ /**
+ * Indicates if the row is currently visible on the screen.
+ * @param {Number} row The row to check
+ *
+ * @returns {Boolean}
+ **/
+ isRowVisible(row) {
+ return (row >= this.getFirstVisibleRow() && row <= this.getLastVisibleRow());
+ }
+
+ /**
+ * Indicates if the entire row is currently visible on the screen.
+ * @param {Number} row The row to check
+ *
+ *
+ * @returns {Boolean}
+ **/
+ isRowFullyVisible(row) {
+ return (row >= this.renderer.getFirstFullyVisibleRow() && row <= this.renderer.getLastFullyVisibleRow());
+ }
+
+ /**
+ * Returns the number of currently visible rows.
+ * @returns {Number}
+ **/
+ $getVisibleRowCount() {
+ return this.renderer.getScrollBottomRow() - this.renderer.getScrollTopRow() + 1;
+ }
+
+ $moveByPage(dir, select) {
+ var renderer = this.renderer;
+ var config = this.renderer.layerConfig;
+ var rows = dir * Math.floor(config.height / config.lineHeight);
+
+ if (select === true) {
+ this.selection.$moveSelection(function(){
+ this.moveCursorBy(rows, 0);
+ });
+ } else if (select === false) {
+ this.selection.moveCursorBy(rows, 0);
+ this.selection.clearSelection();
+ }
+
+ var scrollTop = renderer.scrollTop;
+
+ renderer.scrollBy(0, rows * config.lineHeight);
+ if (select != null)
+ renderer.scrollCursorIntoView(null, 0.5);
+
+ renderer.animateScrolling(scrollTop);
+ }
+
+ /**
+ * Selects the text from the current position of the document until where a "page down" finishes.
+ **/
+ selectPageDown() {
+ this.$moveByPage(1, true);
+ }
+
+ /**
+ * Selects the text from the current position of the document until where a "page up" finishes.
+ **/
+ selectPageUp() {
+ this.$moveByPage(-1, true);
+ }
+
+ /**
+ * Shifts the document to wherever "page down" is, as well as moving the cursor position.
+ **/
+ gotoPageDown() {
+ this.$moveByPage(1, false);
+ }
+
+ /**
+ * Shifts the document to wherever "page up" is, as well as moving the cursor position.
+ **/
+ gotoPageUp() {
+ this.$moveByPage(-1, false);
+ }
+
+ /**
+ * Scrolls the document to wherever "page down" is, without changing the cursor position.
+ **/
+ scrollPageDown() {
+ this.$moveByPage(1);
+ }
+
+ /**
+ * Scrolls the document to wherever "page up" is, without changing the cursor position.
+ **/
+ scrollPageUp() {
+ this.$moveByPage(-1);
+ }
+
+ /**
+ * Moves the editor to the specified row.
+ * @related VirtualRenderer.scrollToRow
+ * @param {number} row
+ */
+ scrollToRow(row) {
+ this.renderer.scrollToRow(row);
+ }
+
+ /**
+ * Scrolls to a line. If `center` is `true`, it puts the line in middle of screen (or attempts to).
+ * @param {Number} line The line to scroll to
+ * @param {Boolean} center If `true`
+ * @param {Boolean} animate If `true` animates scrolling
+ * @param {() => void} [callback] Function to be called when the animation has finished
+ *
+ * @related VirtualRenderer.scrollToLine
+ **/
+ scrollToLine(line, center, animate, callback) {
+ this.renderer.scrollToLine(line, center, animate, callback);
+ }
+
+ /**
+ * Attempts to center the current selection on the screen.
+ **/
+ centerSelection() {
+ var range = this.getSelectionRange();
+ var pos = {
+ row: Math.floor(range.start.row + (range.end.row - range.start.row) / 2),
+ column: Math.floor(range.start.column + (range.end.column - range.start.column) / 2)
+ };
+ this.renderer.alignCursor(pos, 0.5);
+ }
+
+ /**
+ * Gets the current position of the cursor.
+ * @returns {Point} An object that looks something like this:
+ *
+ * ```json
+ * { row: currRow, column: currCol }
+ * ```
+ *
+ * @related Selection.getCursor
+ **/
+ getCursorPosition() {
+ return this.selection.getCursor();
+ }
+
+ /**
+ * Returns the screen position of the cursor.
+ * @returns {Point}
+ * @related EditSession.documentToScreenPosition
+ **/
+ getCursorPositionScreen() {
+ return this.session.documentToScreenPosition(this.getCursorPosition());
+ }
+
+ /**
+ * {:Selection.getRange}
+ * @returns {Range}
+ * @related Selection.getRange
+ **/
+ getSelectionRange() {
+ return this.selection.getRange();
+ }
+
+ /**
+ * Selects all the text in editor.
+ * @related Selection.selectAll
+ **/
+ selectAll() {
+ this.selection.selectAll();
+ }
+
+ /**
+ * {:Selection.clearSelection}
+ * @related Selection.clearSelection
+ **/
+ clearSelection() {
+ this.selection.clearSelection();
+ }
+
+ /**
+ * Moves the cursor to the specified row and column. Note that this does not de-select the current selection.
+ * @param {Number} row The new row number
+ * @param {Number} column The new column number
+ * @related Selection.moveCursorTo
+ **/
+ moveCursorTo(row, column) {
+ this.selection.moveCursorTo(row, column);
+ }
+
+ /**
+ * Moves the cursor to the position indicated by `pos.row` and `pos.column`.
+ * @param {Point} pos An object with two properties, row and column
+ * @related Selection.moveCursorToPosition
+ **/
+ moveCursorToPosition(pos) {
+ this.selection.moveCursorToPosition(pos);
+ }
+
+ /**
+ * Moves the cursor's row and column to the next matching bracket or HTML tag.
+ * @param {boolean} [select]
+ * @param {boolean} [expand]
+ */
+ jumpToMatching(select, expand) {
+ var cursor = this.getCursorPosition();
+ var iterator = new TokenIterator(this.session, cursor.row, cursor.column);
+ var prevToken = iterator.getCurrentToken();
+ var tokenCount = 0;
+ if (prevToken && prevToken.type.indexOf('tag-name') !== -1) {
+ prevToken = iterator.stepBackward();
+ }
+ var token = prevToken || iterator.stepForward();
+
+ if (!token) return;
+
+ //get next closing tag or bracket
+ var matchType;
+ var found = false;
+ var depth = {};
+ var i = cursor.column - token.start;
+ var bracketType;
+ var brackets = {
+ ")": "(",
+ "(": "(",
+ "]": "[",
+ "[": "[",
+ "{": "{",
+ "}": "{"
+ };
+
+ do {
+ if (token.value.match(/[{}()\[\]]/g)) {
+ for (; i < token.value.length && !found; i++) {
+ if (!brackets[token.value[i]]) {
+ continue;
+ }
+
+ bracketType = brackets[token.value[i]] + '.' + token.type.replace("rparen", "lparen");
+
+ if (isNaN(depth[bracketType])) {
+ depth[bracketType] = 0;
+ }
+
+ switch (token.value[i]) {
+ case '(':
+ case '[':
+ case '{':
+ depth[bracketType]++;
+ break;
+ case ')':
+ case ']':
+ case '}':
+ depth[bracketType]--;
+
+ if (depth[bracketType] === -1) {
+ matchType = 'bracket';
+ found = true;
+ }
+ break;
+ }
+ }
+ }
+ else if (token.type.indexOf('tag-name') !== -1) {
+ if (isNaN(depth[token.value])) {
+ depth[token.value] = 0;
+ }
+
+ if (prevToken.value === '<' && tokenCount > 1) {
+ depth[token.value]++;
+ }
+ else if (prevToken.value === '') {
+ depth[token.value]--;
+ }
+
+ if (depth[token.value] === -1) {
+ matchType = 'tag';
+ found = true;
+ }
+ }
+
+ if (!found) {
+ prevToken = token;
+ tokenCount++;
+ token = iterator.stepForward();
+ i = 0;
+ }
+ } while (token && !found);
+
+ //no match found
+ if (!matchType) return;
+
+ var range, pos;
+ if (matchType === 'bracket') {
+ range = this.session.getBracketRange(cursor);
+ if (!range) {
+ range = new Range(iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() + i - 1,
+ iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() + i - 1
+ );
+ pos = range.start;
+ if (expand || pos.row === cursor.row && Math.abs(pos.column - cursor.column)
+ < 2) range = this.session.getBracketRange(pos);
+ }
+ }
+ else if (matchType === 'tag') {
+ if (!token || token.type.indexOf('tag-name') === -1) return;
+ range = new Range(iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() - 2,
+ iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() - 2
+ );
+
+ //find matching tag
+ if (range.compare(cursor.row, cursor.column) === 0) {
+ var tagsRanges = this.session.getMatchingTags(cursor);
+ if (tagsRanges) {
+ if (tagsRanges.openTag.contains(cursor.row, cursor.column)) {
+ range = tagsRanges.closeTag;
+ pos = range.start;
+ }
+ else {
+ range = tagsRanges.openTag;
+ if (tagsRanges.closeTag.start.row === cursor.row && tagsRanges.closeTag.start.column
+ === cursor.column) pos = range.end; else pos = range.start;
+ }
+ }
+ }
+
+ //we found it
+ pos = pos || range.start;
+ }
+
+ pos = range && range.cursor || pos;
+ if (pos) {
+ if (select) {
+ if (range && expand) {
+ this.selection.setRange(range);
+ }
+ else if (range && range.isEqual(this.getSelectionRange())) {
+ this.clearSelection();
+ }
+ else {
+ this.selection.selectTo(pos.row, pos.column);
+ }
+ }
+ else {
+ this.selection.moveTo(pos.row, pos.column);
+ }
+ }
+ }
+
+ /**
+ * Moves the cursor to the specified line number, and also into the indicated column.
+ * @param {Number} lineNumber The line number to go to
+ * @param {Number} [column] A column number to go to
+ * @param {Boolean} [animate] If `true` animates scolling
+ **/
+ gotoLine(lineNumber, column, animate) {
+ this.selection.clearSelection();
+ this.session.unfold({row: lineNumber - 1, column: column || 0});
+
+ // todo: find a way to automatically exit multiselect mode
+ this.exitMultiSelectMode && this.exitMultiSelectMode();
+ this.moveCursorTo(lineNumber - 1, column || 0);
+
+ if (!this.isRowFullyVisible(lineNumber - 1))
+ this.scrollToLine(lineNumber - 1, true, animate);
+ }
+
+ /**
+ * Moves the cursor to the specified row and column. Note that this does de-select the current selection.
+ * @param {Number} row The new row number
+ * @param {Number} column The new column number
+ *
+ * @related Editor.moveCursorTo
+ **/
+ navigateTo(row, column) {
+ this.selection.moveTo(row, column);
+ }
+
+ /**
+ * Moves the cursor up in the document the specified number of times. Note that this does de-select the current selection.
+ * @param {Number} [times] The number of times to change navigation
+ *
+ **/
+ navigateUp(times) {
+ if (this.selection.isMultiLine() && !this.selection.isBackwards()) {
+ var selectionStart = this.selection.anchor.getPosition();
+ return this.moveCursorToPosition(selectionStart);
+ }
+ this.selection.clearSelection();
+ this.selection.moveCursorBy(-times || -1, 0);
+ }
+
+ /**
+ * Moves the cursor down in the document the specified number of times. Note that this does de-select the current selection.
+ * @param {Number} [times] The number of times to change navigation
+ *
+ **/
+ navigateDown(times) {
+ if (this.selection.isMultiLine() && this.selection.isBackwards()) {
+ var selectionEnd = this.selection.anchor.getPosition();
+ return this.moveCursorToPosition(selectionEnd);
+ }
+ this.selection.clearSelection();
+ this.selection.moveCursorBy(times || 1, 0);
+ }
+
+ /**
+ * Moves the cursor left in the document the specified number of times. Note that this does de-select the current selection.
+ * @param {Number} [times] The number of times to change navigation
+ *
+ **/
+ navigateLeft(times) {
+ if (!this.selection.isEmpty()) {
+ var selectionStart = this.getSelectionRange().start;
+ this.moveCursorToPosition(selectionStart);
+ }
+ else {
+ times = times || 1;
+ while (times--) {
+ this.selection.moveCursorLeft();
+ }
+ }
+ this.clearSelection();
+ }
+
+ /**
+ * Moves the cursor right in the document the specified number of times. Note that this does de-select the current selection.
+ * @param {Number} [times] The number of times to change navigation
+ *
+ **/
+ navigateRight(times) {
+ if (!this.selection.isEmpty()) {
+ var selectionEnd = this.getSelectionRange().end;
+ this.moveCursorToPosition(selectionEnd);
+ }
+ else {
+ times = times || 1;
+ while (times--) {
+ this.selection.moveCursorRight();
+ }
+ }
+ this.clearSelection();
+ }
+
+ /**
+ *
+ * Moves the cursor to the start of the current line. Note that this does de-select the current selection.
+ **/
+ navigateLineStart() {
+ this.selection.moveCursorLineStart();
+ this.clearSelection();
+ }
+
+ /**
+ *
+ * Moves the cursor to the end of the current line. Note that this does de-select the current selection.
+ **/
+ navigateLineEnd() {
+ this.selection.moveCursorLineEnd();
+ this.clearSelection();
+ }
+
+ /**
+ *
+ * Moves the cursor to the end of the current file. Note that this does de-select the current selection.
+ **/
+ navigateFileEnd() {
+ this.selection.moveCursorFileEnd();
+ this.clearSelection();
+ }
+
+ /**
+ *
+ * Moves the cursor to the start of the current file. Note that this does de-select the current selection.
+ **/
+ navigateFileStart() {
+ this.selection.moveCursorFileStart();
+ this.clearSelection();
+ }
+
+ /**
+ *
+ * Moves the cursor to the word immediately to the right of the current position. Note that this does de-select the current selection.
+ **/
+ navigateWordRight() {
+ this.selection.moveCursorWordRight();
+ this.clearSelection();
+ }
+
+ /**
+ *
+ * Moves the cursor to the word immediately to the left of the current position. Note that this does de-select the current selection.
+ **/
+ navigateWordLeft() {
+ this.selection.moveCursorWordLeft();
+ this.clearSelection();
+ }
+
+ /**
+ * Replaces the first occurrence of `options.needle` with the value in `replacement`.
+ * @param {String} [replacement] The text to replace with
+ * @param {Partial} [options] The [[Search `Search`]] options to use
+ * @return {number}
+ **/
+ replace(replacement, options) {
+ if (options)
+ this.$search.set(options);
+
+ var range = this.$search.find(this.session);
+ var replaced = 0;
+ if (!range)
+ return replaced;
+
+ if (this.$tryReplace(range, replacement)) {
+ replaced = 1;
+ }
+
+ this.selection.setSelectionRange(range);
+ this.renderer.scrollSelectionIntoView(range.start, range.end);
+
+ return replaced;
+ }
+
+ /**
+ * Replaces all occurrences of `options.needle` with the value in `replacement`.
+ * @param {String} [replacement] The text to replace with
+ * @param {Partial} [options] The [[Search `Search`]] options to use
+ * @return {number}
+ **/
+ replaceAll(replacement, options) {
+ if (options) {
+ this.$search.set(options);
+ }
+
+ var ranges = this.$search.findAll(this.session);
+ var replaced = 0;
+ if (!ranges.length)
+ return replaced;
+
+ var selection = this.getSelectionRange();
+ this.selection.moveTo(0, 0);
+
+ for (var i = ranges.length - 1; i >= 0; --i) {
+ if(this.$tryReplace(ranges[i], replacement)) {
+ replaced++;
+ }
+ }
+
+ this.selection.setSelectionRange(selection);
+
+ return replaced;
+ }
+
+ /**
+ * @param {import("../ace-internal").Ace.IRange} range
+ * @param {string} [replacement]
+ */
+ $tryReplace(range, replacement) {
+ var input = this.session.getTextRange(range);
+ replacement = this.$search.replace(input, replacement);
+ if (replacement !== null) {
+ range.end = this.session.replace(range, replacement);
+ return range;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * {:Search.getOptions} For more information on `options`, see [[Search `Search`]].
+ * @related Search.getOptions
+ * @returns {Partial}
+ **/
+ getLastSearchOptions() {
+ return this.$search.getOptions();
+ }
+
+ /**
+ * Attempts to find `needle` within the document. For more information on `options`, see [[Search `Search`]].
+ * @param {String|RegExp|Object} needle The text to search for (optional)
+ * @param {Partial} [options] An object defining various search properties
+ * @param {Boolean} [animate] If `true` animate scrolling
+ * @related Search.find
+ **/
+ find(needle, options, animate) {
+ if (!options)
+ options = {};
+
+ if (typeof needle == "string" || needle instanceof RegExp)
+ options.needle = needle;
+ else if (typeof needle == "object")
+ oop.mixin(options, needle);
+
+ var range = this.selection.getRange();
+ if (options.needle == null) {
+ needle = this.session.getTextRange(range)
+ || this.$search.$options.needle;
+ if (!needle) {
+ range = this.session.getWordRange(range.start.row, range.start.column);
+ needle = this.session.getTextRange(range);
+ }
+ this.$search.set({needle: needle});
+ }
+
+ this.$search.set(options);
+ if (!options.start)
+ this.$search.set({start: range});
+
+ var newRange = this.$search.find(this.session);
+ if (options.preventScroll)
+ return newRange;
+ if (newRange) {
+ this.revealRange(newRange, animate);
+ return newRange;
+ }
+ // clear selection if nothing is found
+ if (options.backwards)
+ range.start = range.end;
+ else
+ range.end = range.start;
+ this.selection.setRange(range);
+ }
+
+ /**
+ * Performs another search for `needle` in the document. For more information on `options`, see [[Search `Search`]].
+ * @param {Partial} [options] search options
+ * @param {Boolean} [animate] If `true` animate scrolling
+ *
+ * @related Editor.find
+ **/
+ findNext(options, animate) {
+ this.find({skipCurrent: true, backwards: false}, options, animate);
+ }
+
+ /**
+ * Performs a search for `needle` backwards. For more information on `options`, see [[Search `Search`]].
+ * @param {Partial} [options] search options
+ * @param {Boolean} [animate] If `true` animate scrolling
+ *
+ * @related Editor.find
+ **/
+ findPrevious(options, animate) {
+ this.find(options, {skipCurrent: true, backwards: true}, animate);
+ }
+
+ /**
+ *
+ * @param {Range} range
+ * @param {boolean} [animate]
+ */
+ revealRange(range, animate) {
+ this.session.unfold(range);
+ this.selection.setSelectionRange(range);
+
+ var scrollTop = this.renderer.scrollTop;
+ this.renderer.scrollSelectionIntoView(range.start, range.end, 0.5);
+ if (animate !== false)
+ this.renderer.animateScrolling(scrollTop);
+ }
+
+ /**
+ * {:UndoManager.undo}
+ * @related UndoManager.undo
+ **/
+ undo() {
+ this.session.getUndoManager().undo(this.session);
+ this.renderer.scrollCursorIntoView(null, 0.5);
+ }
+
+ /**
+ * {:UndoManager.redo}
+ * @related UndoManager.redo
+ **/
+ redo() {
+ this.session.getUndoManager().redo(this.session);
+ this.renderer.scrollCursorIntoView(null, 0.5);
+ }
+
+ /**
+ *
+ * Cleans up the entire editor.
+ **/
+ destroy() {
+ if (this.$toDestroy) {
+ this.$toDestroy.forEach(function(el) {
+ el.destroy();
+ });
+ this.$toDestroy = null;
+ }
+ if (this.$mouseHandler)
+ this.$mouseHandler.destroy();
+ this.renderer.destroy();
+ this._signal("destroy", this);
+ if (this.session)
+ this.session.destroy();
+ if (this._$emitInputEvent)
+ this._$emitInputEvent.cancel();
+ this.removeAllListeners();
+
+ }
+
+ /**
+ * Enables automatic scrolling of the cursor into view when editor itself is inside scrollable element
+ * @param {Boolean} enable default true
+ **/
+ setAutoScrollEditorIntoView(enable) {
+ if (!enable)
+ return;
+ var rect;
+ var self = this;
+ var shouldScroll = false;
+ if (!this.$scrollAnchor)
+ this.$scrollAnchor = document.createElement("div");
+ var scrollAnchor = this.$scrollAnchor;
+ scrollAnchor.style.cssText = "position:absolute";
+ this.container.insertBefore(scrollAnchor, this.container.firstChild);
+ var onChangeSelection = this.on("changeSelection", function() {
+ shouldScroll = true;
+ });
+ // needed to not trigger sync reflow
+ var onBeforeRender = this.renderer.on("beforeRender", function() {
+ if (shouldScroll)
+ rect = self.renderer.container.getBoundingClientRect();
+ });
+ var onAfterRender = this.renderer.on("afterRender", function() {
+ if (shouldScroll && rect && (self.isFocused()
+ || self.searchBox && self.searchBox.isFocused())
+ ) {
+ var renderer = self.renderer;
+ var pos = renderer.$cursorLayer.$pixelPos;
+ var config = renderer.layerConfig;
+ var top = pos.top - config.offset;
+ if (pos.top >= 0 && top + rect.top < 0) {
+ shouldScroll = true;
+ } else if (pos.top < config.height &&
+ pos.top + rect.top + config.lineHeight > window.innerHeight) {
+ shouldScroll = false;
+ } else {
+ shouldScroll = null;
+ }
+ if (shouldScroll != null) {
+ scrollAnchor.style.top = top + "px";
+ scrollAnchor.style.left = pos.left + "px";
+ scrollAnchor.style.height = config.lineHeight + "px";
+ scrollAnchor.scrollIntoView(shouldScroll);
+ }
+ shouldScroll = rect = null;
+ }
+ });
+ this.setAutoScrollEditorIntoView = function(enable) {
+ if (enable)
+ return;
+ delete this.setAutoScrollEditorIntoView;
+ this.off("changeSelection", onChangeSelection);
+ this.renderer.off("afterRender", onAfterRender);
+ this.renderer.off("beforeRender", onBeforeRender);
+ };
+ }
+
+ $resetCursorStyle() {
+ var style = this.$cursorStyle || "ace";
+ var cursorLayer = this.renderer.$cursorLayer;
+ if (!cursorLayer)
+ return;
+ cursorLayer.setSmoothBlinking(/smooth/.test(style));
+ cursorLayer.isBlinking = !this.$readOnly && style != "wide";
+ dom.setCssClass(cursorLayer.element, "ace_slim-cursors", /slim/.test(style));
+ }
+
+ /**
+ * opens a prompt displaying message
+ **/
+ prompt(message, options, callback) {
+ var editor = this;
+ config.loadModule("ace/ext/prompt", function (module) {
+ module.prompt(editor, message, options, callback);
+ });
+ }
+
+}
+
+Editor.$uid = 0;
+Editor.prototype.curOp = null;
+Editor.prototype.prevOp = {};
+// TODO use property on commands instead of this
+Editor.prototype.$mergeableCommands = ["backspace", "del", "insertstring"];
+Editor.prototype.$toggleWordPairs = [
+ ["first", "last"],
+ ["true", "false"],
+ ["yes", "no"],
+ ["width", "height"],
+ ["top", "bottom"],
+ ["right", "left"],
+ ["on", "off"],
+ ["x", "y"],
+ ["get", "set"],
+ ["max", "min"],
+ ["horizontal", "vertical"],
+ ["show", "hide"],
+ ["add", "remove"],
+ ["up", "down"],
+ ["before", "after"],
+ ["even", "odd"],
+ ["in", "out"],
+ ["inside", "outside"],
+ ["next", "previous"],
+ ["increase", "decrease"],
+ ["attach", "detach"],
+ ["&&", "||"],
+ ["==", "!="]
+];
+
+oop.implement(Editor.prototype, EventEmitter);
+
+
+config.defineOptions(Editor.prototype, "editor", {
+ selectionStyle: {
+ set: function(style) {
+ this.onSelectionChange();
+ this._signal("changeSelectionStyle", {data: style});
+ },
+ initialValue: "line"
+ },
+ highlightActiveLine: {
+ set: function() {this.$updateHighlightActiveLine();},
+ initialValue: true
+ },
+ highlightSelectedWord: {
+ set: function(shouldHighlight) {this.$onSelectionChange();},
+ initialValue: true
+ },
+ readOnly: {
+ set: function(/**@type{boolean}*/readOnly) {
+ this.textInput.setReadOnly(readOnly);
+ this.$resetCursorStyle();
+ if (!this.$readOnlyCallback) {
+ this.$readOnlyCallback = (e) => {
+ var shouldShow = false;
+ if (e && e.type == "keydown") {
+ shouldShow = e && e.key && e.key.length == 1 && !e.ctrlKey && !e.metaKey;
+ if (!shouldShow) return;
+ } else if (e && e.type !== "exec") {
+ shouldShow = true;
+ }
+ if (shouldShow) {
+ if (!this.hoverTooltip) {
+ this.hoverTooltip = new HoverTooltip();
+ }
+ var domNode = dom.createElement("div");
+ domNode.textContent = nls("editor.tooltip.disable-editing", "Editing is disabled");
+ if (!this.hoverTooltip.isOpen) {
+ this.hoverTooltip.showForRange(this, this.getSelectionRange(), domNode);
+ }
+ } else if (this.hoverTooltip && this.hoverTooltip.isOpen) {
+ this.hoverTooltip.hide();
+ }
+ };
+ }
+ var textArea = this.textInput.getElement();
+ if (readOnly) {
+ event.addListener(textArea, "keydown", this.$readOnlyCallback, this);
+ this.commands.on("exec", this.$readOnlyCallback);
+ this.commands.on("commandUnavailable", this.$readOnlyCallback);
+ } else {
+ event.removeListener(textArea, "keydown", this.$readOnlyCallback);
+ this.commands.off("exec", this.$readOnlyCallback);
+ this.commands.off("commandUnavailable", this.$readOnlyCallback);
+ if (this.hoverTooltip) {
+ this.hoverTooltip.destroy();
+ this.hoverTooltip = null;
+ }
+ }
+ },
+ initialValue: false
+ },
+ copyWithEmptySelection: {
+ set: function(value) {
+ this.textInput.setCopyWithEmptySelection(value);
+ },
+ initialValue: false
+ },
+ cursorStyle: {
+ set: function(val) { this.$resetCursorStyle(); },
+ values: ["ace", "slim", "smooth", "wide"],
+ initialValue: "ace"
+ },
+ mergeUndoDeltas: {
+ values: [false, true, "always"],
+ initialValue: true
+ },
+ behavioursEnabled: {initialValue: true},
+ wrapBehavioursEnabled: {initialValue: true},
+ enableAutoIndent: {initialValue: true},
+ autoScrollEditorIntoView: {
+ set: function(val) {this.setAutoScrollEditorIntoView(val);}
+ },
+ keyboardHandler: {
+ set: function(val) { this.setKeyboardHandler(val); },
+ get: function() { return this.$keybindingId; },
+ handlesSet: true
+ },
+ value: {
+ set: function(val) { this.session.setValue(val); },
+ get: function() { return this.getValue(); },
+ handlesSet: true,
+ hidden: true
+ },
+ session: {
+ set: function(val) { this.setSession(val); },
+ get: function() { return this.session; },
+ handlesSet: true,
+ hidden: true
+ },
+
+ showLineNumbers: {
+ set: function(show) {
+ this.renderer.$gutterLayer.setShowLineNumbers(show);
+ this.renderer.$loop.schedule(this.renderer.CHANGE_GUTTER);
+ if (show && this.$relativeLineNumbers)
+ relativeNumberRenderer.attach(this);
+ else
+ relativeNumberRenderer.detach(this);
+ },
+ initialValue: true
+ },
+ relativeLineNumbers: {
+ set: function(value) {
+ if (this.$showLineNumbers && value)
+ relativeNumberRenderer.attach(this);
+ else
+ relativeNumberRenderer.detach(this);
+ }
+ },
+ placeholder: {
+ /**
+ * @param message
+ */
+ set: function(message) {
+ if (!this.$updatePlaceholder) {
+ this.$updatePlaceholder = function() {
+ var hasValue = this.session && (this.renderer.$composition ||
+ this.session.getLength() > 1 || this.session.getLine(0).length > 0);
+ if (hasValue && this.renderer.placeholderNode) {
+ this.renderer.off("afterRender", this.$updatePlaceholder);
+ dom.removeCssClass(this.container, "ace_hasPlaceholder");
+ this.renderer.placeholderNode.remove();
+ this.renderer.placeholderNode = null;
+ } else if (!hasValue && !this.renderer.placeholderNode) {
+ this.renderer.on("afterRender", this.$updatePlaceholder);
+ dom.addCssClass(this.container, "ace_hasPlaceholder");
+ var el = dom.createElement("div");
+ el.className = "ace_placeholder";
+ el.textContent = this.$placeholder || "";
+ this.renderer.placeholderNode = el;
+ this.renderer.content.appendChild(this.renderer.placeholderNode);
+ } else if (!hasValue && this.renderer.placeholderNode) {
+ this.renderer.placeholderNode.textContent = this.$placeholder || "";
+ }
+ }.bind(this);
+ // @ts-ignore
+ this.on("input", this.$updatePlaceholder);
+ }
+ this.$updatePlaceholder();
+ }
+ },
+ enableKeyboardAccessibility: {
+ set: function(value) {
+ var blurCommand = {
+ name: "blurTextInput",
+ description: "Set focus to the editor content div to allow tabbing through the page",
+ bindKey: "Esc",
+ exec: function(editor) {
+ editor.blur();
+ editor.renderer.scroller.focus();
+ },
+ readOnly: true
+ };
+
+ var focusOnEnterKeyup = function (e) {
+ if (e.target == this.renderer.scroller && e.keyCode === keys['enter']){
+ e.preventDefault();
+ var row = this.getCursorPosition().row;
+
+ if (!this.isRowVisible(row))
+ this.scrollToLine(row, true, true);
+
+ this.focus();
+ }
+ };
+ /**@type {GutterKeyboardHandler}*/
+ var gutterKeyboardHandler;
+
+ // If keyboard a11y mode is enabled we:
+ // - Enable keyboard operability gutter.
+ // - Prevent tab-trapping.
+ // - Hide irrelevant elements from assistive technology.
+ // - On Windows, set more lines to the textarea.
+ // - set aria-label to the text input.
+ if (value){
+ this.renderer.enableKeyboardAccessibility = true;
+ this.renderer.keyboardFocusClassName = "ace_keyboard-focus";
+
+ this.textInput.getElement().setAttribute("tabindex", -1);
+ // VoiceOver on Mac OS works best with single line in the textarea, the screen readers on
+ // Windows work best with multiple lines in the textarea.
+ this.textInput.setNumberOfExtraLines(useragent.isWin ? 3 : 0);
+ this.renderer.scroller.setAttribute("tabindex", 0);
+ this.renderer.scroller.setAttribute("role", "group");
+ this.renderer.scroller.setAttribute("aria-roledescription", nls("editor.scroller.aria-roledescription", "editor"));
+ this.renderer.scroller.classList.add(this.renderer.keyboardFocusClassName);
+ this.renderer.scroller.setAttribute("aria-label",
+ nls("editor.scroller.aria-label", "Editor content, press Enter to start editing, press Escape to exit")
+ );
+
+ this.renderer.scroller.addEventListener("keyup", focusOnEnterKeyup.bind(this));
+ this.commands.addCommand(blurCommand);
+
+ this.renderer.$gutter.setAttribute("tabindex", 0);
+ this.renderer.$gutter.setAttribute("aria-hidden", false);
+ this.renderer.$gutter.setAttribute("role", "group");
+ this.renderer.$gutter.setAttribute("aria-roledescription", nls("editor.gutter.aria-roledescription", "editor gutter"));
+ this.renderer.$gutter.setAttribute("aria-label",
+ nls("editor.gutter.aria-label", "Editor gutter, press Enter to interact with controls using arrow keys, press Escape to exit")
+ );
+ this.renderer.$gutter.classList.add(this.renderer.keyboardFocusClassName);
+
+ this.renderer.content.setAttribute("aria-hidden", true);
+
+ if (!gutterKeyboardHandler)
+ gutterKeyboardHandler = new GutterKeyboardHandler(this);
+
+ gutterKeyboardHandler.addListener();
+
+ this.textInput.setAriaOptions({
+ setLabel: true
+ });
+ } else {
+ this.renderer.enableKeyboardAccessibility = false;
+
+ this.textInput.getElement().setAttribute("tabindex", 0);
+ this.textInput.setNumberOfExtraLines(0);
+ this.renderer.scroller.setAttribute("tabindex", -1);
+ this.renderer.scroller.removeAttribute("role");
+ this.renderer.scroller.removeAttribute("aria-roledescription");
+ this.renderer.scroller.classList.remove(this.renderer.keyboardFocusClassName);
+ this.renderer.scroller.removeAttribute("aria-label");
+
+ this.renderer.scroller.removeEventListener("keyup", focusOnEnterKeyup.bind(this));
+ this.commands.removeCommand(blurCommand);
+
+ this.renderer.content.removeAttribute("aria-hidden");
+
+ this.renderer.$gutter.setAttribute("tabindex", -1);
+ this.renderer.$gutter.setAttribute("aria-hidden", true);
+ this.renderer.$gutter.removeAttribute("role");
+ this.renderer.$gutter.removeAttribute("aria-roledescription");
+ this.renderer.$gutter.removeAttribute("aria-label");
+ this.renderer.$gutter.classList.remove(this.renderer.keyboardFocusClassName);
+
+ if (gutterKeyboardHandler)
+ gutterKeyboardHandler.removeListener();
+ }
+ },
+ initialValue: false
+ },
+ textInputAriaLabel: {
+ set: function(val) { this.$textInputAriaLabel = val; },
+ initialValue: ""
+ },
+ enableMobileMenu: {
+ /**
+ * @param {boolean} val
+ */
+ set: function(val) { this.$enableMobileMenu = val; },
+ initialValue: true
+ },
+ customScrollbar: "renderer",
+ hScrollBarAlwaysVisible: "renderer",
+ vScrollBarAlwaysVisible: "renderer",
+ highlightGutterLine: "renderer",
+ animatedScroll: "renderer",
+ showInvisibles: "renderer",
+ showPrintMargin: "renderer",
+ printMarginColumn: "renderer",
+ printMargin: "renderer",
+ fadeFoldWidgets: "renderer",
+ showFoldWidgets: "renderer",
+ displayIndentGuides: "renderer",
+ highlightIndentGuides: "renderer",
+ showGutter: "renderer",
+ fontSize: "renderer",
+ fontFamily: "renderer",
+ maxLines: "renderer",
+ minLines: "renderer",
+ scrollPastEnd: "renderer",
+ fixedWidthGutter: "renderer",
+ theme: "renderer",
+ hasCssTransforms: "renderer",
+ maxPixelHeight: "renderer",
+ useTextareaForIME: "renderer",
+ useResizeObserver: "renderer",
+ useSvgGutterIcons: "renderer",
+ showFoldedAnnotations: "renderer",
+
+ scrollSpeed: "$mouseHandler",
+ dragDelay: "$mouseHandler",
+ dragEnabled: "$mouseHandler",
+ focusTimeout: "$mouseHandler",
+ tooltipFollowsMouse: "$mouseHandler",
+
+ firstLineNumber: "session",
+ overwrite: "session",
+ newLineMode: "session",
+ useWorker: "session",
+ useSoftTabs: "session",
+ navigateWithinSoftTabs: "session",
+ tabSize: "session",
+ wrap: "session",
+ indentedSoftWrap: "session",
+ foldStyle: "session",
+ mode: "session"
+});
+
+
+var relativeNumberRenderer = {
+ getText: function(/**@type{EditSession}*/session, /**@type{number}*/row) {
+ return (Math.abs(session.selection.lead.row - row) || (row + 1 + (row < 9 ? "\xb7" : ""))) + "";
+ },
+ getWidth: function(session, /**@type{number}*/lastLineNumber, config) {
+ return Math.max(
+ lastLineNumber.toString().length,
+ (config.lastRow + 1).toString().length,
+ 2
+ ) * config.characterWidth;
+ },
+ update: function(e, /**@type{Editor}*/editor) {
+ editor.renderer.$loop.schedule(editor.renderer.CHANGE_GUTTER);
+ },
+ attach: function(/**@type{Editor}*/editor) {
+ editor.renderer.$gutterLayer.$renderer = this;
+ editor.on("changeSelection", this.update);
+ this.update(null, editor);
+ },
+ detach: function(/**@type{Editor}*/editor) {
+ if (editor.renderer.$gutterLayer.$renderer == this)
+ editor.renderer.$gutterLayer.$renderer = null;
+ editor.off("changeSelection", this.update);
+ this.update(null, editor);
+ }
+};
+exports.Editor = Editor;
diff --git a/demo/diff/index.html b/demo/diff/index.html
new file mode 100644
index 00000000000..d582a713c6e
--- /dev/null
+++ b/demo/diff/index.html
@@ -0,0 +1,273 @@
+
+
+
+ Ace-diff - Simple Demo #1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/emmet.html b/demo/emmet.html
index bd0d4abed7a..2b159bfe9cf 100644
--- a/demo/emmet.html
+++ b/demo/emmet.html
@@ -23,12 +23,12 @@
-
+
+
+
+
+