Skip to content

Commit

Permalink
Move fix into getWordAtPosition
Browse files Browse the repository at this point in the history
  • Loading branch information
mjbvz committed Mar 16, 2018
1 parent 1714875 commit 4ebe821
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 17 deletions.
31 changes: 30 additions & 1 deletion src/vs/editor/common/model/textModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1746,6 +1746,7 @@ export class TextModel extends Disposable implements model.ITextModel {
const tokenIndex = lineTokens.findTokenIndexAtOffset(offset);
const languageId = lineTokens.getLanguageId(tokenIndex);

// First try checking right biased word
// go left until a different language is hit
let startOffset: number;
for (let i = tokenIndex; i >= 0 && lineTokens.getLanguageId(i) === languageId; i--) {
Expand All @@ -1758,12 +1759,40 @@ export class TextModel extends Disposable implements model.ITextModel {
endOffset = lineTokens.getEndOffset(i);
}

return getWordAtText(
const rightBiasedWord = getWordAtText(
position.column,
LanguageConfigurationRegistry.getWordDefinition(languageId),
lineContent.substring(startOffset, endOffset),
startOffset
);

if (rightBiasedWord) {
return rightBiasedWord;
}

if (tokenIndex === 0) {
return null;
}

// Else, if we were at a language boundary, check the left biased word
const cursorLang = lineTokens.getLanguageId(tokenIndex);
const preLang = lineTokens.getLanguageId(tokenIndex - 1);
if (cursorLang !== preLang && tokenIndex < lineTokens.getCount()) {
// go left until a different language is hit
let startOffset: number;
for (let i = tokenIndex - 1; i >= 0 && lineTokens.getLanguageId(i) === preLang; i--) {
startOffset = lineTokens.getStartOffset(i);
}

return getWordAtText(
position.column,
LanguageConfigurationRegistry.getWordDefinition(languageId),
lineContent.substring(startOffset, lineTokens.getEndOffset(tokenIndex)),
startOffset
);
}

return null;
}

public getWordUntilPosition(position: IPosition): model.IWordAtPosition {
Expand Down
13 changes: 1 addition & 12 deletions src/vs/editor/contrib/suggest/suggestModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,7 @@ export class LineContext {
const pos = editor.getPosition();
model.tokenizeIfCheap(pos.lineNumber);

// When we are at an embedded language boundary, check the word at the previous character
const cursorLang = model.getLanguageIdAtPosition(pos.lineNumber, pos.column);
const preLang = model.getLanguageIdAtPosition(pos.lineNumber, pos.column - 1);
let posToCheckWordAt = pos;
if (cursorLang !== preLang) {
const postLang = model.getLanguageIdAtPosition(pos.lineNumber, pos.column + 1);
if (postLang === cursorLang) {
posToCheckWordAt = new Position(pos.lineNumber, Math.max(pos.column - 1, 1));
}
}

const word = model.getWordAtPosition(posToCheckWordAt);
const word = model.getWordAtPosition(pos);
if (!word) {
return false;
}
Expand Down
75 changes: 71 additions & 4 deletions src/vs/editor/test/common/model/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import {
ModelRawLinesDeleted, ModelRawLinesInserted
} from 'vs/editor/common/model/textModelEvents';
import { TextModel } from 'vs/editor/common/model/textModel';
import { LanguageIdentifier, TokenizationRegistry, IState, MetadataConsts } from 'vs/editor/common/modes';
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { TokenizationResult2 } from 'vs/editor/common/core/token';
import { NULL_STATE } from 'vs/editor/common/modes/nullMode';
import { dispose, Disposable } from 'vs/base/common/lifecycle';

// --------- utils

Expand Down Expand Up @@ -365,18 +371,62 @@ suite('Editor Model - Model Line Separators', () => {

suite('Editor Model - Words', () => {

var thisModel: TextModel;
const OUTER_LANGUAGE_ID = new LanguageIdentifier('outerMode', 3);
const INNER_LANGUAGE_ID = new LanguageIdentifier('innerMode', 4);

class OuterMode extends MockMode {
constructor() {
super(OUTER_LANGUAGE_ID);
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {}));

this._register(TokenizationRegistry.register(this.getLanguageIdentifier().language, {
getInitialState: (): IState => NULL_STATE,
tokenize: undefined,
tokenize2: (line: string, state: IState): TokenizationResult2 => {
const tokensArr: number[] = [];
let prevLanguageId: LanguageIdentifier = undefined;
for (let i = 0; i < line.length; i++) {
const languageId = (line.charAt(i) === 'x' ? INNER_LANGUAGE_ID : OUTER_LANGUAGE_ID);
if (prevLanguageId !== languageId) {
tokensArr.push(i);
tokensArr.push((languageId.id << MetadataConsts.LANGUAGEID_OFFSET));
}
prevLanguageId = languageId;
}

const tokens = new Uint32Array(tokensArr.length);
for (let i = 0; i < tokens.length; i++) {
tokens[i] = tokensArr[i];
}
return new TokenizationResult2(tokens, state);
}
}));
}
}

class InnerMode extends MockMode {
constructor() {
super(INNER_LANGUAGE_ID);
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {}));
}
}

let disposables: Disposable[] = [];

setup(() => {
var text = ['This text has some words. '];
thisModel = TextModel.createFromString(text.join('\n'));
disposables = [];
});

teardown(() => {
thisModel.dispose();
dispose(disposables);
disposables = [];
});

test('Get word at position', () => {
const text = ['This text has some words. '];
const thisModel = TextModel.createFromString(text.join('\n'));
disposables.push(thisModel);

assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 1)), { word: 'This', startColumn: 1, endColumn: 5 });
assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 2)), { word: 'This', startColumn: 1, endColumn: 5 });
assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 4)), { word: 'This', startColumn: 1, endColumn: 5 });
Expand All @@ -389,4 +439,21 @@ suite('Editor Model - Words', () => {
assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 27)), null);
assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 28)), null);
});

test('getWordAtPosition at embedded language boundaries', () => {
const outerMode = new OuterMode();
const innerMode = new InnerMode();
disposables.push(outerMode, innerMode);

const model = TextModel.createFromString('ab<xx>ab<x>', undefined, outerMode.getLanguageIdentifier());
disposables.push(model);

assert.deepEqual(model.getWordAtPosition(new Position(1, 1)), { word: 'ab', startColumn: 1, endColumn: 3 });
assert.deepEqual(model.getWordAtPosition(new Position(1, 2)), { word: 'ab', startColumn: 1, endColumn: 3 });
assert.deepEqual(model.getWordAtPosition(new Position(1, 3)), { word: 'ab', startColumn: 1, endColumn: 3 });
assert.deepEqual(model.getWordAtPosition(new Position(1, 4)), { word: 'xx', startColumn: 4, endColumn: 6 });
assert.deepEqual(model.getWordAtPosition(new Position(1, 5)), { word: 'xx', startColumn: 4, endColumn: 6 });
assert.deepEqual(model.getWordAtPosition(new Position(1, 6)), { word: 'xx', startColumn: 4, endColumn: 6 });
assert.deepEqual(model.getWordAtPosition(new Position(1, 7)), { word: 'ab', startColumn: 7, endColumn: 9 });
});
});

0 comments on commit 4ebe821

Please sign in to comment.