提交 19a881b7 编写于 作者: A Alex Dima

Fixes #20891: All cursors should do the same thing when typing

上级 c2eba7cc
......@@ -1399,7 +1399,11 @@ export class Cursor extends EventEmitter {
// Here we must interpret each typed character individually, that's why we create a new context
ctx.hasExecutedCommands = this._createAndInterpretHandlerCtx(ctx.eventSource, ctx.eventData, (charHandlerCtx: IMultipleCursorOperationContext) => {
this._applyEditForAll(charHandlerCtx, (cursor) => TypeOperations.typeWithInterceptors(cursor.config, cursor.model, cursor.modelState, chr));
// Decide what all cursors will do up-front
const cursors = this.cursors.getAll();
const states = cursors.map(cursor => cursor.modelState);
const editOperations = TypeOperations.typeWithInterceptors(cursors[0].config, cursors[0].model, states, chr);
this._applyEditForAll(charHandlerCtx, (cursor, cursorIndex) => editOperations[cursorIndex]);
// The last typed character gets to win
ctx.cursorPositionChangeReason = charHandlerCtx.cursorPositionChangeReason;
......
......@@ -273,37 +273,32 @@ export class TypeOperations {
});
}
private static _typeInterceptorEnter(config: CursorConfiguration, model: ITokenizedModel, cursor: SingleCursorState, ch: string): EditOperationResult {
if (ch !== '\n') {
return null;
}
return TypeOperations._enter(config, model, false, cursor.selection);
}
private static isAutoClosingCloseCharType(config: CursorConfiguration, model: ITokenizedModel, cursor: SingleCursorState, ch: string): boolean {
if (!config.autoClosingBrackets) {
private static _isAutoClosingCloseCharType(config: CursorConfiguration, model: ITokenizedModel, cursors: SingleCursorState[], ch: string): boolean {
if (!config.autoClosingBrackets || !config.autoClosingPairsClose.hasOwnProperty(ch)) {
return false;
}
const selection = cursor.selection;
const position = cursor.position;
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const selection = cursor.selection;
if (!selection.isEmpty() || !config.autoClosingPairsClose.hasOwnProperty(ch)) {
return false;
}
if (!selection.isEmpty()) {
return false;
}
const lineText = model.getLineContent(position.lineNumber);
const afterCharacter = lineText.charAt(position.column - 1);
const position = cursor.position;
const lineText = model.getLineContent(position.lineNumber);
const afterCharacter = lineText.charAt(position.column - 1);
if (afterCharacter !== ch) {
return false;
if (afterCharacter !== ch) {
return false;
}
}
return true;
}
private static runAutoClosingCloseCharType(config: CursorConfiguration, model: ITokenizedModel, cursor: SingleCursorState, ch: string): EditOperationResult {
private static _runAutoClosingCloseCharType(config: CursorConfiguration, model: ITokenizedModel, cursor: SingleCursorState, ch: string): EditOperationResult {
const position = cursor.position;
const typeSelection = new Range(position.lineNumber, position.column, position.lineNumber, position.column + 1);
return new EditOperationResult(new ReplaceCommand(typeSelection, ch), {
......@@ -312,55 +307,61 @@ export class TypeOperations {
});
}
private static isAutoClosingOpenCharType(config: CursorConfiguration, model: ITokenizedModel, cursor: SingleCursorState, ch: string): boolean {
if (!config.autoClosingBrackets) {
return false;
}
const selection = cursor.selection;
const position = cursor.position;
if (!selection.isEmpty() || !config.autoClosingPairsOpen.hasOwnProperty(ch)) {
private static _isAutoClosingOpenCharType(config: CursorConfiguration, model: ITokenizedModel, cursors: SingleCursorState[], ch: string): boolean {
if (!config.autoClosingBrackets || !config.autoClosingPairsOpen.hasOwnProperty(ch)) {
return false;
}
const lineText = model.getLineContent(position.lineNumber);
const afterCharacter = lineText.charAt(position.column - 1);
// Only consider auto closing the pair if a space follows or if another autoclosed pair follows
if (afterCharacter) {
const thisBraceIsSymmetric = (config.autoClosingPairsOpen[ch] === ch);
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const selection = cursor.selection;
if (!selection.isEmpty()) {
return false;
}
let isBeforeCloseBrace = false;
for (let otherCloseBrace in config.autoClosingPairsClose) {
const otherBraceIsSymmetric = (config.autoClosingPairsOpen[otherCloseBrace] === otherCloseBrace);
if (!thisBraceIsSymmetric && otherBraceIsSymmetric) {
continue;
const position = cursor.position;
const lineText = model.getLineContent(position.lineNumber);
const afterCharacter = lineText.charAt(position.column - 1);
// Only consider auto closing the pair if a space follows or if another autoclosed pair follows
if (afterCharacter) {
const thisBraceIsSymmetric = (config.autoClosingPairsOpen[ch] === ch);
let isBeforeCloseBrace = false;
for (let otherCloseBrace in config.autoClosingPairsClose) {
const otherBraceIsSymmetric = (config.autoClosingPairsOpen[otherCloseBrace] === otherCloseBrace);
if (!thisBraceIsSymmetric && otherBraceIsSymmetric) {
continue;
}
if (afterCharacter === otherCloseBrace) {
isBeforeCloseBrace = true;
break;
}
}
if (afterCharacter === otherCloseBrace) {
isBeforeCloseBrace = true;
break;
if (!isBeforeCloseBrace && !/\s/.test(afterCharacter)) {
return false;
}
}
if (!isBeforeCloseBrace && !/\s/.test(afterCharacter)) {
return false;
}
}
model.forceTokenization(position.lineNumber);
const lineTokens = model.getLineTokens(position.lineNumber);
model.forceTokenization(position.lineNumber);
const lineTokens = model.getLineTokens(position.lineNumber);
let shouldAutoClosePair = false;
try {
shouldAutoClosePair = LanguageConfigurationRegistry.shouldAutoClosePair(ch, lineTokens, position.column);
} catch (e) {
onUnexpectedError(e);
let shouldAutoClosePair = false;
try {
shouldAutoClosePair = LanguageConfigurationRegistry.shouldAutoClosePair(ch, lineTokens, position.column);
} catch (e) {
onUnexpectedError(e);
}
if (!shouldAutoClosePair) {
return false;
}
}
return shouldAutoClosePair;
return true;
}
private static runAutoClosingOpenCharType(config: CursorConfiguration, model: ITokenizedModel, cursor: SingleCursorState, ch: string): EditOperationResult {
private static _runAutoClosingOpenCharType(config: CursorConfiguration, model: ITokenizedModel, cursor: SingleCursorState, ch: string): EditOperationResult {
const selection = cursor.selection;
const closeCharacter = config.autoClosingPairsOpen[ch];
return new EditOperationResult(new ReplaceCommandWithOffsetCursorState(selection, ch + closeCharacter, 0, -closeCharacter.length), {
......@@ -369,35 +370,42 @@ export class TypeOperations {
});
}
private static isSurroundSelectionType(config: CursorConfiguration, model: ITokenizedModel, cursor: SingleCursorState, ch: string): boolean {
if (!config.autoClosingBrackets) {
private static _isSurroundSelectionType(config: CursorConfiguration, model: ITokenizedModel, cursors: SingleCursorState[], ch: string): boolean {
if (!config.autoClosingBrackets || !config.surroundingPairs.hasOwnProperty(ch)) {
return false;
}
const selection = cursor.selection;
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const selection = cursor.selection;
if (selection.isEmpty() || !config.surroundingPairs.hasOwnProperty(ch)) {
return false;
}
if (selection.isEmpty()) {
return false;
}
let selectionContainsOnlyWhitespace = true;
let selectionContainsOnlyWhitespace = true;
for (let lineNumber = selection.startLineNumber; lineNumber <= selection.endLineNumber; lineNumber++) {
const lineText = model.getLineContent(lineNumber);
const startIndex = (lineNumber === selection.startLineNumber ? selection.startColumn - 1 : 0);
const endIndex = (lineNumber === selection.endLineNumber ? selection.endColumn - 1 : lineText.length);
const selectedText = lineText.substring(startIndex, endIndex);
if (/[^ \t]/.test(selectedText)) {
// this selected text contains something other than whitespace
selectionContainsOnlyWhitespace = false;
break;
for (let lineNumber = selection.startLineNumber; lineNumber <= selection.endLineNumber; lineNumber++) {
const lineText = model.getLineContent(lineNumber);
const startIndex = (lineNumber === selection.startLineNumber ? selection.startColumn - 1 : 0);
const endIndex = (lineNumber === selection.endLineNumber ? selection.endColumn - 1 : lineText.length);
const selectedText = lineText.substring(startIndex, endIndex);
if (/[^ \t]/.test(selectedText)) {
// this selected text contains something other than whitespace
selectionContainsOnlyWhitespace = false;
break;
}
}
if (selectionContainsOnlyWhitespace) {
return false;
}
}
return (!selectionContainsOnlyWhitespace);
return true;
}
private static runSurroundSelectionType(config: CursorConfiguration, model: ITokenizedModel, cursor: SingleCursorState, ch: string): EditOperationResult {
private static _runSurroundSelectionType(config: CursorConfiguration, model: ITokenizedModel, cursor: SingleCursorState, ch: string): EditOperationResult {
const selection = cursor.selection;
const closeCharacter = config.surroundingPairs[ch];
......@@ -468,24 +476,51 @@ export class TypeOperations {
return null;
}
public static typeWithInterceptors(config: CursorConfiguration, model: ITokenizedModel, cursor: SingleCursorState, ch: string): EditOperationResult {
let r: EditOperationResult = null;
public static typeWithInterceptors(config: CursorConfiguration, model: ITokenizedModel, cursors: SingleCursorState[], ch: string): EditOperationResult[] {
let r2: EditOperationResult[] = [];
r = r || this._typeInterceptorEnter(config, model, cursor, ch);
if (ch === '\n') {
for (let i = 0, len = cursors.length; i < len; i++) {
r2[i] = TypeOperations._enter(config, model, false, cursors[i].selection);
}
return r2;
}
if (this._isAutoClosingCloseCharType(config, model, cursors, ch)) {
for (let i = 0, len = cursors.length; i < len; i++) {
r2[i] = this._runAutoClosingCloseCharType(config, model, cursors[i], ch);
}
return r2;
}
if (!r && this.isAutoClosingCloseCharType(config, model, cursor, ch)) {
r = this.runAutoClosingCloseCharType(config, model, cursor, ch);
if (this._isAutoClosingOpenCharType(config, model, cursors, ch)) {
for (let i = 0, len = cursors.length; i < len; i++) {
r2[i] = this._runAutoClosingOpenCharType(config, model, cursors[i], ch);
}
return r2;
}
if (!r && this.isAutoClosingOpenCharType(config, model, cursor, ch)) {
r = this.runAutoClosingOpenCharType(config, model, cursor, ch);
if (this._isSurroundSelectionType(config, model, cursors, ch)) {
for (let i = 0, len = cursors.length; i < len; i++) {
r2[i] = this._runSurroundSelectionType(config, model, cursors[i], ch);
}
return r2;
}
if (!r && this.isSurroundSelectionType(config, model, cursor, ch)) {
r = this.runSurroundSelectionType(config, model, cursor, ch);
// Electric characters make sense only when dealing with a single cursor,
// as multiple cursors typing brackets for example would interfer with bracket matching
if (cursors.length === 1) {
const r = this._typeInterceptorElectricChar(config, model, cursors[0], ch);
if (r) {
r2[0] = r;
return r2;
}
}
r = r || this._typeInterceptorElectricChar(config, model, cursor, ch);
r = r || this.typeWithoutInterceptors(config, model, cursor, ch);
return r;
for (let i = 0, len = cursors.length; i < len; i++) {
r2[i] = this.typeWithoutInterceptors(config, model, cursors[i], ch);
}
return r2;
}
public static typeWithoutInterceptors(config: CursorConfiguration, model: ITokenizedModel, cursor: SingleCursorState, str: string): EditOperationResult {
......
......@@ -3627,4 +3627,26 @@ suite('autoClosingPairs', () => {
});
mode.dispose();
});
test('issue #20891: All cursors should do the same thing', () => {
let mode = new AutoClosingMode();
usingCursor({
text: [
'var a = asd'
],
languageIdentifier: mode.getLanguageIdentifier()
}, (model, cursor) => {
cursor.setSelections('test', [
new Selection(1, 9, 1, 9),
new Selection(1, 12, 1, 12),
]);
// type a `
cursorCommand(cursor, H.Type, { text: '`' }, 'keyboard');
assert.equal(model.getValue(), 'var a = `asd`');
});
mode.dispose();
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册