mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 27ada910e121e23a6d95ecca9cae595fb98ab568
This commit is contained in:
@@ -103,14 +103,14 @@ export class ShiftCommand implements ICommand {
|
||||
const { tabSize, indentSize, insertSpaces } = this._opts;
|
||||
const shouldIndentEmptyLines = (startLine === endLine);
|
||||
|
||||
// if indenting or outdenting on a whitespace only line
|
||||
if (this._selection.isEmpty()) {
|
||||
if (/^\s*$/.test(model.getLineContent(startLine))) {
|
||||
this._useLastEditRangeForCursorEndPosition = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._opts.useTabStops) {
|
||||
// if indenting or outdenting on a whitespace only line
|
||||
if (this._selection.isEmpty()) {
|
||||
if (/^\s*$/.test(model.getLineContent(startLine))) {
|
||||
this._useLastEditRangeForCursorEndPosition = true;
|
||||
}
|
||||
}
|
||||
|
||||
// keep track of previous line's "miss-alignment"
|
||||
let previousLineExtraSpaces = 0, extraSpaces = 0;
|
||||
for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++, previousLineExtraSpaces = extraSpaces) {
|
||||
@@ -188,6 +188,11 @@ export class ShiftCommand implements ICommand {
|
||||
}
|
||||
} else {
|
||||
|
||||
// if indenting or outdenting on a whitespace only line
|
||||
if (!this._opts.isUnshift && this._selection.isEmpty() && model.getLineLength(startLine) === 0) {
|
||||
this._useLastEditRangeForCursorEndPosition = true;
|
||||
}
|
||||
|
||||
const oneIndent = (insertSpaces ? cachedStringRepeat(' ', indentSize) : '\t');
|
||||
|
||||
for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++) {
|
||||
|
||||
@@ -34,7 +34,6 @@ import { withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer';
|
||||
import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2 } from 'vs/editor/common/model/tokensStore';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { EditorTheme } from 'vs/editor/common/view/viewContext';
|
||||
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { TextChange } from 'vs/editor/common/model/textChange';
|
||||
@@ -172,6 +171,21 @@ const enum StringOffsetValidationType {
|
||||
SurrogatePairs = 1,
|
||||
}
|
||||
|
||||
type ContinueBracketSearchPredicate = null | (() => boolean);
|
||||
|
||||
class BracketSearchCanceled {
|
||||
public static INSTANCE = new BracketSearchCanceled();
|
||||
_searchCanceledBrand = undefined;
|
||||
private constructor() { }
|
||||
}
|
||||
|
||||
function stripBracketSearchCanceled<T>(result: T | null | BracketSearchCanceled): T | null {
|
||||
if (result instanceof BracketSearchCanceled) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
private static readonly MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB
|
||||
@@ -2014,7 +2028,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._findMatchingBracketUp(data, position);
|
||||
return stripBracketSearchCanceled(this._findMatchingBracketUp(data, position, null));
|
||||
}
|
||||
|
||||
public matchBracket(position: IPosition): [Range, Range] | null {
|
||||
@@ -2062,8 +2076,11 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
// check that we didn't hit a bracket too far away from position
|
||||
if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {
|
||||
const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();
|
||||
const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]);
|
||||
const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText], null);
|
||||
if (r) {
|
||||
if (r instanceof BracketSearchCanceled) {
|
||||
return null;
|
||||
}
|
||||
bestResult = r;
|
||||
}
|
||||
}
|
||||
@@ -2100,8 +2117,11 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
// check that we didn't hit a bracket too far away from position
|
||||
if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {
|
||||
const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();
|
||||
const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText]);
|
||||
const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText], null);
|
||||
if (r) {
|
||||
if (r instanceof BracketSearchCanceled) {
|
||||
return null;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@@ -2111,35 +2131,41 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean): [Range, Range] | null {
|
||||
private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean, continueSearchPredicate: ContinueBracketSearchPredicate): [Range, Range] | null | BracketSearchCanceled {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isOpen) {
|
||||
let matched = this._findMatchingBracketDown(data, foundBracket.getEndPosition());
|
||||
if (matched) {
|
||||
return [foundBracket, matched];
|
||||
}
|
||||
} else {
|
||||
let matched = this._findMatchingBracketUp(data, foundBracket.getStartPosition());
|
||||
if (matched) {
|
||||
return [foundBracket, matched];
|
||||
}
|
||||
const matched = (
|
||||
isOpen
|
||||
? this._findMatchingBracketDown(data, foundBracket.getEndPosition(), continueSearchPredicate)
|
||||
: this._findMatchingBracketUp(data, foundBracket.getStartPosition(), continueSearchPredicate)
|
||||
);
|
||||
|
||||
if (!matched) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (matched instanceof BracketSearchCanceled) {
|
||||
return matched;
|
||||
}
|
||||
|
||||
return [foundBracket, matched];
|
||||
}
|
||||
|
||||
private _findMatchingBracketUp(bracket: RichEditBracket, position: Position): Range | null {
|
||||
private _findMatchingBracketUp(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled {
|
||||
// console.log('_findMatchingBracketUp: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position));
|
||||
|
||||
const languageId = bracket.languageIdentifier.id;
|
||||
const reversedBracketRegex = bracket.reversedRegex;
|
||||
let count = -1;
|
||||
|
||||
const searchPrevMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null => {
|
||||
let totalCallCount = 0;
|
||||
const searchPrevMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => {
|
||||
while (true) {
|
||||
if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
|
||||
return BracketSearchCanceled.INSTANCE;
|
||||
}
|
||||
const r = BracketsUtils.findPrevBracketInRange(reversedBracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (!r) {
|
||||
break;
|
||||
@@ -2214,15 +2240,19 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
private _findMatchingBracketDown(bracket: RichEditBracket, position: Position): Range | null {
|
||||
private _findMatchingBracketDown(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled {
|
||||
// console.log('_findMatchingBracketDown: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position));
|
||||
|
||||
const languageId = bracket.languageIdentifier.id;
|
||||
const bracketRegex = bracket.forwardRegex;
|
||||
let count = 1;
|
||||
|
||||
const searchNextMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null => {
|
||||
let totalCallCount = 0;
|
||||
const searchNextMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => {
|
||||
while (true) {
|
||||
if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
|
||||
return BracketSearchCanceled.INSTANCE;
|
||||
}
|
||||
const r = BracketsUtils.findNextBracketInRange(bracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (!r) {
|
||||
break;
|
||||
@@ -2452,7 +2482,16 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
public findEnclosingBrackets(_position: IPosition, maxDuration = Constants.MAX_SAFE_SMALL_INTEGER): [Range, Range] | null {
|
||||
public findEnclosingBrackets(_position: IPosition, maxDuration?: number): [Range, Range] | null {
|
||||
let continueSearchPredicate: ContinueBracketSearchPredicate;
|
||||
if (typeof maxDuration === 'undefined') {
|
||||
continueSearchPredicate = null;
|
||||
} else {
|
||||
const startTime = Date.now();
|
||||
continueSearchPredicate = () => {
|
||||
return (Date.now() - startTime <= maxDuration);
|
||||
};
|
||||
}
|
||||
const position = this.validatePosition(_position);
|
||||
const lineCount = this.getLineCount();
|
||||
const savedCounts = new Map<number, number[]>();
|
||||
@@ -2468,8 +2507,13 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
}
|
||||
counts = savedCounts.get(languageId)!;
|
||||
};
|
||||
const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null => {
|
||||
|
||||
let totalCallCount = 0;
|
||||
const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null | BracketSearchCanceled => {
|
||||
while (true) {
|
||||
if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
|
||||
return BracketSearchCanceled.INSTANCE;
|
||||
}
|
||||
const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (!r) {
|
||||
break;
|
||||
@@ -2485,7 +2529,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
}
|
||||
|
||||
if (counts[bracket.index] === -1) {
|
||||
return this._matchFoundBracket(r, bracket, false);
|
||||
return this._matchFoundBracket(r, bracket, false, continueSearchPredicate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2496,12 +2540,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
let languageId: LanguageId = -1;
|
||||
let modeBrackets: RichEditBrackets | null = null;
|
||||
const startTime = Date.now();
|
||||
for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
if (elapsedTime > maxDuration) {
|
||||
return null;
|
||||
}
|
||||
const lineTokens = this._getLineTokens(lineNumber);
|
||||
const tokenCount = lineTokens.getCount();
|
||||
const lineText = this._buffer.getLineContent(lineNumber);
|
||||
@@ -2530,7 +2569,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
|
||||
const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (r) {
|
||||
return r;
|
||||
return stripBracketSearchCanceled(r);
|
||||
}
|
||||
prevSearchInToken = false;
|
||||
}
|
||||
@@ -2555,7 +2594,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
|
||||
const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (r) {
|
||||
return r;
|
||||
return stripBracketSearchCanceled(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2566,7 +2605,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
|
||||
const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (r) {
|
||||
return r;
|
||||
return stripBracketSearchCanceled(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1034,10 +1034,12 @@ export class TokensStore2 {
|
||||
aIndex++;
|
||||
}
|
||||
|
||||
if (aIndex < aLen && aTokens.getEndOffset(aIndex) === bEndCharacter) {
|
||||
// `a` ends exactly at the same spot as `b`!
|
||||
emitToken(aTokens.getEndOffset(aIndex), (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask));
|
||||
aIndex++;
|
||||
if (aIndex < aLen) {
|
||||
emitToken(bEndCharacter, (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask));
|
||||
if (aTokens.getEndOffset(aIndex) === bEndCharacter) {
|
||||
// `a` ends exactly at the same spot as `b`!
|
||||
aIndex++;
|
||||
}
|
||||
} else {
|
||||
const aMergeIndex = Math.min(Math.max(0, aIndex - 1), aLen - 1);
|
||||
|
||||
|
||||
@@ -94,13 +94,15 @@ export function getWordAtText(column: number, wordDefinition: RegExp, text: stri
|
||||
// should stop so that subsequent search don't repeat previous searches
|
||||
const regexIndex = pos - config.windowSize * i;
|
||||
wordDefinition.lastIndex = Math.max(0, regexIndex);
|
||||
match = _findRegexMatchEnclosingPosition(wordDefinition, text, pos, prevRegexIndex);
|
||||
const thisMatch = _findRegexMatchEnclosingPosition(wordDefinition, text, pos, prevRegexIndex);
|
||||
|
||||
// stop: found something
|
||||
if (match) {
|
||||
if (!thisMatch && match) {
|
||||
// stop: we have something
|
||||
break;
|
||||
}
|
||||
|
||||
match = thisMatch;
|
||||
|
||||
// stop: searched at start
|
||||
if (regexIndex <= 0) {
|
||||
break;
|
||||
@@ -112,7 +114,7 @@ export function getWordAtText(column: number, wordDefinition: RegExp, text: stri
|
||||
let result = {
|
||||
word: match[0],
|
||||
startColumn: textOffset + 1 + match.index!,
|
||||
endColumn: textOffset + 1 + wordDefinition.lastIndex
|
||||
endColumn: textOffset + 1 + match.index! + match[0].length
|
||||
};
|
||||
wordDefinition.lastIndex = 0;
|
||||
return result;
|
||||
|
||||
@@ -1531,6 +1531,21 @@ export interface CommentReaction {
|
||||
readonly canEdit?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface CommentOptions {
|
||||
/**
|
||||
* An optional string to show on the comment input box when it's collapsed.
|
||||
*/
|
||||
prompt?: string;
|
||||
|
||||
/**
|
||||
* An optional string to show as placeholder in the comment input box when it's focused.
|
||||
*/
|
||||
placeHolder?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@ import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
// Define extension point ids
|
||||
export const Extensions = {
|
||||
@@ -30,9 +31,19 @@ export class EditorModesRegistry {
|
||||
|
||||
// --- languages
|
||||
|
||||
public registerLanguage(def: ILanguageExtensionPoint): void {
|
||||
public registerLanguage(def: ILanguageExtensionPoint): IDisposable {
|
||||
this._languages.push(def);
|
||||
this._onDidChangeLanguages.fire(undefined);
|
||||
return {
|
||||
dispose: () => {
|
||||
for (let i = 0, len = this._languages.length; i < len; i++) {
|
||||
if (this._languages[i] === def) {
|
||||
this._languages.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
public setDynamicLanguages(def: ILanguageExtensionPoint[]): void {
|
||||
this._dynamicLanguages = def;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
@@ -28,13 +27,9 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IUndoRedoService, IUndoRedoElement, IPastFutureElements } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { StringSHA1 } from 'vs/base/common/hash';
|
||||
import { SingleModelEditStackElement, MultiModelEditStackElement, EditStackElement } from 'vs/editor/common/model/editStack';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling';
|
||||
|
||||
export const MAINTAIN_UNDO_REDO_STACK = true;
|
||||
|
||||
export interface IEditorSemanticHighlightingOptions {
|
||||
enabled?: boolean;
|
||||
}
|
||||
@@ -143,6 +138,8 @@ function isEditStackElements(elements: IUndoRedoElement[]): elements is EditStac
|
||||
class DisposedModelInfo {
|
||||
constructor(
|
||||
public readonly uri: URI,
|
||||
public readonly time: number,
|
||||
public readonly heapSize: number,
|
||||
public readonly sha1: string,
|
||||
public readonly versionId: number,
|
||||
public readonly alternativeVersionId: number,
|
||||
@@ -151,8 +148,6 @@ class DisposedModelInfo {
|
||||
|
||||
export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
|
||||
private static _PROMPT_UNDO_REDO_SIZE_LIMIT = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
public _serviceBrand: undefined;
|
||||
|
||||
private readonly _onModelAdded: Emitter<ITextModel> = this._register(new Emitter<ITextModel>());
|
||||
@@ -171,6 +166,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
*/
|
||||
private readonly _models: { [modelId: string]: ModelData; };
|
||||
private readonly _disposedModels: Map<string, DisposedModelInfo>;
|
||||
private _disposedModelsHeapSize: number;
|
||||
private readonly _semanticStyling: SemanticStyling;
|
||||
|
||||
constructor(
|
||||
@@ -179,12 +175,12 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
) {
|
||||
super();
|
||||
this._modelCreationOptionsByLanguageAndResource = Object.create(null);
|
||||
this._models = {};
|
||||
this._disposedModels = new Map<string, DisposedModelInfo>();
|
||||
this._disposedModelsHeapSize = 0;
|
||||
this._semanticStyling = this._register(new SemanticStyling(this._themeService, this._logService));
|
||||
|
||||
this._register(this._configurationService.onDidChangeConfiguration(() => this._updateModelOptions()));
|
||||
@@ -267,6 +263,14 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
return platform.OS === platform.OperatingSystem.Linux || platform.OS === platform.OperatingSystem.Macintosh ? '\n' : '\r\n';
|
||||
}
|
||||
|
||||
private _getMaxMemoryForClosedFilesUndoStack(): number {
|
||||
const result = this._configurationService.getValue<number>('files.maxMemoryForClosedFilesUndoStackMB');
|
||||
if (typeof result === 'number') {
|
||||
return result * 1024 * 1024;
|
||||
}
|
||||
return 20 * 1024 * 1024;
|
||||
}
|
||||
|
||||
public getCreationOptions(language: string, resource: URI | undefined, isForSimpleWidget: boolean): ITextModelCreationOptions {
|
||||
let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource];
|
||||
if (!creationOptions) {
|
||||
@@ -328,13 +332,40 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
|
||||
// --- begin IModelService
|
||||
|
||||
private _insertDisposedModel(disposedModelData: DisposedModelInfo): void {
|
||||
this._disposedModels.set(MODEL_ID(disposedModelData.uri), disposedModelData);
|
||||
this._disposedModelsHeapSize += disposedModelData.heapSize;
|
||||
}
|
||||
|
||||
private _removeDisposedModel(resource: URI): DisposedModelInfo | undefined {
|
||||
const disposedModelData = this._disposedModels.get(MODEL_ID(resource));
|
||||
if (disposedModelData) {
|
||||
this._disposedModelsHeapSize -= disposedModelData.heapSize;
|
||||
}
|
||||
this._disposedModels.delete(MODEL_ID(resource));
|
||||
return disposedModelData;
|
||||
}
|
||||
|
||||
private _ensureDisposedModelsHeapSize(maxModelsHeapSize: number): void {
|
||||
if (this._disposedModelsHeapSize > maxModelsHeapSize) {
|
||||
// we must remove some old undo stack elements to free up some memory
|
||||
const disposedModels: DisposedModelInfo[] = [];
|
||||
this._disposedModels.forEach(entry => disposedModels.push(entry));
|
||||
disposedModels.sort((a, b) => a.time - b.time);
|
||||
while (disposedModels.length > 0 && this._disposedModelsHeapSize > maxModelsHeapSize) {
|
||||
const disposedModel = disposedModels.shift()!;
|
||||
this._removeDisposedModel(disposedModel.uri);
|
||||
this._undoRedoService.removeElements(disposedModel.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI | undefined, isForSimpleWidget: boolean): ModelData {
|
||||
// create & save the model
|
||||
const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget);
|
||||
const model: TextModel = new TextModel(value, options, languageIdentifier, resource, this._undoRedoService);
|
||||
if (resource && this._disposedModels.has(MODEL_ID(resource))) {
|
||||
const disposedModelData = this._disposedModels.get(MODEL_ID(resource))!;
|
||||
this._disposedModels.delete(MODEL_ID(resource));
|
||||
const disposedModelData = this._removeDisposedModel(resource)!;
|
||||
const elements = this._undoRedoService.getElements(resource);
|
||||
if (computeModelSha1(model) === disposedModelData.sha1 && isEditStackPastFutureElements(elements)) {
|
||||
for (const element of elements.past) {
|
||||
@@ -473,7 +504,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
const model = modelData.model;
|
||||
let maintainUndoRedoStack = false;
|
||||
let heapSize = 0;
|
||||
if (MAINTAIN_UNDO_REDO_STACK && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData)) {
|
||||
if (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData) {
|
||||
const elements = this._undoRedoService.getElements(resource);
|
||||
if ((elements.past.length > 0 || elements.future.length > 0) && isEditStackPastFutureElements(elements)) {
|
||||
maintainUndoRedoStack = true;
|
||||
@@ -490,37 +521,27 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
}
|
||||
}
|
||||
|
||||
if (maintainUndoRedoStack) {
|
||||
// We only invalidate the elements, but they remain in the undo-redo service.
|
||||
this._undoRedoService.setElementsIsValid(resource, false);
|
||||
this._disposedModels.set(MODEL_ID(resource), new DisposedModelInfo(resource, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId()));
|
||||
} else {
|
||||
if (!maintainUndoRedoStack) {
|
||||
this._undoRedoService.removeElements(resource);
|
||||
modelData.model.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
const maxMemory = this._getMaxMemoryForClosedFilesUndoStack();
|
||||
if (heapSize > maxMemory) {
|
||||
// the undo stack for this file would never fit in the configured memory, so don't bother with it.
|
||||
this._undoRedoService.removeElements(resource);
|
||||
modelData.model.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
this._ensureDisposedModelsHeapSize(maxMemory - heapSize);
|
||||
|
||||
// We only invalidate the elements, but they remain in the undo-redo service.
|
||||
this._undoRedoService.setElementsIsValid(resource, false);
|
||||
this._insertDisposedModel(new DisposedModelInfo(resource, Date.now(), heapSize, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId()));
|
||||
|
||||
modelData.model.dispose();
|
||||
|
||||
// After disposing the model, prompt and ask if we should keep the undo-redo stack
|
||||
if (maintainUndoRedoStack && heapSize > ModelServiceImpl._PROMPT_UNDO_REDO_SIZE_LIMIT) {
|
||||
const mbSize = (heapSize / 1024 / 1024).toFixed(1);
|
||||
this._dialogService.show(
|
||||
Severity.Info,
|
||||
nls.localize('undoRedoConfirm', "Keep the undo-redo stack for {0} in memory ({1} MB)?", (resource.scheme === Schemas.file ? resource.fsPath : resource.path), mbSize),
|
||||
[
|
||||
nls.localize('nok', "Discard"),
|
||||
nls.localize('ok', "Keep"),
|
||||
],
|
||||
{
|
||||
cancelId: 2
|
||||
}
|
||||
).then((result) => {
|
||||
const discard = (result.choice === 2 || result.choice === 0);
|
||||
if (discard) {
|
||||
this._disposedModels.delete(MODEL_ID(resource));
|
||||
this._undoRedoService.removeElements(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getModels(): ITextModel[] {
|
||||
|
||||
@@ -36,6 +36,7 @@ export const editorBracketMatchBackground = registerColor('editorBracketMatch.ba
|
||||
export const editorBracketMatchBorder = registerColor('editorBracketMatch.border', { dark: '#888', light: '#B9B9B9', hc: contrastBorder }, nls.localize('editorBracketMatchBorder', 'Color for matching brackets boxes'));
|
||||
|
||||
export const editorOverviewRulerBorder = registerColor('editorOverviewRuler.border', { dark: '#7f7f7f4d', light: '#7f7f7f4d', hc: '#7f7f7f4d' }, nls.localize('editorOverviewRulerBorder', 'Color of the overview ruler border.'));
|
||||
export const editorOverviewRulerBackground = registerColor('editorOverviewRuler.background', null, nls.localize('editorOverviewRulerBackground', 'Background color of the editor overview ruler. Only used when the minimap is enabled and placed on the right side of the editor.'));
|
||||
|
||||
export const editorGutter = registerColor('editorGutter.background', { dark: editorBackground, light: editorBackground, hc: editorBackground }, nls.localize('editorGutter', 'Background color of the editor gutter. The gutter contains the glyph margins and the line numbers.'));
|
||||
|
||||
|
||||
@@ -119,6 +119,8 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
let breakingOffsets: number[] = arrPool1;
|
||||
let breakingOffsetsVisibleColumn: number[] = arrPool2;
|
||||
let breakingOffsetsCount: number = 0;
|
||||
let lastBreakingOffset = 0;
|
||||
let lastBreakingOffsetVisibleColumn = 0;
|
||||
|
||||
let breakingColumn = firstLineBreakColumn;
|
||||
const prevLen = prevBreakingOffsets.length;
|
||||
@@ -138,8 +140,12 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
|
||||
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 prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex];
|
||||
let prevBreakOffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex];
|
||||
if (lastBreakingOffset > prevBreakOffset) {
|
||||
prevBreakOffset = lastBreakingOffset;
|
||||
prevBreakOffsetVisibleColumn = lastBreakingOffsetVisibleColumn;
|
||||
}
|
||||
|
||||
let breakOffset = 0;
|
||||
let breakOffsetVisibleColumn = 0;
|
||||
@@ -148,10 +154,10 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
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);
|
||||
if (prevBreakOffsetVisibleColumn <= breakingColumn) {
|
||||
let visibleColumn = prevBreakOffsetVisibleColumn;
|
||||
let prevCharCode = prevBreakOffset === 0 ? CharCode.Null : lineText.charCodeAt(prevBreakOffset - 1);
|
||||
let prevCharCodeClass = prevBreakOffset === 0 ? CharacterClass.NONE : classifier.get(prevCharCode);
|
||||
let entireLineFits = true;
|
||||
for (let i = prevBreakOffset; i < len; i++) {
|
||||
const charStartOffset = i;
|
||||
@@ -169,7 +175,7 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
|
||||
}
|
||||
|
||||
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
|
||||
if (charStartOffset > lastBreakingOffset && canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
|
||||
breakOffset = charStartOffset;
|
||||
breakOffsetVisibleColumn = visibleColumn;
|
||||
}
|
||||
@@ -179,8 +185,14 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
// 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 (charStartOffset > lastBreakingOffset) {
|
||||
forcedBreakOffset = charStartOffset;
|
||||
forcedBreakOffsetVisibleColumn = visibleColumn - charWidth;
|
||||
} else {
|
||||
// we need to advance at least by one character
|
||||
forcedBreakOffset = i + 1;
|
||||
forcedBreakOffsetVisibleColumn = visibleColumn;
|
||||
}
|
||||
|
||||
if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) {
|
||||
// Cannot break at `breakOffset` => reset it if it was set
|
||||
@@ -198,7 +210,7 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
if (entireLineFits) {
|
||||
// there is no more need to break => stop the outer loop!
|
||||
if (breakingOffsetsCount > 0) {
|
||||
// Add last segment
|
||||
// Add last segment, no need to assign to `lastBreakingOffset` and `lastBreakingOffsetVisibleColumn`
|
||||
breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1];
|
||||
breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1];
|
||||
breakingOffsetsCount++;
|
||||
@@ -209,11 +221,11 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
|
||||
if (breakOffset === 0) {
|
||||
// must search left
|
||||
let visibleColumn = prevBreakoffsetVisibleColumn;
|
||||
let visibleColumn = prevBreakOffsetVisibleColumn;
|
||||
let charCode = lineText.charCodeAt(prevBreakOffset);
|
||||
let charCodeClass = classifier.get(charCode);
|
||||
let hitATabCharacter = false;
|
||||
for (let i = prevBreakOffset - 1; i >= 0; i--) {
|
||||
for (let i = prevBreakOffset - 1; i >= lastBreakingOffset; i--) {
|
||||
const charStartOffset = i + 1;
|
||||
const prevCharCode = lineText.charCodeAt(i);
|
||||
|
||||
@@ -290,7 +302,9 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
breakOffsetVisibleColumn = forcedBreakOffsetVisibleColumn;
|
||||
}
|
||||
|
||||
lastBreakingOffset = breakOffset;
|
||||
breakingOffsets[breakingOffsetsCount] = breakOffset;
|
||||
lastBreakingOffsetVisibleColumn = breakOffsetVisibleColumn;
|
||||
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
|
||||
breakingOffsetsCount++;
|
||||
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn;
|
||||
|
||||
Reference in New Issue
Block a user