UNPKG

monaco-editor-core

Version:

A browser based code editor

223 lines (222 loc) • 12.6 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as strings from '../../../base/common/strings.js'; import { Range } from '../../common/core/range.js'; export const _debugComposition = false; export class TextAreaState { static { this.EMPTY = new TextAreaState('', 0, 0, null, undefined); } constructor(value, /** the offset where selection starts inside `value` */ selectionStart, /** the offset where selection ends inside `value` */ selectionEnd, /** the editor range in the view coordinate system that matches the selection inside `value` */ selection, /** the visible line count (wrapped, not necessarily matching \n characters) for the text in `value` before `selectionStart` */ newlineCountBeforeSelection) { this.value = value; this.selectionStart = selectionStart; this.selectionEnd = selectionEnd; this.selection = selection; this.newlineCountBeforeSelection = newlineCountBeforeSelection; } toString() { return `[ <${this.value}>, selectionStart: ${this.selectionStart}, selectionEnd: ${this.selectionEnd}]`; } static readFromTextArea(textArea, previousState) { const value = textArea.getValue(); const selectionStart = textArea.getSelectionStart(); const selectionEnd = textArea.getSelectionEnd(); let newlineCountBeforeSelection = undefined; if (previousState) { const valueBeforeSelectionStart = value.substring(0, selectionStart); const previousValueBeforeSelectionStart = previousState.value.substring(0, previousState.selectionStart); if (valueBeforeSelectionStart === previousValueBeforeSelectionStart) { newlineCountBeforeSelection = previousState.newlineCountBeforeSelection; } } return new TextAreaState(value, selectionStart, selectionEnd, null, newlineCountBeforeSelection); } collapseSelection() { if (this.selectionStart === this.value.length) { return this; } return new TextAreaState(this.value, this.value.length, this.value.length, null, undefined); } writeToTextArea(reason, textArea, select) { if (_debugComposition) { console.log(`writeToTextArea ${reason}: ${this.toString()}`); } textArea.setValue(reason, this.value); if (select) { textArea.setSelectionRange(reason, this.selectionStart, this.selectionEnd); } } deduceEditorPosition(offset) { if (offset <= this.selectionStart) { const str = this.value.substring(offset, this.selectionStart); return this._finishDeduceEditorPosition(this.selection?.getStartPosition() ?? null, str, -1); } if (offset >= this.selectionEnd) { const str = this.value.substring(this.selectionEnd, offset); return this._finishDeduceEditorPosition(this.selection?.getEndPosition() ?? null, str, 1); } const str1 = this.value.substring(this.selectionStart, offset); if (str1.indexOf(String.fromCharCode(8230)) === -1) { return this._finishDeduceEditorPosition(this.selection?.getStartPosition() ?? null, str1, 1); } const str2 = this.value.substring(offset, this.selectionEnd); return this._finishDeduceEditorPosition(this.selection?.getEndPosition() ?? null, str2, -1); } _finishDeduceEditorPosition(anchor, deltaText, signum) { let lineFeedCnt = 0; let lastLineFeedIndex = -1; while ((lastLineFeedIndex = deltaText.indexOf('\n', lastLineFeedIndex + 1)) !== -1) { lineFeedCnt++; } return [anchor, signum * deltaText.length, lineFeedCnt]; } static deduceInput(previousState, currentState, couldBeEmojiInput) { if (!previousState) { // This is the EMPTY state return { text: '', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }; } if (_debugComposition) { console.log('------------------------deduceInput'); console.log(`PREVIOUS STATE: ${previousState.toString()}`); console.log(`CURRENT STATE: ${currentState.toString()}`); } const prefixLength = Math.min(strings.commonPrefixLength(previousState.value, currentState.value), previousState.selectionStart, currentState.selectionStart); const suffixLength = Math.min(strings.commonSuffixLength(previousState.value, currentState.value), previousState.value.length - previousState.selectionEnd, currentState.value.length - currentState.selectionEnd); const previousValue = previousState.value.substring(prefixLength, previousState.value.length - suffixLength); const currentValue = currentState.value.substring(prefixLength, currentState.value.length - suffixLength); const previousSelectionStart = previousState.selectionStart - prefixLength; const previousSelectionEnd = previousState.selectionEnd - prefixLength; const currentSelectionStart = currentState.selectionStart - prefixLength; const currentSelectionEnd = currentState.selectionEnd - prefixLength; if (_debugComposition) { console.log(`AFTER DIFFING PREVIOUS STATE: <${previousValue}>, selectionStart: ${previousSelectionStart}, selectionEnd: ${previousSelectionEnd}`); console.log(`AFTER DIFFING CURRENT STATE: <${currentValue}>, selectionStart: ${currentSelectionStart}, selectionEnd: ${currentSelectionEnd}`); } if (currentSelectionStart === currentSelectionEnd) { // no current selection const replacePreviousCharacters = (previousState.selectionStart - prefixLength); if (_debugComposition) { console.log(`REMOVE PREVIOUS: ${replacePreviousCharacters} chars`); } return { text: currentValue, replacePrevCharCnt: replacePreviousCharacters, replaceNextCharCnt: 0, positionDelta: 0 }; } // there is a current selection => composition case const replacePreviousCharacters = previousSelectionEnd - previousSelectionStart; return { text: currentValue, replacePrevCharCnt: replacePreviousCharacters, replaceNextCharCnt: 0, positionDelta: 0 }; } static deduceAndroidCompositionInput(previousState, currentState) { if (!previousState) { // This is the EMPTY state return { text: '', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }; } if (_debugComposition) { console.log('------------------------deduceAndroidCompositionInput'); console.log(`PREVIOUS STATE: ${previousState.toString()}`); console.log(`CURRENT STATE: ${currentState.toString()}`); } if (previousState.value === currentState.value) { return { text: '', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: currentState.selectionEnd - previousState.selectionEnd }; } const prefixLength = Math.min(strings.commonPrefixLength(previousState.value, currentState.value), previousState.selectionEnd); const suffixLength = Math.min(strings.commonSuffixLength(previousState.value, currentState.value), previousState.value.length - previousState.selectionEnd); const previousValue = previousState.value.substring(prefixLength, previousState.value.length - suffixLength); const currentValue = currentState.value.substring(prefixLength, currentState.value.length - suffixLength); const previousSelectionStart = previousState.selectionStart - prefixLength; const previousSelectionEnd = previousState.selectionEnd - prefixLength; const currentSelectionStart = currentState.selectionStart - prefixLength; const currentSelectionEnd = currentState.selectionEnd - prefixLength; if (_debugComposition) { console.log(`AFTER DIFFING PREVIOUS STATE: <${previousValue}>, selectionStart: ${previousSelectionStart}, selectionEnd: ${previousSelectionEnd}`); console.log(`AFTER DIFFING CURRENT STATE: <${currentValue}>, selectionStart: ${currentSelectionStart}, selectionEnd: ${currentSelectionEnd}`); } return { text: currentValue, replacePrevCharCnt: previousSelectionEnd, replaceNextCharCnt: previousValue.length - previousSelectionEnd, positionDelta: currentSelectionEnd - currentValue.length }; } } export class PagedScreenReaderStrategy { static _getPageOfLine(lineNumber, linesPerPage) { return Math.floor((lineNumber - 1) / linesPerPage); } static _getRangeForPage(page, linesPerPage) { const offset = page * linesPerPage; const startLineNumber = offset + 1; const endLineNumber = offset + linesPerPage; return new Range(startLineNumber, 1, endLineNumber + 1, 1); } static fromEditorSelection(model, selection, linesPerPage, trimLongText) { // Chromium handles very poorly text even of a few thousand chars // Cut text to avoid stalling the entire UI const LIMIT_CHARS = 500; const selectionStartPage = PagedScreenReaderStrategy._getPageOfLine(selection.startLineNumber, linesPerPage); const selectionStartPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionStartPage, linesPerPage); const selectionEndPage = PagedScreenReaderStrategy._getPageOfLine(selection.endLineNumber, linesPerPage); const selectionEndPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionEndPage, linesPerPage); let pretextRange = selectionStartPageRange.intersectRanges(new Range(1, 1, selection.startLineNumber, selection.startColumn)); if (trimLongText && model.getValueLengthInRange(pretextRange, 1 /* EndOfLinePreference.LF */) > LIMIT_CHARS) { const pretextStart = model.modifyPosition(pretextRange.getEndPosition(), -LIMIT_CHARS); pretextRange = Range.fromPositions(pretextStart, pretextRange.getEndPosition()); } const pretext = model.getValueInRange(pretextRange, 1 /* EndOfLinePreference.LF */); const lastLine = model.getLineCount(); const lastLineMaxColumn = model.getLineMaxColumn(lastLine); let posttextRange = selectionEndPageRange.intersectRanges(new Range(selection.endLineNumber, selection.endColumn, lastLine, lastLineMaxColumn)); if (trimLongText && model.getValueLengthInRange(posttextRange, 1 /* EndOfLinePreference.LF */) > LIMIT_CHARS) { const posttextEnd = model.modifyPosition(posttextRange.getStartPosition(), LIMIT_CHARS); posttextRange = Range.fromPositions(posttextRange.getStartPosition(), posttextEnd); } const posttext = model.getValueInRange(posttextRange, 1 /* EndOfLinePreference.LF */); let text; if (selectionStartPage === selectionEndPage || selectionStartPage + 1 === selectionEndPage) { // take full selection text = model.getValueInRange(selection, 1 /* EndOfLinePreference.LF */); } else { const selectionRange1 = selectionStartPageRange.intersectRanges(selection); const selectionRange2 = selectionEndPageRange.intersectRanges(selection); text = (model.getValueInRange(selectionRange1, 1 /* EndOfLinePreference.LF */) + String.fromCharCode(8230) + model.getValueInRange(selectionRange2, 1 /* EndOfLinePreference.LF */)); } if (trimLongText && text.length > 2 * LIMIT_CHARS) { text = text.substring(0, LIMIT_CHARS) + String.fromCharCode(8230) + text.substring(text.length - LIMIT_CHARS, text.length); } return new TextAreaState(pretext + text + posttext, pretext.length, pretext.length + text.length, selection, pretextRange.endLineNumber - pretextRange.startLineNumber); } }