Merge branch 'master' into aeschli/ts-sem
This commit is contained in:
commit
fbad0c368e
|
@ -18,7 +18,7 @@ export default class TsConfigProvider {
|
|||
return [];
|
||||
}
|
||||
const configs = new Map<string, TSConfig>();
|
||||
for (const config of await vscode.workspace.findFiles('**/tsconfig*.json', '**/node_modules/**')) {
|
||||
for (const config of await vscode.workspace.findFiles('**/tsconfig*.json', '**/{node_modules,.*}/**')) {
|
||||
const root = vscode.workspace.getWorkspaceFolder(config);
|
||||
if (root) {
|
||||
configs.set(config.fsPath, {
|
||||
|
|
|
@ -13,6 +13,7 @@ const webviewId = 'myWebview';
|
|||
|
||||
const testDocument = join(vscode.workspace.rootPath || '', './bower.json');
|
||||
|
||||
// TODO: Re-enable after https://github.com/microsoft/vscode/issues/88415
|
||||
suite.skip('Webview tests', () => {
|
||||
const disposables: vscode.Disposable[] = [];
|
||||
|
||||
|
|
|
@ -57,12 +57,3 @@ export function toUint32(v: number): number {
|
|||
}
|
||||
return v | 0;
|
||||
}
|
||||
|
||||
export function toUint32Array(arr: number[]): Uint32Array {
|
||||
const len = arr.length;
|
||||
const r = new Uint32Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
r[i] = toUint32(arr[i]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
|
|
@ -191,7 +191,8 @@ export class CodeApplication extends Disposable {
|
|||
webPreferences.nodeIntegration = false;
|
||||
|
||||
// Verify URLs being loaded
|
||||
if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preload)) {
|
||||
// https://github.com/electron/electron/issues/21553
|
||||
if (isValidWebviewSource(params.src) && isValidWebviewSource((webPreferences as { preloadURL: string }).preloadURL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -136,14 +136,16 @@ export class IndentGuidesOverlay extends DynamicViewOverlay {
|
|||
const indent = indents[lineIndex];
|
||||
|
||||
let result = '';
|
||||
const leftMostVisiblePosition = ctx.visibleRangeForPosition(new Position(lineNumber, 1));
|
||||
let left = leftMostVisiblePosition ? leftMostVisiblePosition.left : 0;
|
||||
for (let i = 1; i <= indent; i++) {
|
||||
const className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr');
|
||||
result += `<div class="${className}" style="left:${left}px;height:${lineHeight}px;width:${indentWidth}px"></div>`;
|
||||
left += indentWidth;
|
||||
if (left > scrollWidth || (this._maxIndentLeft > 0 && left > this._maxIndentLeft)) {
|
||||
break;
|
||||
if (indent >= 1) {
|
||||
const leftMostVisiblePosition = ctx.visibleRangeForPosition(new Position(lineNumber, 1));
|
||||
let left = leftMostVisiblePosition ? leftMostVisiblePosition.left : 0;
|
||||
for (let i = 1; i <= indent; i++) {
|
||||
const className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr');
|
||||
result += `<div class="${className}" style="left:${left}px;height:${lineHeight}px;width:${indentWidth}px"></div>`;
|
||||
left += indentWidth;
|
||||
if (left > scrollWidth || (this._maxIndentLeft > 0 && left > this._maxIndentLeft)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -213,6 +213,7 @@ export class ViewLine implements IVisibleLine {
|
|||
lineData.tokens,
|
||||
actualInlineDecorations,
|
||||
lineData.tabSize,
|
||||
lineData.startVisibleColumn,
|
||||
options.spaceWidth,
|
||||
options.stopRenderingLineAfter,
|
||||
options.renderWhitespace,
|
||||
|
|
|
@ -50,6 +50,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
|||
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
|
||||
|
||||
let EDITOR_ID = 0;
|
||||
|
||||
|
@ -1329,7 +1330,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
|
|||
|
||||
model.onBeforeAttached();
|
||||
|
||||
const viewModel = new ViewModel(this._id, this._configuration, model, (callback) => dom.scheduleAtNextAnimationFrame(callback));
|
||||
const viewModel = new ViewModel(this._id, this._configuration, model, MonospaceLineBreaksComputerFactory.create(this._configuration.options), (callback) => dom.scheduleAtNextAnimationFrame(callback));
|
||||
|
||||
listenersToRemove.push(model.onDidChangeDecorations((e) => this._onDidChangeModelDecorations.fire(e)));
|
||||
listenersToRemove.push(model.onDidChangeLanguage((e) => {
|
||||
|
|
|
@ -2144,6 +2144,7 @@ class InlineViewZonesComputer extends ViewZonesComputer {
|
|||
lineTokens,
|
||||
actualDecorations,
|
||||
tabSize,
|
||||
0,
|
||||
fontInfo.spaceWidth,
|
||||
options.get(EditorOption.stopRenderingLineAfter),
|
||||
options.get(EditorOption.renderWhitespace),
|
||||
|
|
|
@ -780,6 +780,7 @@ export class DiffReview extends Disposable {
|
|||
lineTokens,
|
||||
[],
|
||||
tabSize,
|
||||
0,
|
||||
fontInfo.spaceWidth,
|
||||
options.get(EditorOption.stopRenderingLineAfter),
|
||||
options.get(EditorOption.renderWhitespace),
|
||||
|
|
|
@ -264,19 +264,14 @@ export interface IEditorOptions {
|
|||
wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent';
|
||||
/**
|
||||
* Configure word wrapping characters. A break will be introduced before these characters.
|
||||
* Defaults to '{([+'.
|
||||
* Defaults to '([{‘“〈《「『【〔([{「£¥$£¥++'.
|
||||
*/
|
||||
wordWrapBreakBeforeCharacters?: string;
|
||||
/**
|
||||
* Configure word wrapping characters. A break will be introduced after these characters.
|
||||
* Defaults to ' \t})]?|&,;'.
|
||||
* Defaults to ' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」'.
|
||||
*/
|
||||
wordWrapBreakAfterCharacters?: string;
|
||||
/**
|
||||
* Configure word wrapping characters. A break will be introduced after these characters only if no `wordWrapBreakBeforeCharacters` or `wordWrapBreakAfterCharacters` were found.
|
||||
* Defaults to '.'.
|
||||
*/
|
||||
wordWrapBreakObtrusiveCharacters?: string;
|
||||
/**
|
||||
* Performance guard: Stop rendering a line after x characters.
|
||||
* Defaults to 10000.
|
||||
|
@ -3154,7 +3149,6 @@ export const enum EditorOption {
|
|||
wordWrap,
|
||||
wordWrapBreakAfterCharacters,
|
||||
wordWrapBreakBeforeCharacters,
|
||||
wordWrapBreakObtrusiveCharacters,
|
||||
wordWrapColumn,
|
||||
wordWrapMinified,
|
||||
wrappingIndent,
|
||||
|
@ -3681,16 +3675,12 @@ export const EditorOptions = {
|
|||
)),
|
||||
wordWrapBreakAfterCharacters: register(new EditorStringOption(
|
||||
EditorOption.wordWrapBreakAfterCharacters, 'wordWrapBreakAfterCharacters',
|
||||
' \t})]?|/&,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」',
|
||||
' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」',
|
||||
)),
|
||||
wordWrapBreakBeforeCharacters: register(new EditorStringOption(
|
||||
EditorOption.wordWrapBreakBeforeCharacters, 'wordWrapBreakBeforeCharacters',
|
||||
'([{‘“〈《「『【〔([{「£¥$£¥++'
|
||||
)),
|
||||
wordWrapBreakObtrusiveCharacters: register(new EditorStringOption(
|
||||
EditorOption.wordWrapBreakObtrusiveCharacters, 'wordWrapBreakObtrusiveCharacters',
|
||||
'.'
|
||||
)),
|
||||
wordWrapColumn: register(new EditorIntOption(
|
||||
EditorOption.wordWrapColumn, 'wordWrapColumn',
|
||||
80, 1, Constants.MAX_SAFE_SMALL_INTEGER,
|
||||
|
|
|
@ -12,14 +12,14 @@ export class CharacterClassifier<T extends number> {
|
|||
/**
|
||||
* Maintain a compact (fully initialized ASCII map for quickly classifying ASCII characters - used more often in code).
|
||||
*/
|
||||
private _asciiMap: Uint8Array;
|
||||
protected _asciiMap: Uint8Array;
|
||||
|
||||
/**
|
||||
* The entire map (sparse array).
|
||||
*/
|
||||
private _map: Map<number, number>;
|
||||
protected _map: Map<number, number>;
|
||||
|
||||
private _defaultValue: number;
|
||||
protected _defaultValue: number;
|
||||
|
||||
constructor(_defaultValue: T) {
|
||||
let defaultValue = toUint8(_defaultValue);
|
||||
|
|
|
@ -511,7 +511,93 @@ export class PieceTreeBase {
|
|||
}
|
||||
|
||||
public getLinesContent(): string[] {
|
||||
return this.getContentOfSubTree(this.root).split(/\r\n|\r|\n/);
|
||||
let lines: string[] = [];
|
||||
let linesLength = 0;
|
||||
let currentLine = '';
|
||||
let danglingCR = false;
|
||||
|
||||
this.iterate(this.root, node => {
|
||||
if (node === SENTINEL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const piece = node.piece;
|
||||
let pieceLength = piece.length;
|
||||
if (pieceLength === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const buffer = this._buffers[piece.bufferIndex].buffer;
|
||||
const lineStarts = this._buffers[piece.bufferIndex].lineStarts;
|
||||
|
||||
const pieceStartLine = piece.start.line;
|
||||
const pieceEndLine = piece.end.line;
|
||||
let pieceStartOffset = lineStarts[pieceStartLine] + piece.start.column;
|
||||
|
||||
if (danglingCR) {
|
||||
if (buffer.charCodeAt(pieceStartOffset) === CharCode.LineFeed) {
|
||||
// pretend the \n was in the previous piece..
|
||||
pieceStartOffset++;
|
||||
pieceLength--;
|
||||
}
|
||||
lines[linesLength++] = currentLine;
|
||||
currentLine = '';
|
||||
danglingCR = false;
|
||||
if (pieceLength === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (pieceStartLine === pieceEndLine) {
|
||||
// this piece has no new lines
|
||||
if (!this._EOLNormalized && buffer.charCodeAt(pieceStartOffset + pieceLength - 1) === CharCode.CarriageReturn) {
|
||||
danglingCR = true;
|
||||
currentLine += buffer.substr(pieceStartOffset, pieceLength - 1);
|
||||
} else {
|
||||
currentLine += buffer.substr(pieceStartOffset, pieceLength);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// add the text before the first line start in this piece
|
||||
currentLine += (
|
||||
this._EOLNormalized
|
||||
? buffer.substring(pieceStartOffset, Math.max(pieceStartOffset, lineStarts[pieceStartLine + 1] - this._EOLLength))
|
||||
: buffer.substring(pieceStartOffset, lineStarts[pieceStartLine + 1]).replace(/(\r\n|\r|\n)$/, '')
|
||||
);
|
||||
lines[linesLength++] = currentLine;
|
||||
|
||||
for (let line = pieceStartLine + 1; line < pieceEndLine; line++) {
|
||||
currentLine = (
|
||||
this._EOLNormalized
|
||||
? buffer.substring(lineStarts[line], lineStarts[line + 1] - this._EOLLength)
|
||||
: buffer.substring(lineStarts[line], lineStarts[line + 1]).replace(/(\r\n|\r|\n)$/, '')
|
||||
);
|
||||
lines[linesLength++] = currentLine;
|
||||
}
|
||||
|
||||
if (!this._EOLNormalized && buffer.charCodeAt(lineStarts[pieceEndLine] + piece.end.column - 1) === CharCode.CarriageReturn) {
|
||||
danglingCR = true;
|
||||
if (piece.end.column === 0) {
|
||||
// The last line ended with a \r, let's undo the push, it will be pushed by next iteration
|
||||
linesLength--;
|
||||
} else {
|
||||
currentLine = buffer.substr(lineStarts[pieceEndLine], piece.end.column - 1);
|
||||
}
|
||||
} else {
|
||||
currentLine = buffer.substr(lineStarts[pieceEndLine], piece.end.column);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (danglingCR) {
|
||||
lines[linesLength++] = currentLine;
|
||||
currentLine = '';
|
||||
}
|
||||
|
||||
lines[linesLength++] = currentLine;
|
||||
return lines;
|
||||
}
|
||||
|
||||
public getLength(): number {
|
||||
|
@ -728,7 +814,7 @@ export class PieceTreeBase {
|
|||
// #endregion
|
||||
|
||||
// #region Piece Table
|
||||
insert(offset: number, value: string, eolNormalized: boolean = false): void {
|
||||
public insert(offset: number, value: string, eolNormalized: boolean = false): void {
|
||||
this._EOLNormalized = this._EOLNormalized && eolNormalized;
|
||||
this._lastVisitedLine.lineNumber = 0;
|
||||
this._lastVisitedLine.value = '';
|
||||
|
@ -826,7 +912,7 @@ export class PieceTreeBase {
|
|||
this.computeBufferMetadata();
|
||||
}
|
||||
|
||||
delete(offset: number, cnt: number): void {
|
||||
public delete(offset: number, cnt: number): void {
|
||||
this._lastVisitedLine.lineNumber = 0;
|
||||
this._lastVisitedLine.value = '';
|
||||
|
||||
|
@ -899,7 +985,7 @@ export class PieceTreeBase {
|
|||
this.computeBufferMetadata();
|
||||
}
|
||||
|
||||
insertContentToNodeLeft(value: string, node: TreeNode) {
|
||||
private insertContentToNodeLeft(value: string, node: TreeNode) {
|
||||
// we are inserting content to the beginning of node
|
||||
let nodesToDel: TreeNode[] = [];
|
||||
if (this.shouldCheckCRLF() && this.endWithCR(value) && this.startWithLF(node)) {
|
||||
|
@ -934,7 +1020,7 @@ export class PieceTreeBase {
|
|||
this.deleteNodes(nodesToDel);
|
||||
}
|
||||
|
||||
insertContentToNodeRight(value: string, node: TreeNode) {
|
||||
private insertContentToNodeRight(value: string, node: TreeNode) {
|
||||
// we are inserting to the right of this node.
|
||||
if (this.adjustCarriageReturnFromNext(value, node)) {
|
||||
// move \n to the new node.
|
||||
|
@ -952,9 +1038,9 @@ export class PieceTreeBase {
|
|||
this.validateCRLFWithPrevNode(newNode);
|
||||
}
|
||||
|
||||
positionInBuffer(node: TreeNode, remainder: number): BufferCursor;
|
||||
positionInBuffer(node: TreeNode, remainder: number, ret: BufferCursor): null;
|
||||
positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor | null {
|
||||
private positionInBuffer(node: TreeNode, remainder: number): BufferCursor;
|
||||
private positionInBuffer(node: TreeNode, remainder: number, ret: BufferCursor): null;
|
||||
private positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor | null {
|
||||
let piece = node.piece;
|
||||
let bufferIndex = node.piece.bufferIndex;
|
||||
let lineStarts = this._buffers[bufferIndex].lineStarts;
|
||||
|
@ -1002,7 +1088,7 @@ export class PieceTreeBase {
|
|||
};
|
||||
}
|
||||
|
||||
getLineFeedCnt(bufferIndex: number, start: BufferCursor, end: BufferCursor): number {
|
||||
private getLineFeedCnt(bufferIndex: number, start: BufferCursor, end: BufferCursor): number {
|
||||
// we don't need to worry about start: abc\r|\n, or abc|\r, or abc|\n, or abc|\r\n doesn't change the fact that, there is one line break after start.
|
||||
// now let's take care of end: abc\r|\n, if end is in between \r and \n, we need to add line feed count by 1
|
||||
if (end.column === 0) {
|
||||
|
@ -1032,18 +1118,18 @@ export class PieceTreeBase {
|
|||
}
|
||||
}
|
||||
|
||||
offsetInBuffer(bufferIndex: number, cursor: BufferCursor): number {
|
||||
private offsetInBuffer(bufferIndex: number, cursor: BufferCursor): number {
|
||||
let lineStarts = this._buffers[bufferIndex].lineStarts;
|
||||
return lineStarts[cursor.line] + cursor.column;
|
||||
}
|
||||
|
||||
deleteNodes(nodes: TreeNode[]): void {
|
||||
private deleteNodes(nodes: TreeNode[]): void {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
rbDelete(this, nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
createNewPieces(text: string): Piece[] {
|
||||
private createNewPieces(text: string): Piece[] {
|
||||
if (text.length > AverageBufferSize) {
|
||||
// the content is large, operations like substring, charCode becomes slow
|
||||
// so here we split it into smaller chunks, just like what we did for CR/LF normalization
|
||||
|
@ -1128,11 +1214,11 @@ export class PieceTreeBase {
|
|||
return [newPiece];
|
||||
}
|
||||
|
||||
getLinesRawContent(): string {
|
||||
public getLinesRawContent(): string {
|
||||
return this.getContentOfSubTree(this.root);
|
||||
}
|
||||
|
||||
getLineRawContent(lineNumber: number, endOffset: number = 0): string {
|
||||
public getLineRawContent(lineNumber: number, endOffset: number = 0): string {
|
||||
let x = this.root;
|
||||
|
||||
let ret = '';
|
||||
|
@ -1204,7 +1290,7 @@ export class PieceTreeBase {
|
|||
return ret;
|
||||
}
|
||||
|
||||
computeBufferMetadata() {
|
||||
private computeBufferMetadata() {
|
||||
let x = this.root;
|
||||
|
||||
let lfCnt = 1;
|
||||
|
@ -1222,7 +1308,7 @@ export class PieceTreeBase {
|
|||
}
|
||||
|
||||
// #region node operations
|
||||
getIndexOf(node: TreeNode, accumulatedValue: number): { index: number, remainder: number } {
|
||||
private getIndexOf(node: TreeNode, accumulatedValue: number): { index: number, remainder: number } {
|
||||
let piece = node.piece;
|
||||
let pos = this.positionInBuffer(node, accumulatedValue);
|
||||
let lineCnt = pos.line - piece.start.line;
|
||||
|
@ -1239,7 +1325,7 @@ export class PieceTreeBase {
|
|||
return { index: lineCnt, remainder: pos.column };
|
||||
}
|
||||
|
||||
getAccumulatedValue(node: TreeNode, index: number) {
|
||||
private getAccumulatedValue(node: TreeNode, index: number) {
|
||||
if (index < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -1253,7 +1339,7 @@ export class PieceTreeBase {
|
|||
}
|
||||
}
|
||||
|
||||
deleteNodeTail(node: TreeNode, pos: BufferCursor) {
|
||||
private deleteNodeTail(node: TreeNode, pos: BufferCursor) {
|
||||
const piece = node.piece;
|
||||
const originalLFCnt = piece.lineFeedCnt;
|
||||
const originalEndOffset = this.offsetInBuffer(piece.bufferIndex, piece.end);
|
||||
|
@ -1277,7 +1363,7 @@ export class PieceTreeBase {
|
|||
updateTreeMetadata(this, node, size_delta, lf_delta);
|
||||
}
|
||||
|
||||
deleteNodeHead(node: TreeNode, pos: BufferCursor) {
|
||||
private deleteNodeHead(node: TreeNode, pos: BufferCursor) {
|
||||
const piece = node.piece;
|
||||
const originalLFCnt = piece.lineFeedCnt;
|
||||
const originalStartOffset = this.offsetInBuffer(piece.bufferIndex, piece.start);
|
||||
|
@ -1299,7 +1385,7 @@ export class PieceTreeBase {
|
|||
updateTreeMetadata(this, node, size_delta, lf_delta);
|
||||
}
|
||||
|
||||
shrinkNode(node: TreeNode, start: BufferCursor, end: BufferCursor) {
|
||||
private shrinkNode(node: TreeNode, start: BufferCursor, end: BufferCursor) {
|
||||
const piece = node.piece;
|
||||
const originalStartPos = piece.start;
|
||||
const originalEndPos = piece.end;
|
||||
|
@ -1334,7 +1420,7 @@ export class PieceTreeBase {
|
|||
this.validateCRLFWithPrevNode(newNode);
|
||||
}
|
||||
|
||||
appendToNode(node: TreeNode, value: string): void {
|
||||
private appendToNode(node: TreeNode, value: string): void {
|
||||
if (this.adjustCarriageReturnFromNext(value, node)) {
|
||||
value += '\n';
|
||||
}
|
||||
|
@ -1374,7 +1460,7 @@ export class PieceTreeBase {
|
|||
updateTreeMetadata(this, node, value.length, lf_delta);
|
||||
}
|
||||
|
||||
nodeAt(offset: number): NodePosition {
|
||||
private nodeAt(offset: number): NodePosition {
|
||||
let x = this.root;
|
||||
let cache = this._searchCache.get(offset);
|
||||
if (cache) {
|
||||
|
@ -1409,7 +1495,7 @@ export class PieceTreeBase {
|
|||
return null!;
|
||||
}
|
||||
|
||||
nodeAt2(lineNumber: number, column: number): NodePosition {
|
||||
private nodeAt2(lineNumber: number, column: number): NodePosition {
|
||||
let x = this.root;
|
||||
let nodeStartOffset = 0;
|
||||
|
||||
|
@ -1476,7 +1562,7 @@ export class PieceTreeBase {
|
|||
return null!;
|
||||
}
|
||||
|
||||
nodeCharCodeAt(node: TreeNode, offset: number): number {
|
||||
private nodeCharCodeAt(node: TreeNode, offset: number): number {
|
||||
if (node.piece.lineFeedCnt < 1) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -1485,7 +1571,7 @@ export class PieceTreeBase {
|
|||
return buffer.buffer.charCodeAt(newOffset);
|
||||
}
|
||||
|
||||
offsetOfNode(node: TreeNode): number {
|
||||
private offsetOfNode(node: TreeNode): number {
|
||||
if (!node) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -1504,11 +1590,11 @@ export class PieceTreeBase {
|
|||
// #endregion
|
||||
|
||||
// #region CRLF
|
||||
shouldCheckCRLF() {
|
||||
private shouldCheckCRLF() {
|
||||
return !(this._EOLNormalized && this._EOL === '\n');
|
||||
}
|
||||
|
||||
startWithLF(val: string | TreeNode): boolean {
|
||||
private startWithLF(val: string | TreeNode): boolean {
|
||||
if (typeof val === 'string') {
|
||||
return val.charCodeAt(0) === 10;
|
||||
}
|
||||
|
@ -1532,7 +1618,7 @@ export class PieceTreeBase {
|
|||
return this._buffers[piece.bufferIndex].buffer.charCodeAt(startOffset) === 10;
|
||||
}
|
||||
|
||||
endWithCR(val: string | TreeNode): boolean {
|
||||
private endWithCR(val: string | TreeNode): boolean {
|
||||
if (typeof val === 'string') {
|
||||
return val.charCodeAt(val.length - 1) === 13;
|
||||
}
|
||||
|
@ -1544,7 +1630,7 @@ export class PieceTreeBase {
|
|||
return this.nodeCharCodeAt(val, val.piece.length - 1) === 13;
|
||||
}
|
||||
|
||||
validateCRLFWithPrevNode(nextNode: TreeNode) {
|
||||
private validateCRLFWithPrevNode(nextNode: TreeNode) {
|
||||
if (this.shouldCheckCRLF() && this.startWithLF(nextNode)) {
|
||||
let node = nextNode.prev();
|
||||
if (this.endWithCR(node)) {
|
||||
|
@ -1553,7 +1639,7 @@ export class PieceTreeBase {
|
|||
}
|
||||
}
|
||||
|
||||
validateCRLFWithNextNode(node: TreeNode) {
|
||||
private validateCRLFWithNextNode(node: TreeNode) {
|
||||
if (this.shouldCheckCRLF() && this.endWithCR(node)) {
|
||||
let nextNode = node.next();
|
||||
if (this.startWithLF(nextNode)) {
|
||||
|
@ -1562,7 +1648,7 @@ export class PieceTreeBase {
|
|||
}
|
||||
}
|
||||
|
||||
fixCRLF(prev: TreeNode, next: TreeNode) {
|
||||
private fixCRLF(prev: TreeNode, next: TreeNode) {
|
||||
let nodesToDel: TreeNode[] = [];
|
||||
// update node
|
||||
let lineStarts = this._buffers[prev.piece.bufferIndex].lineStarts;
|
||||
|
@ -1617,7 +1703,7 @@ export class PieceTreeBase {
|
|||
}
|
||||
}
|
||||
|
||||
adjustCarriageReturnFromNext(value: string, node: TreeNode): boolean {
|
||||
private adjustCarriageReturnFromNext(value: string, node: TreeNode): boolean {
|
||||
if (this.shouldCheckCRLF() && this.endWithCR(value)) {
|
||||
let nextNode = node.next();
|
||||
if (this.startWithLF(nextNode)) {
|
||||
|
@ -1667,7 +1753,7 @@ export class PieceTreeBase {
|
|||
return callback(node) && this.iterate(node.right, callback);
|
||||
}
|
||||
|
||||
getNodeContent(node: TreeNode) {
|
||||
private getNodeContent(node: TreeNode) {
|
||||
if (node === SENTINEL) {
|
||||
return '';
|
||||
}
|
||||
|
@ -1695,7 +1781,7 @@ export class PieceTreeBase {
|
|||
* /
|
||||
* z
|
||||
*/
|
||||
rbInsertRight(node: TreeNode | null, p: Piece): TreeNode {
|
||||
private rbInsertRight(node: TreeNode | null, p: Piece): TreeNode {
|
||||
let z = new TreeNode(p, NodeColor.Red);
|
||||
z.left = SENTINEL;
|
||||
z.right = SENTINEL;
|
||||
|
@ -1727,7 +1813,7 @@ export class PieceTreeBase {
|
|||
* \
|
||||
* z
|
||||
*/
|
||||
rbInsertLeft(node: TreeNode | null, p: Piece): TreeNode {
|
||||
private rbInsertLeft(node: TreeNode | null, p: Piece): TreeNode {
|
||||
let z = new TreeNode(p, NodeColor.Red);
|
||||
z.left = SENTINEL;
|
||||
z.right = SENTINEL;
|
||||
|
@ -1751,7 +1837,7 @@ export class PieceTreeBase {
|
|||
return z;
|
||||
}
|
||||
|
||||
getContentOfSubTree(node: TreeNode): string {
|
||||
private getContentOfSubTree(node: TreeNode): string {
|
||||
let str = '';
|
||||
|
||||
this.iterate(node, node => {
|
||||
|
|
|
@ -481,6 +481,7 @@ export interface CompletionItem {
|
|||
export interface CompletionList {
|
||||
suggestions: CompletionItem[];
|
||||
incomplete?: boolean;
|
||||
isDetailsResolved?: boolean;
|
||||
dispose?(): void;
|
||||
}
|
||||
|
||||
|
|
|
@ -53,9 +53,14 @@ export interface ITextEditorModel extends IEditorModel {
|
|||
createSnapshot(this: ITextEditorModel): ITextSnapshot | null;
|
||||
|
||||
/**
|
||||
* Signals if this model is readonly or not.
|
||||
* Signals if this model is readonly or not.
|
||||
*/
|
||||
isReadonly(): boolean;
|
||||
|
||||
/**
|
||||
* Figure out if this model is resolved or not.
|
||||
*/
|
||||
isResolved(): this is IResolvedTextEditorModel;
|
||||
}
|
||||
|
||||
export interface IResolvedTextEditorModel extends ITextEditorModel {
|
||||
|
|
|
@ -66,6 +66,7 @@ export class RenderLineInput {
|
|||
public readonly lineTokens: IViewLineTokens;
|
||||
public readonly lineDecorations: LineDecoration[];
|
||||
public readonly tabSize: number;
|
||||
public readonly startVisibleColumn: number;
|
||||
public readonly spaceWidth: number;
|
||||
public readonly stopRenderingLineAfter: number;
|
||||
public readonly renderWhitespace: RenderWhitespace;
|
||||
|
@ -89,6 +90,7 @@ export class RenderLineInput {
|
|||
lineTokens: IViewLineTokens,
|
||||
lineDecorations: LineDecoration[],
|
||||
tabSize: number,
|
||||
startVisibleColumn: number,
|
||||
spaceWidth: number,
|
||||
stopRenderingLineAfter: number,
|
||||
renderWhitespace: 'none' | 'boundary' | 'selection' | 'all',
|
||||
|
@ -106,6 +108,7 @@ export class RenderLineInput {
|
|||
this.lineTokens = lineTokens;
|
||||
this.lineDecorations = lineDecorations;
|
||||
this.tabSize = tabSize;
|
||||
this.startVisibleColumn = startVisibleColumn;
|
||||
this.spaceWidth = spaceWidth;
|
||||
this.stopRenderingLineAfter = stopRenderingLineAfter;
|
||||
this.renderWhitespace = (
|
||||
|
@ -154,6 +157,7 @@ export class RenderLineInput {
|
|||
&& this.containsRTL === other.containsRTL
|
||||
&& this.fauxIndentLength === other.fauxIndentLength
|
||||
&& this.tabSize === other.tabSize
|
||||
&& this.startVisibleColumn === other.startVisibleColumn
|
||||
&& this.spaceWidth === other.spaceWidth
|
||||
&& this.stopRenderingLineAfter === other.stopRenderingLineAfter
|
||||
&& this.renderWhitespace === other.renderWhitespace
|
||||
|
@ -368,7 +372,9 @@ class ResolvedRenderLineInput {
|
|||
public readonly isOverflowing: boolean,
|
||||
public readonly parts: LinePart[],
|
||||
public readonly containsForeignElements: ForeignElementType,
|
||||
public readonly fauxIndentLength: number,
|
||||
public readonly tabSize: number,
|
||||
public readonly startVisibleColumn: number,
|
||||
public readonly containsRTL: boolean,
|
||||
public readonly spaceWidth: number,
|
||||
public readonly renderWhitespace: RenderWhitespace,
|
||||
|
@ -395,7 +401,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
|
|||
|
||||
let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len);
|
||||
if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary || (input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine)) {
|
||||
tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, useMonospaceOptimizations, input.selectionsOnLine, input.renderWhitespace === RenderWhitespace.Boundary);
|
||||
tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, input.startVisibleColumn, useMonospaceOptimizations, input.selectionsOnLine, input.renderWhitespace === RenderWhitespace.Boundary);
|
||||
}
|
||||
let containsForeignElements = ForeignElementType.None;
|
||||
if (input.lineDecorations.length > 0) {
|
||||
|
@ -425,7 +431,9 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
|
|||
isOverflowing,
|
||||
tokens,
|
||||
containsForeignElements,
|
||||
input.fauxIndentLength,
|
||||
input.tabSize,
|
||||
input.startVisibleColumn,
|
||||
input.containsRTL,
|
||||
input.spaceWidth,
|
||||
input.renderWhitespace,
|
||||
|
@ -537,7 +545,7 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces:
|
|||
* Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (→ or ·) do not have the same width as .
|
||||
* The rendering phase will generate `style="width:..."` for these tokens.
|
||||
*/
|
||||
function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, useMonospaceOptimizations: boolean, selections: LineRange[] | null, onlyBoundary: boolean): LinePart[] {
|
||||
function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, startVisibleColumn: number, useMonospaceOptimizations: boolean, selections: LineRange[] | null, onlyBoundary: boolean): LinePart[] {
|
||||
|
||||
let result: LinePart[] = [], resultLen = 0;
|
||||
let tokenIndex = 0;
|
||||
|
@ -555,21 +563,10 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW
|
|||
lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent);
|
||||
}
|
||||
|
||||
let tmpIndent = 0;
|
||||
for (let charIndex = 0; charIndex < fauxIndentLength; charIndex++) {
|
||||
const chCode = lineContent.charCodeAt(charIndex);
|
||||
if (chCode === CharCode.Tab) {
|
||||
tmpIndent = tabSize;
|
||||
} else if (strings.isFullWidthCharacter(chCode)) {
|
||||
tmpIndent += 2;
|
||||
} else {
|
||||
tmpIndent++;
|
||||
}
|
||||
}
|
||||
tmpIndent = tmpIndent % tabSize;
|
||||
let wasInWhitespace = false;
|
||||
let currentSelectionIndex = 0;
|
||||
let currentSelection = selections && selections[currentSelectionIndex];
|
||||
let tmpIndent = startVisibleColumn % tabSize;
|
||||
for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) {
|
||||
const chCode = lineContent.charCodeAt(charIndex);
|
||||
|
||||
|
@ -729,7 +726,9 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
|
|||
const len = input.len;
|
||||
const isOverflowing = input.isOverflowing;
|
||||
const parts = input.parts;
|
||||
const fauxIndentLength = input.fauxIndentLength;
|
||||
const tabSize = input.tabSize;
|
||||
const startVisibleColumn = input.startVisibleColumn;
|
||||
const containsRTL = input.containsRTL;
|
||||
const spaceWidth = input.spaceWidth;
|
||||
const renderWhitespace = input.renderWhitespace;
|
||||
|
@ -738,7 +737,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
|
|||
const characterMapping = new CharacterMapping(len + 1, parts.length);
|
||||
|
||||
let charIndex = 0;
|
||||
let tabsCharDelta = 0;
|
||||
let visibleColumn = startVisibleColumn;
|
||||
let charOffsetInPart = 0;
|
||||
|
||||
let prevPartContentCnt = 0;
|
||||
|
@ -764,18 +763,14 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
|
|||
let partContentCnt = 0;
|
||||
{
|
||||
let _charIndex = charIndex;
|
||||
let _tabsCharDelta = tabsCharDelta;
|
||||
let _visibleColumn = visibleColumn;
|
||||
|
||||
for (; _charIndex < partEndIndex; _charIndex++) {
|
||||
const charCode = lineContent.charCodeAt(_charIndex);
|
||||
|
||||
if (charCode === CharCode.Tab) {
|
||||
let insertSpacesCount = tabSize - (_charIndex + _tabsCharDelta) % tabSize;
|
||||
_tabsCharDelta += insertSpacesCount - 1;
|
||||
partContentCnt += insertSpacesCount;
|
||||
} else {
|
||||
// must be CharCode.Space
|
||||
partContentCnt++;
|
||||
const charWidth = (charCode === CharCode.Tab ? (tabSize - (_visibleColumn % tabSize)) : 1) | 0;
|
||||
partContentCnt += charWidth;
|
||||
if (_charIndex >= fauxIndentLength) {
|
||||
_visibleColumn += charWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -793,29 +788,30 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
|
|||
for (; charIndex < partEndIndex; charIndex++) {
|
||||
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
|
||||
const charCode = lineContent.charCodeAt(charIndex);
|
||||
let charWidth: number;
|
||||
|
||||
if (charCode === CharCode.Tab) {
|
||||
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
|
||||
tabsCharDelta += insertSpacesCount - 1;
|
||||
charOffsetInPart += insertSpacesCount - 1;
|
||||
if (insertSpacesCount > 0) {
|
||||
if (!canUseHalfwidthRightwardsArrow || insertSpacesCount > 1) {
|
||||
sb.write1(0x2192); // RIGHTWARDS ARROW
|
||||
} else {
|
||||
sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW
|
||||
}
|
||||
insertSpacesCount--;
|
||||
charWidth = (tabSize - (visibleColumn % tabSize)) | 0;
|
||||
|
||||
if (!canUseHalfwidthRightwardsArrow || charWidth > 1) {
|
||||
sb.write1(0x2192); // RIGHTWARDS ARROW
|
||||
} else {
|
||||
sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW
|
||||
}
|
||||
while (insertSpacesCount > 0) {
|
||||
for (let space = 2; space <= charWidth; space++) {
|
||||
sb.write1(0xA0); //
|
||||
insertSpacesCount--;
|
||||
}
|
||||
} else {
|
||||
// must be CharCode.Space
|
||||
|
||||
} else { // must be CharCode.Space
|
||||
charWidth = 1;
|
||||
|
||||
sb.write1(0xB7); // ·
|
||||
}
|
||||
|
||||
charOffsetInPart++;
|
||||
charOffsetInPart += charWidth;
|
||||
if (charIndex >= fauxIndentLength) {
|
||||
visibleColumn += charWidth;
|
||||
}
|
||||
}
|
||||
|
||||
prevPartContentCnt = partContentCnt;
|
||||
|
@ -833,63 +829,59 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
|
|||
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
|
||||
const charCode = lineContent.charCodeAt(charIndex);
|
||||
|
||||
let producedCharacters = 1;
|
||||
let charWidth = 1;
|
||||
|
||||
switch (charCode) {
|
||||
case CharCode.Tab:
|
||||
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
|
||||
tabsCharDelta += insertSpacesCount - 1;
|
||||
charOffsetInPart += insertSpacesCount - 1;
|
||||
while (insertSpacesCount > 0) {
|
||||
producedCharacters = (tabSize - (visibleColumn % tabSize));
|
||||
charWidth = producedCharacters;
|
||||
for (let space = 1; space <= producedCharacters; space++) {
|
||||
sb.write1(0xA0); //
|
||||
partContentCnt++;
|
||||
insertSpacesCount--;
|
||||
}
|
||||
break;
|
||||
|
||||
case CharCode.Space:
|
||||
sb.write1(0xA0); //
|
||||
partContentCnt++;
|
||||
break;
|
||||
|
||||
case CharCode.LessThan:
|
||||
sb.appendASCIIString('<');
|
||||
partContentCnt++;
|
||||
break;
|
||||
|
||||
case CharCode.GreaterThan:
|
||||
sb.appendASCIIString('>');
|
||||
partContentCnt++;
|
||||
break;
|
||||
|
||||
case CharCode.Ampersand:
|
||||
sb.appendASCIIString('&');
|
||||
partContentCnt++;
|
||||
break;
|
||||
|
||||
case CharCode.Null:
|
||||
sb.appendASCIIString('�');
|
||||
partContentCnt++;
|
||||
break;
|
||||
|
||||
case CharCode.UTF8_BOM:
|
||||
case CharCode.LINE_SEPARATOR_2028:
|
||||
sb.write1(0xFFFD);
|
||||
partContentCnt++;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (strings.isFullWidthCharacter(charCode)) {
|
||||
tabsCharDelta++;
|
||||
charWidth++;
|
||||
}
|
||||
if (renderControlCharacters && charCode < 32) {
|
||||
sb.write1(9216 + charCode);
|
||||
partContentCnt++;
|
||||
} else {
|
||||
sb.write1(charCode);
|
||||
partContentCnt++;
|
||||
}
|
||||
}
|
||||
|
||||
charOffsetInPart++;
|
||||
charOffsetInPart += producedCharacters;
|
||||
partContentCnt += producedCharacters;
|
||||
if (charIndex >= fauxIndentLength) {
|
||||
visibleColumn += charWidth;
|
||||
}
|
||||
}
|
||||
|
||||
prevPartContentCnt = partContentCnt;
|
||||
|
|
|
@ -1,293 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
|
||||
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
|
||||
import { toUint32Array } from 'vs/base/common/uint';
|
||||
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
|
||||
import { ILineMapperFactory, ILineMapping, OutputPosition } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
|
||||
const enum CharacterClass {
|
||||
NONE = 0,
|
||||
BREAK_BEFORE = 1,
|
||||
BREAK_AFTER = 2,
|
||||
BREAK_OBTRUSIVE = 3,
|
||||
BREAK_IDEOGRAPHIC = 4 // for Han and Kana.
|
||||
}
|
||||
|
||||
class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
|
||||
|
||||
constructor(BREAK_BEFORE: string, BREAK_AFTER: string, BREAK_OBTRUSIVE: string) {
|
||||
super(CharacterClass.NONE);
|
||||
|
||||
for (let i = 0; i < BREAK_BEFORE.length; i++) {
|
||||
this.set(BREAK_BEFORE.charCodeAt(i), CharacterClass.BREAK_BEFORE);
|
||||
}
|
||||
|
||||
for (let i = 0; i < BREAK_AFTER.length; i++) {
|
||||
this.set(BREAK_AFTER.charCodeAt(i), CharacterClass.BREAK_AFTER);
|
||||
}
|
||||
|
||||
for (let i = 0; i < BREAK_OBTRUSIVE.length; i++) {
|
||||
this.set(BREAK_OBTRUSIVE.charCodeAt(i), CharacterClass.BREAK_OBTRUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
public get(charCode: number): CharacterClass {
|
||||
// Initialize CharacterClass.BREAK_IDEOGRAPHIC for these Unicode ranges:
|
||||
// 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF)
|
||||
// 2. CJK Unified Ideographs Extension A (0x3400 -- 0x4DBF)
|
||||
// 3. Hiragana and Katakana (0x3040 -- 0x30FF)
|
||||
if (
|
||||
(charCode >= 0x3040 && charCode <= 0x30FF)
|
||||
|| (charCode >= 0x3400 && charCode <= 0x4DBF)
|
||||
|| (charCode >= 0x4E00 && charCode <= 0x9FFF)
|
||||
) {
|
||||
return CharacterClass.BREAK_IDEOGRAPHIC;
|
||||
}
|
||||
|
||||
return super.get(charCode);
|
||||
}
|
||||
}
|
||||
|
||||
export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactory {
|
||||
|
||||
private readonly classifier: WrappingCharacterClassifier;
|
||||
|
||||
constructor(breakBeforeChars: string, breakAfterChars: string, breakObtrusiveChars: string) {
|
||||
this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars, breakObtrusiveChars);
|
||||
}
|
||||
|
||||
// TODO@Alex -> duplicated in lineCommentCommand
|
||||
private static nextVisibleColumn(currentVisibleColumn: number, tabSize: number, isTab: boolean, columnSize: number): number {
|
||||
currentVisibleColumn = +currentVisibleColumn; //@perf
|
||||
tabSize = +tabSize; //@perf
|
||||
columnSize = +columnSize; //@perf
|
||||
|
||||
if (isTab) {
|
||||
return currentVisibleColumn + (tabSize - (currentVisibleColumn % tabSize));
|
||||
}
|
||||
return currentVisibleColumn + columnSize;
|
||||
}
|
||||
|
||||
public createLineMapping(lineText: string, tabSize: number, breakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): ILineMapping | null {
|
||||
if (breakingColumn === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
tabSize = +tabSize; //@perf
|
||||
breakingColumn = +breakingColumn; //@perf
|
||||
columnsForFullWidthChar = +columnsForFullWidthChar; //@perf
|
||||
hardWrappingIndent = +hardWrappingIndent; //@perf
|
||||
|
||||
let wrappedTextIndentVisibleColumn = 0;
|
||||
let wrappedTextIndent = '';
|
||||
|
||||
let firstNonWhitespaceIndex = -1;
|
||||
if (hardWrappingIndent !== WrappingIndent.None) {
|
||||
firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
|
||||
if (firstNonWhitespaceIndex !== -1) {
|
||||
// Track existing indent
|
||||
wrappedTextIndent = lineText.substring(0, firstNonWhitespaceIndex);
|
||||
for (let i = 0; i < firstNonWhitespaceIndex; i++) {
|
||||
wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, lineText.charCodeAt(i) === CharCode.Tab, 1);
|
||||
}
|
||||
|
||||
// Increase indent of continuation lines, if desired
|
||||
let numberOfAdditionalTabs = 0;
|
||||
if (hardWrappingIndent === WrappingIndent.Indent) {
|
||||
numberOfAdditionalTabs = 1;
|
||||
} else if (hardWrappingIndent === WrappingIndent.DeepIndent) {
|
||||
numberOfAdditionalTabs = 2;
|
||||
}
|
||||
for (let i = 0; i < numberOfAdditionalTabs; i++) {
|
||||
wrappedTextIndent += '\t';
|
||||
wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, true, 1);
|
||||
}
|
||||
|
||||
// Force sticking to beginning of line if no character would fit except for the indentation
|
||||
if (wrappedTextIndentVisibleColumn + columnsForFullWidthChar > breakingColumn) {
|
||||
wrappedTextIndent = '';
|
||||
wrappedTextIndentVisibleColumn = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let classifier = this.classifier;
|
||||
let lastBreakingOffset = 0; // Last 0-based offset in the lineText at which a break happened
|
||||
let breakingLengths: number[] = []; // The length of each broken-up line text
|
||||
let breakingLengthsIndex: number = 0; // The count of breaks already done
|
||||
let visibleColumn = 0; // Visible column since the beginning of the current line
|
||||
let niceBreakOffset = -1; // Last index of a character that indicates a break should happen before it (more desirable)
|
||||
let niceBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `niceBreakOffset`
|
||||
let obtrusiveBreakOffset = -1; // Last index of a character that indicates a break should happen before it (less desirable)
|
||||
let obtrusiveBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `obtrusiveBreakOffset`
|
||||
let len = lineText.length;
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
// At this point, there is a certainty that the character before `i` fits on the current line,
|
||||
// but the character at `i` might not fit
|
||||
|
||||
let charCode = lineText.charCodeAt(i);
|
||||
let charCodeIsTab = (charCode === CharCode.Tab);
|
||||
let charCodeClass = classifier.get(charCode);
|
||||
|
||||
if (strings.isLowSurrogate(charCode)/* && i + 1 < len */) {
|
||||
// A surrogate pair must always be considered as a single unit, so it is never to be broken
|
||||
// => advance visibleColumn by 1 and advance to next char code...
|
||||
visibleColumn = visibleColumn + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (charCodeClass === CharacterClass.BREAK_BEFORE) {
|
||||
// This is a character that indicates that a break should happen before it
|
||||
// Since we are certain the character before `i` fits, there's no extra checking needed,
|
||||
// just mark it as a nice breaking opportunity
|
||||
niceBreakOffset = i;
|
||||
niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
|
||||
}
|
||||
|
||||
// CJK breaking : before break
|
||||
if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i > 0) {
|
||||
let prevCode = lineText.charCodeAt(i - 1);
|
||||
let prevClass = classifier.get(prevCode);
|
||||
if (prevClass !== CharacterClass.BREAK_BEFORE) { // Kinsoku Shori: Don't break after a leading character, like an open bracket
|
||||
niceBreakOffset = i;
|
||||
niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
|
||||
}
|
||||
}
|
||||
|
||||
let charColumnSize = 1;
|
||||
if (strings.isFullWidthCharacter(charCode)) {
|
||||
charColumnSize = columnsForFullWidthChar;
|
||||
}
|
||||
|
||||
// Advance visibleColumn with character at `i`
|
||||
visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(visibleColumn, tabSize, charCodeIsTab, charColumnSize);
|
||||
|
||||
if (visibleColumn > breakingColumn && i !== 0) {
|
||||
// We need to break at least before character at `i`:
|
||||
// - break before niceBreakLastOffset if it exists (and re-establish a correct visibleColumn by using niceBreakVisibleColumn + charAt(i))
|
||||
// - otherwise, break before obtrusiveBreakLastOffset if it exists (and re-establish a correct visibleColumn by using obtrusiveBreakVisibleColumn + charAt(i))
|
||||
// - otherwise, break before i (and re-establish a correct visibleColumn by charAt(i))
|
||||
|
||||
let breakBeforeOffset: number;
|
||||
let restoreVisibleColumnFrom: number;
|
||||
|
||||
if (niceBreakOffset !== -1 && niceBreakVisibleColumn <= breakingColumn) {
|
||||
|
||||
// We will break before `niceBreakLastOffset`
|
||||
breakBeforeOffset = niceBreakOffset;
|
||||
restoreVisibleColumnFrom = niceBreakVisibleColumn;
|
||||
|
||||
} else if (obtrusiveBreakOffset !== -1 && obtrusiveBreakVisibleColumn <= breakingColumn) {
|
||||
|
||||
// We will break before `obtrusiveBreakLastOffset`
|
||||
breakBeforeOffset = obtrusiveBreakOffset;
|
||||
restoreVisibleColumnFrom = obtrusiveBreakVisibleColumn;
|
||||
|
||||
} else {
|
||||
|
||||
// We will break before `i`
|
||||
breakBeforeOffset = i;
|
||||
restoreVisibleColumnFrom = wrappedTextIndentVisibleColumn;
|
||||
|
||||
}
|
||||
|
||||
// Break before character at `breakBeforeOffset`
|
||||
breakingLengths[breakingLengthsIndex++] = breakBeforeOffset - lastBreakingOffset;
|
||||
lastBreakingOffset = breakBeforeOffset;
|
||||
|
||||
// Re-establish visibleColumn by taking character at `i` into account
|
||||
visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(restoreVisibleColumnFrom, tabSize, charCodeIsTab, charColumnSize);
|
||||
|
||||
// Reset markers
|
||||
niceBreakOffset = -1;
|
||||
niceBreakVisibleColumn = 0;
|
||||
obtrusiveBreakOffset = -1;
|
||||
obtrusiveBreakVisibleColumn = 0;
|
||||
}
|
||||
|
||||
// At this point, there is a certainty that the character at `i` fits on the current line
|
||||
|
||||
if (niceBreakOffset !== -1) {
|
||||
// Advance niceBreakVisibleColumn
|
||||
niceBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(niceBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize);
|
||||
}
|
||||
if (obtrusiveBreakOffset !== -1) {
|
||||
// Advance obtrusiveBreakVisibleColumn
|
||||
obtrusiveBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(obtrusiveBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize);
|
||||
}
|
||||
|
||||
if (charCodeClass === CharacterClass.BREAK_AFTER && (hardWrappingIndent === WrappingIndent.None || i >= firstNonWhitespaceIndex)) {
|
||||
// This is a character that indicates that a break should happen after it
|
||||
niceBreakOffset = i + 1;
|
||||
niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
|
||||
}
|
||||
|
||||
// CJK breaking : after break
|
||||
if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i < len - 1) {
|
||||
let nextCode = lineText.charCodeAt(i + 1);
|
||||
let nextClass = classifier.get(nextCode);
|
||||
if (nextClass !== CharacterClass.BREAK_AFTER) { // Kinsoku Shori: Don't break before a trailing character, like a period
|
||||
niceBreakOffset = i + 1;
|
||||
niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
|
||||
}
|
||||
}
|
||||
|
||||
if (charCodeClass === CharacterClass.BREAK_OBTRUSIVE) {
|
||||
// This is an obtrusive character that indicates that a break should happen after it
|
||||
obtrusiveBreakOffset = i + 1;
|
||||
obtrusiveBreakVisibleColumn = wrappedTextIndentVisibleColumn;
|
||||
}
|
||||
}
|
||||
|
||||
if (breakingLengthsIndex === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add last segment
|
||||
breakingLengths[breakingLengthsIndex++] = len - lastBreakingOffset;
|
||||
|
||||
return new CharacterHardWrappingLineMapping(
|
||||
new PrefixSumComputer(toUint32Array(breakingLengths)),
|
||||
wrappedTextIndent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CharacterHardWrappingLineMapping implements ILineMapping {
|
||||
|
||||
private readonly _prefixSums: PrefixSumComputer;
|
||||
private readonly _wrappedLinesIndent: string;
|
||||
|
||||
constructor(prefixSums: PrefixSumComputer, wrappedLinesIndent: string) {
|
||||
this._prefixSums = prefixSums;
|
||||
this._wrappedLinesIndent = wrappedLinesIndent;
|
||||
}
|
||||
|
||||
public getOutputLineCount(): number {
|
||||
return this._prefixSums.getCount();
|
||||
}
|
||||
|
||||
public getWrappedLinesIndent(): string {
|
||||
return this._wrappedLinesIndent;
|
||||
}
|
||||
|
||||
public getInputOffsetOfOutputPosition(outputLineIndex: number, outputOffset: number): number {
|
||||
if (outputLineIndex === 0) {
|
||||
return outputOffset;
|
||||
} else {
|
||||
return this._prefixSums.getAccumulatedValue(outputLineIndex - 1) + outputOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public getOutputPositionOfInputOffset(inputOffset: number): OutputPosition {
|
||||
let r = this._prefixSums.getIndexOf(inputOffset);
|
||||
return new OutputPosition(r.index, r.remainder);
|
||||
}
|
||||
}
|
468
src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts
Normal file
468
src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts
Normal file
|
@ -0,0 +1,468 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
|
||||
import { ILineBreaksComputerFactory, LineBreakData, ILineBreaksComputer } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
|
||||
const enum CharacterClass {
|
||||
NONE = 0,
|
||||
BREAK_BEFORE = 1,
|
||||
BREAK_AFTER = 2,
|
||||
BREAK_IDEOGRAPHIC = 3 // for Han and Kana.
|
||||
}
|
||||
|
||||
class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
|
||||
|
||||
constructor(BREAK_BEFORE: string, BREAK_AFTER: string) {
|
||||
super(CharacterClass.NONE);
|
||||
|
||||
for (let i = 0; i < BREAK_BEFORE.length; i++) {
|
||||
this.set(BREAK_BEFORE.charCodeAt(i), CharacterClass.BREAK_BEFORE);
|
||||
}
|
||||
|
||||
for (let i = 0; i < BREAK_AFTER.length; i++) {
|
||||
this.set(BREAK_AFTER.charCodeAt(i), CharacterClass.BREAK_AFTER);
|
||||
}
|
||||
}
|
||||
|
||||
public get(charCode: number): CharacterClass {
|
||||
if (charCode >= 0 && charCode < 256) {
|
||||
return <CharacterClass>this._asciiMap[charCode];
|
||||
} else {
|
||||
// Initialize CharacterClass.BREAK_IDEOGRAPHIC for these Unicode ranges:
|
||||
// 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF)
|
||||
// 2. CJK Unified Ideographs Extension A (0x3400 -- 0x4DBF)
|
||||
// 3. Hiragana and Katakana (0x3040 -- 0x30FF)
|
||||
if (
|
||||
(charCode >= 0x3040 && charCode <= 0x30FF)
|
||||
|| (charCode >= 0x3400 && charCode <= 0x4DBF)
|
||||
|| (charCode >= 0x4E00 && charCode <= 0x9FFF)
|
||||
) {
|
||||
return CharacterClass.BREAK_IDEOGRAPHIC;
|
||||
}
|
||||
|
||||
return <CharacterClass>(this._map.get(charCode) || this._defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFactory {
|
||||
|
||||
public static create(options: IComputedEditorOptions): MonospaceLineBreaksComputerFactory {
|
||||
return new MonospaceLineBreaksComputerFactory(
|
||||
options.get(EditorOption.wordWrapBreakBeforeCharacters),
|
||||
options.get(EditorOption.wordWrapBreakAfterCharacters)
|
||||
);
|
||||
}
|
||||
|
||||
private readonly classifier: WrappingCharacterClassifier;
|
||||
|
||||
constructor(breakBeforeChars: string, breakAfterChars: string) {
|
||||
this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars);
|
||||
}
|
||||
|
||||
public createLineBreaksComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineBreaksComputer {
|
||||
tabSize = tabSize | 0; //@perf
|
||||
wrappingColumn = +wrappingColumn; //@perf
|
||||
columnsForFullWidthChar = +columnsForFullWidthChar; //@perf
|
||||
|
||||
let requests: string[] = [];
|
||||
let previousBreakingData: (LineBreakData | null)[] = [];
|
||||
return {
|
||||
addRequest: (lineText: string, previousLineBreakData: LineBreakData | null) => {
|
||||
requests.push(lineText);
|
||||
previousBreakingData.push(previousLineBreakData);
|
||||
},
|
||||
finalize: () => {
|
||||
let result: (LineBreakData | null)[] = [];
|
||||
for (let i = 0, len = requests.length; i < len; i++) {
|
||||
const previousLineBreakData = previousBreakingData[i];
|
||||
if (previousLineBreakData) {
|
||||
result[i] = createLineBreaksFromPreviousLineBreaks(this.classifier, previousLineBreakData, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
|
||||
} else {
|
||||
result[i] = createLineBreaks(this.classifier, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let arrPool1: number[] = [];
|
||||
let arrPool2: number[] = [];
|
||||
function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterClassifier, previousBreakingData: LineBreakData, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): LineBreakData | null {
|
||||
if (firstLineBreakColumn === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const len = lineText.length;
|
||||
if (len <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const prevBreakingOffsets = previousBreakingData.breakOffsets;
|
||||
const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakOffsetsVisibleColumn;
|
||||
|
||||
const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakColumn, columnsForFullWidthChar, wrappingIndent);
|
||||
const wrappedLineBreakColumn = firstLineBreakColumn - wrappedTextIndentLength;
|
||||
|
||||
let breakingOffsets: number[] = arrPool1;
|
||||
let breakingOffsetsVisibleColumn: number[] = arrPool2;
|
||||
let breakingOffsetsCount: number = 0;
|
||||
|
||||
let breakingColumn = firstLineBreakColumn;
|
||||
const prevLen = prevBreakingOffsets.length;
|
||||
let prevIndex = 0;
|
||||
|
||||
if (prevIndex >= 0) {
|
||||
let bestDistance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn);
|
||||
while (prevIndex + 1 < prevLen) {
|
||||
const distance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn);
|
||||
if (distance >= bestDistance) {
|
||||
break;
|
||||
}
|
||||
bestDistance = distance;
|
||||
prevIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
while (prevIndex < prevLen) {
|
||||
// Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break)
|
||||
const prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex];
|
||||
const prevBreakoffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex];
|
||||
|
||||
let breakOffset = 0;
|
||||
let breakOffsetVisibleColumn = 0;
|
||||
|
||||
let forcedBreakOffset = 0;
|
||||
let forcedBreakOffsetVisibleColumn = 0;
|
||||
|
||||
// initially, we search as much as possible to the right (if it fits)
|
||||
if (prevBreakoffsetVisibleColumn <= breakingColumn) {
|
||||
let visibleColumn = prevBreakoffsetVisibleColumn;
|
||||
let prevCharCode = lineText.charCodeAt(prevBreakOffset - 1);
|
||||
let prevCharCodeClass = classifier.get(prevCharCode);
|
||||
let entireLineFits = true;
|
||||
for (let i = prevBreakOffset; i < len; i++) {
|
||||
const charStartOffset = i;
|
||||
const charCode = lineText.charCodeAt(i);
|
||||
let charCodeClass: number;
|
||||
let charWidth: number;
|
||||
|
||||
if (strings.isHighSurrogate(charCode)) {
|
||||
// A surrogate pair must always be considered as a single unit, so it is never to be broken
|
||||
i++;
|
||||
charCodeClass = CharacterClass.NONE;
|
||||
charWidth = 2;
|
||||
} else {
|
||||
charCodeClass = classifier.get(charCode);
|
||||
charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
|
||||
}
|
||||
|
||||
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
|
||||
breakOffset = charStartOffset;
|
||||
breakOffsetVisibleColumn = visibleColumn;
|
||||
}
|
||||
|
||||
visibleColumn += charWidth;
|
||||
|
||||
// check if adding character at `i` will go over the breaking column
|
||||
if (visibleColumn > breakingColumn) {
|
||||
// We need to break at least before character at `i`:
|
||||
forcedBreakOffset = charStartOffset;
|
||||
forcedBreakOffsetVisibleColumn = visibleColumn - charWidth;
|
||||
|
||||
if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) {
|
||||
// Cannot break at `breakOffset` => reset it if it was set
|
||||
breakOffset = 0;
|
||||
}
|
||||
|
||||
entireLineFits = false;
|
||||
break;
|
||||
}
|
||||
|
||||
prevCharCode = charCode;
|
||||
prevCharCodeClass = charCodeClass;
|
||||
}
|
||||
|
||||
if (entireLineFits) {
|
||||
// there is no more need to break => stop the outer loop!
|
||||
if (breakingOffsetsCount > 0) {
|
||||
// Add last segment
|
||||
breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1];
|
||||
breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1];
|
||||
breakingOffsetsCount++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (breakOffset === 0) {
|
||||
// must search left
|
||||
let visibleColumn = prevBreakoffsetVisibleColumn;
|
||||
let charCode = lineText.charCodeAt(prevBreakOffset);
|
||||
let charCodeClass = classifier.get(charCode);
|
||||
let hitATabCharacter = false;
|
||||
for (let i = prevBreakOffset - 1; i >= 0; i--) {
|
||||
const charStartOffset = i + 1;
|
||||
const prevCharCode = lineText.charCodeAt(i);
|
||||
|
||||
if (prevCharCode === CharCode.Tab) {
|
||||
// cannot determine the width of a tab when going backwards, so we must go forwards
|
||||
hitATabCharacter = true;
|
||||
break;
|
||||
}
|
||||
|
||||
let prevCharCodeClass: number;
|
||||
let prevCharWidth: number;
|
||||
|
||||
if (strings.isLowSurrogate(prevCharCode)) {
|
||||
// A surrogate pair must always be considered as a single unit, so it is never to be broken
|
||||
i--;
|
||||
prevCharCodeClass = CharacterClass.NONE;
|
||||
prevCharWidth = 2;
|
||||
} else {
|
||||
prevCharCodeClass = classifier.get(prevCharCode);
|
||||
prevCharWidth = (strings.isFullWidthCharacter(prevCharCode) ? columnsForFullWidthChar : 1);
|
||||
}
|
||||
|
||||
if (visibleColumn <= breakingColumn) {
|
||||
if (forcedBreakOffset === 0) {
|
||||
forcedBreakOffset = charStartOffset;
|
||||
forcedBreakOffsetVisibleColumn = visibleColumn;
|
||||
}
|
||||
|
||||
if (visibleColumn <= breakingColumn - wrappedLineBreakColumn) {
|
||||
// went too far!
|
||||
break;
|
||||
}
|
||||
|
||||
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
|
||||
breakOffset = charStartOffset;
|
||||
breakOffsetVisibleColumn = visibleColumn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
visibleColumn -= prevCharWidth;
|
||||
charCode = prevCharCode;
|
||||
charCodeClass = prevCharCodeClass;
|
||||
}
|
||||
|
||||
if (breakOffset !== 0) {
|
||||
const remainingWidthOfNextLine = wrappedLineBreakColumn - (forcedBreakOffsetVisibleColumn - breakOffsetVisibleColumn);
|
||||
if (remainingWidthOfNextLine <= tabSize) {
|
||||
const charCodeAtForcedBreakOffset = lineText.charCodeAt(forcedBreakOffset);
|
||||
let charWidth: number;
|
||||
if (strings.isHighSurrogate(charCodeAtForcedBreakOffset)) {
|
||||
// A surrogate pair must always be considered as a single unit, so it is never to be broken
|
||||
charWidth = 2;
|
||||
} else {
|
||||
charWidth = computeCharWidth(charCodeAtForcedBreakOffset, forcedBreakOffsetVisibleColumn, tabSize, columnsForFullWidthChar);
|
||||
}
|
||||
if (remainingWidthOfNextLine - charWidth < 0) {
|
||||
// it is not worth it to break at breakOffset, it just introduces an extra needless line!
|
||||
breakOffset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hitATabCharacter) {
|
||||
// cannot determine the width of a tab when going backwards, so we must go forwards from the previous break
|
||||
prevIndex--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (breakOffset === 0) {
|
||||
// Could not find a good breaking point
|
||||
breakOffset = forcedBreakOffset;
|
||||
breakOffsetVisibleColumn = forcedBreakOffsetVisibleColumn;
|
||||
}
|
||||
|
||||
breakingOffsets[breakingOffsetsCount] = breakOffset;
|
||||
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
|
||||
breakingOffsetsCount++;
|
||||
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn;
|
||||
|
||||
while (prevIndex < 0 || (prevIndex < prevLen && prevBreakingOffsetsVisibleColumn[prevIndex] < breakOffsetVisibleColumn)) {
|
||||
prevIndex++;
|
||||
}
|
||||
|
||||
let bestDistance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn);
|
||||
while (prevIndex + 1 < prevLen) {
|
||||
const distance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn);
|
||||
if (distance >= bestDistance) {
|
||||
break;
|
||||
}
|
||||
bestDistance = distance;
|
||||
prevIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (breakingOffsetsCount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Doing here some object reuse which ends up helping a huge deal with GC pauses!
|
||||
breakingOffsets.length = breakingOffsetsCount;
|
||||
breakingOffsetsVisibleColumn.length = breakingOffsetsCount;
|
||||
arrPool1 = previousBreakingData.breakOffsets;
|
||||
arrPool2 = previousBreakingData.breakOffsetsVisibleColumn;
|
||||
previousBreakingData.breakOffsets = breakingOffsets;
|
||||
previousBreakingData.breakOffsetsVisibleColumn = breakingOffsetsVisibleColumn;
|
||||
previousBreakingData.wrappedTextIndentLength = wrappedTextIndentLength;
|
||||
return previousBreakingData;
|
||||
}
|
||||
|
||||
function createLineBreaks(classifier: WrappingCharacterClassifier, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): LineBreakData | null {
|
||||
if (firstLineBreakColumn === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const len = lineText.length;
|
||||
if (len <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakColumn, columnsForFullWidthChar, wrappingIndent);
|
||||
const wrappedLineBreakColumn = firstLineBreakColumn - wrappedTextIndentLength;
|
||||
|
||||
let breakingOffsets: number[] = [];
|
||||
let breakingOffsetsVisibleColumn: number[] = [];
|
||||
let breakingOffsetsCount: number = 0;
|
||||
let breakOffset = 0;
|
||||
let breakOffsetVisibleColumn = 0;
|
||||
|
||||
let breakingColumn = firstLineBreakColumn;
|
||||
let prevCharCode = lineText.charCodeAt(0);
|
||||
let prevCharCodeClass = classifier.get(prevCharCode);
|
||||
let visibleColumn = computeCharWidth(prevCharCode, 0, tabSize, columnsForFullWidthChar);
|
||||
|
||||
let startOffset = 1;
|
||||
if (strings.isHighSurrogate(prevCharCode)) {
|
||||
// A surrogate pair must always be considered as a single unit, so it is never to be broken
|
||||
visibleColumn += 1;
|
||||
prevCharCode = lineText.charCodeAt(1);
|
||||
prevCharCodeClass = classifier.get(prevCharCode);
|
||||
startOffset++;
|
||||
}
|
||||
|
||||
for (let i = startOffset; i < len; i++) {
|
||||
const charStartOffset = i;
|
||||
const charCode = lineText.charCodeAt(i);
|
||||
let charCodeClass: number;
|
||||
let charWidth: number;
|
||||
|
||||
if (strings.isHighSurrogate(charCode)) {
|
||||
// A surrogate pair must always be considered as a single unit, so it is never to be broken
|
||||
i++;
|
||||
charCodeClass = CharacterClass.NONE;
|
||||
charWidth = 2;
|
||||
} else {
|
||||
charCodeClass = classifier.get(charCode);
|
||||
charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
|
||||
}
|
||||
|
||||
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
|
||||
breakOffset = charStartOffset;
|
||||
breakOffsetVisibleColumn = visibleColumn;
|
||||
}
|
||||
|
||||
visibleColumn += charWidth;
|
||||
|
||||
// check if adding character at `i` will go over the breaking column
|
||||
if (visibleColumn > breakingColumn) {
|
||||
// We need to break at least before character at `i`:
|
||||
|
||||
if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) {
|
||||
// Cannot break at `breakOffset`, must break at `i`
|
||||
breakOffset = charStartOffset;
|
||||
breakOffsetVisibleColumn = visibleColumn - charWidth;
|
||||
}
|
||||
|
||||
breakingOffsets[breakingOffsetsCount] = breakOffset;
|
||||
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
|
||||
breakingOffsetsCount++;
|
||||
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn;
|
||||
breakOffset = 0;
|
||||
}
|
||||
|
||||
prevCharCode = charCode;
|
||||
prevCharCodeClass = charCodeClass;
|
||||
}
|
||||
|
||||
if (breakingOffsetsCount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add last segment
|
||||
breakingOffsets[breakingOffsetsCount] = len;
|
||||
breakingOffsetsVisibleColumn[breakingOffsetsCount] = visibleColumn;
|
||||
|
||||
return new LineBreakData(breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength);
|
||||
}
|
||||
|
||||
function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number {
|
||||
if (charCode === CharCode.Tab) {
|
||||
return (tabSize - (visibleColumn % tabSize));
|
||||
}
|
||||
if (strings.isFullWidthCharacter(charCode)) {
|
||||
return columnsForFullWidthChar;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
function tabCharacterWidth(visibleColumn: number, tabSize: number): number {
|
||||
return (tabSize - (visibleColumn % tabSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* Kinsoku Shori : Don't break after a leading character, like an open bracket
|
||||
* Kinsoku Shori : Don't break before a trailing character, like a period
|
||||
*/
|
||||
function canBreak(prevCharCode: number, prevCharCodeClass: CharacterClass, charCode: number, charCodeClass: CharacterClass): boolean {
|
||||
return (
|
||||
charCode !== CharCode.Space
|
||||
&& (
|
||||
(prevCharCodeClass === CharacterClass.BREAK_AFTER)
|
||||
|| (prevCharCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && charCodeClass !== CharacterClass.BREAK_AFTER)
|
||||
|| (charCodeClass === CharacterClass.BREAK_BEFORE)
|
||||
|| (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && prevCharCodeClass !== CharacterClass.BREAK_BEFORE)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function computeWrappedTextIndentLength(lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): number {
|
||||
let wrappedTextIndentLength = 0;
|
||||
if (wrappingIndent !== WrappingIndent.None) {
|
||||
const firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
|
||||
if (firstNonWhitespaceIndex !== -1) {
|
||||
// Track existing indent
|
||||
|
||||
for (let i = 0; i < firstNonWhitespaceIndex; i++) {
|
||||
const charWidth = (lineText.charCodeAt(i) === CharCode.Tab ? tabCharacterWidth(wrappedTextIndentLength, tabSize) : 1);
|
||||
wrappedTextIndentLength += charWidth;
|
||||
}
|
||||
|
||||
// Increase indent of continuation lines, if desired
|
||||
const numberOfAdditionalTabs = (wrappingIndent === WrappingIndent.DeepIndent ? 2 : wrappingIndent === WrappingIndent.Indent ? 1 : 0);
|
||||
for (let i = 0; i < numberOfAdditionalTabs; i++) {
|
||||
const charWidth = tabCharacterWidth(wrappedTextIndentLength, tabSize);
|
||||
wrappedTextIndentLength += charWidth;
|
||||
}
|
||||
|
||||
// Force sticking to beginning of line if no character would fit except for the indentation
|
||||
if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakColumn) {
|
||||
wrappedTextIndentLength = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return wrappedTextIndentLength;
|
||||
}
|
|
@ -187,73 +187,3 @@ export class PrefixSumComputer {
|
|||
return new PrefixSumIndexOfResult(mid, accumulatedValue - midStart);
|
||||
}
|
||||
}
|
||||
|
||||
export class PrefixSumComputerWithCache {
|
||||
|
||||
private readonly _actual: PrefixSumComputer;
|
||||
private _cacheAccumulatedValueStart: number = 0;
|
||||
private _cache: PrefixSumIndexOfResult[] | null = null;
|
||||
|
||||
constructor(values: Uint32Array) {
|
||||
this._actual = new PrefixSumComputer(values);
|
||||
this._bustCache();
|
||||
}
|
||||
|
||||
private _bustCache(): void {
|
||||
this._cacheAccumulatedValueStart = 0;
|
||||
this._cache = null;
|
||||
}
|
||||
|
||||
public insertValues(insertIndex: number, insertValues: Uint32Array): void {
|
||||
if (this._actual.insertValues(insertIndex, insertValues)) {
|
||||
this._bustCache();
|
||||
}
|
||||
}
|
||||
|
||||
public changeValue(index: number, value: number): void {
|
||||
if (this._actual.changeValue(index, value)) {
|
||||
this._bustCache();
|
||||
}
|
||||
}
|
||||
|
||||
public removeValues(startIndex: number, cnt: number): void {
|
||||
if (this._actual.removeValues(startIndex, cnt)) {
|
||||
this._bustCache();
|
||||
}
|
||||
}
|
||||
|
||||
public getTotalValue(): number {
|
||||
return this._actual.getTotalValue();
|
||||
}
|
||||
|
||||
public getAccumulatedValue(index: number): number {
|
||||
return this._actual.getAccumulatedValue(index);
|
||||
}
|
||||
|
||||
public getIndexOf(accumulatedValue: number): PrefixSumIndexOfResult {
|
||||
accumulatedValue = Math.floor(accumulatedValue); //@perf
|
||||
|
||||
if (this._cache !== null) {
|
||||
let cacheIndex = accumulatedValue - this._cacheAccumulatedValueStart;
|
||||
if (cacheIndex >= 0 && cacheIndex < this._cache.length) {
|
||||
// Cache hit!
|
||||
return this._cache[cacheIndex];
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss!
|
||||
return this._actual.getIndexOf(accumulatedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives a hint that a lot of requests are about to come in for these accumulated values.
|
||||
*/
|
||||
public warmUpCache(accumulatedValueStart: number, accumulatedValueEnd: number): void {
|
||||
let newCache: PrefixSumIndexOfResult[] = [];
|
||||
for (let accumulatedValue = accumulatedValueStart; accumulatedValue <= accumulatedValueEnd; accumulatedValue++) {
|
||||
newCache[accumulatedValue - accumulatedValueStart] = this.getIndexOf(accumulatedValue);
|
||||
}
|
||||
this._cache = newCache;
|
||||
this._cacheAccumulatedValueStart = accumulatedValueStart;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
|
||||
import { LineTokens } from 'vs/editor/common/core/lineTokens';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
|
@ -10,13 +11,12 @@ import { IRange, Range } from 'vs/editor/common/core/range';
|
|||
import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecoration, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
|
||||
import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { PrefixSumComputerWithCache } from 'vs/editor/common/viewModel/prefixSumComputer';
|
||||
import { PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer';
|
||||
import { ICoordinatesConverter, IOverviewRulerDecorations, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class OutputPosition {
|
||||
_outputPositionBrand: void;
|
||||
outputLineIndex: number;
|
||||
outputOffset: number;
|
||||
|
||||
|
@ -26,15 +26,56 @@ export class OutputPosition {
|
|||
}
|
||||
}
|
||||
|
||||
export interface ILineMapping {
|
||||
getOutputLineCount(): number;
|
||||
getWrappedLinesIndent(): string;
|
||||
getInputOffsetOfOutputPosition(outputLineIndex: number, outputOffset: number): number;
|
||||
getOutputPositionOfInputOffset(inputOffset: number): OutputPosition;
|
||||
export class LineBreakData {
|
||||
constructor(
|
||||
public breakOffsets: number[],
|
||||
public breakOffsetsVisibleColumn: number[],
|
||||
public wrappedTextIndentLength: number
|
||||
) { }
|
||||
|
||||
public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number {
|
||||
if (outputLineIndex === 0) {
|
||||
return outputOffset;
|
||||
} else {
|
||||
return breakOffsets[outputLineIndex - 1] + outputOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public static getOutputPositionOfInputOffset(breakOffsets: number[], inputOffset: number): OutputPosition {
|
||||
let low = 0;
|
||||
let high = breakOffsets.length - 1;
|
||||
let mid = 0;
|
||||
let midStart = 0;
|
||||
|
||||
while (low <= high) {
|
||||
mid = low + ((high - low) / 2) | 0;
|
||||
|
||||
const midStop = breakOffsets[mid];
|
||||
midStart = mid > 0 ? breakOffsets[mid - 1] : 0;
|
||||
|
||||
if (inputOffset < midStart) {
|
||||
high = mid - 1;
|
||||
} else if (inputOffset >= midStop) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new OutputPosition(mid, inputOffset - midStart);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ILineMapperFactory {
|
||||
createLineMapping(lineText: string, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineMapping | null;
|
||||
export interface ILineBreaksComputer {
|
||||
/**
|
||||
* Pass in `previousLineBreakData` if the only difference is in breaking columns!!!
|
||||
*/
|
||||
addRequest(lineText: string, previousLineBreakData: LineBreakData | null): void;
|
||||
finalize(): (LineBreakData | null)[];
|
||||
}
|
||||
|
||||
export interface ILineBreaksComputerFactory {
|
||||
createLineBreaksComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineBreaksComputer;
|
||||
}
|
||||
|
||||
export interface ISimpleModel {
|
||||
|
@ -50,6 +91,7 @@ export interface ISplitLine {
|
|||
isVisible(): boolean;
|
||||
setVisible(isVisible: boolean): ISplitLine;
|
||||
|
||||
getLineBreakData(): LineBreakData | null;
|
||||
getViewLineCount(): number;
|
||||
getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string;
|
||||
getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number;
|
||||
|
@ -71,14 +113,14 @@ export interface IViewModelLinesCollection extends IDisposable {
|
|||
getHiddenAreas(): Range[];
|
||||
setHiddenAreas(_ranges: Range[]): boolean;
|
||||
|
||||
createLineBreaksComputer(): ILineBreaksComputer;
|
||||
onModelFlushed(): void;
|
||||
onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null;
|
||||
onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, text: string[]): viewEvents.ViewLinesInsertedEvent | null;
|
||||
onModelLineChanged(versionId: number, lineNumber: number, newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null];
|
||||
onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, lineBreaks: (LineBreakData | null)[]): viewEvents.ViewLinesInsertedEvent | null;
|
||||
onModelLineChanged(versionId: number, lineNumber: number, lineBreakData: LineBreakData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null];
|
||||
acceptVersionId(versionId: number): void;
|
||||
|
||||
getViewLineCount(): number;
|
||||
warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void;
|
||||
getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo;
|
||||
getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[];
|
||||
getViewLineContent(viewLineNumber: number): string;
|
||||
|
@ -107,9 +149,7 @@ export class CoordinatesConverter implements ICoordinatesConverter {
|
|||
}
|
||||
|
||||
public convertViewRangeToModelRange(viewRange: Range): Range {
|
||||
let start = this._lines.convertViewPositionToModelPosition(viewRange.startLineNumber, viewRange.startColumn);
|
||||
let end = this._lines.convertViewPositionToModelPosition(viewRange.endLineNumber, viewRange.endColumn);
|
||||
return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
|
||||
return this._lines.convertViewRangeToModelRange(viewRange);
|
||||
}
|
||||
|
||||
public validateViewPosition(viewPosition: Position, expectedModelPosition: Position): Position {
|
||||
|
@ -117,9 +157,7 @@ export class CoordinatesConverter implements ICoordinatesConverter {
|
|||
}
|
||||
|
||||
public validateViewRange(viewRange: Range, expectedModelRange: Range): Range {
|
||||
const validViewStart = this._lines.validateViewPosition(viewRange.startLineNumber, viewRange.startColumn, expectedModelRange.getStartPosition());
|
||||
const validViewEnd = this._lines.validateViewPosition(viewRange.endLineNumber, viewRange.endColumn, expectedModelRange.getEndPosition());
|
||||
return new Range(validViewStart.lineNumber, validViewStart.column, validViewEnd.lineNumber, validViewEnd.column);
|
||||
return this._lines.validateViewRange(viewRange, expectedModelRange);
|
||||
}
|
||||
|
||||
// Model -> View conversion and related methods
|
||||
|
@ -135,7 +173,6 @@ export class CoordinatesConverter implements ICoordinatesConverter {
|
|||
public modelPositionIsVisible(modelPosition: Position): boolean {
|
||||
return this._lines.modelPositionIsVisible(modelPosition.lineNumber, modelPosition.column);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const enum IndentGuideRepeatOption {
|
||||
|
@ -144,6 +181,89 @@ const enum IndentGuideRepeatOption {
|
|||
BlockAll = 2
|
||||
}
|
||||
|
||||
class LineNumberMapper {
|
||||
|
||||
private _counts: number[];
|
||||
private _isValid: boolean;
|
||||
private _validEndIndex: number;
|
||||
|
||||
private _modelToView: number[];
|
||||
private _viewToModel: number[];
|
||||
|
||||
constructor(viewLineCounts: number[]) {
|
||||
this._counts = viewLineCounts;
|
||||
this._isValid = false;
|
||||
this._validEndIndex = -1;
|
||||
this._modelToView = [];
|
||||
this._viewToModel = [];
|
||||
}
|
||||
|
||||
private _invalidate(index: number): void {
|
||||
this._isValid = false;
|
||||
this._validEndIndex = Math.min(this._validEndIndex, index - 1);
|
||||
}
|
||||
|
||||
private _ensureValid(): void {
|
||||
if (this._isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = this._validEndIndex + 1, len = this._counts.length; i < len; i++) {
|
||||
const viewLineCount = this._counts[i];
|
||||
const viewLinesAbove = (i > 0 ? this._modelToView[i - 1] : 0);
|
||||
|
||||
this._modelToView[i] = viewLinesAbove + viewLineCount;
|
||||
for (let j = 0; j < viewLineCount; j++) {
|
||||
this._viewToModel[viewLinesAbove + j] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// trim things
|
||||
this._modelToView.length = this._counts.length;
|
||||
this._viewToModel.length = this._modelToView[this._modelToView.length - 1];
|
||||
|
||||
// mark as valid
|
||||
this._isValid = true;
|
||||
this._validEndIndex = this._counts.length - 1;
|
||||
}
|
||||
|
||||
public changeValue(index: number, value: number): void {
|
||||
if (this._counts[index] === value) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
this._counts[index] = value;
|
||||
this._invalidate(index);
|
||||
}
|
||||
|
||||
public removeValues(start: number, deleteCount: number): void {
|
||||
this._counts.splice(start, deleteCount);
|
||||
this._invalidate(start);
|
||||
}
|
||||
|
||||
public insertValues(insertIndex: number, insertArr: number[]): void {
|
||||
this._counts = arrays.arrayInsert(this._counts, insertIndex, insertArr);
|
||||
this._invalidate(insertIndex);
|
||||
}
|
||||
|
||||
public getTotalValue(): number {
|
||||
this._ensureValid();
|
||||
return this._viewToModel.length;
|
||||
}
|
||||
|
||||
public getAccumulatedValue(index: number): number {
|
||||
this._ensureValid();
|
||||
return this._modelToView[index];
|
||||
}
|
||||
|
||||
public getIndexOf(accumulatedValue: number): PrefixSumIndexOfResult {
|
||||
this._ensureValid();
|
||||
const modelLineIndex = this._viewToModel[accumulatedValue];
|
||||
const viewLinesAbove = (modelLineIndex > 0 ? this._modelToView[modelLineIndex - 1] : 0);
|
||||
return new PrefixSumIndexOfResult(modelLineIndex, accumulatedValue - viewLinesAbove);
|
||||
}
|
||||
}
|
||||
|
||||
export class SplitLinesCollection implements IViewModelLinesCollection {
|
||||
|
||||
private readonly model: ITextModel;
|
||||
|
@ -155,13 +275,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
private tabSize: number;
|
||||
private lines!: ISplitLine[];
|
||||
|
||||
private prefixSumComputer!: PrefixSumComputerWithCache;
|
||||
private prefixSumComputer!: LineNumberMapper;
|
||||
|
||||
private readonly linePositionMapperFactory: ILineMapperFactory;
|
||||
private readonly linePositionMapperFactory: ILineBreaksComputerFactory;
|
||||
|
||||
private hiddenAreasIds!: string[];
|
||||
|
||||
constructor(model: ITextModel, linePositionMapperFactory: ILineMapperFactory, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent) {
|
||||
constructor(model: ITextModel, linePositionMapperFactory: ILineBreaksComputerFactory, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent) {
|
||||
this.model = model;
|
||||
this._validModelVersionId = -1;
|
||||
this.tabSize = tabSize;
|
||||
|
@ -170,7 +290,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
this.wrappingIndent = wrappingIndent;
|
||||
this.linePositionMapperFactory = linePositionMapperFactory;
|
||||
|
||||
this._constructLines(true);
|
||||
this._constructLines(/*resetHiddenAreas*/true, null);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
@ -181,19 +301,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
return new CoordinatesConverter(this);
|
||||
}
|
||||
|
||||
private _ensureValidState(): void {
|
||||
let modelVersion = this.model.getVersionId();
|
||||
if (modelVersion !== this._validModelVersionId) {
|
||||
// This is pretty bad, it means we lost track of the model...
|
||||
throw new Error(`ViewModel is out of sync with Model!`);
|
||||
}
|
||||
if (this.lines.length !== this.model.getLineCount()) {
|
||||
// This is pretty bad, it means we lost track of the model...
|
||||
this._constructLines(false);
|
||||
}
|
||||
}
|
||||
|
||||
private _constructLines(resetHiddenAreas: boolean): void {
|
||||
private _constructLines(resetHiddenAreas: boolean, previousLineBreaks: ((LineBreakData | null)[]) | null): void {
|
||||
this.lines = [];
|
||||
|
||||
if (resetHiddenAreas) {
|
||||
|
@ -201,8 +309,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
}
|
||||
|
||||
let linesContent = this.model.getLinesContent();
|
||||
let lineCount = linesContent.length;
|
||||
let values = new Uint32Array(lineCount);
|
||||
const lineCount = linesContent.length;
|
||||
const lineBreaksComputer = this.linePositionMapperFactory.createLineBreaksComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent);
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
lineBreaksComputer.addRequest(linesContent[i], previousLineBreaks ? previousLineBreaks[i] : null);
|
||||
}
|
||||
const linesBreaks = lineBreaksComputer.finalize();
|
||||
|
||||
let values: number[] = [];
|
||||
|
||||
let hiddenAreas = this.hiddenAreasIds.map((areaId) => this.model.getDecorationRange(areaId)!).sort(Range.compareRangesUsingStarts);
|
||||
let hiddenAreaStart = 1, hiddenAreaEnd = 0;
|
||||
|
@ -220,14 +334,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
}
|
||||
|
||||
let isInHiddenArea = (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd);
|
||||
let line = createSplitLine(this.linePositionMapperFactory, linesContent[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, !isInHiddenArea);
|
||||
let line = createSplitLine(linesBreaks[i], !isInHiddenArea);
|
||||
values[i] = line.getViewLineCount();
|
||||
this.lines[i] = line;
|
||||
}
|
||||
|
||||
this._validModelVersionId = this.model.getVersionId();
|
||||
|
||||
this.prefixSumComputer = new PrefixSumComputerWithCache(values);
|
||||
this.prefixSumComputer = new LineNumberMapper(values);
|
||||
}
|
||||
|
||||
public getHiddenAreas(): Range[] {
|
||||
|
@ -351,7 +465,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
}
|
||||
this.tabSize = newTabSize;
|
||||
|
||||
this._constructLines(false);
|
||||
this._constructLines(/*resetHiddenAreas*/false, null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -361,17 +475,31 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
return false;
|
||||
}
|
||||
|
||||
const onlyWrappingColumnChanged = (this.wrappingIndent === wrappingIndent && this.wrappingColumn !== wrappingColumn && this.columnsForFullWidthChar === columnsForFullWidthChar);
|
||||
|
||||
this.wrappingIndent = wrappingIndent;
|
||||
this.wrappingColumn = wrappingColumn;
|
||||
this.columnsForFullWidthChar = columnsForFullWidthChar;
|
||||
|
||||
this._constructLines(false);
|
||||
let previousLineBreaks: ((LineBreakData | null)[]) | null = null;
|
||||
if (onlyWrappingColumnChanged) {
|
||||
previousLineBreaks = [];
|
||||
for (let i = 0, len = this.lines.length; i < len; i++) {
|
||||
previousLineBreaks[i] = this.lines[i].getLineBreakData();
|
||||
}
|
||||
}
|
||||
|
||||
this._constructLines(/*resetHiddenAreas*/false, previousLineBreaks);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public createLineBreaksComputer(): ILineBreaksComputer {
|
||||
return this.linePositionMapperFactory.createLineBreaksComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent);
|
||||
}
|
||||
|
||||
public onModelFlushed(): void {
|
||||
this._constructLines(true);
|
||||
this._constructLines(/*resetHiddenAreas*/true, null);
|
||||
}
|
||||
|
||||
public onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null {
|
||||
|
@ -390,7 +518,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
return new viewEvents.ViewLinesDeletedEvent(outputFromLineNumber, outputToLineNumber);
|
||||
}
|
||||
|
||||
public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, text: string[]): viewEvents.ViewLinesInsertedEvent | null {
|
||||
public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, lineBreaks: (LineBreakData | null)[]): viewEvents.ViewLinesInsertedEvent | null {
|
||||
if (versionId <= this._validModelVersionId) {
|
||||
// Here we check for versionId in case the lines were reconstructed in the meantime.
|
||||
// We don't want to apply stale change events on top of a newer read model state.
|
||||
|
@ -411,10 +539,10 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
|
||||
let totalOutputLineCount = 0;
|
||||
let insertLines: ISplitLine[] = [];
|
||||
let insertPrefixSumValues = new Uint32Array(text.length);
|
||||
let insertPrefixSumValues: number[] = [];
|
||||
|
||||
for (let i = 0, len = text.length; i < len; i++) {
|
||||
let line = createSplitLine(this.linePositionMapperFactory, text[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, !isInHiddenArea);
|
||||
for (let i = 0, len = lineBreaks.length; i < len; i++) {
|
||||
let line = createSplitLine(lineBreaks[i], !isInHiddenArea);
|
||||
insertLines.push(line);
|
||||
|
||||
let outputLineCount = line.getViewLineCount();
|
||||
|
@ -430,7 +558,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
return new viewEvents.ViewLinesInsertedEvent(outputFromLineNumber, outputFromLineNumber + totalOutputLineCount - 1);
|
||||
}
|
||||
|
||||
public onModelLineChanged(versionId: number, lineNumber: number, newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
|
||||
public onModelLineChanged(versionId: number, lineNumber: number, lineBreakData: LineBreakData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
|
||||
if (versionId <= this._validModelVersionId) {
|
||||
// Here we check for versionId in case the lines were reconstructed in the meantime.
|
||||
// We don't want to apply stale change events on top of a newer read model state.
|
||||
|
@ -441,7 +569,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
|
||||
let oldOutputLineCount = this.lines[lineIndex].getViewLineCount();
|
||||
let isVisible = this.lines[lineIndex].isVisible();
|
||||
let line = createSplitLine(this.linePositionMapperFactory, newText, this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, isVisible);
|
||||
let line = createSplitLine(lineBreakData, isVisible);
|
||||
this.lines[lineIndex] = line;
|
||||
let newOutputLineCount = this.lines[lineIndex].getViewLineCount();
|
||||
|
||||
|
@ -488,7 +616,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
}
|
||||
|
||||
public getViewLineCount(): number {
|
||||
this._ensureValidState();
|
||||
return this.prefixSumComputer.getTotalValue();
|
||||
}
|
||||
|
||||
|
@ -496,22 +623,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
if (viewLineNumber < 1) {
|
||||
return 1;
|
||||
}
|
||||
let viewLineCount = this.getViewLineCount();
|
||||
const viewLineCount = this.getViewLineCount();
|
||||
if (viewLineNumber > viewLineCount) {
|
||||
return viewLineCount;
|
||||
}
|
||||
return viewLineNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives a hint that a lot of requests are about to come in for these line numbers.
|
||||
*/
|
||||
public warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void {
|
||||
this.prefixSumComputer.warmUpCache(viewStartLineNumber - 1, viewEndLineNumber - 1);
|
||||
return viewLineNumber | 0;
|
||||
}
|
||||
|
||||
public getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo {
|
||||
this._ensureValidState();
|
||||
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
|
||||
minLineNumber = this._toValidViewLineNumber(minLineNumber);
|
||||
maxLineNumber = this._toValidViewLineNumber(maxLineNumber);
|
||||
|
@ -531,7 +650,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
}
|
||||
|
||||
public getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[] {
|
||||
this._ensureValidState();
|
||||
viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber);
|
||||
viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber);
|
||||
|
||||
|
@ -602,7 +720,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
}
|
||||
|
||||
public getViewLineContent(viewLineNumber: number): string {
|
||||
this._ensureValidState();
|
||||
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
|
||||
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
|
||||
let lineIndex = r.index;
|
||||
|
@ -612,7 +729,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
}
|
||||
|
||||
public getViewLineLength(viewLineNumber: number): number {
|
||||
this._ensureValidState();
|
||||
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
|
||||
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
|
||||
let lineIndex = r.index;
|
||||
|
@ -622,7 +738,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
}
|
||||
|
||||
public getViewLineMinColumn(viewLineNumber: number): number {
|
||||
this._ensureValidState();
|
||||
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
|
||||
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
|
||||
let lineIndex = r.index;
|
||||
|
@ -632,7 +747,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
}
|
||||
|
||||
public getViewLineMaxColumn(viewLineNumber: number): number {
|
||||
this._ensureValidState();
|
||||
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
|
||||
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
|
||||
let lineIndex = r.index;
|
||||
|
@ -642,7 +756,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
}
|
||||
|
||||
public getViewLineData(viewLineNumber: number): ViewLineData {
|
||||
this._ensureValidState();
|
||||
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
|
||||
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
|
||||
let lineIndex = r.index;
|
||||
|
@ -652,7 +765,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
}
|
||||
|
||||
public getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): ViewLineData[] {
|
||||
this._ensureValidState();
|
||||
|
||||
viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber);
|
||||
viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber);
|
||||
|
@ -691,7 +803,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
}
|
||||
|
||||
public validateViewPosition(viewLineNumber: number, viewColumn: number, expectedModelPosition: Position): Position {
|
||||
this._ensureValidState();
|
||||
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
|
||||
|
||||
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
|
||||
|
@ -719,8 +830,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
return this.convertModelPositionToViewPosition(expectedModelPosition.lineNumber, expectedModelPosition.column);
|
||||
}
|
||||
|
||||
public validateViewRange(viewRange: Range, expectedModelRange: Range): Range {
|
||||
const validViewStart = this.validateViewPosition(viewRange.startLineNumber, viewRange.startColumn, expectedModelRange.getStartPosition());
|
||||
const validViewEnd = this.validateViewPosition(viewRange.endLineNumber, viewRange.endColumn, expectedModelRange.getEndPosition());
|
||||
return new Range(validViewStart.lineNumber, validViewStart.column, validViewEnd.lineNumber, validViewEnd.column);
|
||||
}
|
||||
|
||||
public convertViewPositionToModelPosition(viewLineNumber: number, viewColumn: number): Position {
|
||||
this._ensureValidState();
|
||||
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
|
||||
|
||||
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
|
||||
|
@ -732,8 +848,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
|||
return this.model.validatePosition(new Position(lineIndex + 1, inputColumn));
|
||||
}
|
||||
|
||||
public convertViewRangeToModelRange(viewRange: Range): Range {
|
||||
const start = this.convertViewPositionToModelPosition(viewRange.startLineNumber, viewRange.startColumn);
|
||||
const end = this.convertViewPositionToModelPosition(viewRange.endLineNumber, viewRange.endColumn);
|
||||
return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
|
||||
}
|
||||
|
||||
public convertModelPositionToViewPosition(_modelLineNumber: number, _modelColumn: number): Position {
|
||||
this._ensureValidState();
|
||||
|
||||
const validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn));
|
||||
const inputLineNumber = validPosition.lineNumber;
|
||||
|
@ -898,6 +1019,10 @@ class VisibleIdentitySplitLine implements ISplitLine {
|
|||
return InvisibleIdentitySplitLine.INSTANCE;
|
||||
}
|
||||
|
||||
public getLineBreakData(): LineBreakData | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getViewLineCount(): number {
|
||||
return 1;
|
||||
}
|
||||
|
@ -926,6 +1051,7 @@ class VisibleIdentitySplitLine implements ISplitLine {
|
|||
false,
|
||||
1,
|
||||
lineContent.length + 1,
|
||||
0,
|
||||
lineTokens.inflate()
|
||||
);
|
||||
}
|
||||
|
@ -968,6 +1094,10 @@ class InvisibleIdentitySplitLine implements ISplitLine {
|
|||
return VisibleIdentitySplitLine.INSTANCE;
|
||||
}
|
||||
|
||||
public getLineBreakData(): LineBreakData | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getViewLineCount(): number {
|
||||
return 0;
|
||||
}
|
||||
|
@ -1011,18 +1141,11 @@ class InvisibleIdentitySplitLine implements ISplitLine {
|
|||
|
||||
export class SplitLine implements ISplitLine {
|
||||
|
||||
private readonly positionMapper: ILineMapping;
|
||||
private readonly outputLineCount: number;
|
||||
|
||||
private readonly wrappedIndent: string;
|
||||
private readonly wrappedIndentLength: number;
|
||||
private readonly _lineBreakData: LineBreakData;
|
||||
private _isVisible: boolean;
|
||||
|
||||
constructor(positionMapper: ILineMapping, isVisible: boolean) {
|
||||
this.positionMapper = positionMapper;
|
||||
this.wrappedIndent = this.positionMapper.getWrappedLinesIndent();
|
||||
this.wrappedIndentLength = this.wrappedIndent.length;
|
||||
this.outputLineCount = this.positionMapper.getOutputLineCount();
|
||||
constructor(lineBreakData: LineBreakData, isVisible: boolean) {
|
||||
this._lineBreakData = lineBreakData;
|
||||
this._isVisible = isVisible;
|
||||
}
|
||||
|
||||
|
@ -1035,22 +1158,26 @@ export class SplitLine implements ISplitLine {
|
|||
return this;
|
||||
}
|
||||
|
||||
public getLineBreakData(): LineBreakData | null {
|
||||
return this._lineBreakData;
|
||||
}
|
||||
|
||||
public getViewLineCount(): number {
|
||||
if (!this._isVisible) {
|
||||
return 0;
|
||||
}
|
||||
return this.outputLineCount;
|
||||
return this._lineBreakData.breakOffsets.length;
|
||||
}
|
||||
|
||||
private getInputStartOffsetOfOutputLineIndex(outputLineIndex: number): number {
|
||||
return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex, 0);
|
||||
return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex, 0);
|
||||
}
|
||||
|
||||
private getInputEndOffsetOfOutputLineIndex(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
|
||||
if (outputLineIndex + 1 === this.outputLineCount) {
|
||||
if (outputLineIndex + 1 === this._lineBreakData.breakOffsets.length) {
|
||||
return model.getLineMaxColumn(modelLineNumber) - 1;
|
||||
}
|
||||
return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex + 1, 0);
|
||||
return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex + 1, 0);
|
||||
}
|
||||
|
||||
public getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string {
|
||||
|
@ -1067,7 +1194,7 @@ export class SplitLine implements ISplitLine {
|
|||
});
|
||||
|
||||
if (outputLineIndex > 0) {
|
||||
r = this.wrappedIndent + r;
|
||||
r = spaces(this._lineBreakData.wrappedTextIndentLength) + r;
|
||||
}
|
||||
|
||||
return r;
|
||||
|
@ -1082,7 +1209,7 @@ export class SplitLine implements ISplitLine {
|
|||
let r = endOffset - startOffset;
|
||||
|
||||
if (outputLineIndex > 0) {
|
||||
r = this.wrappedIndent.length + r;
|
||||
r = this._lineBreakData.wrappedTextIndentLength + r;
|
||||
}
|
||||
|
||||
return r;
|
||||
|
@ -1093,7 +1220,7 @@ export class SplitLine implements ISplitLine {
|
|||
throw new Error('Not supported');
|
||||
}
|
||||
if (outputLineIndex > 0) {
|
||||
return this.wrappedIndentLength + 1;
|
||||
return this._lineBreakData.wrappedTextIndentLength + 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
@ -1121,25 +1248,28 @@ export class SplitLine implements ISplitLine {
|
|||
});
|
||||
|
||||
if (outputLineIndex > 0) {
|
||||
lineContent = this.wrappedIndent + lineContent;
|
||||
lineContent = spaces(this._lineBreakData.wrappedTextIndentLength) + lineContent;
|
||||
}
|
||||
|
||||
let minColumn = (outputLineIndex > 0 ? this.wrappedIndentLength + 1 : 1);
|
||||
let minColumn = (outputLineIndex > 0 ? this._lineBreakData.wrappedTextIndentLength + 1 : 1);
|
||||
let maxColumn = lineContent.length + 1;
|
||||
|
||||
let continuesWithWrappedLine = (outputLineIndex + 1 < this.getViewLineCount());
|
||||
|
||||
let deltaStartIndex = 0;
|
||||
if (outputLineIndex > 0) {
|
||||
deltaStartIndex = this.wrappedIndentLength;
|
||||
deltaStartIndex = this._lineBreakData.wrappedTextIndentLength;
|
||||
}
|
||||
let lineTokens = model.getLineTokens(modelLineNumber);
|
||||
|
||||
const startVisibleColumn = (outputLineIndex === 0 ? 0 : this._lineBreakData.breakOffsetsVisibleColumn[outputLineIndex - 1]);
|
||||
|
||||
return new ViewLineData(
|
||||
lineContent,
|
||||
continuesWithWrappedLine,
|
||||
minColumn,
|
||||
maxColumn,
|
||||
startVisibleColumn,
|
||||
lineTokens.sliceAndInflate(startOffset, endOffset, deltaStartIndex)
|
||||
);
|
||||
}
|
||||
|
@ -1165,25 +1295,25 @@ export class SplitLine implements ISplitLine {
|
|||
}
|
||||
let adjustedColumn = outputColumn - 1;
|
||||
if (outputLineIndex > 0) {
|
||||
if (adjustedColumn < this.wrappedIndentLength) {
|
||||
if (adjustedColumn < this._lineBreakData.wrappedTextIndentLength) {
|
||||
adjustedColumn = 0;
|
||||
} else {
|
||||
adjustedColumn -= this.wrappedIndentLength;
|
||||
adjustedColumn -= this._lineBreakData.wrappedTextIndentLength;
|
||||
}
|
||||
}
|
||||
return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex, adjustedColumn) + 1;
|
||||
return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex, adjustedColumn) + 1;
|
||||
}
|
||||
|
||||
public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position {
|
||||
if (!this._isVisible) {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
let r = this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1);
|
||||
let r = LineBreakData.getOutputPositionOfInputOffset(this._lineBreakData.breakOffsets, inputColumn - 1);
|
||||
let outputLineIndex = r.outputLineIndex;
|
||||
let outputColumn = r.outputOffset + 1;
|
||||
|
||||
if (outputLineIndex > 0) {
|
||||
outputColumn += this.wrappedIndentLength;
|
||||
outputColumn += this._lineBreakData.wrappedTextIndentLength;
|
||||
}
|
||||
|
||||
// console.log('in -> out ' + deltaLineNumber + ',' + inputColumn + ' ===> ' + (deltaLineNumber+outputLineIndex) + ',' + outputColumn);
|
||||
|
@ -1194,21 +1324,33 @@ export class SplitLine implements ISplitLine {
|
|||
if (!this._isVisible) {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
const r = this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1);
|
||||
const r = LineBreakData.getOutputPositionOfInputOffset(this._lineBreakData.breakOffsets, inputColumn - 1);
|
||||
return (deltaLineNumber + r.outputLineIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function createSplitLine(linePositionMapperFactory: ILineMapperFactory, text: string, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, isVisible: boolean): ISplitLine {
|
||||
let positionMapper = linePositionMapperFactory.createLineMapping(text, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
|
||||
if (positionMapper === null) {
|
||||
let _spaces: string[] = [''];
|
||||
function spaces(count: number): string {
|
||||
if (count >= _spaces.length) {
|
||||
for (let i = 1; i <= count; i++) {
|
||||
_spaces[i] = _makeSpaces(i);
|
||||
}
|
||||
}
|
||||
return _spaces[count];
|
||||
}
|
||||
function _makeSpaces(count: number): string {
|
||||
return new Array(count + 1).join(' ');
|
||||
}
|
||||
|
||||
function createSplitLine(lineBreakData: LineBreakData | null, isVisible: boolean): ISplitLine {
|
||||
if (lineBreakData === null) {
|
||||
// No mapping needed
|
||||
if (isVisible) {
|
||||
return VisibleIdentitySplitLine.INSTANCE;
|
||||
}
|
||||
return InvisibleIdentitySplitLine.INSTANCE;
|
||||
} else {
|
||||
return new SplitLine(positionMapper, isVisible);
|
||||
return new SplitLine(lineBreakData, isVisible);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1298,6 +1440,18 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
|
|||
return false;
|
||||
}
|
||||
|
||||
public createLineBreaksComputer(): ILineBreaksComputer {
|
||||
let result: null[] = [];
|
||||
return {
|
||||
addRequest: (lineText: string, previousLineBreakData: LineBreakData | null) => {
|
||||
result.push(null);
|
||||
},
|
||||
finalize: () => {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public onModelFlushed(): void {
|
||||
}
|
||||
|
||||
|
@ -1305,11 +1459,11 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
|
|||
return new viewEvents.ViewLinesDeletedEvent(fromLineNumber, toLineNumber);
|
||||
}
|
||||
|
||||
public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, _text: string[]): viewEvents.ViewLinesInsertedEvent | null {
|
||||
public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, lineBreaks: (LineBreakData | null)[]): viewEvents.ViewLinesInsertedEvent | null {
|
||||
return new viewEvents.ViewLinesInsertedEvent(fromLineNumber, toLineNumber);
|
||||
}
|
||||
|
||||
public onModelLineChanged(_versionId: number, lineNumber: number, _newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
|
||||
public onModelLineChanged(_versionId: number, lineNumber: number, lineBreakData: LineBreakData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
|
||||
return [false, new viewEvents.ViewLinesChangedEvent(lineNumber, lineNumber), null, null];
|
||||
}
|
||||
|
||||
|
@ -1320,9 +1474,6 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
|
|||
return this.model.getLineCount();
|
||||
}
|
||||
|
||||
public warmUpLookupCache(_viewStartLineNumber: number, _viewEndLineNumber: number): void {
|
||||
}
|
||||
|
||||
public getActiveIndentGuide(viewLineNumber: number, _minLineNumber: number, _maxLineNumber: number): IActiveIndentGuideInfo {
|
||||
return {
|
||||
startLineNumber: viewLineNumber,
|
||||
|
@ -1364,6 +1515,7 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
|
|||
false,
|
||||
1,
|
||||
lineContent.length + 1,
|
||||
0,
|
||||
lineTokens.inflate()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -174,6 +174,10 @@ export class ViewLineData {
|
|||
* The maximum allowed column at this view line.
|
||||
*/
|
||||
public readonly maxColumn: number;
|
||||
/**
|
||||
* The visible column at the start of the line (after the fauxIndent).
|
||||
*/
|
||||
public readonly startVisibleColumn: number;
|
||||
/**
|
||||
* The tokens at this view line.
|
||||
*/
|
||||
|
@ -184,12 +188,14 @@ export class ViewLineData {
|
|||
continuesWithWrappedLine: boolean,
|
||||
minColumn: number,
|
||||
maxColumn: number,
|
||||
startVisibleColumn: number,
|
||||
tokens: IViewLineTokens
|
||||
) {
|
||||
this.content = content;
|
||||
this.continuesWithWrappedLine = continuesWithWrappedLine;
|
||||
this.minColumn = minColumn;
|
||||
this.maxColumn = maxColumn;
|
||||
this.startVisibleColumn = startVisibleColumn;
|
||||
this.tokens = tokens;
|
||||
}
|
||||
}
|
||||
|
@ -231,6 +237,10 @@ export class ViewLineRenderingData {
|
|||
* The tab size for this view model.
|
||||
*/
|
||||
public readonly tabSize: number;
|
||||
/**
|
||||
* The visible column at the start of the line (after the fauxIndent)
|
||||
*/
|
||||
public readonly startVisibleColumn: number;
|
||||
|
||||
constructor(
|
||||
minColumn: number,
|
||||
|
@ -241,7 +251,8 @@ export class ViewLineRenderingData {
|
|||
mightContainNonBasicASCII: boolean,
|
||||
tokens: IViewLineTokens,
|
||||
inlineDecorations: InlineDecoration[],
|
||||
tabSize: number
|
||||
tabSize: number,
|
||||
startVisibleColumn: number
|
||||
) {
|
||||
this.minColumn = minColumn;
|
||||
this.maxColumn = maxColumn;
|
||||
|
@ -254,6 +265,7 @@ export class ViewLineRenderingData {
|
|||
this.tokens = tokens;
|
||||
this.inlineDecorations = inlineDecorations;
|
||||
this.tabSize = tabSize;
|
||||
this.startVisibleColumn = startVisibleColumn;
|
||||
}
|
||||
|
||||
public static isBasicASCII(lineContent: string, mightContainNonBasicASCII: boolean): boolean {
|
||||
|
|
|
@ -18,8 +18,7 @@ import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer';
|
|||
import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout';
|
||||
import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper';
|
||||
import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
|
||||
import { ITheme } from 'vs/platform/theme/common/themeService';
|
||||
|
@ -43,7 +42,13 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
|||
public readonly viewLayout: ViewLayout;
|
||||
private readonly decorations: ViewModelDecorations;
|
||||
|
||||
constructor(editorId: number, configuration: editorCommon.IConfiguration, model: ITextModel, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) {
|
||||
constructor(
|
||||
editorId: number,
|
||||
configuration: editorCommon.IConfiguration,
|
||||
model: ITextModel,
|
||||
lineMapperFactory: ILineBreaksComputerFactory,
|
||||
scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable
|
||||
) {
|
||||
super();
|
||||
|
||||
this.editorId = editorId;
|
||||
|
@ -63,20 +68,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
|||
const options = this.configuration.options;
|
||||
const wrappingInfo = options.get(EditorOption.wrappingInfo);
|
||||
const fontInfo = options.get(EditorOption.fontInfo);
|
||||
const wordWrapBreakAfterCharacters = options.get(EditorOption.wordWrapBreakAfterCharacters);
|
||||
const wordWrapBreakBeforeCharacters = options.get(EditorOption.wordWrapBreakBeforeCharacters);
|
||||
const wordWrapBreakObtrusiveCharacters = options.get(EditorOption.wordWrapBreakObtrusiveCharacters);
|
||||
const wrappingIndent = options.get(EditorOption.wrappingIndent);
|
||||
|
||||
let hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory(
|
||||
wordWrapBreakBeforeCharacters,
|
||||
wordWrapBreakAfterCharacters,
|
||||
wordWrapBreakObtrusiveCharacters
|
||||
);
|
||||
|
||||
this.lines = new SplitLinesCollection(
|
||||
this.model,
|
||||
hardWrappingLineMapperFactory,
|
||||
lineMapperFactory,
|
||||
this.model.getOptions().tabSize,
|
||||
wrappingInfo.wrappingColumn,
|
||||
fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth,
|
||||
|
@ -200,8 +196,26 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
|||
const changes = e.changes;
|
||||
const versionId = e.versionId;
|
||||
|
||||
for (let j = 0, lenJ = changes.length; j < lenJ; j++) {
|
||||
const change = changes[j];
|
||||
// Do a first pass to compute line mappings, and a second pass to actually interpret them
|
||||
const lineBreaksComputer = this.lines.createLineBreaksComputer();
|
||||
for (const change of changes) {
|
||||
switch (change.changeType) {
|
||||
case textModelEvents.RawContentChangedType.LinesInserted: {
|
||||
for (const line of change.detail) {
|
||||
lineBreaksComputer.addRequest(line, null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case textModelEvents.RawContentChangedType.LineChanged: {
|
||||
lineBreaksComputer.addRequest(change.detail, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const lineBreaks = lineBreaksComputer.finalize();
|
||||
let lineBreaksOffset = 0;
|
||||
|
||||
for (const change of changes) {
|
||||
|
||||
switch (change.changeType) {
|
||||
case textModelEvents.RawContentChangedType.Flush: {
|
||||
|
@ -222,7 +236,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
|||
break;
|
||||
}
|
||||
case textModelEvents.RawContentChangedType.LinesInserted: {
|
||||
const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, change.detail);
|
||||
const insertedLineBreaks = lineBreaks.slice(lineBreaksOffset, lineBreaksOffset + change.detail.length);
|
||||
lineBreaksOffset += change.detail.length;
|
||||
|
||||
const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLineBreaks);
|
||||
if (linesInsertedEvent !== null) {
|
||||
eventsCollector.emit(linesInsertedEvent);
|
||||
this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber);
|
||||
|
@ -231,7 +248,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
|||
break;
|
||||
}
|
||||
case textModelEvents.RawContentChangedType.LineChanged: {
|
||||
const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, change.detail);
|
||||
const changedLineBreakData = lineBreaks[lineBreaksOffset];
|
||||
lineBreaksOffset++;
|
||||
|
||||
const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, changedLineBreakData);
|
||||
hadModelLineChangeThatChangedLineMapping = lineMappingChanged;
|
||||
if (linesChangedEvent) {
|
||||
eventsCollector.emit(linesChangedEvent);
|
||||
|
@ -475,8 +495,6 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
|||
* Gives a hint that a lot of requests are about to come in for these line numbers.
|
||||
*/
|
||||
public setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void {
|
||||
this.lines.warmUpLookupCache(startLineNumber, endLineNumber);
|
||||
|
||||
this.viewportStartLine = startLineNumber;
|
||||
let position = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(startLineNumber, this.getLineMinColumn(startLineNumber)));
|
||||
this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, new Range(position.lineNumber, position.column, position.lineNumber, position.column), TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);
|
||||
|
@ -546,7 +564,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
|||
mightContainNonBasicASCII,
|
||||
lineData.tokens,
|
||||
inlineDecorations,
|
||||
tabSize
|
||||
tabSize,
|
||||
lineData.startVisibleColumn
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -381,7 +381,6 @@ export class LineCommentCommand implements editorCommon.ICommand {
|
|||
return res;
|
||||
}
|
||||
|
||||
// TODO@Alex -> duplicated in characterHardWrappingLineMapper
|
||||
private static nextVisibleColumn(currentVisibleColumn: number, tabSize: number, isTab: boolean, columnSize: number): number {
|
||||
if (isTab) {
|
||||
return currentVisibleColumn + (tabSize - (currentVisibleColumn % tabSize));
|
||||
|
|
|
@ -47,6 +47,9 @@ export class CompletionItem {
|
|||
idx?: number;
|
||||
word?: string;
|
||||
|
||||
//
|
||||
readonly isDetailsResolved: boolean;
|
||||
|
||||
constructor(
|
||||
readonly position: IPosition,
|
||||
readonly completion: modes.CompletionItem,
|
||||
|
@ -70,6 +73,8 @@ export class CompletionItem {
|
|||
this.editReplaceEnd = new Position(completion.range.replace.endLineNumber, completion.range.replace.endColumn);
|
||||
}
|
||||
|
||||
this.isDetailsResolved = container.isDetailsResolved || typeof provider.resolveCompletionItem === 'undefined';
|
||||
|
||||
// create the suggestion resolver
|
||||
const { resolveCompletionItem } = provider;
|
||||
if (typeof resolveCompletionItem !== 'function') {
|
||||
|
|
|
@ -125,6 +125,7 @@ export class Colorizer {
|
|||
[],
|
||||
tabSize,
|
||||
0,
|
||||
0,
|
||||
-1,
|
||||
'none',
|
||||
false,
|
||||
|
@ -193,6 +194,7 @@ function _fakeColorize(lines: string[], tabSize: number): string {
|
|||
[],
|
||||
tabSize,
|
||||
0,
|
||||
0,
|
||||
-1,
|
||||
'none',
|
||||
false,
|
||||
|
@ -230,6 +232,7 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport:
|
|||
[],
|
||||
tabSize,
|
||||
0,
|
||||
0,
|
||||
-1,
|
||||
'none',
|
||||
false,
|
||||
|
|
|
@ -80,6 +80,10 @@ export class SimpleModel implements IResolvedTextEditorModel {
|
|||
public dispose(): void {
|
||||
this._onDispose.fire();
|
||||
}
|
||||
|
||||
public isResolved(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IOpenEditorDelegate {
|
||||
|
|
|
@ -14,6 +14,7 @@ import { TextModel } from 'vs/editor/common/model/textModel';
|
|||
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
|
||||
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
|
||||
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
|
||||
|
||||
function testCommand(lines: string[], selections: Selection[], edits: IIdentifiedSingleEditOperation[], expectedLines: string[], expectedSelections: Selection[]): void {
|
||||
withTestCodeEditor(lines, {}, (editor, cursor) => {
|
||||
|
@ -200,7 +201,7 @@ suite('SideEditing', () => {
|
|||
function _runTest(selection: Selection, editRange: Range, editText: string, editForceMoveMarkers: boolean, expected: Selection, msg: string): void {
|
||||
const model = TextModel.createFromString(LINES.join('\n'));
|
||||
const config = new TestConfiguration({});
|
||||
const viewModel = new ViewModel(0, config, model, null!);
|
||||
const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
|
||||
const cursor = new Cursor(config, model, viewModel);
|
||||
|
||||
cursor.setSelections('tests', [selection]);
|
||||
|
|
|
@ -25,6 +25,7 @@ import { IRelaxedTextModelCreationOptions, createTextModel } from 'vs/editor/tes
|
|||
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
|
||||
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
|
||||
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules';
|
||||
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
|
||||
|
||||
const H = Handler;
|
||||
|
||||
|
@ -152,7 +153,7 @@ suite('Editor Controller - Cursor', () => {
|
|||
|
||||
thisModel = createTextModel(text);
|
||||
thisConfiguration = new TestConfiguration({});
|
||||
thisViewModel = new ViewModel(0, thisConfiguration, thisModel, null!);
|
||||
thisViewModel = new ViewModel(0, thisConfiguration, thisModel, MonospaceLineBreaksComputerFactory.create(thisConfiguration.options), null!);
|
||||
|
||||
thisCursor = new Cursor(thisConfiguration, thisModel, thisViewModel);
|
||||
});
|
||||
|
@ -776,7 +777,7 @@ suite('Editor Controller - Cursor', () => {
|
|||
'var newer = require("gulp-newer");',
|
||||
].join('\n'));
|
||||
const config = new TestConfiguration({});
|
||||
const viewModel = new ViewModel(0, config, model, null!);
|
||||
const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
|
||||
const cursor = new Cursor(config, model, viewModel);
|
||||
|
||||
moveTo(cursor, 1, 4, false);
|
||||
|
@ -816,7 +817,7 @@ suite('Editor Controller - Cursor', () => {
|
|||
'<property id="SomeThing" key="SomeKey" value="00X"/>',
|
||||
].join('\n'));
|
||||
const config = new TestConfiguration({});
|
||||
const viewModel = new ViewModel(0, config, model, null!);
|
||||
const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
|
||||
const cursor = new Cursor(config, model, viewModel);
|
||||
|
||||
moveTo(cursor, 10, 10, false);
|
||||
|
@ -880,7 +881,7 @@ suite('Editor Controller - Cursor', () => {
|
|||
'<property id="SomeThing" key="SomeKey" value="00X"/>',
|
||||
].join('\n'));
|
||||
const config = new TestConfiguration({});
|
||||
const viewModel = new ViewModel(0, config, model, null!);
|
||||
const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
|
||||
const cursor = new Cursor(config, model, viewModel);
|
||||
|
||||
moveTo(cursor, 10, 10, false);
|
||||
|
@ -929,7 +930,7 @@ suite('Editor Controller - Cursor', () => {
|
|||
'var newer = require("gulp-newer");',
|
||||
].join('\n'));
|
||||
const config = new TestConfiguration({});
|
||||
const viewModel = new ViewModel(0, config, model, null!);
|
||||
const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
|
||||
const cursor = new Cursor(config, model, viewModel);
|
||||
|
||||
moveTo(cursor, 1, 4, false);
|
||||
|
@ -2074,7 +2075,7 @@ suite('Editor Controller - Regression tests', () => {
|
|||
wordWrap: 'wordWrapColumn',
|
||||
wordWrapColumn: 100
|
||||
});
|
||||
const viewModel = new ViewModel(0, config, model, null!);
|
||||
const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
|
||||
const cursor = new Cursor(config, model, viewModel);
|
||||
|
||||
moveTo(cursor, 1, 43, false);
|
||||
|
@ -3834,7 +3835,7 @@ function usingCursor(opts: ICursorOpts, callback: (model: TextModel, cursor: Cur
|
|||
let model = createTextModel(opts.text.join('\n'), opts.modelOpts, opts.languageIdentifier);
|
||||
model.forceTokenization(model.getLineCount());
|
||||
let config = new TestConfiguration(opts.editorOpts || {});
|
||||
let viewModel = new ViewModel(0, config, model, null!);
|
||||
let viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
|
||||
let cursor = new Cursor(config, model, viewModel);
|
||||
|
||||
callback(model, cursor);
|
||||
|
|
|
@ -13,6 +13,7 @@ import { Selection } from 'vs/editor/common/core/selection';
|
|||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
|
||||
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
|
||||
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
|
||||
|
||||
suite('Cursor move command test', () => {
|
||||
|
||||
|
@ -32,7 +33,7 @@ suite('Cursor move command test', () => {
|
|||
|
||||
thisModel = TextModel.createFromString(text);
|
||||
thisConfiguration = new TestConfiguration({});
|
||||
thisViewModel = new ViewModel(0, thisConfiguration, thisModel, null!);
|
||||
thisViewModel = new ViewModel(0, thisConfiguration, thisModel, MonospaceLineBreaksComputerFactory.create(thisConfiguration.options), null!);
|
||||
thisCursor = new Cursor(thisConfiguration, thisModel, thisViewModel);
|
||||
});
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ suite('viewLineRenderer.renderLine', () => {
|
|||
[],
|
||||
tabSize,
|
||||
0,
|
||||
0,
|
||||
-1,
|
||||
'none',
|
||||
false,
|
||||
|
@ -88,6 +89,7 @@ suite('viewLineRenderer.renderLine', () => {
|
|||
[],
|
||||
tabSize,
|
||||
0,
|
||||
0,
|
||||
-1,
|
||||
'none',
|
||||
false,
|
||||
|
@ -140,6 +142,7 @@ suite('viewLineRenderer.renderLine', () => {
|
|||
]),
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
6,
|
||||
'boundary',
|
||||
|
@ -232,6 +235,7 @@ suite('viewLineRenderer.renderLine', () => {
|
|||
lineParts,
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'boundary',
|
||||
|
@ -295,6 +299,7 @@ suite('viewLineRenderer.renderLine', () => {
|
|||
lineParts,
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
|
@ -358,6 +363,7 @@ suite('viewLineRenderer.renderLine', () => {
|
|||
lineParts,
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
|
@ -398,6 +404,7 @@ suite('viewLineRenderer.renderLine', () => {
|
|||
lineParts,
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
|
@ -429,6 +436,7 @@ suite('viewLineRenderer.renderLine', () => {
|
|||
lineParts,
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
|
@ -530,6 +538,7 @@ suite('viewLineRenderer.renderLine', () => {
|
|||
lineParts,
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
|
@ -569,6 +578,7 @@ suite('viewLineRenderer.renderLine', () => {
|
|||
lineParts,
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
|
@ -599,6 +609,7 @@ suite('viewLineRenderer.renderLine', () => {
|
|||
lineParts,
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
|
@ -646,6 +657,7 @@ suite('viewLineRenderer.renderLine', () => {
|
|||
lineParts,
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
|
@ -728,6 +740,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens(tokens),
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
renderWhitespace,
|
||||
|
@ -754,6 +767,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(21, 3)]),
|
||||
[new LineDecoration(1, 22, 'link', InlineDecorationType.Regular)],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
|
@ -794,6 +808,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
new LineDecoration(13, 51, 'detected-link', InlineDecorationType.Regular)
|
||||
],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
|
@ -1209,6 +1224,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
new LineDecoration(2, 8, 'c', InlineDecorationType.Regular),
|
||||
],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
|
@ -1250,6 +1266,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(4, 3)]),
|
||||
[new LineDecoration(1, 2, 'before', InlineDecorationType.Before)],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'all',
|
||||
|
@ -1283,6 +1300,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(4, 3)]),
|
||||
[new LineDecoration(2, 3, 'before', InlineDecorationType.Before)],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'all',
|
||||
|
@ -1317,6 +1335,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(0, 3)]),
|
||||
[new LineDecoration(1, 2, 'before', InlineDecorationType.Before)],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'all',
|
||||
|
@ -1347,6 +1366,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(7, 3)]),
|
||||
[new LineDecoration(7, 8, 'inline-folded', InlineDecorationType.After)],
|
||||
2,
|
||||
0,
|
||||
10,
|
||||
10000,
|
||||
'none',
|
||||
|
@ -1381,6 +1401,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
new LineDecoration(0, 1, 'after', InlineDecorationType.After),
|
||||
],
|
||||
2,
|
||||
0,
|
||||
10,
|
||||
10000,
|
||||
'none',
|
||||
|
@ -1414,6 +1435,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-4 ced-TextEditorDecorationType2-4', InlineDecorationType.After),
|
||||
],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
10000,
|
||||
'none',
|
||||
|
@ -1445,6 +1467,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(15, 3)]),
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
10000,
|
||||
'none',
|
||||
|
@ -1475,6 +1498,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(15, 3)]),
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
10000,
|
||||
'all',
|
||||
|
@ -1511,6 +1535,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(53, 3)]),
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
10000,
|
||||
'none',
|
||||
|
@ -1541,6 +1566,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(100, 3)]),
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
10000,
|
||||
'none',
|
||||
|
@ -1573,6 +1599,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(105, 3)]),
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
10000,
|
||||
'none',
|
||||
|
@ -1604,6 +1631,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(59, 3)]),
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
10000,
|
||||
'boundary',
|
||||
|
@ -1633,6 +1661,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(194, 3)]),
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
10000,
|
||||
'none',
|
||||
|
@ -1666,6 +1695,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens([createPart(194, 3)]),
|
||||
[],
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
10000,
|
||||
'none',
|
||||
|
@ -1695,6 +1725,7 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
createViewLineTokens(parts),
|
||||
[],
|
||||
tabSize,
|
||||
0,
|
||||
10,
|
||||
-1,
|
||||
'none',
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
|
||||
import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper';
|
||||
import { ILineMapperFactory, ILineMapping } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
|
||||
function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAfter: number, annotatedText: string, wrappingIndent = WrappingIndent.None): ILineMapping | null {
|
||||
// Create version of `annotatedText` with line break markers removed
|
||||
let rawText = '';
|
||||
let currentLineIndex = 0;
|
||||
let lineIndices: number[] = [];
|
||||
for (let i = 0, len = annotatedText.length; i < len; i++) {
|
||||
if (annotatedText.charAt(i) === '|') {
|
||||
currentLineIndex++;
|
||||
} else {
|
||||
rawText += annotatedText.charAt(i);
|
||||
lineIndices[rawText.length - 1] = currentLineIndex;
|
||||
}
|
||||
}
|
||||
|
||||
const mapper = factory.createLineMapping(rawText, tabSize, breakAfter, 2, wrappingIndent);
|
||||
|
||||
// Insert line break markers again, according to algorithm
|
||||
let actualAnnotatedText = '';
|
||||
if (mapper) {
|
||||
let previousLineIndex = 0;
|
||||
for (let i = 0, len = rawText.length; i < len; i++) {
|
||||
let r = mapper.getOutputPositionOfInputOffset(i);
|
||||
if (previousLineIndex !== r.outputLineIndex) {
|
||||
previousLineIndex = r.outputLineIndex;
|
||||
actualAnnotatedText += '|';
|
||||
}
|
||||
actualAnnotatedText += rawText.charAt(i);
|
||||
}
|
||||
} else {
|
||||
// No wrapping
|
||||
actualAnnotatedText = rawText;
|
||||
}
|
||||
|
||||
assert.equal(actualAnnotatedText, annotatedText);
|
||||
|
||||
return mapper;
|
||||
}
|
||||
|
||||
suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => {
|
||||
test('CharacterHardWrappingLineMapper', () => {
|
||||
|
||||
let factory = new CharacterHardWrappingLineMapperFactory('(', ')', '.');
|
||||
|
||||
// Empty string
|
||||
assertLineMapping(factory, 4, 5, '');
|
||||
|
||||
// No wrapping if not necessary
|
||||
assertLineMapping(factory, 4, 5, 'aaa');
|
||||
assertLineMapping(factory, 4, 5, 'aaaaa');
|
||||
assertLineMapping(factory, 4, -1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
|
||||
|
||||
// Acts like hard wrapping if no char found
|
||||
assertLineMapping(factory, 4, 5, 'aaaaa|a');
|
||||
|
||||
// Honors obtrusive wrapping character
|
||||
assertLineMapping(factory, 4, 5, 'aaaaa|.');
|
||||
assertLineMapping(factory, 4, 5, 'aaaaa|a.|aaa.|aa');
|
||||
assertLineMapping(factory, 4, 5, 'aaaaa|a..|aaa.|aa');
|
||||
assertLineMapping(factory, 4, 5, 'aaaaa|a...|aaa.|aa');
|
||||
assertLineMapping(factory, 4, 5, 'aaaaa|a....|aaa.|aa');
|
||||
|
||||
// Honors tabs when computing wrapping position
|
||||
assertLineMapping(factory, 4, 5, '\t');
|
||||
assertLineMapping(factory, 4, 5, '\ta|aa');
|
||||
assertLineMapping(factory, 4, 5, '\ta|\ta|a');
|
||||
assertLineMapping(factory, 4, 5, 'aa\ta');
|
||||
assertLineMapping(factory, 4, 5, 'aa\ta|a');
|
||||
|
||||
// Honors wrapping before characters (& gives it priority)
|
||||
assertLineMapping(factory, 4, 5, 'aaa.|aa');
|
||||
assertLineMapping(factory, 4, 5, 'aaa|(.aa');
|
||||
|
||||
// Honors wrapping after characters (& gives it priority)
|
||||
assertLineMapping(factory, 4, 5, 'aaa))|).aaa');
|
||||
assertLineMapping(factory, 4, 5, 'aaa))|)|.aaaa');
|
||||
assertLineMapping(factory, 4, 5, 'aaa)|()|.aaa');
|
||||
assertLineMapping(factory, 4, 5, 'aaa(|()|.aaa');
|
||||
assertLineMapping(factory, 4, 5, 'aa.(|()|.aaa');
|
||||
assertLineMapping(factory, 4, 5, 'aa.|(.)|.aaa');
|
||||
});
|
||||
|
||||
test('CharacterHardWrappingLineMapper - CJK and Kinsoku Shori', () => {
|
||||
let factory = new CharacterHardWrappingLineMapperFactory('(', ')', '.');
|
||||
assertLineMapping(factory, 4, 5, 'aa \u5b89|\u5b89');
|
||||
assertLineMapping(factory, 4, 5, '\u3042 \u5b89|\u5b89');
|
||||
assertLineMapping(factory, 4, 5, '\u3042\u3042|\u5b89\u5b89');
|
||||
assertLineMapping(factory, 4, 5, 'aa |\u5b89)\u5b89|\u5b89');
|
||||
assertLineMapping(factory, 4, 5, 'aa \u3042|\u5b89\u3042)|\u5b89');
|
||||
assertLineMapping(factory, 4, 5, 'aa |(\u5b89aa|\u5b89');
|
||||
});
|
||||
|
||||
test('CharacterHardWrappingLineMapper - WrappingIndent.Same', () => {
|
||||
let factory = new CharacterHardWrappingLineMapperFactory('', ' ', '');
|
||||
assertLineMapping(factory, 4, 38, ' *123456789012345678901234567890123456|7890', WrappingIndent.Same);
|
||||
});
|
||||
|
||||
test('issue #16332: Scroll bar overlaying on top of text', () => {
|
||||
let factory = new CharacterHardWrappingLineMapperFactory('', ' ', '');
|
||||
assertLineMapping(factory, 4, 24, 'a/ very/long/line/of/tex|t/that/expands/beyon|d/your/typical/line/|of/code/', WrappingIndent.Indent);
|
||||
});
|
||||
|
||||
test('issue #35162: wrappingIndent not consistently working', () => {
|
||||
let factory = new CharacterHardWrappingLineMapperFactory('', ' ', '');
|
||||
let mapper = assertLineMapping(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent);
|
||||
assert.equal(mapper!.getWrappedLinesIndent(), ' \t');
|
||||
});
|
||||
|
||||
test('issue #75494: surrogate pairs', () => {
|
||||
let factory = new CharacterHardWrappingLineMapperFactory('', ' ', '');
|
||||
assertLineMapping(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', WrappingIndent.Same);
|
||||
});
|
||||
|
||||
test('CharacterHardWrappingLineMapper - WrappingIndent.DeepIndent', () => {
|
||||
let factory = new CharacterHardWrappingLineMapperFactory('', ' ', '');
|
||||
let mapper = assertLineMapping(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent);
|
||||
assert.equal(mapper!.getWrappedLinesIndent(), ' \t\t');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,240 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { WrappingIndent, EditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
|
||||
import { ILineBreaksComputerFactory, LineBreakData } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
|
||||
function parseAnnotatedText(annotatedText: string): { text: string; indices: number[]; } {
|
||||
let text = '';
|
||||
let currentLineIndex = 0;
|
||||
let indices: number[] = [];
|
||||
for (let i = 0, len = annotatedText.length; i < len; i++) {
|
||||
if (annotatedText.charAt(i) === '|') {
|
||||
currentLineIndex++;
|
||||
} else {
|
||||
text += annotatedText.charAt(i);
|
||||
indices[text.length - 1] = currentLineIndex;
|
||||
}
|
||||
}
|
||||
return { text: text, indices: indices };
|
||||
}
|
||||
|
||||
function toAnnotatedText(text: string, lineBreakData: LineBreakData | null): string {
|
||||
// Insert line break markers again, according to algorithm
|
||||
let actualAnnotatedText = '';
|
||||
if (lineBreakData) {
|
||||
let previousLineIndex = 0;
|
||||
for (let i = 0, len = text.length; i < len; i++) {
|
||||
let r = LineBreakData.getOutputPositionOfInputOffset(lineBreakData.breakOffsets, i);
|
||||
if (previousLineIndex !== r.outputLineIndex) {
|
||||
previousLineIndex = r.outputLineIndex;
|
||||
actualAnnotatedText += '|';
|
||||
}
|
||||
actualAnnotatedText += text.charAt(i);
|
||||
}
|
||||
} else {
|
||||
// No wrapping
|
||||
actualAnnotatedText = text;
|
||||
}
|
||||
return actualAnnotatedText;
|
||||
}
|
||||
|
||||
function getLineBreakData(factory: ILineBreaksComputerFactory, tabSize: number, breakAfter: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, text: string, previousLineBreakData: LineBreakData | null): LineBreakData | null {
|
||||
const lineBreaksComputer = factory.createLineBreaksComputer(tabSize, breakAfter, columnsForFullWidthChar, wrappingIndent);
|
||||
const previousLineBreakDataClone = previousLineBreakData ? new LineBreakData(previousLineBreakData.breakOffsets.slice(0), previousLineBreakData.breakOffsetsVisibleColumn.slice(0), previousLineBreakData.wrappedTextIndentLength) : null;
|
||||
lineBreaksComputer.addRequest(text, previousLineBreakDataClone);
|
||||
return lineBreaksComputer.finalize()[0];
|
||||
}
|
||||
|
||||
function assertLineBreaks(factory: ILineBreaksComputerFactory, tabSize: number, breakAfter: number, annotatedText: string, wrappingIndent = WrappingIndent.None): LineBreakData | null {
|
||||
// Create version of `annotatedText` with line break markers removed
|
||||
const text = parseAnnotatedText(annotatedText).text;
|
||||
const lineBreakData = getLineBreakData(factory, tabSize, breakAfter, 2, wrappingIndent, text, null);
|
||||
const actualAnnotatedText = toAnnotatedText(text, lineBreakData);
|
||||
|
||||
assert.equal(actualAnnotatedText, annotatedText);
|
||||
|
||||
return lineBreakData;
|
||||
}
|
||||
|
||||
suite('Editor ViewModel - MonospaceLineBreaksComputer', () => {
|
||||
test('MonospaceLineBreaksComputer', () => {
|
||||
|
||||
let factory = new MonospaceLineBreaksComputerFactory('(', '\t).');
|
||||
|
||||
// Empty string
|
||||
assertLineBreaks(factory, 4, 5, '');
|
||||
|
||||
// No wrapping if not necessary
|
||||
assertLineBreaks(factory, 4, 5, 'aaa');
|
||||
assertLineBreaks(factory, 4, 5, 'aaaaa');
|
||||
assertLineBreaks(factory, 4, -1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
|
||||
|
||||
// Acts like hard wrapping if no char found
|
||||
assertLineBreaks(factory, 4, 5, 'aaaaa|a');
|
||||
|
||||
// Honors wrapping character
|
||||
assertLineBreaks(factory, 4, 5, 'aaaaa|.');
|
||||
assertLineBreaks(factory, 4, 5, 'aaaaa|a.|aaa.|aa');
|
||||
assertLineBreaks(factory, 4, 5, 'aaaaa|a..|aaa.|aa');
|
||||
assertLineBreaks(factory, 4, 5, 'aaaaa|a...|aaa.|aa');
|
||||
assertLineBreaks(factory, 4, 5, 'aaaaa|a....|aaa.|aa');
|
||||
|
||||
// Honors tabs when computing wrapping position
|
||||
assertLineBreaks(factory, 4, 5, '\t');
|
||||
assertLineBreaks(factory, 4, 5, '\t|aaa');
|
||||
assertLineBreaks(factory, 4, 5, '\t|a\t|aa');
|
||||
assertLineBreaks(factory, 4, 5, 'aa\ta');
|
||||
assertLineBreaks(factory, 4, 5, 'aa\t|aa');
|
||||
|
||||
// Honors wrapping before characters (& gives it priority)
|
||||
assertLineBreaks(factory, 4, 5, 'aaa.|aa');
|
||||
assertLineBreaks(factory, 4, 5, 'aaa(.|aa');
|
||||
|
||||
// Honors wrapping after characters (& gives it priority)
|
||||
assertLineBreaks(factory, 4, 5, 'aaa))|).aaa');
|
||||
assertLineBreaks(factory, 4, 5, 'aaa))|).|aaaa');
|
||||
assertLineBreaks(factory, 4, 5, 'aaa)|().|aaa');
|
||||
assertLineBreaks(factory, 4, 5, 'aaa(|().|aaa');
|
||||
assertLineBreaks(factory, 4, 5, 'aa.(|().|aaa');
|
||||
assertLineBreaks(factory, 4, 5, 'aa.(.|).aaa');
|
||||
});
|
||||
|
||||
function assertIncrementalLineBreaks(factory: ILineBreaksComputerFactory, text: string, tabSize: number, breakAfter1: number, annotatedText1: string, breakAfter2: number, annotatedText2: string, wrappingIndent = WrappingIndent.None): void {
|
||||
// sanity check the test
|
||||
assert.equal(text, parseAnnotatedText(annotatedText1).text);
|
||||
assert.equal(text, parseAnnotatedText(annotatedText2).text);
|
||||
|
||||
// check that the direct mapping is ok for 1
|
||||
const directLineBreakData1 = getLineBreakData(factory, tabSize, breakAfter1, 2, wrappingIndent, text, null);
|
||||
assert.equal(toAnnotatedText(text, directLineBreakData1), annotatedText1);
|
||||
|
||||
// check that the direct mapping is ok for 2
|
||||
const directLineBreakData2 = getLineBreakData(factory, tabSize, breakAfter2, 2, wrappingIndent, text, null);
|
||||
assert.equal(toAnnotatedText(text, directLineBreakData2), annotatedText2);
|
||||
|
||||
// check that going from 1 to 2 is ok
|
||||
const lineBreakData2from1 = getLineBreakData(factory, tabSize, breakAfter2, 2, wrappingIndent, text, directLineBreakData1);
|
||||
assert.equal(toAnnotatedText(text, lineBreakData2from1), annotatedText2);
|
||||
assert.deepEqual(lineBreakData2from1, directLineBreakData2);
|
||||
|
||||
// check that going from 2 to 1 is ok
|
||||
const lineBreakData1from2 = getLineBreakData(factory, tabSize, breakAfter1, 2, wrappingIndent, text, directLineBreakData2);
|
||||
assert.equal(toAnnotatedText(text, lineBreakData1from2), annotatedText1);
|
||||
assert.deepEqual(lineBreakData1from2, directLineBreakData1);
|
||||
}
|
||||
|
||||
test('MonospaceLineBreaksComputer incremental 1', () => {
|
||||
|
||||
let factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
|
||||
|
||||
assertIncrementalLineBreaks(
|
||||
factory, 'just some text and more', 4,
|
||||
10, 'just some |text and |more',
|
||||
15, 'just some text |and more'
|
||||
);
|
||||
|
||||
assertIncrementalLineBreaks(
|
||||
factory, 'Cu scripserit suscipiantur eos, in affert pericula contentiones sed, cetero sanctus et pro. Ius vidit magna regione te, sit ei elaboraret liberavisse. Mundi verear eu mea, eam vero scriptorem in, vix in menandri assueverit. Natum definiebas cu vim. Vim doming vocibus efficiantur id. In indoctum deseruisse voluptatum vim, ad debitis verterem sed.', 4,
|
||||
47, 'Cu scripserit suscipiantur eos, in affert |pericula contentiones sed, cetero sanctus et |pro. Ius vidit magna regione te, sit ei |elaboraret liberavisse. Mundi verear eu mea, |eam vero scriptorem in, vix in menandri |assueverit. Natum definiebas cu vim. Vim |doming vocibus efficiantur id. In indoctum |deseruisse voluptatum vim, ad debitis verterem |sed.',
|
||||
142, 'Cu scripserit suscipiantur eos, in affert pericula contentiones sed, cetero sanctus et pro. Ius vidit magna regione te, sit ei elaboraret |liberavisse. Mundi verear eu mea, eam vero scriptorem in, vix in menandri assueverit. Natum definiebas cu vim. Vim doming vocibus efficiantur |id. In indoctum deseruisse voluptatum vim, ad debitis verterem sed.',
|
||||
);
|
||||
|
||||
assertIncrementalLineBreaks(
|
||||
factory, 'An his legere persecuti, oblique delicata efficiantur ex vix, vel at graecis officiis maluisset. Et per impedit voluptua, usu discere maiorum at. Ut assum ornatus temporibus vis, an sea melius pericula. Ea dicunt oblique phaedrum nam, eu duo movet nobis. His melius facilis eu, vim malorum temporibus ne. Nec no sale regione, meliore civibus placerat id eam. Mea alii fabulas definitionem te, agam volutpat ad vis, et per bonorum nonumes repudiandae.', 4,
|
||||
57, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt |oblique phaedrum nam, eu duo movet nobis. His melius |facilis eu, vim malorum temporibus ne. Nec no sale |regione, meliore civibus placerat id eam. Mea alii |fabulas definitionem te, agam volutpat ad vis, et per |bonorum nonumes repudiandae.',
|
||||
58, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt oblique |phaedrum nam, eu duo movet nobis. His melius facilis eu, |vim malorum temporibus ne. Nec no sale regione, meliore |civibus placerat id eam. Mea alii fabulas definitionem |te, agam volutpat ad vis, et per bonorum nonumes |repudiandae.'
|
||||
);
|
||||
|
||||
assertIncrementalLineBreaks(
|
||||
factory, '\t\t"owner": "vscode",', 4,
|
||||
14, '\t\t"owner|": |"vscod|e",',
|
||||
16, '\t\t"owner":| |"vscode"|,',
|
||||
WrappingIndent.Same
|
||||
);
|
||||
|
||||
assertIncrementalLineBreaks(
|
||||
factory, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇&👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', 4,
|
||||
51, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇&|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬',
|
||||
50, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇|&|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬',
|
||||
WrappingIndent.Same
|
||||
);
|
||||
|
||||
assertIncrementalLineBreaks(
|
||||
factory, '🐇👬&🌞🌖', 4,
|
||||
5, '🐇👬&|🌞🌖',
|
||||
4, '🐇👬|&|🌞🌖',
|
||||
WrappingIndent.Same
|
||||
);
|
||||
|
||||
assertIncrementalLineBreaks(
|
||||
factory, '\t\tfunc(\'🌞🏇🍼🌞🏇🍼🐇&👬🌖🌞👬🌖🌞🏇🍼🐇👬\', WrappingIndent.Same);', 4,
|
||||
26, '\t\tfunc|(\'🌞🏇🍼🌞🏇🍼🐇&|👬🌖🌞👬🌖🌞🏇🍼🐇|👬\', |WrappingIndent.|Same);',
|
||||
27, '\t\tfunc|(\'🌞🏇🍼🌞🏇🍼🐇&|👬🌖🌞👬🌖🌞🏇🍼🐇|👬\', |WrappingIndent.|Same);',
|
||||
WrappingIndent.Same
|
||||
);
|
||||
|
||||
assertIncrementalLineBreaks(
|
||||
factory, 'factory, "xtxtfunc(x"🌞🏇🍼🌞🏇🍼🐇&👬🌖🌞👬🌖🌞🏇🍼🐇👬x"', 4,
|
||||
16, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼|🐇&|👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"',
|
||||
17, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼🐇|&👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"',
|
||||
WrappingIndent.Same
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
test('MonospaceLineBreaksComputer - CJK and Kinsoku Shori', () => {
|
||||
let factory = new MonospaceLineBreaksComputerFactory('(', '\t)');
|
||||
assertLineBreaks(factory, 4, 5, 'aa \u5b89|\u5b89');
|
||||
assertLineBreaks(factory, 4, 5, '\u3042 \u5b89|\u5b89');
|
||||
assertLineBreaks(factory, 4, 5, '\u3042\u3042|\u5b89\u5b89');
|
||||
assertLineBreaks(factory, 4, 5, 'aa |\u5b89)\u5b89|\u5b89');
|
||||
assertLineBreaks(factory, 4, 5, 'aa \u3042|\u5b89\u3042)|\u5b89');
|
||||
assertLineBreaks(factory, 4, 5, 'aa |(\u5b89aa|\u5b89');
|
||||
});
|
||||
|
||||
test('MonospaceLineBreaksComputer - WrappingIndent.Same', () => {
|
||||
let factory = new MonospaceLineBreaksComputerFactory('', '\t ');
|
||||
assertLineBreaks(factory, 4, 38, ' *123456789012345678901234567890123456|7890', WrappingIndent.Same);
|
||||
});
|
||||
|
||||
test('issue #16332: Scroll bar overlaying on top of text', () => {
|
||||
let factory = new MonospaceLineBreaksComputerFactory('', '\t ');
|
||||
assertLineBreaks(factory, 4, 24, 'a/ very/long/line/of/tex|t/that/expands/beyon|d/your/typical/line/|of/code/', WrappingIndent.Indent);
|
||||
});
|
||||
|
||||
test('issue #35162: wrappingIndent not consistently working', () => {
|
||||
let factory = new MonospaceLineBreaksComputerFactory('', '\t ');
|
||||
let mapper = assertLineBreaks(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent);
|
||||
assert.equal(mapper!.wrappedTextIndentLength, ' '.length);
|
||||
});
|
||||
|
||||
test('issue #75494: surrogate pairs', () => {
|
||||
let factory = new MonospaceLineBreaksComputerFactory('\t', ' ');
|
||||
assertLineBreaks(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼|🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼|🐇👬', WrappingIndent.Same);
|
||||
});
|
||||
|
||||
test('issue #75494: surrogate pairs overrun 1', () => {
|
||||
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
|
||||
assertLineBreaks(factory, 4, 4, '🐇👬|&|🌞🌖', WrappingIndent.Same);
|
||||
});
|
||||
|
||||
test('issue #75494: surrogate pairs overrun 2', () => {
|
||||
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
|
||||
assertLineBreaks(factory, 4, 17, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼🐇|&👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"', WrappingIndent.Same);
|
||||
});
|
||||
|
||||
test('MonospaceLineBreaksComputer - WrappingIndent.DeepIndent', () => {
|
||||
let factory = new MonospaceLineBreaksComputerFactory('', '\t ');
|
||||
let mapper = assertLineBreaks(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent);
|
||||
assert.equal(mapper!.wrappedTextIndentLength, ' '.length);
|
||||
});
|
||||
|
||||
test('issue #33366: Word wrap algorithm behaves differently around punctuation', () => {
|
||||
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
|
||||
assertLineBreaks(factory, 4, 23, 'this is a line of |text, text that sits |on a line', WrappingIndent.Same);
|
||||
});
|
||||
});
|
|
@ -4,9 +4,18 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { toUint32Array } from 'vs/base/common/uint';
|
||||
import { toUint32 } from 'vs/base/common/uint';
|
||||
import { PrefixSumComputer, PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer';
|
||||
|
||||
function toUint32Array(arr: number[]): Uint32Array {
|
||||
const len = arr.length;
|
||||
const r = new Uint32Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
r[i] = toUint32(arr[i]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
suite('Editor ViewModel - PrefixSumComputer', () => {
|
||||
|
||||
test('PrefixSumComputer', () => {
|
||||
|
|
|
@ -9,14 +9,12 @@ import { IViewLineTokens } from 'vs/editor/common/core/lineTokens';
|
|||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { TokenizationResult2 } from 'vs/editor/common/core/token';
|
||||
import { toUint32Array } from 'vs/base/common/uint';
|
||||
import { EndOfLinePreference } from 'vs/editor/common/model';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { NULL_STATE } from 'vs/editor/common/modes/nullMode';
|
||||
import { CharacterHardWrappingLineMapperFactory, CharacterHardWrappingLineMapping } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper';
|
||||
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
|
||||
import { ILineMapping, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
|
||||
import { LineBreakData, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
import { ViewLineData } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
|
@ -25,7 +23,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
|||
suite('Editor ViewModel - SplitLinesCollection', () => {
|
||||
test('SplitLine', () => {
|
||||
let model1 = createModel('My First LineMy Second LineAnd another one');
|
||||
let line1 = createSplitLine([13, 14, 15], '');
|
||||
let line1 = createSplitLine([13, 14, 15], [13, 13 + 14, 13 + 14 + 15], 0);
|
||||
|
||||
assert.equal(line1.getViewLineCount(), 3);
|
||||
assert.equal(line1.getViewLineContent(model1, 1, 0), 'My First Line');
|
||||
|
@ -54,38 +52,38 @@ suite('Editor ViewModel - SplitLinesCollection', () => {
|
|||
}
|
||||
|
||||
model1 = createModel('My First LineMy Second LineAnd another one');
|
||||
line1 = createSplitLine([13, 14, 15], '\t');
|
||||
line1 = createSplitLine([13, 14, 15], [13, 13 + 14, 13 + 14 + 15], 4);
|
||||
|
||||
assert.equal(line1.getViewLineCount(), 3);
|
||||
assert.equal(line1.getViewLineContent(model1, 1, 0), 'My First Line');
|
||||
assert.equal(line1.getViewLineContent(model1, 1, 1), '\tMy Second Line');
|
||||
assert.equal(line1.getViewLineContent(model1, 1, 2), '\tAnd another one');
|
||||
assert.equal(line1.getViewLineContent(model1, 1, 1), ' My Second Line');
|
||||
assert.equal(line1.getViewLineContent(model1, 1, 2), ' And another one');
|
||||
assert.equal(line1.getViewLineMaxColumn(model1, 1, 0), 14);
|
||||
assert.equal(line1.getViewLineMaxColumn(model1, 1, 1), 16);
|
||||
assert.equal(line1.getViewLineMaxColumn(model1, 1, 2), 17);
|
||||
for (let col = 1; col <= 14; col++) {
|
||||
assert.equal(line1.getModelColumnOfViewPosition(0, col), col, 'getInputColumnOfOutputPosition(0, ' + col + ')');
|
||||
}
|
||||
for (let col = 1; col <= 1; col++) {
|
||||
assert.equal(line1.getModelColumnOfViewPosition(1, 1), 13 + col, 'getInputColumnOfOutputPosition(1, ' + col + ')');
|
||||
}
|
||||
for (let col = 2; col <= 16; col++) {
|
||||
assert.equal(line1.getModelColumnOfViewPosition(1, col), 13 + col - 1, 'getInputColumnOfOutputPosition(1, ' + col + ')');
|
||||
}
|
||||
for (let col = 1; col <= 1; col++) {
|
||||
assert.equal(line1.getModelColumnOfViewPosition(2, col), 13 + 14 + col, 'getInputColumnOfOutputPosition(2, ' + col + ')');
|
||||
}
|
||||
for (let col = 2; col <= 17; col++) {
|
||||
assert.equal(line1.getModelColumnOfViewPosition(2, col), 13 + 14 + col - 1, 'getInputColumnOfOutputPosition(2, ' + col + ')');
|
||||
assert.equal(line1.getViewLineMaxColumn(model1, 1, 1), 19);
|
||||
assert.equal(line1.getViewLineMaxColumn(model1, 1, 2), 20);
|
||||
|
||||
let actualViewColumnMapping: number[][] = [];
|
||||
for (let lineIndex = 0; lineIndex < line1.getViewLineCount(); lineIndex++) {
|
||||
let actualLineViewColumnMapping: number[] = [];
|
||||
for (let col = 1; col <= line1.getViewLineMaxColumn(model1, 1, lineIndex); col++) {
|
||||
actualLineViewColumnMapping.push(line1.getModelColumnOfViewPosition(lineIndex, col));
|
||||
}
|
||||
actualViewColumnMapping.push(actualLineViewColumnMapping);
|
||||
}
|
||||
assert.deepEqual(actualViewColumnMapping, [
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
|
||||
[14, 14, 14, 14, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28],
|
||||
[28, 28, 28, 28, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43],
|
||||
]);
|
||||
|
||||
for (let col = 1; col <= 13; col++) {
|
||||
assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(0, col), 'getOutputPositionOfInputPosition(' + col + ')');
|
||||
assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(0, col), '6.getOutputPositionOfInputPosition(' + col + ')');
|
||||
}
|
||||
for (let col = 1 + 13; col <= 14 + 13; col++) {
|
||||
assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(1, 1 + col - 13), 'getOutputPositionOfInputPosition(' + col + ')');
|
||||
assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(1, 4 + col - 13), '7.getOutputPositionOfInputPosition(' + col + ')');
|
||||
}
|
||||
for (let col = 1 + 13 + 14; col <= 15 + 14 + 13; col++) {
|
||||
assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(2, 1 + col - 13 - 14), 'getOutputPositionOfInputPosition(' + col + ')');
|
||||
assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(2, 4 + col - 13 - 14), '8.getOutputPositionOfInputPosition(' + col + ')');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -95,13 +93,11 @@ suite('Editor ViewModel - SplitLinesCollection', () => {
|
|||
const fontInfo = config.options.get(EditorOption.fontInfo);
|
||||
const wordWrapBreakAfterCharacters = config.options.get(EditorOption.wordWrapBreakAfterCharacters);
|
||||
const wordWrapBreakBeforeCharacters = config.options.get(EditorOption.wordWrapBreakBeforeCharacters);
|
||||
const wordWrapBreakObtrusiveCharacters = config.options.get(EditorOption.wordWrapBreakObtrusiveCharacters);
|
||||
const wrappingIndent = config.options.get(EditorOption.wrappingIndent);
|
||||
|
||||
const hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory(
|
||||
const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory(
|
||||
wordWrapBreakBeforeCharacters,
|
||||
wordWrapBreakAfterCharacters,
|
||||
wordWrapBreakObtrusiveCharacters
|
||||
wordWrapBreakAfterCharacters
|
||||
);
|
||||
|
||||
const model = TextModel.createFromString([
|
||||
|
@ -115,7 +111,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => {
|
|||
|
||||
const linesCollection = new SplitLinesCollection(
|
||||
model,
|
||||
hardWrappingLineMapperFactory,
|
||||
lineBreaksComputerFactory,
|
||||
model.getOptions().tabSize,
|
||||
wrappingInfo.wrappingColumn,
|
||||
fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth,
|
||||
|
@ -616,12 +612,12 @@ suite('SplitLinesCollection', () => {
|
|||
]
|
||||
},
|
||||
{
|
||||
content: ' world");',
|
||||
minColumn: 4,
|
||||
maxColumn: 12,
|
||||
content: ' world");',
|
||||
minColumn: 13,
|
||||
maxColumn: 21,
|
||||
tokens: [
|
||||
{ endIndex: 9, value: 15 },
|
||||
{ endIndex: 11, value: 16 },
|
||||
{ endIndex: 18, value: 15 },
|
||||
{ endIndex: 20, value: 16 },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -658,28 +654,28 @@ suite('SplitLinesCollection', () => {
|
|||
]
|
||||
},
|
||||
{
|
||||
content: ' world, this is a ',
|
||||
minColumn: 4,
|
||||
maxColumn: 21,
|
||||
content: ' world, this is a ',
|
||||
minColumn: 13,
|
||||
maxColumn: 30,
|
||||
tokens: [
|
||||
{ endIndex: 20, value: 28 },
|
||||
{ endIndex: 29, value: 28 },
|
||||
]
|
||||
},
|
||||
{
|
||||
content: ' somewhat longer ',
|
||||
minColumn: 4,
|
||||
content: ' somewhat longer ',
|
||||
minColumn: 13,
|
||||
maxColumn: 29,
|
||||
tokens: [
|
||||
{ endIndex: 28, value: 28 },
|
||||
]
|
||||
},
|
||||
{
|
||||
content: ' line");',
|
||||
minColumn: 13,
|
||||
maxColumn: 20,
|
||||
tokens: [
|
||||
{ endIndex: 19, value: 28 },
|
||||
]
|
||||
},
|
||||
{
|
||||
content: ' line");',
|
||||
minColumn: 4,
|
||||
maxColumn: 11,
|
||||
tokens: [
|
||||
{ endIndex: 8, value: 28 },
|
||||
{ endIndex: 10, value: 29 },
|
||||
{ endIndex: 17, value: 28 },
|
||||
{ endIndex: 19, value: 29 },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -749,13 +745,11 @@ suite('SplitLinesCollection', () => {
|
|||
const fontInfo = configuration.options.get(EditorOption.fontInfo);
|
||||
const wordWrapBreakAfterCharacters = configuration.options.get(EditorOption.wordWrapBreakAfterCharacters);
|
||||
const wordWrapBreakBeforeCharacters = configuration.options.get(EditorOption.wordWrapBreakBeforeCharacters);
|
||||
const wordWrapBreakObtrusiveCharacters = configuration.options.get(EditorOption.wordWrapBreakObtrusiveCharacters);
|
||||
const wrappingIndent = configuration.options.get(EditorOption.wrappingIndent);
|
||||
|
||||
const factory = new CharacterHardWrappingLineMapperFactory(
|
||||
const factory = new MonospaceLineBreaksComputerFactory(
|
||||
wordWrapBreakBeforeCharacters,
|
||||
wordWrapBreakAfterCharacters,
|
||||
wordWrapBreakObtrusiveCharacters
|
||||
wordWrapBreakAfterCharacters
|
||||
);
|
||||
|
||||
const linesCollection = new SplitLinesCollection(
|
||||
|
@ -778,15 +772,16 @@ function pos(lineNumber: number, column: number): Position {
|
|||
return new Position(lineNumber, column);
|
||||
}
|
||||
|
||||
function createSplitLine(splitLengths: number[], wrappedLinesPrefix: string, isVisible: boolean = true): SplitLine {
|
||||
return new SplitLine(createLineMapping(splitLengths, wrappedLinesPrefix), isVisible);
|
||||
function createSplitLine(splitLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number, isVisible: boolean = true): SplitLine {
|
||||
return new SplitLine(createLineBreakData(splitLengths, breakingOffsetsVisibleColumn, wrappedTextIndentWidth), isVisible);
|
||||
}
|
||||
|
||||
function createLineMapping(breakingLengths: number[], wrappedLinesPrefix: string): ILineMapping {
|
||||
return new CharacterHardWrappingLineMapping(
|
||||
new PrefixSumComputer(toUint32Array(breakingLengths)),
|
||||
wrappedLinesPrefix
|
||||
);
|
||||
function createLineBreakData(breakingLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number): LineBreakData {
|
||||
let sums: number[] = [];
|
||||
for (let i = 0; i < breakingLengths.length; i++) {
|
||||
sums[i] = (i > 0 ? sums[i - 1] : 0) + breakingLengths[i];
|
||||
}
|
||||
return new LineBreakData(sums, breakingOffsetsVisibleColumn, wrappedTextIndentWidth);
|
||||
}
|
||||
|
||||
function createModel(text: string): ISimpleModel {
|
||||
|
|
|
@ -7,6 +7,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
|||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
|
||||
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
|
||||
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
|
||||
|
||||
export function testViewModel(text: string[], options: IEditorOptions, callback: (viewModel: ViewModel, model: TextModel) => void): void {
|
||||
const EDITOR_ID = 1;
|
||||
|
@ -15,7 +16,7 @@ export function testViewModel(text: string[], options: IEditorOptions, callback:
|
|||
|
||||
let model = TextModel.createFromString(text.join('\n'));
|
||||
|
||||
let viewModel = new ViewModel(EDITOR_ID, configuration, model, null!);
|
||||
let viewModel = new ViewModel(EDITOR_ID, configuration, model, MonospaceLineBreaksComputerFactory.create(configuration.options), null!);
|
||||
|
||||
callback(viewModel, model);
|
||||
|
||||
|
|
10
src/vs/monaco.d.ts
vendored
10
src/vs/monaco.d.ts
vendored
|
@ -2709,19 +2709,14 @@ declare namespace monaco.editor {
|
|||
wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent';
|
||||
/**
|
||||
* Configure word wrapping characters. A break will be introduced before these characters.
|
||||
* Defaults to '{([+'.
|
||||
* Defaults to '([{‘“〈《「『【〔([{「£¥$£¥++'.
|
||||
*/
|
||||
wordWrapBreakBeforeCharacters?: string;
|
||||
/**
|
||||
* Configure word wrapping characters. A break will be introduced after these characters.
|
||||
* Defaults to ' \t})]?|&,;'.
|
||||
* Defaults to ' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」'.
|
||||
*/
|
||||
wordWrapBreakAfterCharacters?: string;
|
||||
/**
|
||||
* Configure word wrapping characters. A break will be introduced after these characters only if no `wordWrapBreakBeforeCharacters` or `wordWrapBreakAfterCharacters` were found.
|
||||
* Defaults to '.'.
|
||||
*/
|
||||
wordWrapBreakObtrusiveCharacters?: string;
|
||||
/**
|
||||
* Performance guard: Stop rendering a line after x characters.
|
||||
* Defaults to 10000.
|
||||
|
@ -4854,6 +4849,7 @@ declare namespace monaco.languages {
|
|||
export interface CompletionList {
|
||||
suggestions: CompletionItem[];
|
||||
incomplete?: boolean;
|
||||
isDetailsResolved?: boolean;
|
||||
dispose?(): void;
|
||||
}
|
||||
|
||||
|
|
1
src/vs/vscode.d.ts
vendored
1
src/vs/vscode.d.ts
vendored
|
@ -1193,7 +1193,6 @@ declare module 'vscode' {
|
|||
* A complex edit that will be applied in one transaction on a TextEditor.
|
||||
* This holds a description of the edits and if the edits are valid (i.e. no overlapping regions, document was not changed in the meantime, etc.)
|
||||
* they can be applied on a [document](#TextDocument) associated with a [text editor](#TextEditor).
|
||||
*
|
||||
*/
|
||||
export interface TextEditorEdit {
|
||||
/**
|
||||
|
|
11
src/vs/vscode.proposed.d.ts
vendored
11
src/vs/vscode.proposed.d.ts
vendored
|
@ -1504,4 +1504,15 @@ declare module 'vscode' {
|
|||
export const onDidChangeActiveColorTheme: Event<ColorTheme>;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region https://github.com/microsoft/vscode/issues/39441
|
||||
|
||||
export interface CompletionList {
|
||||
isDetailsResolved?: boolean;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
}
|
||||
|
|
|
@ -367,6 +367,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
|||
return {
|
||||
suggestions: result.b.map(d => MainThreadLanguageFeatures._inflateSuggestDto(result.a, d)),
|
||||
incomplete: result.c,
|
||||
isDetailsResolved: result.d,
|
||||
dispose: () => typeof result.x === 'number' && this._proxy.$releaseCompletionItems(handle, result.x)
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1012,6 +1012,7 @@ export interface ISuggestResultDto {
|
|||
a: { insert: IRange, replace: IRange; };
|
||||
b: ISuggestDataDto[];
|
||||
c?: boolean;
|
||||
d?: boolean;
|
||||
}
|
||||
|
||||
export interface ISignatureHelpDto {
|
||||
|
|
|
@ -787,7 +787,8 @@ class SuggestAdapter {
|
|||
x: pid,
|
||||
b: [],
|
||||
a: { replace: typeConvert.Range.from(replaceRange), insert: typeConvert.Range.from(insertRange) },
|
||||
c: list.isIncomplete || undefined
|
||||
c: list.isIncomplete || undefined,
|
||||
d: list.isDetailsResolved || undefined
|
||||
};
|
||||
|
||||
for (let i = 0; i < list.items.length; i++) {
|
||||
|
|
|
@ -1395,7 +1395,7 @@ export class CompletionItem implements vscode.CompletionItem {
|
|||
export class CompletionList {
|
||||
|
||||
isIncomplete?: boolean;
|
||||
|
||||
isDetailsResolved?: boolean;
|
||||
items: vscode.CompletionItem[];
|
||||
|
||||
constructor(items: vscode.CompletionItem[] = [], isIncomplete: boolean = false) {
|
||||
|
|
|
@ -190,10 +190,19 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
|||
|
||||
private parseIpAddress(hex: string): string {
|
||||
let result = '';
|
||||
for (let i = hex.length - 2; (i >= 0); i -= 2) {
|
||||
result += parseInt(hex.substr(i, 2), 16);
|
||||
if (i !== 0) {
|
||||
result += '.';
|
||||
if (hex.length === 8) {
|
||||
for (let i = hex.length - 2; i >= 0; i -= 2) {
|
||||
result += parseInt(hex.substr(i, 2), 16);
|
||||
if (i !== 0) {
|
||||
result += '.';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = hex.length - 4; i >= 0; i -= 4) {
|
||||
result += parseInt(hex.substr(i, 4), 16).toString(16);
|
||||
if (i !== 0) {
|
||||
result += ':';
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -4,16 +4,22 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { Disposable, DisposableStore, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IFilesConfigurationService, AutoSaveMode, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { SaveReason, IEditorIdentifier, IEditorInput, GroupIdentifier } from 'vs/workbench/common/editor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
|
||||
export class EditorAutoSave extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
// Auto save: after delay
|
||||
private autoSaveAfterDelay: number | undefined;
|
||||
private readonly pendingAutoSavesAfterDelay = new Map<IWorkingCopy, IDisposable>();
|
||||
|
||||
// Auto save: focus change & window change
|
||||
private lastActiveEditor: IEditorInput | undefined = undefined;
|
||||
private lastActiveGroupId: GroupIdentifier | undefined = undefined;
|
||||
private lastActiveEditorControlDisposable = this._register(new DisposableStore());
|
||||
|
@ -22,17 +28,22 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution
|
|||
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
|
||||
@IHostService private readonly hostService: IHostService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService
|
||||
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
|
||||
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Figure out initial auto save config
|
||||
this.onAutoSaveConfigurationChange(filesConfigurationService.getAutoSaveConfiguration(), false);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.hostService.onDidChangeFocus(focused => this.onWindowFocusChange(focused)));
|
||||
this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange()));
|
||||
this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(() => this.onAutoSaveConfigurationChange()));
|
||||
this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(config => this.onAutoSaveConfigurationChange(config, true)));
|
||||
this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.onDidWorkingCopyChangeDirty(workingCopy)));
|
||||
}
|
||||
|
||||
private onWindowFocusChange(focused: boolean): void {
|
||||
|
@ -85,24 +96,55 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution
|
|||
}
|
||||
}
|
||||
|
||||
private onAutoSaveConfigurationChange(): void {
|
||||
let reason: SaveReason | undefined = undefined;
|
||||
switch (this.filesConfigurationService.getAutoSaveMode()) {
|
||||
case AutoSaveMode.ON_FOCUS_CHANGE:
|
||||
reason = SaveReason.FOCUS_CHANGE;
|
||||
break;
|
||||
case AutoSaveMode.ON_WINDOW_CHANGE:
|
||||
reason = SaveReason.WINDOW_CHANGE;
|
||||
break;
|
||||
case AutoSaveMode.AFTER_SHORT_DELAY:
|
||||
case AutoSaveMode.AFTER_LONG_DELAY:
|
||||
reason = SaveReason.AUTO;
|
||||
break;
|
||||
}
|
||||
private onAutoSaveConfigurationChange(config: IAutoSaveConfiguration, fromEvent: boolean): void {
|
||||
|
||||
// Update auto save after delay config
|
||||
this.autoSaveAfterDelay = (typeof config.autoSaveDelay === 'number') && config.autoSaveDelay > 0 ? config.autoSaveDelay : undefined;
|
||||
|
||||
// Trigger a save-all when auto save is enabled
|
||||
if (reason) {
|
||||
this.editorService.saveAll({ reason });
|
||||
if (fromEvent) {
|
||||
let reason: SaveReason | undefined = undefined;
|
||||
switch (this.filesConfigurationService.getAutoSaveMode()) {
|
||||
case AutoSaveMode.ON_FOCUS_CHANGE:
|
||||
reason = SaveReason.FOCUS_CHANGE;
|
||||
break;
|
||||
case AutoSaveMode.ON_WINDOW_CHANGE:
|
||||
reason = SaveReason.WINDOW_CHANGE;
|
||||
break;
|
||||
case AutoSaveMode.AFTER_SHORT_DELAY:
|
||||
case AutoSaveMode.AFTER_LONG_DELAY:
|
||||
reason = SaveReason.AUTO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (reason) {
|
||||
this.editorService.saveAll({ reason });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDidWorkingCopyChangeDirty(workingCopy: IWorkingCopy): void {
|
||||
if (typeof this.autoSaveAfterDelay !== 'number') {
|
||||
return; // auto save after delay must be enabled
|
||||
}
|
||||
|
||||
if (workingCopy.capabilities & WorkingCopyCapabilities.Untitled) {
|
||||
return; // we never auto save untitled working copies
|
||||
}
|
||||
|
||||
// Clear any running auto save operation
|
||||
dispose(this.pendingAutoSavesAfterDelay.get(workingCopy));
|
||||
this.pendingAutoSavesAfterDelay.delete(workingCopy);
|
||||
|
||||
// Working copy got dirty - start auto save
|
||||
if (workingCopy.isDirty()) {
|
||||
const handle = setTimeout(() => {
|
||||
if (workingCopy.isDirty()) {
|
||||
workingCopy.save({ reason: SaveReason.AUTO });
|
||||
}
|
||||
}, this.autoSaveAfterDelay);
|
||||
|
||||
this.pendingAutoSavesAfterDelay.set(workingCopy, toDisposable(() => clearTimeout(handle)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorInput, ITextEditorModel, IModeSupport } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, ITextEditorModel, IModeSupport, GroupIdentifier, isTextEditor } from 'vs/workbench/common/editor';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IReference } from 'vs/base/common/lifecycle';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import type { IEditorViewState } from 'vs/editor/common/editorCommon';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
/**
|
||||
* A read-only text editor input whos contents are made of the provided resource that points to an existing
|
||||
|
@ -26,7 +29,9 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport {
|
|||
private description: string | undefined,
|
||||
private readonly resource: URI,
|
||||
private preferredMode: string | undefined,
|
||||
@ITextModelService private readonly textModelResolverService: ITextModelService
|
||||
@ITextModelService private readonly textModelResolverService: ITextModelService,
|
||||
@ITextFileService private readonly textFileService: ITextFileService,
|
||||
@IEditorService private readonly editorService: IEditorService
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -104,6 +109,28 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport {
|
|||
return model;
|
||||
}
|
||||
|
||||
async saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<boolean> {
|
||||
|
||||
// Preserve view state by opening the editor first. In addition
|
||||
// this allows the user to review the contents of the editor.
|
||||
let viewState: IEditorViewState | undefined = undefined;
|
||||
const editor = await this.editorService.openEditor(this, undefined, group);
|
||||
if (isTextEditor(editor)) {
|
||||
viewState = editor.getViewState();
|
||||
}
|
||||
|
||||
// Save as
|
||||
const target = await this.textFileService.saveAs(this.resource, undefined, options);
|
||||
if (!target) {
|
||||
return false; // save cancelled
|
||||
}
|
||||
|
||||
// Open the target
|
||||
await this.editorService.openEditor({ resource: target, options: { viewState, pinned: true } }, group);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
matches(otherInput: unknown): boolean {
|
||||
if (super.matches(otherInput) === true) {
|
||||
return true;
|
||||
|
|
|
@ -13,6 +13,7 @@ import * as nls from 'vs/nls';
|
|||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { FileOperation, IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import * as colorRegistry from 'vs/platform/theme/common/colorRegistry';
|
||||
|
@ -25,7 +26,7 @@ import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS,
|
|||
import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager';
|
||||
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
|
||||
import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { CustomFileEditorInput } from './customEditorInput';
|
||||
|
@ -81,8 +82,10 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
|
|||
constructor(
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IWorkingCopyService workingCopyService: IWorkingCopyService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IWebviewService private readonly webviewService: IWebviewService,
|
||||
|
@ -112,6 +115,13 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
|
|||
this._webviewHasOwnEditFunctions = webviewHasOwnEditFunctionsContext.bindTo(contextKeyService);
|
||||
|
||||
this._register(this.editorService.onDidActiveEditorChange(() => this.updateContexts()));
|
||||
|
||||
this._register(fileService.onAfterOperation(e => {
|
||||
if (e.isOperation(FileOperation.MOVE)) {
|
||||
this.handleMovedFileInOpenedFileEditors(e.resource, e.target.resource);
|
||||
}
|
||||
}));
|
||||
|
||||
this.updateContexts();
|
||||
}
|
||||
|
||||
|
@ -262,6 +272,31 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
|
|||
this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomFileEditorInput);
|
||||
this._webviewHasOwnEditFunctions.set(true);
|
||||
}
|
||||
|
||||
private handleMovedFileInOpenedFileEditors(oldResource: URI, newResource: URI): void {
|
||||
for (const group of this.editorGroupService.groups) {
|
||||
for (const editor of group.editors) {
|
||||
if (!(editor instanceof CustomFileEditorInput)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const editorInfo = this._editorInfoStore.get(editor.viewType);
|
||||
if (!editorInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!editorInfo.matches(newResource)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const replacement = this.createInput(newResource, editor.viewType, group);
|
||||
this.editorService.replaceEditors([{
|
||||
editor: editor,
|
||||
replacement: replacement,
|
||||
}], group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const customEditorsAssociationsKey = 'workbench.experimental.editorAssociations';
|
||||
|
|
|
@ -202,9 +202,9 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi
|
|||
let { message, detail } = getMoveToTrashMessage(distinctElements);
|
||||
detail += detail ? '\n' : '';
|
||||
if (isWindows) {
|
||||
detail += nls.localize('undoBin', "You can restore from the Recycle Bin.");
|
||||
detail += distinctElements.length > 1 ? nls.localize('undoBinFiles', "You can restore these files from the Recycle Bin.") : nls.localize('undoBin', "You can restore this file from the Recycle Bin.");
|
||||
} else {
|
||||
detail += nls.localize('undoTrash', "You can restore from the Trash.");
|
||||
detail += distinctElements.length > 1 ? nls.localize('undoTrashFiles', "You can restore these files from the Trash.") : nls.localize('undoTrash', "You can restore this file from the Trash.");
|
||||
}
|
||||
|
||||
confirmDeletePromise = dialogService.confirm({
|
||||
|
|
|
@ -69,14 +69,16 @@ export class FileEditorInput extends TextEditorInput implements IFileEditorInput
|
|||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Model changes
|
||||
// Dirty changes
|
||||
this._register(this.textFileService.models.onModelDirty(e => this.onDirtyStateChange(e)));
|
||||
this._register(this.textFileService.models.onModelSaveError(e => this.onDirtyStateChange(e)));
|
||||
this._register(this.textFileService.models.onModelSaved(e => this.onDirtyStateChange(e)));
|
||||
this._register(this.textFileService.models.onModelReverted(e => this.onDirtyStateChange(e)));
|
||||
this._register(this.textFileService.models.onModelOrphanedChanged(e => this.onModelOrphanedChanged(e)));
|
||||
|
||||
// Label changes
|
||||
this._register(this.labelService.onDidChangeFormatters(() => FileEditorInput.MEMOIZER.clear()));
|
||||
this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(() => FileEditorInput.MEMOIZER.clear()));
|
||||
this._register(this.textFileService.models.onModelOrphanedChanged(e => this.onModelOrphanedChanged(e)));
|
||||
}
|
||||
|
||||
private onDirtyStateChange(e: TextFileModelChangeEvent): void {
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { toResource } from 'vs/base/test/common/utils';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { workbenchInstantiationService, TestTextFileService, TestFileService, TestFilesConfigurationService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
|
||||
import { ITextFileService, IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { EditorInput } from 'vs/workbench/common/editor';
|
||||
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
|
||||
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
|
||||
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
|
||||
import { EditorService } from 'vs/workbench/services/editor/browser/editorService';
|
||||
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
|
||||
import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
||||
|
||||
class ServiceAccessor {
|
||||
constructor(
|
||||
@IEditorService public editorService: IEditorService,
|
||||
@IEditorGroupsService public editorGroupService: IEditorGroupsService,
|
||||
@ITextFileService public textFileService: TestTextFileService,
|
||||
@IFileService public fileService: TestFileService,
|
||||
@IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService,
|
||||
@IConfigurationService public configurationService: TestConfigurationService
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
suite('EditorAutoSave', () => {
|
||||
|
||||
let disposables: IDisposable[] = [];
|
||||
|
||||
setup(() => {
|
||||
disposables.push(Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
EditorDescriptor.create(
|
||||
TextFileEditor,
|
||||
TextFileEditor.ID,
|
||||
'Text File Editor'
|
||||
),
|
||||
[new SyncDescriptor<EditorInput>(FileEditorInput)]
|
||||
));
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
dispose(disposables);
|
||||
disposables = [];
|
||||
});
|
||||
|
||||
test('editor auto saves after short delay if configured', async function () {
|
||||
const instantiationService = workbenchInstantiationService();
|
||||
|
||||
const configurationService = new TestConfigurationService();
|
||||
configurationService.setUserConfiguration('files', { autoSave: 'afterDelay', autoSaveDelay: 1 });
|
||||
instantiationService.stub(IConfigurationService, configurationService);
|
||||
|
||||
instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(
|
||||
<IContextKeyService>instantiationService.createInstance(MockContextKeyService),
|
||||
configurationService,
|
||||
TestEnvironmentService
|
||||
));
|
||||
|
||||
const part = instantiationService.createInstance(EditorPart);
|
||||
part.create(document.createElement('div'));
|
||||
part.layout(400, 300);
|
||||
|
||||
instantiationService.stub(IEditorGroupsService, part);
|
||||
|
||||
const editorService: EditorService = instantiationService.createInstance(EditorService);
|
||||
instantiationService.stub(IEditorService, editorService);
|
||||
|
||||
const accessor = instantiationService.createInstance(ServiceAccessor);
|
||||
|
||||
const editorAutoSave = instantiationService.createInstance(EditorAutoSave);
|
||||
|
||||
const resource = toResource.call(this, '/path/index.txt');
|
||||
|
||||
const model = await accessor.textFileService.models.loadOrCreate(resource) as IResolvedTextFileEditorModel;
|
||||
|
||||
model.textEditorModel.setValue('Super Good');
|
||||
|
||||
assert.ok(model.isDirty());
|
||||
|
||||
await awaitModelSaved(model);
|
||||
|
||||
assert.ok(!model.isDirty());
|
||||
|
||||
part.dispose();
|
||||
editorAutoSave.dispose();
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).clear();
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).dispose();
|
||||
});
|
||||
|
||||
function awaitModelSaved(model: ITextFileEditorModel): Promise<void> {
|
||||
return new Promise(c => {
|
||||
Event.once(model.onDidChangeDirty)(c);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -18,15 +18,19 @@ import { LOG_SCHEME } from 'vs/workbench/contrib/output/common/output';
|
|||
import { IFileOutputChannelDescriptor } from 'vs/workbench/services/output/common/output';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
|
||||
export class LogViewerInput extends ResourceEditorInput {
|
||||
|
||||
static readonly ID = 'workbench.editorinputs.output';
|
||||
|
||||
constructor(private readonly outputChannelDescriptor: IFileOutputChannelDescriptor,
|
||||
@ITextModelService textModelResolverService: ITextModelService
|
||||
constructor(
|
||||
private readonly outputChannelDescriptor: IFileOutputChannelDescriptor,
|
||||
@ITextModelService textModelResolverService: ITextModelService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorService editorService: IEditorService
|
||||
) {
|
||||
super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), undefined, textModelResolverService);
|
||||
super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), undefined, textModelResolverService, textFileService, editorService);
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
|
|
|
@ -21,6 +21,8 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService
|
|||
import { writeTransientState } from 'vs/workbench/contrib/codeEditor/browser/toggleWordWrap';
|
||||
import { mergeSort } from 'vs/base/common/arrays';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
export class PerfviewContrib {
|
||||
|
||||
|
@ -44,14 +46,18 @@ export class PerfviewInput extends ResourceEditorInput {
|
|||
static readonly Uri = URI.from({ scheme: 'perf', path: 'Startup Performance' });
|
||||
|
||||
constructor(
|
||||
@ITextModelService textModelResolverService: ITextModelService
|
||||
@ITextModelService textModelResolverService: ITextModelService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorService editorService: IEditorService
|
||||
) {
|
||||
super(
|
||||
localize('name', "Startup Performance"),
|
||||
undefined,
|
||||
PerfviewInput.Uri,
|
||||
undefined,
|
||||
textModelResolverService
|
||||
textModelResolverService,
|
||||
textFileService,
|
||||
editorService
|
||||
);
|
||||
}
|
||||
|
||||
|
|
165
src/vs/workbench/contrib/search/browser/media/searchEditor.css
Normal file
165
src/vs/workbench/contrib/search/browser/media/searchEditor.css
Normal file
|
@ -0,0 +1,165 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.search-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-editor .search-results {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-editor .query-container {
|
||||
margin: 0px 12px 12px 2px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .toggle-replace-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .search-container,
|
||||
.search-editor .search-widget .replace-container {
|
||||
margin-left: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .monaco-findInput {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .monaco-inputbox > .wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .monaco-inputbox > .wrapper > .mirror,
|
||||
.search-editor .search-widget .monaco-inputbox > .wrapper > textarea.input {
|
||||
padding: 3px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .monaco-inputbox > .wrapper > .mirror {
|
||||
max-height: 134px;
|
||||
}
|
||||
|
||||
/* NOTE: height is also used in searchWidget.ts as a constant*/
|
||||
.search-editor .search-widget .monaco-inputbox > .wrapper > textarea.input {
|
||||
overflow: initial;
|
||||
height: 24px; /* set initial height before measure */
|
||||
}
|
||||
|
||||
.search-editor .monaco-inputbox > .wrapper > textarea.input {
|
||||
scrollbar-width: none; /* Firefox: hide scrollbar */
|
||||
}
|
||||
|
||||
.search-editor .monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .context-lines-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-editor .search-widget.show-context .context-lines-input {
|
||||
display: inherit;
|
||||
margin-left: 5px;
|
||||
margin-right: 2px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
|
||||
.search-editor .search-widget .replace-container {
|
||||
margin-top: 6px;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .replace-input {
|
||||
position: relative;
|
||||
display: flex;
|
||||
vertical-align: middle;
|
||||
width: auto !important;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .replace-input > .controls {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 2px;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .replace-container.disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .replace-container .monaco-action-bar {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .replace-container .monaco-action-bar {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.search-editor .search-widget .replace-container .monaco-action-bar .action-item .codicon {
|
||||
background-repeat: no-repeat;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search-editor .includes-excludes {
|
||||
min-height: 1em;
|
||||
position: relative;
|
||||
margin: 0 0 0 17px;
|
||||
}
|
||||
|
||||
.search-editor .includes-excludes .expand {
|
||||
position: absolute;
|
||||
right: -2px;
|
||||
cursor: pointer;
|
||||
width: 25px;
|
||||
height: 16px;
|
||||
z-index: 2; /* Force it above the search results message, which has a negative top margin */
|
||||
}
|
||||
|
||||
.search-editor .includes-excludes .file-types {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-editor .includes-excludes.expanded .file-types {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.search-editor .includes-excludes.expanded .file-types:last-child {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.search-editor .includes-excludes.expanded h4 {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding: 4px 0 0;
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
}
|
|
@ -10,7 +10,7 @@ import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
|
|||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IInputValidator, HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler';
|
||||
|
@ -170,6 +170,7 @@ export class PatternInputWidget extends Widget {
|
|||
case KeyCode.Escape:
|
||||
this._onCancel.fire();
|
||||
return;
|
||||
case KeyCode.Tab: case KeyCode.Tab | KeyMod.Shift: return;
|
||||
default:
|
||||
if (this.searchConfig.searchOnType) {
|
||||
this._onCancel.fire();
|
||||
|
|
|
@ -121,9 +121,10 @@ export class ReplaceService implements IReplaceService {
|
|||
revealIfVisible: true
|
||||
}
|
||||
}).then(editor => {
|
||||
const input = editor?.input;
|
||||
const disposable = fileMatch.onDispose(() => {
|
||||
if (editor && editor.input) {
|
||||
editor.input.dispose();
|
||||
if (input) {
|
||||
input.dispose();
|
||||
}
|
||||
disposable.dispose();
|
||||
});
|
||||
|
|
|
@ -39,7 +39,7 @@ import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition
|
|||
import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler';
|
||||
import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler';
|
||||
import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions';
|
||||
import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction, RerunEditorSearchWithContextAction, ExpandAllAction } from 'vs/workbench/contrib/search/browser/searchActions';
|
||||
import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction, RerunEditorSearchWithContextAction, ExpandAllAction, OpenSearchEditorAction } from 'vs/workbench/contrib/search/browser/searchActions';
|
||||
import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel';
|
||||
import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView';
|
||||
import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget';
|
||||
|
@ -56,6 +56,9 @@ import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/ex
|
|||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet';
|
||||
import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor';
|
||||
import { SearchEditor, SearchEditorInput } from 'vs/workbench/contrib/search/browser/searchEditor';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
|
||||
registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true);
|
||||
registerSingleton(ISearchHistoryService, SearchHistoryService, true);
|
||||
|
@ -647,6 +650,11 @@ registry.registerWorkbenchAction(
|
|||
'Search: Open Results in Editor', category,
|
||||
ContextKeyExpr.and(Constants.EnableSearchEditorPreview));
|
||||
|
||||
registry.registerWorkbenchAction(
|
||||
SyncActionDescriptor.create(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL),
|
||||
'Search: Open new Search Editor', category,
|
||||
ContextKeyExpr.and(Constants.EnableSearchEditorPreview));
|
||||
|
||||
const searchEditorCategory = nls.localize({ comment: ['The name of the tabbed search view'], key: 'searcheditor' }, "Search Editor");
|
||||
registry.registerWorkbenchAction(
|
||||
SyncActionDescriptor.create(RerunEditorSearchAction, RerunEditorSearchAction.ID, RerunEditorSearchAction.LABEL,
|
||||
|
@ -828,6 +836,17 @@ configurationRegistry.registerConfiguration({
|
|||
default: false,
|
||||
description: nls.localize('search.enableSearchEditorPreview', "Experimental: When enabled, allows opening workspace search results in an editor.")
|
||||
},
|
||||
'search.searchEditorPreview.doubleClickBehaviour': {
|
||||
type: 'string',
|
||||
enum: ['selectWord', 'goToLocation', 'openLocationToSide'],
|
||||
default: 'goToLocation',
|
||||
enumDescriptions: [
|
||||
nls.localize('search.searchEditorPreview.doubleClickBehaviour.selectWord', "Double clicking selects the word under the cursor."),
|
||||
nls.localize('search.searchEditorPreview.doubleClickBehaviour.goToLocation', "Double clicking opens the result in the active editor group."),
|
||||
nls.localize('search.searchEditorPreview.doubleClickBehaviour.openLocationToSide', "Double clicking opens the result in the editor group to the side, creating one if it does not yet exist."),
|
||||
],
|
||||
markdownDescription: nls.localize('search.searchEditorPreview.doubleClickBehaviour', "Configure effect of double clicking a result in a Search Editor.\n\n `#search.enableSearchEditorPreview#` must be enabled for this setting to have an effect.")
|
||||
},
|
||||
'search.sortOrder': {
|
||||
'type': 'string',
|
||||
'enum': [SearchSortOrder.Default, SearchSortOrder.FileNames, SearchSortOrder.Type, SearchSortOrder.Modified, SearchSortOrder.CountDescending, SearchSortOrder.CountAscending],
|
||||
|
@ -872,3 +891,15 @@ MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
|||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
EditorDescriptor.create(
|
||||
SearchEditor,
|
||||
SearchEditor.ID,
|
||||
nls.localize('defaultPreferencesEditor', "Search Editor")
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(SearchEditorInput)
|
||||
]
|
||||
);
|
||||
|
|
|
@ -29,7 +29,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
|||
import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet';
|
||||
import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel';
|
||||
import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree';
|
||||
import { createEditorFromSearchResult, refreshActiveEditorSearch } from 'vs/workbench/contrib/search/browser/searchEditor';
|
||||
import { createEditorFromSearchResult, refreshActiveEditorSearch, openNewSearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
|
@ -508,6 +508,30 @@ export class CancelSearchAction extends Action {
|
|||
}
|
||||
}
|
||||
|
||||
export class OpenSearchEditorAction extends Action {
|
||||
|
||||
static readonly ID: string = Constants.OpenNewEditorCommandId;
|
||||
static readonly LABEL = nls.localize('search.openNewEditor', "Open new Search Editor");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
get enabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async run() {
|
||||
if (this.configurationService.getValue<ISearchConfigurationProperties>('search').enableSearchEditorPreview) {
|
||||
await openNewSearchEditor(this.editorService, this.instantiationService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenResultsInEditorAction extends Action {
|
||||
|
||||
static readonly ID: string = Constants.OpenInEditorCommandId;
|
||||
|
@ -518,7 +542,8 @@ export class OpenResultsInEditorAction extends Action {
|
|||
@IPanelService private panelService: IPanelService,
|
||||
@ILabelService private labelService: ILabelService,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IConfigurationService private configurationService: IConfigurationService
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
super(id, label, 'codicon-go-to-file');
|
||||
}
|
||||
|
@ -535,7 +560,7 @@ export class OpenResultsInEditorAction extends Action {
|
|||
async run() {
|
||||
const searchView = getSearchView(this.viewletService, this.panelService);
|
||||
if (searchView && this.configurationService.getValue<ISearchConfigurationProperties>('search').enableSearchEditorPreview) {
|
||||
await createEditorFromSearchResult(searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue(), this.labelService, this.editorService);
|
||||
await createEditorFromSearchResult(searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue(), this.labelService, this.editorService, this.instantiationService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,26 +3,356 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Match, searchMatchComparer, FileMatch, SearchResult, SearchModel } from 'vs/workbench/contrib/search/common/searchModel';
|
||||
import { repeat } from 'vs/base/common/strings';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { coalesce, flatten } from 'vs/base/common/arrays';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextQuery, IPatternInfo, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import * as network from 'vs/base/common/network';
|
||||
import { repeat } from 'vs/base/common/strings';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import 'vs/css!./media/searchEditor';
|
||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import type { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ITextModel, TrackedRangeStickiness, EndOfLinePreference } from 'vs/editor/common/model';
|
||||
import { EndOfLinePreference, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { searchEditorFindMatch, searchEditorFindMatchBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
|
||||
import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget';
|
||||
import { SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget';
|
||||
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
|
||||
import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { searchEditorFindMatch, searchEditorFindMatchBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { FileMatch, Match, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
|
||||
const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/;
|
||||
|
||||
type SearchConfiguration = {
|
||||
query: string,
|
||||
includes: string,
|
||||
excludes: string,
|
||||
contextLines: number,
|
||||
wholeWord: boolean,
|
||||
caseSensitive: boolean,
|
||||
regexp: boolean,
|
||||
useIgnores: boolean
|
||||
};
|
||||
|
||||
let searchEditorInputInstances = 0;
|
||||
export class SearchEditorInput extends EditorInput {
|
||||
static readonly ID: string = 'workbench.editorinputs.searchEditorInput';
|
||||
|
||||
public config: SearchConfiguration;
|
||||
private instanceNumber: number = searchEditorInputInstances++;
|
||||
|
||||
constructor(
|
||||
config: SearchConfiguration | undefined,
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@IModeService private readonly modeService: IModeService,
|
||||
) {
|
||||
super();
|
||||
|
||||
if (config === undefined) {
|
||||
this.config = { query: '', includes: '', excludes: '', contextLines: 0, wholeWord: false, caseSensitive: false, regexp: false, useIgnores: true };
|
||||
} else {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
const searchResultMode = this.modeService.create('search-result');
|
||||
this.modelService.createModel('', searchResultMode, this.getResource());
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
return SearchEditorInput.ID;
|
||||
}
|
||||
|
||||
getResource(): URI {
|
||||
return URI.from({ scheme: 'code-search', fragment: `${this.instanceNumber}` });
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return this.config.query ? localize('searchTitle.withQuery', "Search: {0}", this.config.query) : localize('searchTitle', "Search");
|
||||
}
|
||||
|
||||
setConfig(config: SearchConfiguration) {
|
||||
this.config = config;
|
||||
this._onDidChangeLabel.fire();
|
||||
}
|
||||
|
||||
async resolve() {
|
||||
return null;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.modelService.destroyModel(this.getResource());
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class SearchEditor extends BaseEditor {
|
||||
static readonly ID: string = 'workbench.editor.searchEditor';
|
||||
|
||||
private queryEditorWidget!: SearchWidget;
|
||||
private searchResultEditor!: CodeEditorWidget;
|
||||
private queryEditorContainer!: HTMLElement;
|
||||
private dimension?: DOM.Dimension;
|
||||
private inputPatternIncludes!: PatternInputWidget;
|
||||
private inputPatternExcludes!: ExcludePatternInputWidget;
|
||||
private includesExcludesContainer!: HTMLElement;
|
||||
private toggleQueryDetailsButton!: HTMLElement;
|
||||
|
||||
private runSearchDelayer = new Delayer(300);
|
||||
private pauseSearching: boolean = false;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
) {
|
||||
super(SearchEditor.ID, telemetryService, themeService, storageService);
|
||||
}
|
||||
|
||||
createEditor(parent: HTMLElement) {
|
||||
DOM.addClass(parent, 'search-editor');
|
||||
|
||||
// Query
|
||||
this.queryEditorContainer = DOM.append(parent, DOM.$('.query-container'));
|
||||
|
||||
this.queryEditorWidget = this._register(this.instantiationService.createInstance(SearchWidget, this.queryEditorContainer, { _hideReplaceToggle: true, showContextToggle: true }));
|
||||
this._register(this.queryEditorWidget.onReplaceToggled(() => this.reLayout()));
|
||||
this._register(this.queryEditorWidget.onDidHeightChange(() => this.reLayout()));
|
||||
this.queryEditorWidget.onSearchSubmit(() => this.runSearch());
|
||||
this.queryEditorWidget.searchInput.onDidOptionChange(() => this.runSearch());
|
||||
this.queryEditorWidget.onDidToggleContext(() => this.runSearch());
|
||||
|
||||
// Includes/Excludes Dropdown
|
||||
this.includesExcludesContainer = DOM.append(this.queryEditorContainer, DOM.$('.includes-excludes'));
|
||||
// // Toggle query details button
|
||||
this.toggleQueryDetailsButton = DOM.append(this.includesExcludesContainer, DOM.$('.expand.codicon.codicon-ellipsis', { tabindex: 0, role: 'button', title: localize('moreSearch', "Toggle Search Details") }));
|
||||
this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.CLICK, e => {
|
||||
DOM.EventHelper.stop(e);
|
||||
this.toggleQueryDetails();
|
||||
}));
|
||||
this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
|
||||
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
DOM.EventHelper.stop(e);
|
||||
this.toggleQueryDetails();
|
||||
}
|
||||
}));
|
||||
this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
|
||||
if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
||||
if (this.queryEditorWidget.isReplaceActive()) {
|
||||
this.queryEditorWidget.focusReplaceAllAction();
|
||||
} else {
|
||||
this.queryEditorWidget.isReplaceShown() ? this.queryEditorWidget.replaceInput.focusOnPreserve() : this.queryEditorWidget.focusRegexAction();
|
||||
}
|
||||
DOM.EventHelper.stop(e);
|
||||
}
|
||||
}));
|
||||
|
||||
// // Includes
|
||||
|
||||
const folderIncludesList = DOM.append(this.includesExcludesContainer, DOM.$('.file-types.includes'));
|
||||
const filesToIncludeTitle = localize('searchScope.includes', "files to include");
|
||||
DOM.append(folderIncludesList, DOM.$('h4', undefined, filesToIncludeTitle));
|
||||
|
||||
this.inputPatternIncludes = this._register(this.instantiationService.createInstance(PatternInputWidget, folderIncludesList, this.contextViewService, {
|
||||
ariaLabel: localize('label.includes', 'Search Include Patterns'),
|
||||
}));
|
||||
this.inputPatternIncludes.onSubmit(_triggeredOnType => this.runSearch());
|
||||
|
||||
// // Excludes
|
||||
const excludesList = DOM.append(this.includesExcludesContainer, DOM.$('.file-types.excludes'));
|
||||
const excludesTitle = localize('searchScope.excludes', "files to exclude");
|
||||
DOM.append(excludesList, DOM.$('h4', undefined, excludesTitle));
|
||||
|
||||
this.inputPatternExcludes = this._register(this.instantiationService.createInstance(ExcludePatternInputWidget, excludesList, this.contextViewService, {
|
||||
ariaLabel: localize('label.excludes', 'Search Exclude Patterns'),
|
||||
}));
|
||||
|
||||
this.inputPatternExcludes.onSubmit(_triggeredOnType => this.runSearch());
|
||||
this.inputPatternExcludes.onChangeIgnoreBox(() => this.runSearch());
|
||||
|
||||
// Editor
|
||||
const searchResultContainer = DOM.append(parent, DOM.$('.search-results'));
|
||||
const configuration: IEditorOptions = this.configurationService.getValue('editor', { overrideIdentifier: 'search-result' });
|
||||
const options: ICodeEditorWidgetOptions = {};
|
||||
this.searchResultEditor = this._register(this.instantiationService.createInstance(CodeEditorWidget, searchResultContainer, configuration, options));
|
||||
this.searchResultEditor.onMouseUp(e => {
|
||||
|
||||
if (e.event.detail === 2) {
|
||||
const behaviour = this.configurationService.getValue<ISearchConfigurationProperties>('search').searchEditorPreview.doubleClickBehaviour;
|
||||
const position = e.target.position;
|
||||
if (position && behaviour !== 'selectWord') {
|
||||
const line = this.searchResultEditor.getModel()?.getLineContent(position.lineNumber) ?? '';
|
||||
if (line.match(RESULT_LINE_REGEX)) {
|
||||
this.searchResultEditor.setSelection(Range.fromPositions(position));
|
||||
this.commandService.executeCommand(behaviour === 'goToLocation' ? 'editor.action.goToDeclaration' : 'editor.action.openDeclarationToTheSide');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async runSearch() {
|
||||
if (!this.pauseSearching) {
|
||||
this.runSearchDelayer.trigger(() => this.doRunSearch());
|
||||
}
|
||||
}
|
||||
|
||||
private async doRunSearch() {
|
||||
const startInput = this.input;
|
||||
|
||||
const config: SearchConfiguration = {
|
||||
caseSensitive: this.queryEditorWidget.searchInput.getCaseSensitive(),
|
||||
contextLines: this.queryEditorWidget.contextLines(),
|
||||
excludes: this.inputPatternExcludes.getValue(),
|
||||
includes: this.inputPatternIncludes.getValue(),
|
||||
query: this.queryEditorWidget.searchInput.getValue(),
|
||||
regexp: this.queryEditorWidget.searchInput.getRegex(),
|
||||
wholeWord: this.queryEditorWidget.searchInput.getWholeWords(),
|
||||
useIgnores: this.inputPatternExcludes.useExcludesAndIgnoreFiles()
|
||||
};
|
||||
|
||||
const content: IPatternInfo = {
|
||||
pattern: config.query,
|
||||
isRegExp: config.regexp,
|
||||
isCaseSensitive: config.caseSensitive,
|
||||
isWordMatch: config.wholeWord,
|
||||
};
|
||||
|
||||
const options: ITextQueryBuilderOptions = {
|
||||
_reason: 'searchEditor',
|
||||
extraFileResources: this.instantiationService.invokeFunction(getOutOfWorkspaceEditorResources),
|
||||
maxResults: 10000,
|
||||
disregardIgnoreFiles: !config.useIgnores,
|
||||
disregardExcludeSettings: !config.useIgnores,
|
||||
excludePattern: config.excludes,
|
||||
includePattern: config.includes,
|
||||
previewOptions: {
|
||||
matchLines: 1,
|
||||
charsPerLine: 1000
|
||||
},
|
||||
afterContext: config.contextLines,
|
||||
beforeContext: config.contextLines,
|
||||
isSmartCase: this.configurationService.getValue<ISearchConfigurationProperties>('search').smartCase,
|
||||
expandPatterns: true
|
||||
};
|
||||
|
||||
const folderResources = this.contextService.getWorkspace().folders;
|
||||
let query: ITextQuery;
|
||||
try {
|
||||
const queryBuilder = this.instantiationService.createInstance(QueryBuilder);
|
||||
query = queryBuilder.text(content, folderResources.map(folder => folder.uri), options);
|
||||
}
|
||||
catch (err) {
|
||||
return;
|
||||
}
|
||||
const searchModel = this.instantiationService.createInstance(SearchModel);
|
||||
await searchModel.search(query);
|
||||
if (this.input !== startInput) {
|
||||
searchModel.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
(assertIsDefined(this._input) as SearchEditorInput).setConfig(config);
|
||||
|
||||
const labelFormatter = (uri: URI): string => this.labelService.getUriLabel(uri, { relative: true });
|
||||
const results = serializeSearchResultForEditor(searchModel.searchResult, config.includes, config.excludes, config.contextLines, labelFormatter, false);
|
||||
const textModel = assertIsDefined(this.searchResultEditor.getModel());
|
||||
textModel.setValue(results.text.join(lineDelimiter));
|
||||
textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } })));
|
||||
|
||||
searchModel.dispose();
|
||||
}
|
||||
|
||||
layout(dimension: DOM.Dimension) {
|
||||
this.dimension = dimension;
|
||||
this.reLayout();
|
||||
}
|
||||
|
||||
focusInput() {
|
||||
this.queryEditorWidget.focus();
|
||||
}
|
||||
|
||||
private reLayout() {
|
||||
if (this.dimension) {
|
||||
this.queryEditorWidget.setWidth(this.dimension.width - 28 /* container margin */);
|
||||
this.searchResultEditor.layout({ height: this.dimension.height - DOM.getTotalHeight(this.queryEditorContainer), width: this.dimension.width });
|
||||
this.inputPatternExcludes.setWidth(this.dimension.width - 28 /* container margin */);
|
||||
this.inputPatternIncludes.setWidth(this.dimension.width - 28 /* container margin */);
|
||||
}
|
||||
}
|
||||
|
||||
async setInput(newInput: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
|
||||
if (!(newInput instanceof SearchEditorInput)) { return; }
|
||||
this.pauseSearching = true;
|
||||
// TODO: Manage model lifecycle in SearchEditorInput
|
||||
const model = this.modelService.getModel(newInput.getResource());
|
||||
|
||||
this.searchResultEditor.setModel(model);
|
||||
this.queryEditorWidget.setValue(newInput.config.query, true);
|
||||
this.queryEditorWidget.searchInput.setCaseSensitive(newInput.config.caseSensitive);
|
||||
this.queryEditorWidget.searchInput.setRegex(newInput.config.regexp);
|
||||
this.queryEditorWidget.searchInput.setWholeWords(newInput.config.wholeWord);
|
||||
this.queryEditorWidget.setContextLines(newInput.config.contextLines);
|
||||
this.inputPatternExcludes.setValue(newInput.config.excludes);
|
||||
this.inputPatternIncludes.setValue(newInput.config.includes);
|
||||
this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(newInput.config.useIgnores);
|
||||
|
||||
this.focusInput();
|
||||
await super.setInput(newInput, options, token);
|
||||
this.pauseSearching = false;
|
||||
}
|
||||
|
||||
toggleQueryDetails(): void {
|
||||
const cls = 'expanded';
|
||||
const shouldShow = !DOM.hasClass(this.includesExcludesContainer, cls);
|
||||
|
||||
if (shouldShow) {
|
||||
this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'true');
|
||||
DOM.addClass(this.includesExcludesContainer, cls);
|
||||
} else {
|
||||
this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'false');
|
||||
DOM.removeClass(this.includesExcludesContainer, cls);
|
||||
}
|
||||
|
||||
this.reLayout();
|
||||
}
|
||||
|
||||
getModel() {
|
||||
return this.searchResultEditor.getModel();
|
||||
}
|
||||
}
|
||||
|
||||
// Using \r\n on Windows inserts an extra newline between results.
|
||||
const lineDelimiter = '\n';
|
||||
|
@ -221,8 +551,8 @@ const searchHeaderToContentPattern = (header: string[]): SearchHeader => {
|
|||
return query;
|
||||
};
|
||||
|
||||
const serializeSearchResultForEditor = (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, contextLines: number, labelFormatter: (x: URI) => string): SearchResultSerialization => {
|
||||
const header = contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern, contextLines);
|
||||
const serializeSearchResultForEditor = (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, contextLines: number, labelFormatter: (x: URI) => string, includeHeader: boolean): SearchResultSerialization => {
|
||||
const header = includeHeader ? contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern, contextLines) : [];
|
||||
const allResults =
|
||||
flattenSearchResultSerializations(
|
||||
flatten(
|
||||
|
@ -290,20 +620,29 @@ export const refreshActiveEditorSearch =
|
|||
await searchModel.search(query);
|
||||
|
||||
const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true });
|
||||
const results = serializeSearchResultForEditor(searchModel.searchResult, contentPattern.includes, contentPattern.excludes, contextLines, labelFormatter);
|
||||
const results = serializeSearchResultForEditor(searchModel.searchResult, contentPattern.includes, contentPattern.excludes, contextLines, labelFormatter, false);
|
||||
|
||||
textModel.setValue(results.text.join(lineDelimiter));
|
||||
textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } })));
|
||||
};
|
||||
|
||||
export const openNewSearchEditor =
|
||||
async (editorService: IEditorService, instantiationService: IInstantiationService) => {
|
||||
await editorService.openEditor(instantiationService.createInstance(SearchEditorInput, undefined), { pinned: true });
|
||||
};
|
||||
|
||||
export const createEditorFromSearchResult =
|
||||
async (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelService: ILabelService, editorService: IEditorService) => {
|
||||
const searchTerm = searchResult.query?.contentPattern.pattern.replace(/[^\w-_. ]/g, '') || 'Search';
|
||||
async (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelService: ILabelService, editorService: IEditorService, instantiationService: IInstantiationService) => {
|
||||
if (!searchResult.query) {
|
||||
console.error('Expected searchResult.query to be defined. Got', searchResult);
|
||||
return;
|
||||
}
|
||||
|
||||
const searchTerm = searchResult.query.contentPattern.pattern.replace(/[^\w-_. ]/g, '') || 'Search';
|
||||
|
||||
const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true });
|
||||
|
||||
const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter);
|
||||
const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, false);
|
||||
const contents = results.text.join(lineDelimiter);
|
||||
let possible = {
|
||||
contents,
|
||||
|
@ -312,6 +651,7 @@ export const createEditorFromSearchResult =
|
|||
};
|
||||
|
||||
let id = 0;
|
||||
|
||||
let existing = editorService.getOpened(possible);
|
||||
while (existing) {
|
||||
if (existing instanceof UntitledTextEditorInput) {
|
||||
|
@ -325,10 +665,23 @@ export const createEditorFromSearchResult =
|
|||
existing = editorService.getOpened(possible);
|
||||
}
|
||||
|
||||
const editor = await editorService.openEditor(possible);
|
||||
const control = editor?.getControl()!;
|
||||
const model = control.getModel() as ITextModel;
|
||||
const editor = await editorService.openEditor(
|
||||
instantiationService.createInstance(
|
||||
SearchEditorInput,
|
||||
{
|
||||
query: searchResult.query.contentPattern.pattern,
|
||||
regexp: !!searchResult.query.contentPattern.isRegExp,
|
||||
caseSensitive: !!searchResult.query.contentPattern.isCaseSensitive,
|
||||
wholeWord: !!searchResult.query.contentPattern.isWordMatch,
|
||||
includes: rawIncludePattern,
|
||||
excludes: rawExcludePattern,
|
||||
contextLines: 0,
|
||||
useIgnores: !searchResult.query.userDisabledExcludesAndIgnoreFiles,
|
||||
}),
|
||||
{ pinned: true }) as SearchEditor;
|
||||
|
||||
const model = assertIsDefined(editor.getModel());
|
||||
model.setValue(contents);
|
||||
model.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } })));
|
||||
};
|
||||
|
||||
|
|
|
@ -1559,7 +1559,7 @@ export class SearchView extends ViewPane {
|
|||
|
||||
this.messageDisposables.push(dom.addDisposableListener(openInEditorLink, dom.EventType.CLICK, (e: MouseEvent) => {
|
||||
dom.EventHelper.stop(e, false);
|
||||
createEditorFromSearchResult(this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue(), this.labelService, this.editorService);
|
||||
createEditorFromSearchResult(this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue(), this.labelService, this.editorService, this.instantiationService);
|
||||
}));
|
||||
|
||||
} else {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
|||
import { Button, IButtonOptions } from 'vs/base/browser/ui/button/button';
|
||||
import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput';
|
||||
import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput';
|
||||
import { IMessage } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IMessage, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
|
@ -34,6 +34,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
|||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
|
||||
|
||||
/** Specified in searchview.css */
|
||||
export const SingleLineInputHeight = 24;
|
||||
|
@ -47,6 +48,8 @@ export interface ISearchWidgetOptions {
|
|||
searchHistory?: string[];
|
||||
replaceHistory?: string[];
|
||||
preserveCase?: boolean;
|
||||
_hideReplaceToggle?: boolean; // TODO: Search Editor's replace experience
|
||||
showContextToggle?: boolean;
|
||||
}
|
||||
|
||||
class ReplaceAllAction extends Action {
|
||||
|
@ -151,7 +154,13 @@ export class SearchWidget extends Widget {
|
|||
private _onDidHeightChange = this._register(new Emitter<void>());
|
||||
readonly onDidHeightChange: Event<void> = this._onDidHeightChange.event;
|
||||
|
||||
private readonly _onDidToggleContext = new Emitter<void>();
|
||||
readonly onDidToggleContext: Event<void> = this._onDidToggleContext.event;
|
||||
|
||||
private temporarilySkipSearchOnChange = false;
|
||||
private showContextCheckbox!: Checkbox;
|
||||
private contextLinesInput!: InputBox;
|
||||
private _contextLineInputDelayer: Delayer<void>;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
|
@ -168,7 +177,10 @@ export class SearchWidget extends Widget {
|
|||
this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.contextKeyService);
|
||||
this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.contextKeyService);
|
||||
this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.contextKeyService);
|
||||
|
||||
this._replaceHistoryDelayer = new Delayer<void>(500);
|
||||
this._contextLineInputDelayer = new Delayer<void>(300);
|
||||
|
||||
this._searchDelayer = this._register(new Delayer<void>(this.searchConfiguration.searchOnTypeDebouncePeriod));
|
||||
this.render(container, options);
|
||||
|
||||
|
@ -275,7 +287,9 @@ export class SearchWidget extends Widget {
|
|||
this.domNode = dom.append(container, dom.$('.search-widget'));
|
||||
this.domNode.style.position = 'relative';
|
||||
|
||||
this.renderToggleReplaceButton(this.domNode);
|
||||
if (!options._hideReplaceToggle) {
|
||||
this.renderToggleReplaceButton(this.domNode);
|
||||
}
|
||||
|
||||
this.renderSearchInput(this.domNode, options);
|
||||
this.renderReplaceInput(this.domNode, options);
|
||||
|
@ -356,6 +370,36 @@ export class SearchWidget extends Widget {
|
|||
this.ignoreGlobalFindBufferOnNextFocus = false;
|
||||
}));
|
||||
this._register(this.searchInputFocusTracker.onDidBlur(() => this.searchInputBoxFocused.set(false)));
|
||||
|
||||
|
||||
this.showContextCheckbox = new Checkbox({ isChecked: false, title: nls.localize('showContext', "Show Context"), actionClassName: 'codicon-list-selection' });
|
||||
this._register(this.showContextCheckbox.onChange(() => {
|
||||
dom.toggleClass(parent, 'show-context', this.showContextCheckbox.checked);
|
||||
this._onDidToggleContext.fire();
|
||||
}));
|
||||
|
||||
if (options.showContextToggle) {
|
||||
this.contextLinesInput = new InputBox(searchInputContainer, this.contextViewService, { type: 'number' });
|
||||
dom.addClass(this.contextLinesInput.element, 'context-lines-input');
|
||||
this.contextLinesInput.value = '2';
|
||||
this._register(this.contextLinesInput.onDidChange(() => {
|
||||
if (this.contextLinesInput.value.includes('-')) {
|
||||
this.contextLinesInput.value = '0';
|
||||
}
|
||||
this._contextLineInputDelayer.trigger(() => this._onDidToggleContext.fire());
|
||||
}));
|
||||
dom.append(searchInputContainer, this.showContextCheckbox.domNode);
|
||||
}
|
||||
}
|
||||
|
||||
public setContextLines(lines: number) {
|
||||
if (!this.contextLinesInput) { return; }
|
||||
if (lines === 0) {
|
||||
this.showContextCheckbox.checked = false;
|
||||
} else {
|
||||
this.showContextCheckbox.checked = true;
|
||||
this.contextLinesInput.value = '' + lines;
|
||||
}
|
||||
}
|
||||
|
||||
private renderReplaceInput(parent: HTMLElement, options: ISearchWidgetOptions): void {
|
||||
|
@ -580,6 +624,10 @@ export class SearchWidget extends Widget {
|
|||
this._onSearchSubmit.fire(triggeredOnType);
|
||||
}
|
||||
|
||||
contextLines() {
|
||||
return this.showContextCheckbox.checked ? +this.contextLinesInput.value : 0;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.setReplaceAllActionState(false);
|
||||
super.dispose();
|
||||
|
|
|
@ -16,6 +16,7 @@ export const CopyPathCommandId = 'search.action.copyPath';
|
|||
export const CopyMatchCommandId = 'search.action.copyMatch';
|
||||
export const CopyAllCommandId = 'search.action.copyAll';
|
||||
export const OpenInEditorCommandId = 'search.action.openInEditor';
|
||||
export const OpenNewEditorCommandId = 'search.action.openNewEditor';
|
||||
export const RerunEditorSearchCommandId = 'search.action.rerunEditorSearch';
|
||||
export const RerunEditorSearchWithContextCommandId = 'search.action.rerunEditorSearchWithContext';
|
||||
export const ClearSearchHistoryCommandId = 'search.action.clearHistory';
|
||||
|
|
|
@ -543,15 +543,13 @@ export class FolderMatch extends Disposable {
|
|||
|
||||
replace(match: FileMatch): Promise<any> {
|
||||
return this.replaceService.replace([match]).then(() => {
|
||||
this.doRemove(match, false, true);
|
||||
this.doRemove(match);
|
||||
});
|
||||
}
|
||||
|
||||
replaceAll(): Promise<any> {
|
||||
const matches = this.matches();
|
||||
return this.replaceService.replace(matches).then(() => {
|
||||
matches.forEach(match => this.doRemove(match, false, true));
|
||||
});
|
||||
return this.replaceService.replace(matches).then(() => this.doRemove(matches));
|
||||
}
|
||||
|
||||
matches(): FileMatch[] {
|
||||
|
|
|
@ -146,7 +146,7 @@ export class SnippetCompletionProvider implements CompletionItemProvider {
|
|||
i = to;
|
||||
}
|
||||
}
|
||||
return { suggestions };
|
||||
return { suggestions, isDetailsResolved: true };
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
|||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { EndOfLinePreference } from 'vs/editor/common/model';
|
||||
|
||||
export class WalkThroughModel extends EditorModel {
|
||||
|
||||
|
@ -111,7 +112,7 @@ export class WalkThroughInput extends EditorInput {
|
|||
return '';
|
||||
};
|
||||
|
||||
const markdown = ref.object.textEditorModel.getLinesContent().join('\n');
|
||||
const markdown = ref.object.textEditorModel.getValue(EndOfLinePreference.LF);
|
||||
marked(markdown, { renderer });
|
||||
|
||||
return Promise.all(snippets)
|
||||
|
|
|
@ -39,6 +39,7 @@ import { Dimension, size } from 'vs/base/browser/dom';
|
|||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { EndOfLinePreference } from 'vs/editor/common/model';
|
||||
|
||||
export const WALK_THROUGH_FOCUS = new RawContextKey<boolean>('interactivePlaygroundFocus', false);
|
||||
|
||||
|
@ -278,7 +279,7 @@ export class WalkThroughPart extends BaseEditor {
|
|||
return;
|
||||
}
|
||||
|
||||
const content = model.main.textEditorModel.getLinesContent().join('\n');
|
||||
const content = model.main.textEditorModel.getValue(EndOfLinePreference.LF);
|
||||
if (!strings.endsWith(input.getResource().path, '.md')) {
|
||||
this.content.innerHTML = content;
|
||||
this.updateSizeClasses();
|
||||
|
@ -421,7 +422,8 @@ export class WalkThroughPart extends BaseEditor {
|
|||
horizontal: 'auto',
|
||||
useShadows: true,
|
||||
verticalHasArrows: false,
|
||||
horizontalHasArrows: false
|
||||
horizontalHasArrows: false,
|
||||
alwaysConsumeMouseWheel: false
|
||||
},
|
||||
overviewRulerLanes: 3,
|
||||
fixedOverflowWidgets: true,
|
||||
|
|
|
@ -13,6 +13,8 @@ import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorIn
|
|||
import { KeybindingsEditorModel } from 'vs/workbench/services/preferences/common/keybindingsEditorModel';
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
export class PreferencesEditorInput extends SideBySideEditorInput {
|
||||
static readonly ID: string = 'workbench.editorinputs.preferencesEditorInput';
|
||||
|
@ -28,10 +30,13 @@ export class PreferencesEditorInput extends SideBySideEditorInput {
|
|||
|
||||
export class DefaultPreferencesEditorInput extends ResourceEditorInput {
|
||||
static readonly ID = 'workbench.editorinputs.defaultpreferences';
|
||||
constructor(defaultSettingsResource: URI,
|
||||
@ITextModelService textModelResolverService: ITextModelService
|
||||
constructor(
|
||||
defaultSettingsResource: URI,
|
||||
@ITextModelService textModelResolverService: ITextModelService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorService editorService: IEditorService
|
||||
) {
|
||||
super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, undefined, textModelResolverService);
|
||||
super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, undefined, textModelResolverService, textFileService, editorService);
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
|
|
|
@ -45,11 +45,15 @@ export interface Tunnel {
|
|||
closeable?: boolean;
|
||||
}
|
||||
|
||||
export function MakeAddress(host: string, port: number): string {
|
||||
function ToLocalHost(host: string): string {
|
||||
if (host === '127.0.0.1') {
|
||||
host = 'localhost';
|
||||
}
|
||||
return host + ':' + port;
|
||||
return host;
|
||||
}
|
||||
|
||||
export function MakeAddress(host: string, port: number): string {
|
||||
return ToLocalHost(host) + ':' + port;
|
||||
}
|
||||
|
||||
export class TunnelModel extends Disposable {
|
||||
|
@ -201,7 +205,13 @@ export class TunnelModel extends Disposable {
|
|||
return;
|
||||
}
|
||||
if (this._candidateFinder) {
|
||||
this._candidates = await this._candidateFinder();
|
||||
this._candidates = (await this._candidateFinder()).map(value => {
|
||||
return {
|
||||
host: ToLocalHost(value.host),
|
||||
port: value.port,
|
||||
detail: value.detail
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,7 +296,9 @@ class RemoteExplorerService implements IRemoteExplorerService {
|
|||
}
|
||||
|
||||
getEditableData(tunnelItem: ITunnelItem | undefined): IEditableData | undefined {
|
||||
return (this._editable && (!tunnelItem || (this._editable.tunnelItem?.remotePort === tunnelItem.remotePort) && (this._editable.tunnelItem.remoteHost === tunnelItem.remoteHost))) ?
|
||||
return (this._editable &&
|
||||
((!tunnelItem && (tunnelItem === this._editable.tunnelItem)) ||
|
||||
(tunnelItem && (this._editable.tunnelItem?.remotePort === tunnelItem.remotePort) && (this._editable.tunnelItem.remoteHost === tunnelItem.remoteHost)))) ?
|
||||
this._editable.data : undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -335,6 +335,7 @@ export interface ISearchConfigurationProperties {
|
|||
searchOnType: boolean;
|
||||
searchOnTypeDebouncePeriod: number;
|
||||
enableSearchEditorPreview: boolean;
|
||||
searchEditorPreview: { doubleClickBehaviour: 'selectWord' | 'goToLocation' | 'openLocationToSide' };
|
||||
searchEditorPreviewForceAbsolutePaths: boolean;
|
||||
sortOrder: SearchSortOrder;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ export class BrowserTextFileService extends AbstractTextFileService {
|
|||
}
|
||||
|
||||
private doBeforeShutdownSync(): boolean {
|
||||
if (this.models.getAll().some(model => model.hasState(ModelState.PENDING_SAVE) || model.hasState(ModelState.PENDING_AUTO_SAVE))) {
|
||||
if (this.models.getAll().some(model => model.hasState(ModelState.PENDING_SAVE))) {
|
||||
return true; // files are pending to be saved: veto
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Emitter, AsyncEmitter } from 'vs/base/common/event';
|
|||
import * as platform from 'vs/base/common/platform';
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { SaveReason, IRevertOptions } from 'vs/workbench/common/editor';
|
||||
import { SaveReason, IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor';
|
||||
import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IFileService, FileOperationError, FileOperationResult, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files';
|
||||
|
@ -37,6 +37,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/tex
|
|||
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
||||
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
|
||||
|
||||
/**
|
||||
* The workbench file service implementation implements the raw file service spec and adds additional methods on top.
|
||||
|
@ -76,7 +77,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
|||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService,
|
||||
@IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService
|
||||
@IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService,
|
||||
@ITextModelService private readonly textModelService: ITextModelService
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -676,68 +678,77 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
|||
return this.getFileModels(resources).filter(model => model.isDirty());
|
||||
}
|
||||
|
||||
async saveAs(resource: URI, targetResource?: URI, options?: ITextFileSaveOptions): Promise<URI | undefined> {
|
||||
async saveAs(source: URI, target?: URI, options?: ITextFileSaveOptions): Promise<URI | undefined> {
|
||||
|
||||
// Get to target resource
|
||||
if (!targetResource) {
|
||||
let dialogPath = resource;
|
||||
if (resource.scheme === Schemas.untitled) {
|
||||
dialogPath = this.suggestFileName(resource);
|
||||
if (!target) {
|
||||
let dialogPath = source;
|
||||
if (source.scheme === Schemas.untitled) {
|
||||
dialogPath = this.suggestFileName(source);
|
||||
}
|
||||
|
||||
targetResource = await this.promptForPath(resource, dialogPath, options ? options.availableFileSystems : undefined);
|
||||
target = await this.promptForPath(source, dialogPath, options ? options.availableFileSystems : undefined);
|
||||
}
|
||||
|
||||
if (!targetResource) {
|
||||
if (!target) {
|
||||
return; // user canceled
|
||||
}
|
||||
|
||||
// Just save if target is same as models own resource
|
||||
if (resource.toString() === targetResource.toString()) {
|
||||
await this.save(resource, options);
|
||||
if (source.toString() === target.toString()) {
|
||||
await this.save(source, options);
|
||||
|
||||
return resource;
|
||||
return source;
|
||||
}
|
||||
|
||||
// Do it
|
||||
return this.doSaveAs(resource, targetResource, options);
|
||||
return this.doSaveAs(source, target, options);
|
||||
}
|
||||
|
||||
private async doSaveAs(resource: URI, target: URI, options?: ITextFileSaveOptions): Promise<URI> {
|
||||
private async doSaveAs(source: URI, target: URI, options?: ITextFileSaveOptions): Promise<URI> {
|
||||
let success = false;
|
||||
|
||||
// Retrieve text model from provided resource if any
|
||||
let model: ITextFileEditorModel | UntitledTextEditorModel | undefined;
|
||||
if (this.fileService.canHandleResource(resource)) {
|
||||
model = this._models.get(resource);
|
||||
} else if (resource.scheme === Schemas.untitled && this.untitledTextEditorService.exists(resource)) {
|
||||
model = await this.untitledTextEditorService.createOrGet({ resource }).resolve();
|
||||
// If the source is an existing text file model, we can directly
|
||||
// use that model to copy the contents to the target destination
|
||||
const textFileModel = this._models.get(source);
|
||||
if (textFileModel && textFileModel.isResolved()) {
|
||||
success = await this.doSaveAsTextFile(textFileModel, source, target, options);
|
||||
}
|
||||
|
||||
// We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before)
|
||||
let result: boolean;
|
||||
if (model) {
|
||||
result = await this.doSaveTextFileAs(model, resource, target, options);
|
||||
// Otherwise if the source can be handled by the file service
|
||||
// we can simply invoke the copy() function to save as
|
||||
else if (this.fileService.canHandleResource(source)) {
|
||||
await this.fileService.copy(source, target);
|
||||
|
||||
success = true;
|
||||
}
|
||||
|
||||
// Otherwise we can only copy
|
||||
else {
|
||||
await this.fileService.copy(resource, target);
|
||||
// Finally, if the source does not seem to be a file, we have to
|
||||
// try to resolve a text model from the resource to get at the
|
||||
// contents and additional meta data (e.g. encoding).
|
||||
else if (this.textModelService.hasTextModelContentProvider(source.scheme)) {
|
||||
const modelReference = await this.textModelService.createModelReference(source);
|
||||
success = await this.doSaveAsTextFile(modelReference.object, source, target, options);
|
||||
|
||||
result = true;
|
||||
modelReference.dispose(); // free up our use of the reference
|
||||
}
|
||||
|
||||
// Return early if the operation was not running
|
||||
if (!result) {
|
||||
return target;
|
||||
// Revert the source if result is success
|
||||
if (success) {
|
||||
await this.revert(source);
|
||||
}
|
||||
|
||||
// Revert the source
|
||||
await this.revert(resource);
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledTextEditorModel, resource: URI, target: URI, options?: ITextFileSaveOptions): Promise<boolean> {
|
||||
private async doSaveAsTextFile(sourceModel: IResolvedTextEditorModel, source: URI, target: URI, options?: ITextFileSaveOptions): Promise<boolean> {
|
||||
|
||||
// Find source encoding if any
|
||||
let sourceModelEncoding: string | undefined = undefined;
|
||||
const sourceModelWithEncodingSupport = (sourceModel as unknown as IEncodingSupport);
|
||||
if (typeof sourceModelWithEncodingSupport.getEncoding === 'function') {
|
||||
sourceModelEncoding = sourceModelWithEncodingSupport.getEncoding();
|
||||
}
|
||||
|
||||
// Prefer an existing model if it is already loaded for the given target resource
|
||||
let targetExists: boolean = false;
|
||||
|
@ -764,7 +775,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
|||
}
|
||||
}
|
||||
|
||||
targetModel = await this.models.loadOrCreate(target, { encoding: sourceModel.getEncoding(), mode });
|
||||
targetModel = await this.models.loadOrCreate(target, { encoding: sourceModelEncoding, mode });
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -785,7 +796,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
|||
}
|
||||
|
||||
// take over model value, encoding and mode (only if more specific) from source model
|
||||
targetModel.updatePreferredEncoding(sourceModel.getEncoding());
|
||||
targetModel.updatePreferredEncoding(sourceModelEncoding);
|
||||
if (sourceModel.isResolved() && targetModel.isResolved()) {
|
||||
this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()));
|
||||
|
||||
|
@ -809,7 +820,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
|||
) {
|
||||
await this.fileService.del(target);
|
||||
|
||||
return this.doSaveTextFileAs(sourceModel, resource, target, options);
|
||||
return this.doSaveAsTextFile(sourceModel, source, target, options);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
|
|
@ -24,13 +24,12 @@ import { RunOnceScheduler, timeout } from 'vs/base/common/async';
|
|||
import { ITextBufferFactory } from 'vs/editor/common/model';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { isEqual, isEqualOrParent, extname, basename, joinPath } from 'vs/base/common/resources';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { IFilesConfigurationService, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
|
||||
export interface IBackupMetaData {
|
||||
mtime: number;
|
||||
|
@ -92,10 +91,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
|
||||
private lastResolvedFileStat: IFileStatWithMetadata | undefined;
|
||||
|
||||
private autoSaveAfterMillies: number | undefined;
|
||||
private autoSaveAfterMilliesEnabled: boolean | undefined;
|
||||
private readonly autoSaveDisposable = this._register(new MutableDisposable());
|
||||
|
||||
private readonly saveSequentializer = new SaveSequentializer();
|
||||
private lastSaveAttemptTime = 0;
|
||||
|
||||
|
@ -128,8 +123,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
) {
|
||||
super(modelService, modeService);
|
||||
|
||||
this.updateAutoSaveConfiguration(filesConfigurationService.getAutoSaveConfiguration());
|
||||
|
||||
// Make known to working copy service
|
||||
this._register(this.workingCopyService.registerWorkingCopy(this));
|
||||
|
||||
|
@ -138,7 +131,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
|
||||
private registerListeners(): void {
|
||||
this._register(this.fileService.onFileChanges(e => this.onFileChanges(e)));
|
||||
this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(config => this.updateAutoSaveConfiguration(config)));
|
||||
this._register(this.filesConfigurationService.onFilesAssociationChange(e => this.onFilesAssociationChange()));
|
||||
this._register(this.onDidStateChange(e => this.onStateChange(e)));
|
||||
}
|
||||
|
@ -206,13 +198,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
}
|
||||
}
|
||||
|
||||
private updateAutoSaveConfiguration(config: IAutoSaveConfiguration): void {
|
||||
const autoSaveAfterMilliesEnabled = (typeof config.autoSaveDelay === 'number') && config.autoSaveDelay > 0;
|
||||
|
||||
this.autoSaveAfterMilliesEnabled = autoSaveAfterMilliesEnabled;
|
||||
this.autoSaveAfterMillies = autoSaveAfterMilliesEnabled ? config.autoSaveDelay : undefined;
|
||||
}
|
||||
|
||||
private onFilesAssociationChange(): void {
|
||||
if (!this.isResolved()) {
|
||||
return;
|
||||
|
@ -258,9 +243,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
return false;
|
||||
}
|
||||
|
||||
// Cancel any running auto-save
|
||||
this.autoSaveDisposable.clear();
|
||||
|
||||
// Unset flags
|
||||
const wasDirty = this.dirty;
|
||||
const undo = this.setDirty(false);
|
||||
|
@ -474,13 +456,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
this.createTextEditorModel(value, resource, this.preferredMode);
|
||||
|
||||
// We restored a backup so we have to set the model as being dirty
|
||||
// We also want to trigger auto save if it is enabled to simulate the exact same behaviour
|
||||
// you would get if manually making the model dirty (fixes https://github.com/Microsoft/vscode/issues/16977)
|
||||
if (fromBackup) {
|
||||
this.doMakeDirty();
|
||||
if (this.autoSaveAfterMilliesEnabled) {
|
||||
this.doAutoSave(this.versionId);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we are not tracking a stale state
|
||||
|
@ -536,9 +513,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
|
||||
// The contents changed as a matter of Undo and the version reached matches the saved one
|
||||
// In this case we clear the dirty flag and emit a SAVED event to indicate this state.
|
||||
// Note: we currently only do this check when auto-save is turned off because there you see
|
||||
// a dirty indicator that you want to get rid of when undoing to the saved version.
|
||||
if (!this.autoSaveAfterMilliesEnabled && this.isResolved() && this.textEditorModel.getAlternativeVersionId() === this.bufferSavedVersionId) {
|
||||
if (this.isResolved() && this.textEditorModel.getAlternativeVersionId() === this.bufferSavedVersionId) {
|
||||
this.logService.trace('onModelContentChanged() - model content changed back to last saved version', this.resource);
|
||||
|
||||
// Clear flags
|
||||
|
@ -559,15 +534,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
// Mark as dirty
|
||||
this.doMakeDirty();
|
||||
|
||||
// Start auto save process unless we are in conflict resolution mode and unless it is disabled
|
||||
if (this.autoSaveAfterMilliesEnabled) {
|
||||
if (!this.inConflictMode) {
|
||||
this.doAutoSave(this.versionId);
|
||||
} else {
|
||||
this.logService.trace('makeDirty() - prevented save because we are in conflict resolution mode', this.resource);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle content change events
|
||||
this.contentChangeEventScheduler.schedule();
|
||||
}
|
||||
|
@ -593,27 +559,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
}
|
||||
}
|
||||
|
||||
private doAutoSave(versionId: number): void {
|
||||
this.logService.trace(`doAutoSave() - enter for versionId ${versionId}`, this.resource);
|
||||
|
||||
// Cancel any currently running auto saves to make this the one that succeeds
|
||||
this.autoSaveDisposable.clear();
|
||||
|
||||
// Create new save timer and store it for disposal as needed
|
||||
const handle = setTimeout(() => {
|
||||
|
||||
// Clear the timeout now that we are running
|
||||
this.autoSaveDisposable.clear();
|
||||
|
||||
// Only trigger save if the version id has not changed meanwhile
|
||||
if (versionId === this.versionId) {
|
||||
this.doSave(versionId, { reason: SaveReason.AUTO }); // Very important here to not return the promise because if the timeout promise is canceled it will bubble up the error otherwise - do not change
|
||||
}
|
||||
}, this.autoSaveAfterMillies);
|
||||
|
||||
this.autoSaveDisposable.value = toDisposable(() => clearTimeout(handle));
|
||||
}
|
||||
|
||||
async save(options: ITextFileSaveOptions = Object.create(null)): Promise<boolean> {
|
||||
if (!this.isResolved()) {
|
||||
return false;
|
||||
|
@ -621,9 +566,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
|
||||
this.logService.trace('save() - enter', this.resource);
|
||||
|
||||
// Cancel any currently running auto saves to make this the one that succeeds
|
||||
this.autoSaveDisposable.clear();
|
||||
|
||||
await this.doSave(this.versionId, options);
|
||||
|
||||
return true;
|
||||
|
@ -676,8 +618,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
}
|
||||
|
||||
// Push all edit operations to the undo stack so that the user has a chance to
|
||||
// Ctrl+Z back to the saved version. We only do this when auto-save is turned off
|
||||
if (!this.autoSaveAfterMilliesEnabled && this.isResolved()) {
|
||||
// Ctrl+Z back to the saved version.
|
||||
if (this.isResolved()) {
|
||||
this.textEditorModel.pushStackElement();
|
||||
}
|
||||
|
||||
|
@ -953,8 +895,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
return this.inOrphanMode;
|
||||
case ModelState.PENDING_SAVE:
|
||||
return this.saveSequentializer.hasPendingSave();
|
||||
case ModelState.PENDING_AUTO_SAVE:
|
||||
return !!this.autoSaveDisposable.value;
|
||||
case ModelState.SAVED:
|
||||
return !this.dirty;
|
||||
}
|
||||
|
|
|
@ -257,11 +257,6 @@ export const enum ModelState {
|
|||
*/
|
||||
PENDING_SAVE,
|
||||
|
||||
/**
|
||||
* A model is marked for being saved after a specific timeout.
|
||||
*/
|
||||
PENDING_AUTO_SAVE,
|
||||
|
||||
/**
|
||||
* A model is in conflict mode when changes cannot be saved because the
|
||||
* underlying file has changed. Models in conflict mode are always dirty.
|
||||
|
|
|
@ -41,6 +41,7 @@ import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/d
|
|||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
|
||||
export class NativeTextFileService extends AbstractTextFileService {
|
||||
|
||||
|
@ -62,9 +63,10 @@ export class NativeTextFileService extends AbstractTextFileService {
|
|||
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
|
||||
@IElectronService private readonly electronService: IElectronService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService
|
||||
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
|
||||
@ITextModelService textModelService: ITextModelService
|
||||
) {
|
||||
super(contextService, fileService, untitledTextEditorService, lifecycleService, instantiationService, modeService, modelService, environmentService, notificationService, backupFileService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService);
|
||||
super(contextService, fileService, untitledTextEditorService, lifecycleService, instantiationService, modeService, modelService, environmentService, notificationService, backupFileService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService, textModelService);
|
||||
}
|
||||
|
||||
private _encoding: EncodingOracle | undefined;
|
||||
|
|
|
@ -175,6 +175,10 @@ export class TextModelResolverService implements ITextModelService {
|
|||
}
|
||||
|
||||
hasTextModelContentProvider(scheme: string): boolean {
|
||||
if (scheme === network.Schemas.untitled || scheme === network.Schemas.inMemory) {
|
||||
return true; // we handle untitled:// and inMemory:// within
|
||||
}
|
||||
|
||||
return this.resourceModelCollection.hasTextModelContentProvider(scheme);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -192,6 +192,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
|||
|
||||
configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration);
|
||||
|
||||
let colorThemeSetting = this.configurationService.getValue<string>(COLOR_THEME_SETTING);
|
||||
if (colorThemeSetting !== this.currentColorTheme.settingsId) {
|
||||
const theme = await this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined);
|
||||
if (theme) {
|
||||
this.setColorTheme(theme.id, undefined);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentColorTheme.isLoaded) {
|
||||
const themeData = await this.colorThemeStore.findThemeData(this.currentColorTheme.id);
|
||||
if (!themeData) {
|
||||
|
@ -216,6 +225,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
|||
iconThemeSettingSchema.enumDescriptions = [iconThemeSettingSchema.enumDescriptions![0], ...event.themes.map(t => t.description || '')];
|
||||
configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration);
|
||||
|
||||
let iconThemeSetting = this.configurationService.getValue<string | null>(ICON_THEME_SETTING);
|
||||
if (iconThemeSetting !== this.currentIconTheme.settingsId) {
|
||||
const theme = await this.iconThemeStore.findThemeBySettingsId(iconThemeSetting);
|
||||
if (theme) {
|
||||
this.setFileIconTheme(theme.id, undefined);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentIconTheme.isLoaded) {
|
||||
const theme = await this.iconThemeStore.findThemeData(this.currentIconTheme.id);
|
||||
if (!theme) {
|
||||
|
|
|
@ -9,19 +9,25 @@ import { Event, Emitter } from 'vs/base/common/event';
|
|||
import { URI } from 'vs/base/common/uri';
|
||||
import { Disposable, IDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { ISaveOptions } from 'vs/workbench/common/editor';
|
||||
|
||||
export const enum WorkingCopyCapabilities {
|
||||
|
||||
/**
|
||||
* Signals that the working copy requires
|
||||
* additional input when saving, e.g. an
|
||||
* associated path to save to.
|
||||
* associated path to save to.
|
||||
*/
|
||||
Untitled = 1 << 1
|
||||
}
|
||||
|
||||
export interface IWorkingCopy {
|
||||
|
||||
readonly resource: URI;
|
||||
|
||||
readonly capabilities: WorkingCopyCapabilities;
|
||||
|
||||
|
||||
//#region Dirty Tracking
|
||||
|
||||
readonly onDidChangeDirty: Event<void>;
|
||||
|
@ -31,9 +37,11 @@ export interface IWorkingCopy {
|
|||
//#endregion
|
||||
|
||||
|
||||
readonly resource: URI;
|
||||
//#region Save
|
||||
|
||||
readonly capabilities: WorkingCopyCapabilities;
|
||||
save(options?: ISaveOptions): Promise<boolean>;
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
export const IWorkingCopyService = createDecorator<IWorkingCopyService>('workingCopyService');
|
||||
|
|
|
@ -9,6 +9,7 @@ import { URI } from 'vs/base/common/uri';
|
|||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { TestWorkingCopyService } from 'vs/workbench/test/workbenchTestServices';
|
||||
import type { ISaveOptions } from 'vs/workbench/common/editor';
|
||||
|
||||
suite('WorkingCopyService', () => {
|
||||
|
||||
|
@ -41,6 +42,10 @@ suite('WorkingCopyService', () => {
|
|||
return this.dirty;
|
||||
}
|
||||
|
||||
async save(options?: ISaveOptions): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._onDispose.fire();
|
||||
|
||||
|
|
|
@ -213,7 +213,8 @@ export class TestTextFileService extends NativeTextFileService {
|
|||
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
|
||||
@IElectronService electronService: IElectronService,
|
||||
@IProductService productService: IProductService,
|
||||
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService
|
||||
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
|
||||
@ITextModelService textModelService: ITextModelService
|
||||
) {
|
||||
super(
|
||||
contextService,
|
||||
|
@ -233,7 +234,8 @@ export class TestTextFileService extends NativeTextFileService {
|
|||
textResourceConfigurationService,
|
||||
electronService,
|
||||
productService,
|
||||
filesConfigurationService
|
||||
filesConfigurationService,
|
||||
textModelService
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue