mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-27 22:21:24 +00:00
Merge branch 'main' into dev/dmitriv/keybinding-labels
This commit is contained in:
commit
a38b1feb33
@ -7,7 +7,7 @@
|
||||
|
||||
import { Model } from '../model';
|
||||
import { Repository as BaseRepository, Resource } from '../repository';
|
||||
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailsProvider, GitErrorCodes, CloneOptions, CommitShortStat, DiffChange } from './git';
|
||||
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailsProvider, GitErrorCodes, CloneOptions, CommitShortStat, DiffChange, Worktree } from './git';
|
||||
import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode';
|
||||
import { combinedDisposable, filterEvent, mapEvent } from '../util';
|
||||
import { toGitUri } from '../uri';
|
||||
@ -52,6 +52,7 @@ export class ApiRepositoryState implements RepositoryState {
|
||||
get refs(): Ref[] { console.warn('Deprecated. Use ApiRepository.getRefs() instead.'); return []; }
|
||||
get remotes(): Remote[] { return [...this.#repository.remotes]; }
|
||||
get submodules(): Submodule[] { return [...this.#repository.submodules]; }
|
||||
get worktrees(): Worktree[] { return this.#repository.worktrees; }
|
||||
get rebaseCommit(): Commit | undefined { return this.#repository.rebaseCommit; }
|
||||
|
||||
get mergeChanges(): Change[] { return this.#repository.mergeGroup.resourceStates.map(r => new ApiChange(r)); }
|
||||
@ -553,6 +554,7 @@ export function registerAPICommands(extension: GitExtensionImpl): Disposable {
|
||||
refs: state.refs.map(ref),
|
||||
remotes: state.remotes,
|
||||
submodules: state.submodules,
|
||||
worktrees: state.worktrees,
|
||||
rebaseCommit: state.rebaseCommit,
|
||||
mergeChanges: state.mergeChanges.map(change),
|
||||
indexChanges: state.indexChanges.map(change),
|
||||
|
||||
4
extensions/git/src/api/git.d.ts
vendored
4
extensions/git/src/api/git.d.ts
vendored
@ -81,7 +81,6 @@ export interface Worktree {
|
||||
readonly path: string;
|
||||
readonly ref: string;
|
||||
readonly detached: boolean;
|
||||
readonly commitDetails?: Commit;
|
||||
}
|
||||
|
||||
export const enum Status {
|
||||
@ -131,6 +130,7 @@ export interface RepositoryState {
|
||||
readonly refs: Ref[];
|
||||
readonly remotes: Remote[];
|
||||
readonly submodules: Submodule[];
|
||||
readonly worktrees: Worktree[];
|
||||
readonly rebaseCommit: Commit | undefined;
|
||||
|
||||
readonly mergeChanges: Change[];
|
||||
@ -262,7 +262,7 @@ export interface Repository {
|
||||
diffBetween(ref1: string, ref2: string): Promise<Change[]>;
|
||||
diffBetween(ref1: string, ref2: string, path: string): Promise<string>;
|
||||
diffBetweenWithStats(ref1: string, ref2: string, path?: string): Promise<DiffChange[]>;
|
||||
|
||||
|
||||
hashObject(data: string): Promise<string>;
|
||||
|
||||
createBranch(name: string, checkout: boolean, ref?: string): Promise<void>;
|
||||
|
||||
@ -8,8 +8,8 @@ import * as path from 'path';
|
||||
import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages, SourceControlArtifact } from 'vscode';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator';
|
||||
import { ForcePushMode, GitErrorCodes, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote, Branch, Ref, Worktree } from './api/git';
|
||||
import { Git, GitError, Stash } from './git';
|
||||
import { ForcePushMode, GitErrorCodes, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote, Branch, Ref } from './api/git';
|
||||
import { Git, GitError, Stash, Worktree } from './git';
|
||||
import { Model } from './model';
|
||||
import { GitResourceGroup, Repository, Resource, ResourceGroupType } from './repository';
|
||||
import { DiffEditorSelectionHunkToolbarContext, LineChange, applyLineChanges, getIndexDiffInformation, getModifiedRange, getWorkingTreeDiffInformation, intersectDiffWithRange, invertLineChange, toLineChanges, toLineRanges, compareLineChanges } from './staging';
|
||||
|
||||
@ -13,7 +13,7 @@ import { EventEmitter } from 'events';
|
||||
import * as filetype from 'file-type';
|
||||
import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals, isMacintosh, isDescendant, relativePathWithNoFallback, Mutable } from './util';
|
||||
import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, Progress, Uri, workspace } from 'vscode';
|
||||
import { Commit as ApiCommit, Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery as ApiRefQuery, InitOptions, Worktree, DiffChange } from './api/git';
|
||||
import { Commit as ApiCommit, Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery as ApiRefQuery, InitOptions, DiffChange, Worktree as ApiWorktree } from './api/git';
|
||||
import * as byline from 'byline';
|
||||
import { StringDecoder } from 'string_decoder';
|
||||
|
||||
@ -1303,6 +1303,10 @@ export interface PullOptions {
|
||||
readonly cancellationToken?: CancellationToken;
|
||||
}
|
||||
|
||||
export interface Worktree extends ApiWorktree {
|
||||
readonly commitDetails?: ApiCommit;
|
||||
}
|
||||
|
||||
export class Repository {
|
||||
private _isUsingRefTable = false;
|
||||
|
||||
|
||||
@ -457,7 +457,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi
|
||||
@debounce(500)
|
||||
private eventuallyScanPossibleGitRepositories(): void {
|
||||
for (const path of this.possibleGitRepositoryPaths) {
|
||||
this.openRepository(path, false, true);
|
||||
this.openRepository(path);
|
||||
}
|
||||
|
||||
this.possibleGitRepositoryPaths.clear();
|
||||
@ -572,7 +572,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi
|
||||
}
|
||||
|
||||
@sequentialize
|
||||
async openRepository(repoPath: string, openIfClosed = false, openIfParent = false): Promise<void> {
|
||||
async openRepository(repoPath: string, openIfClosed = false): Promise<void> {
|
||||
this.logger.trace(`[Model][openRepository] Repository: ${repoPath}`);
|
||||
const existingRepository = await this.getRepositoryExact(repoPath);
|
||||
if (existingRepository) {
|
||||
@ -621,7 +621,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi
|
||||
const parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt');
|
||||
if (parentRepositoryConfig !== 'always' && this.globalState.get<boolean>(`parentRepository:${repositoryRoot}`) !== true) {
|
||||
const isRepositoryOutsideWorkspace = await this.isRepositoryOutsideWorkspace(repositoryRoot);
|
||||
if (!openIfParent && isRepositoryOutsideWorkspace) {
|
||||
if (isRepositoryOutsideWorkspace) {
|
||||
this.logger.trace(`[Model][openRepository] Repository in parent folder: ${repositoryRoot}`);
|
||||
|
||||
if (!this._parentRepositoriesManager.hasRepository(repositoryRoot)) {
|
||||
@ -1094,6 +1094,14 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi
|
||||
return true;
|
||||
}
|
||||
|
||||
// The repository path may be a worktree (usually stored outside the workspace) so we have
|
||||
// to check the repository path against all the worktree paths of the repositories that have
|
||||
// already been opened.
|
||||
const worktreePaths = this.repositories.map(r => r.worktrees.map(w => w.path)).flat();
|
||||
if (worktreePaths.some(p => pathEquals(p, repositoryPath))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The repository path may be a canonical path or it may contain a symbolic link so we have
|
||||
// to match it against the workspace folders and the canonical paths of the workspace folders
|
||||
const workspaceFolderPaths = new Set<string | undefined>([
|
||||
|
||||
@ -10,11 +10,11 @@ import picomatch from 'picomatch';
|
||||
import { CancellationError, CancellationToken, CancellationTokenSource, Command, commands, Disposable, Event, EventEmitter, FileDecoration, FileType, l10n, LogLevel, LogOutputChannel, Memento, ProgressLocation, ProgressOptions, QuickDiffProvider, RelativePattern, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, TabInputNotebookDiff, TabInputTextDiff, TabInputTextMultiDiff, ThemeColor, ThemeIcon, Uri, window, workspace, WorkspaceEdit } from 'vscode';
|
||||
import { ActionButton } from './actionButton';
|
||||
import { ApiRepository } from './api/api1';
|
||||
import { Branch, BranchQuery, Change, CommitOptions, DiffChange, FetchOptions, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status, Worktree } from './api/git';
|
||||
import { Branch, BranchQuery, Change, CommitOptions, DiffChange, FetchOptions, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status } from './api/git';
|
||||
import { AutoFetcher } from './autofetch';
|
||||
import { GitBranchProtectionProvider, IBranchProtectionProviderRegistry } from './branchProtection';
|
||||
import { debounce, memoize, sequentialize, throttle } from './decorators';
|
||||
import { Repository as BaseRepository, BlameInformation, Commit, CommitShortStat, GitError, LogFileOptions, LsTreeElement, PullOptions, RefQuery, Stash, Submodule } from './git';
|
||||
import { Repository as BaseRepository, BlameInformation, Commit, CommitShortStat, GitError, LogFileOptions, LsTreeElement, PullOptions, RefQuery, Stash, Submodule, Worktree } from './git';
|
||||
import { GitHistoryProvider } from './historyProvider';
|
||||
import { Operation, OperationKind, OperationManager, OperationResult } from './operation';
|
||||
import { CommitCommandsCenter, IPostCommitCommandsProviderRegistry } from './postCommitCommands';
|
||||
|
||||
@ -25,5 +25,40 @@
|
||||
"start": "^\\s*#Region\\b",
|
||||
"end": "^\\s*#End Region\\b"
|
||||
}
|
||||
}
|
||||
},
|
||||
"indentationRules": {
|
||||
"decreaseIndentPattern": {
|
||||
"pattern": "^\\s*((End\\s+(If|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Select|Try|While|For|Property|Get|Set|SyncLock|Using|AddHandler|RaiseEvent|RemoveHandler|Event|Operator))|Else|ElseIf|Case|Catch|Finally|Loop|Next|Wend|Until)\\b",
|
||||
"flags": "i"
|
||||
},
|
||||
"increaseIndentPattern": {
|
||||
"pattern": "^\\s*((If|ElseIf).*Then(?!\\s+(End\\s+If))\\s*(('|REM).*)?$)|\\b(Else|While|For|Do|Select\\s+Case|Case|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Try|Catch|Finally|SyncLock|Using|Property|Get|Set|AddHandler|RaiseEvent|RemoveHandler|Event|Operator)\\b(?!.*\\bEnd\\s+(If|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Select|Try|While|For|Property|Get|Set|SyncLock|Using|AddHandler|RaiseEvent|RemoveHandler|Event|Operator)\\b).*(('|REM).*)?$",
|
||||
"flags": "i"
|
||||
}
|
||||
},
|
||||
"onEnterRules": [
|
||||
// Prevent indent after End statements, block terminators (Else, ElseIf, Loop, Next, etc.)
|
||||
{
|
||||
"beforeText": { "pattern": "^\\s*((End\\s+(If|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Select|Try|While|For|Property|Get|Set|SyncLock|Using|AddHandler|RaiseEvent|RemoveHandler|Event|Operator))|Else|ElseIf|Loop|Next|Wend|Until)\\b.*$", "flags": "i" },
|
||||
"action": {
|
||||
"indent": "none"
|
||||
}
|
||||
},
|
||||
// Prevent indent when pressing Enter on a blank line after End statements or block terminators
|
||||
{
|
||||
"beforeText": "^\\s*$",
|
||||
"previousLineText": { "pattern": "^\\s*((End\\s+(If|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Select|Try|While|For|Property|Get|Set|SyncLock|Using|AddHandler|RaiseEvent|RemoveHandler|Event|Operator))|Else|ElseIf|Loop|Next|Wend|Until)\\b.*$", "flags": "i" },
|
||||
"action": {
|
||||
"indent": "none"
|
||||
}
|
||||
},
|
||||
// Prevent indent after lines ending with closing parenthesis (e.g., function calls, method invocations)
|
||||
{
|
||||
"beforeText": { "pattern": "^[^'\"]*\\)\\s*('.*)?$", "flags": "i" },
|
||||
"afterText": "^(?!\\s*\\))",
|
||||
"action": {
|
||||
"indent": "none"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -364,6 +364,9 @@ export class Range {
|
||||
return new Range(this.startLineNumber + lineCount, this.startColumn, this.endLineNumber + lineCount, this.endColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if this range starts and ends on the same line.
|
||||
*/
|
||||
public isSingleLine(): boolean {
|
||||
return this.startLineNumber === this.endLineNumber;
|
||||
}
|
||||
|
||||
@ -192,10 +192,7 @@ export function guessIndentation(source: ITextBuffer, defaultTabSize: number, de
|
||||
|
||||
// Guess tabSize only if inserting spaces...
|
||||
if (insertSpaces) {
|
||||
let tabSizeScore = (insertSpaces ? 0 : 0.1 * linesCount);
|
||||
|
||||
// console.log("score threshold: " + tabSizeScore);
|
||||
|
||||
let tabSizeScore = 0;
|
||||
ALLOWED_TAB_SIZE_GUESSES.forEach((possibleTabSize) => {
|
||||
const possibleTabSizeScore = spacesDiffCount[possibleTabSize];
|
||||
if (possibleTabSizeScore > tabSizeScore) {
|
||||
@ -204,14 +201,14 @@ export function guessIndentation(source: ITextBuffer, defaultTabSize: number, de
|
||||
}
|
||||
});
|
||||
|
||||
// Let a tabSize of 2 win even if it is not the maximum
|
||||
// (only in case 4 was guessed)
|
||||
if (tabSize === 4 && spacesDiffCount[4] > 0 && spacesDiffCount[2] > 0 && spacesDiffCount[2] >= spacesDiffCount[4] / 2) {
|
||||
// Let a tabSize of 2 win over 4 only if it has at least 2/3 of the occurrences of 4
|
||||
// This helps detect 2-space indentation in cases like YAML files where there might be
|
||||
// some 4-space diffs from deeper nesting, while still preferring 4 when it's clearly predominant
|
||||
if (tabSize === 4 && spacesDiffCount[4] > 0 && spacesDiffCount[2] > 0 && spacesDiffCount[2] >= spacesDiffCount[4] * 2 / 3) {
|
||||
tabSize = 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// console.log('--------------------------');
|
||||
// console.log('linesIndentedWithTabsCount: ' + linesIndentedWithTabsCount + ', linesIndentedWithSpacesCount: ' + linesIndentedWithSpacesCount);
|
||||
// console.log('spacesDiffCount: ' + spacesDiffCount);
|
||||
|
||||
@ -18,7 +18,7 @@ import { NullState } from '../../../../common/languages/nullTokenize.js';
|
||||
import { AutoIndentOnPaste, IndentationToSpacesCommand, IndentationToTabsCommand } from '../../browser/indentation.js';
|
||||
import { withTestCodeEditor } from '../../../../test/browser/testCodeEditor.js';
|
||||
import { testCommand } from '../../../../test/browser/testCommand.js';
|
||||
import { goIndentationRules, htmlIndentationRules, javascriptIndentationRules, latexIndentationRules, luaIndentationRules, phpIndentationRules, rubyIndentationRules } from '../../../../test/common/modes/supports/indentationRules.js';
|
||||
import { goIndentationRules, htmlIndentationRules, javascriptIndentationRules, latexIndentationRules, luaIndentationRules, phpIndentationRules, rubyIndentationRules, vbIndentationRules } from '../../../../test/common/modes/supports/indentationRules.js';
|
||||
import { cppOnEnterRules, htmlOnEnterRules, javascriptOnEnterRules, phpOnEnterRules } from '../../../../test/common/modes/supports/onEnterRules.js';
|
||||
import { TypeOperations } from '../../../../common/cursor/cursorTypeOperations.js';
|
||||
import { cppBracketRules, goBracketRules, htmlBracketRules, latexBracketRules, luaBracketRules, phpBracketRules, rubyBracketRules, typescriptBracketRules, vbBracketRules } from '../../../../test/common/modes/supports/bracketRules.js';
|
||||
@ -94,6 +94,7 @@ export function registerLanguageConfiguration(languageConfigurationService: ILan
|
||||
case Language.VB:
|
||||
return languageConfigurationService.register(language, {
|
||||
brackets: vbBracketRules,
|
||||
indentationRules: vbIndentationRules,
|
||||
});
|
||||
case Language.Latex:
|
||||
return languageConfigurationService.register(language, {
|
||||
@ -1737,14 +1738,14 @@ suite('Auto Indent On Type - Visual Basic', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
|
||||
test.skip('issue #118932: no indentation in visual basic files', () => {
|
||||
test('issue #118932: no indentation in visual basic files', () => {
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/118932
|
||||
|
||||
const model = createTextModel([
|
||||
'if True then',
|
||||
'If True Then',
|
||||
' Some code',
|
||||
' end i',
|
||||
' End I',
|
||||
].join('\n'), languageId, {});
|
||||
disposables.add(model);
|
||||
|
||||
@ -1752,9 +1753,9 @@ suite('Auto Indent On Type - Visual Basic', () => {
|
||||
editor.setSelection(new Selection(3, 10, 3, 10));
|
||||
viewModel.type('f', 'keyboard');
|
||||
assert.strictEqual(model.getValue(), [
|
||||
'if True then',
|
||||
'If True Then',
|
||||
' Some code',
|
||||
'end if',
|
||||
'End If',
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
|
||||
@ -251,7 +251,7 @@ export abstract class AbstractSortLinesAction extends EditorAction {
|
||||
|
||||
const model = editor.getModel();
|
||||
let selections = editor.getSelections();
|
||||
if (selections.length === 1 && selections[0].isEmpty()) {
|
||||
if (selections.length === 1 && selections[0].isSingleLine()) {
|
||||
// Apply to whole document.
|
||||
selections = [new Selection(1, 1, model.getLineCount(), model.getLineMaxColumn(model.getLineCount()))];
|
||||
}
|
||||
@ -322,7 +322,7 @@ export class DeleteDuplicateLinesAction extends EditorAction {
|
||||
let updateSelection = true;
|
||||
|
||||
let selections = editor.getSelections();
|
||||
if (selections.length === 1 && selections[0].isEmpty()) {
|
||||
if (selections.length === 1 && selections[0].isSingleLine()) {
|
||||
// Apply to whole document.
|
||||
selections = [new Selection(1, 1, model.getLineCount(), model.getLineMaxColumn(model.getLineCount()))];
|
||||
updateSelection = false;
|
||||
@ -389,7 +389,7 @@ export class ReverseLinesAction extends EditorAction {
|
||||
const model: ITextModel = editor.getModel();
|
||||
const originalSelections = editor.getSelections();
|
||||
let selections = originalSelections;
|
||||
if (selections.length === 1 && selections[0].isEmpty()) {
|
||||
if (selections.length === 1 && selections[0].isSingleLine()) {
|
||||
// Apply to whole document.
|
||||
selections = [new Selection(1, 1, model.getLineCount(), model.getLineMaxColumn(model.getLineCount()))];
|
||||
}
|
||||
|
||||
@ -106,6 +106,26 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('applies to whole document when selection is single line', function () {
|
||||
withTestCodeEditor(
|
||||
[
|
||||
'omicron',
|
||||
'beta',
|
||||
'alpha'
|
||||
], {}, (editor) => {
|
||||
const model = editor.getModel()!;
|
||||
const sortLinesAscendingAction = new SortLinesAscendingAction();
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 2, 4));
|
||||
executeAction(sortLinesAscendingAction, editor);
|
||||
assert.deepStrictEqual(model.getLinesContent(), [
|
||||
'alpha',
|
||||
'beta',
|
||||
'omicron'
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('SortLinesDescendingAction', () => {
|
||||
@ -248,6 +268,23 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('applies to whole document when selection is single line', function () {
|
||||
withTestCodeEditor(
|
||||
[
|
||||
'alpha',
|
||||
'beta',
|
||||
'alpha',
|
||||
'omicron'
|
||||
], {}, (editor) => {
|
||||
const model = editor.getModel()!;
|
||||
const deleteDuplicateLinesAction = new DeleteDuplicateLinesAction();
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 2, 2));
|
||||
executeAction(deleteDuplicateLinesAction, editor);
|
||||
assert.deepStrictEqual(model.getLinesContent(), ['alpha', 'beta', 'omicron']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -729,7 +766,7 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('handles single line selection', function () {
|
||||
test('applies to whole document when selection is single line', function () {
|
||||
withTestCodeEditor(
|
||||
[
|
||||
'line1',
|
||||
@ -742,8 +779,7 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
// Select only line 2
|
||||
editor.setSelection(new Selection(2, 1, 2, 6));
|
||||
executeAction(reverseLinesAction, editor);
|
||||
// Single line should remain unchanged
|
||||
assert.deepStrictEqual(model.getLinesContent(), ['line1', 'line2', 'line3']);
|
||||
assert.deepStrictEqual(model.getLinesContent(), ['line3', 'line2', 'line1']);
|
||||
});
|
||||
});
|
||||
|
||||
@ -765,6 +801,26 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
assert.deepStrictEqual(model.getLinesContent(), ['line1', 'line3', 'line2', 'line4', 'line5']);
|
||||
});
|
||||
});
|
||||
|
||||
test('applies to whole document when selection is single line', function () {
|
||||
withTestCodeEditor(
|
||||
[
|
||||
'omicron',
|
||||
'beta',
|
||||
'alpha'
|
||||
], {}, (editor) => {
|
||||
const model = editor.getModel()!;
|
||||
const reverseLinesAction = new ReverseLinesAction();
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 2, 4));
|
||||
executeAction(reverseLinesAction, editor);
|
||||
assert.deepStrictEqual(model.getLinesContent(), [
|
||||
'alpha',
|
||||
'beta',
|
||||
'omicron'
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('transpose', () => {
|
||||
|
||||
@ -60,7 +60,6 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
|
||||
private readonly _editor: ICodeEditor;
|
||||
|
||||
private _state: StickyScrollWidgetState | undefined;
|
||||
private _lineHeight: number;
|
||||
private _renderedStickyLines: RenderedStickyLine[] = [];
|
||||
private _lineNumbers: number[] = [];
|
||||
private _lastLineRelativePosition: number = 0;
|
||||
@ -79,7 +78,6 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
|
||||
super();
|
||||
|
||||
this._editor = editor;
|
||||
this._lineHeight = editor.getOption(EditorOption.lineHeight);
|
||||
this._lineNumbersDomNode.className = 'sticky-widget-line-numbers';
|
||||
this._lineNumbersDomNode.setAttribute('role', 'none');
|
||||
|
||||
@ -102,9 +100,6 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
|
||||
if (e.hasChanged(EditorOption.stickyScroll)) {
|
||||
updateScrollLeftPosition();
|
||||
}
|
||||
if (e.hasChanged(EditorOption.lineHeight)) {
|
||||
this._lineHeight = this._editor.getOption(EditorOption.lineHeight);
|
||||
}
|
||||
}));
|
||||
this._register(this._editor.onDidScrollChange((e) => {
|
||||
if (e.scrollLeftChanged) {
|
||||
@ -296,92 +291,15 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
|
||||
}
|
||||
|
||||
private _renderChildNode(viewModel: IViewModel, index: number, line: number, top: number, isLastLine: boolean, foldingModel: FoldingModel | undefined, layoutInfo: EditorLayoutInfo): RenderedStickyLine {
|
||||
const viewLineNumber = viewModel.coordinatesConverter.convertModelPositionToViewPosition(new Position(line, 1)).lineNumber;
|
||||
const lineRenderingData = viewModel.getViewLineRenderingData(viewLineNumber);
|
||||
const lineNumberOption = this._editor.getOption(EditorOption.lineNumbers);
|
||||
const verticalScrollbarSize = this._editor.getOption(EditorOption.scrollbar).verticalScrollbarSize;
|
||||
|
||||
let actualInlineDecorations: LineDecoration[];
|
||||
try {
|
||||
actualInlineDecorations = LineDecoration.filter(lineRenderingData.inlineDecorations, viewLineNumber, lineRenderingData.minColumn, lineRenderingData.maxColumn);
|
||||
} catch (err) {
|
||||
actualInlineDecorations = [];
|
||||
}
|
||||
|
||||
const lineHeight = this._editor.getLineHeightForPosition(new Position(line, 1));
|
||||
const textDirection = viewModel.getTextDirection(line);
|
||||
const renderLineInput: RenderLineInput = new RenderLineInput(true, true, lineRenderingData.content,
|
||||
lineRenderingData.continuesWithWrappedLine,
|
||||
lineRenderingData.isBasicASCII, lineRenderingData.containsRTL, 0,
|
||||
lineRenderingData.tokens, actualInlineDecorations,
|
||||
lineRenderingData.tabSize, lineRenderingData.startVisibleColumn,
|
||||
1, 1, 1, 500, 'none', true, true, null,
|
||||
textDirection, verticalScrollbarSize
|
||||
);
|
||||
|
||||
const sb = new StringBuilder(2000);
|
||||
const renderOutput = renderViewLine(renderLineInput, sb);
|
||||
|
||||
let newLine;
|
||||
if (_ttPolicy) {
|
||||
newLine = _ttPolicy.createHTML(sb.build());
|
||||
} else {
|
||||
newLine = sb.build();
|
||||
}
|
||||
|
||||
const lineHTMLNode = document.createElement('span');
|
||||
lineHTMLNode.setAttribute(STICKY_INDEX_ATTR, String(index));
|
||||
lineHTMLNode.setAttribute(STICKY_IS_LINE_ATTR, '');
|
||||
lineHTMLNode.setAttribute('role', 'listitem');
|
||||
lineHTMLNode.tabIndex = 0;
|
||||
lineHTMLNode.className = 'sticky-line-content';
|
||||
lineHTMLNode.classList.add(`stickyLine${line}`);
|
||||
lineHTMLNode.style.lineHeight = `${lineHeight}px`;
|
||||
lineHTMLNode.innerHTML = newLine as string;
|
||||
|
||||
const lineNumberHTMLNode = document.createElement('span');
|
||||
lineNumberHTMLNode.setAttribute(STICKY_INDEX_ATTR, String(index));
|
||||
lineNumberHTMLNode.setAttribute(STICKY_IS_LINE_NUMBER_ATTR, '');
|
||||
lineNumberHTMLNode.className = 'sticky-line-number';
|
||||
lineNumberHTMLNode.style.lineHeight = `${lineHeight}px`;
|
||||
const lineNumbersWidth = layoutInfo.contentLeft;
|
||||
lineNumberHTMLNode.style.width = `${lineNumbersWidth}px`;
|
||||
|
||||
const innerLineNumberHTML = document.createElement('span');
|
||||
if (lineNumberOption.renderType === RenderLineNumbersType.On || lineNumberOption.renderType === RenderLineNumbersType.Interval && line % 10 === 0) {
|
||||
innerLineNumberHTML.innerText = line.toString();
|
||||
} else if (lineNumberOption.renderType === RenderLineNumbersType.Relative) {
|
||||
innerLineNumberHTML.innerText = Math.abs(line - this._editor.getPosition()!.lineNumber).toString();
|
||||
}
|
||||
innerLineNumberHTML.className = 'sticky-line-number-inner';
|
||||
innerLineNumberHTML.style.width = `${layoutInfo.lineNumbersWidth}px`;
|
||||
innerLineNumberHTML.style.paddingLeft = `${layoutInfo.lineNumbersLeft}px`;
|
||||
|
||||
lineNumberHTMLNode.appendChild(innerLineNumberHTML);
|
||||
const foldingIcon = this._renderFoldingIconForLine(foldingModel, line);
|
||||
if (foldingIcon) {
|
||||
lineNumberHTMLNode.appendChild(foldingIcon.domNode);
|
||||
foldingIcon.domNode.style.left = `${layoutInfo.lineNumbersWidth + layoutInfo.lineNumbersLeft}px`;
|
||||
foldingIcon.domNode.style.lineHeight = `${lineHeight}px`;
|
||||
}
|
||||
|
||||
this._editor.applyFontInfo(lineHTMLNode);
|
||||
this._editor.applyFontInfo(lineNumberHTMLNode);
|
||||
|
||||
lineNumberHTMLNode.style.lineHeight = `${lineHeight}px`;
|
||||
lineHTMLNode.style.lineHeight = `${lineHeight}px`;
|
||||
lineNumberHTMLNode.style.height = `${lineHeight}px`;
|
||||
lineHTMLNode.style.height = `${lineHeight}px`;
|
||||
|
||||
const renderedLine = new RenderedStickyLine(
|
||||
this._editor,
|
||||
viewModel,
|
||||
layoutInfo,
|
||||
foldingModel,
|
||||
this._isOnGlyphMargin,
|
||||
index,
|
||||
line,
|
||||
lineHTMLNode,
|
||||
lineNumberHTMLNode,
|
||||
foldingIcon,
|
||||
renderOutput.characterMapping,
|
||||
lineHTMLNode.scrollWidth,
|
||||
lineHeight
|
||||
line
|
||||
);
|
||||
return this._updatePosition(renderedLine, top, isLastLine);
|
||||
}
|
||||
@ -406,25 +324,6 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
|
||||
return stickyLine;
|
||||
}
|
||||
|
||||
private _renderFoldingIconForLine(foldingModel: FoldingModel | undefined, line: number): StickyFoldingIcon | undefined {
|
||||
const showFoldingControls: 'mouseover' | 'always' | 'never' = this._editor.getOption(EditorOption.showFoldingControls);
|
||||
if (!foldingModel || showFoldingControls === 'never') {
|
||||
return;
|
||||
}
|
||||
const foldingRegions = foldingModel.regions;
|
||||
const indexOfFoldingRegion = foldingRegions.findRange(line);
|
||||
const startLineNumber = foldingRegions.getStartLineNumber(indexOfFoldingRegion);
|
||||
const isFoldingScope = line === startLineNumber;
|
||||
if (!isFoldingScope) {
|
||||
return;
|
||||
}
|
||||
const isCollapsed = foldingRegions.isCollapsed(indexOfFoldingRegion);
|
||||
const foldingIcon = new StickyFoldingIcon(isCollapsed, startLineNumber, foldingRegions.getEndLineNumber(indexOfFoldingRegion), this._lineHeight);
|
||||
foldingIcon.setVisible(this._isOnGlyphMargin ? true : (isCollapsed || showFoldingControls === 'always'));
|
||||
foldingIcon.domNode.setAttribute(STICKY_IS_FOLDING_ICON_ATTR, '');
|
||||
return foldingIcon;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return 'editor.contrib.stickyScrollWidget';
|
||||
}
|
||||
@ -522,16 +421,127 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
|
||||
}
|
||||
|
||||
class RenderedStickyLine {
|
||||
|
||||
public readonly lineDomNode: HTMLElement;
|
||||
public readonly lineNumberDomNode: HTMLElement;
|
||||
|
||||
public readonly foldingIcon: StickyFoldingIcon | undefined;
|
||||
public readonly characterMapping: CharacterMapping;
|
||||
|
||||
public readonly scrollWidth: number;
|
||||
public readonly height: number;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
viewModel: IViewModel,
|
||||
layoutInfo: EditorLayoutInfo,
|
||||
foldingModel: FoldingModel | undefined,
|
||||
isOnGlyphMargin: boolean,
|
||||
public readonly index: number,
|
||||
public readonly lineNumber: number,
|
||||
public readonly lineDomNode: HTMLElement,
|
||||
public readonly lineNumberDomNode: HTMLElement,
|
||||
public readonly foldingIcon: StickyFoldingIcon | undefined,
|
||||
public readonly characterMapping: CharacterMapping,
|
||||
public readonly scrollWidth: number,
|
||||
public readonly height: number
|
||||
) { }
|
||||
) {
|
||||
const viewLineNumber = viewModel.coordinatesConverter.convertModelPositionToViewPosition(new Position(lineNumber, 1)).lineNumber;
|
||||
const lineRenderingData = viewModel.getViewLineRenderingData(viewLineNumber);
|
||||
const lineNumberOption = editor.getOption(EditorOption.lineNumbers);
|
||||
const verticalScrollbarSize = editor.getOption(EditorOption.scrollbar).verticalScrollbarSize;
|
||||
|
||||
let actualInlineDecorations: LineDecoration[];
|
||||
try {
|
||||
actualInlineDecorations = LineDecoration.filter(lineRenderingData.inlineDecorations, viewLineNumber, lineRenderingData.minColumn, lineRenderingData.maxColumn);
|
||||
} catch (err) {
|
||||
actualInlineDecorations = [];
|
||||
}
|
||||
|
||||
const lineHeight = editor.getLineHeightForPosition(new Position(lineNumber, 1));
|
||||
const textDirection = viewModel.getTextDirection(lineNumber);
|
||||
const renderLineInput: RenderLineInput = new RenderLineInput(true, true, lineRenderingData.content,
|
||||
lineRenderingData.continuesWithWrappedLine,
|
||||
lineRenderingData.isBasicASCII, lineRenderingData.containsRTL, 0,
|
||||
lineRenderingData.tokens, actualInlineDecorations,
|
||||
lineRenderingData.tabSize, lineRenderingData.startVisibleColumn,
|
||||
1, 1, 1, 500, 'none', true, true, null,
|
||||
textDirection, verticalScrollbarSize
|
||||
);
|
||||
|
||||
const sb = new StringBuilder(2000);
|
||||
const renderOutput = renderViewLine(renderLineInput, sb);
|
||||
this.characterMapping = renderOutput.characterMapping;
|
||||
|
||||
let newLine;
|
||||
if (_ttPolicy) {
|
||||
newLine = _ttPolicy.createHTML(sb.build());
|
||||
} else {
|
||||
newLine = sb.build();
|
||||
}
|
||||
|
||||
const lineHTMLNode = document.createElement('span');
|
||||
lineHTMLNode.setAttribute(STICKY_INDEX_ATTR, String(index));
|
||||
lineHTMLNode.setAttribute(STICKY_IS_LINE_ATTR, '');
|
||||
lineHTMLNode.setAttribute('role', 'listitem');
|
||||
lineHTMLNode.tabIndex = 0;
|
||||
lineHTMLNode.className = 'sticky-line-content';
|
||||
lineHTMLNode.classList.add(`stickyLine${lineNumber}`);
|
||||
lineHTMLNode.style.lineHeight = `${lineHeight}px`;
|
||||
lineHTMLNode.innerHTML = newLine as string;
|
||||
|
||||
const lineNumberHTMLNode = document.createElement('span');
|
||||
lineNumberHTMLNode.setAttribute(STICKY_INDEX_ATTR, String(index));
|
||||
lineNumberHTMLNode.setAttribute(STICKY_IS_LINE_NUMBER_ATTR, '');
|
||||
lineNumberHTMLNode.className = 'sticky-line-number';
|
||||
lineNumberHTMLNode.style.lineHeight = `${lineHeight}px`;
|
||||
const lineNumbersWidth = layoutInfo.contentLeft;
|
||||
lineNumberHTMLNode.style.width = `${lineNumbersWidth}px`;
|
||||
|
||||
const innerLineNumberHTML = document.createElement('span');
|
||||
if (lineNumberOption.renderType === RenderLineNumbersType.On || lineNumberOption.renderType === RenderLineNumbersType.Interval && lineNumber % 10 === 0) {
|
||||
innerLineNumberHTML.innerText = lineNumber.toString();
|
||||
} else if (lineNumberOption.renderType === RenderLineNumbersType.Relative) {
|
||||
innerLineNumberHTML.innerText = Math.abs(lineNumber - editor.getPosition()!.lineNumber).toString();
|
||||
}
|
||||
innerLineNumberHTML.className = 'sticky-line-number-inner';
|
||||
innerLineNumberHTML.style.width = `${layoutInfo.lineNumbersWidth}px`;
|
||||
innerLineNumberHTML.style.paddingLeft = `${layoutInfo.lineNumbersLeft}px`;
|
||||
|
||||
lineNumberHTMLNode.appendChild(innerLineNumberHTML);
|
||||
const foldingIcon = this._renderFoldingIconForLine(editor, foldingModel, lineNumber, lineHeight, isOnGlyphMargin);
|
||||
if (foldingIcon) {
|
||||
lineNumberHTMLNode.appendChild(foldingIcon.domNode);
|
||||
foldingIcon.domNode.style.left = `${layoutInfo.lineNumbersWidth + layoutInfo.lineNumbersLeft}px`;
|
||||
foldingIcon.domNode.style.lineHeight = `${lineHeight}px`;
|
||||
}
|
||||
|
||||
editor.applyFontInfo(lineHTMLNode);
|
||||
editor.applyFontInfo(lineNumberHTMLNode);
|
||||
|
||||
lineNumberHTMLNode.style.lineHeight = `${lineHeight}px`;
|
||||
lineHTMLNode.style.lineHeight = `${lineHeight}px`;
|
||||
lineNumberHTMLNode.style.height = `${lineHeight}px`;
|
||||
lineHTMLNode.style.height = `${lineHeight}px`;
|
||||
|
||||
this.scrollWidth = lineHTMLNode.scrollWidth;
|
||||
this.lineDomNode = lineHTMLNode;
|
||||
this.lineNumberDomNode = lineNumberHTMLNode;
|
||||
this.height = lineHeight;
|
||||
}
|
||||
|
||||
private _renderFoldingIconForLine(editor: ICodeEditor, foldingModel: FoldingModel | undefined, line: number, lineHeight: number, isOnGlyphMargin: boolean): StickyFoldingIcon | undefined {
|
||||
const showFoldingControls: 'mouseover' | 'always' | 'never' = editor.getOption(EditorOption.showFoldingControls);
|
||||
if (!foldingModel || showFoldingControls === 'never') {
|
||||
return;
|
||||
}
|
||||
const foldingRegions = foldingModel.regions;
|
||||
const indexOfFoldingRegion = foldingRegions.findRange(line);
|
||||
const startLineNumber = foldingRegions.getStartLineNumber(indexOfFoldingRegion);
|
||||
const isFoldingScope = line === startLineNumber;
|
||||
if (!isFoldingScope) {
|
||||
return;
|
||||
}
|
||||
const isCollapsed = foldingRegions.isCollapsed(indexOfFoldingRegion);
|
||||
const foldingIcon = new StickyFoldingIcon(isCollapsed, startLineNumber, foldingRegions.getEndLineNumber(indexOfFoldingRegion), lineHeight);
|
||||
foldingIcon.setVisible(isOnGlyphMargin ? true : (isCollapsed || showFoldingControls === 'always'));
|
||||
foldingIcon.domNode.setAttribute(STICKY_IS_FOLDING_ICON_ATTR, '');
|
||||
return foldingIcon;
|
||||
}
|
||||
}
|
||||
|
||||
class StickyFoldingIcon {
|
||||
|
||||
@ -708,6 +708,74 @@ suite('Editor Model - TextModel', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('issue #65668: YAML file indented with 2 spaces', () => {
|
||||
// Full YAML file from the issue - should detect as 2 spaces
|
||||
assertGuess(true, 2, [
|
||||
'version: 2',
|
||||
'',
|
||||
'jobs:',
|
||||
' build:',
|
||||
' docker:',
|
||||
' - circleci/golang:1.11',
|
||||
'',
|
||||
' environment:',
|
||||
' TEST_RESULTS: /tmp/test-results',
|
||||
'',
|
||||
' steps:',
|
||||
' - checkout',
|
||||
' - run: mkdir -p $TEST_RESULTS',
|
||||
'',
|
||||
' - restore_cache:',
|
||||
' keys:',
|
||||
' - v1-pkg-cache',
|
||||
'',
|
||||
' - run:',
|
||||
' name: dep ensure',
|
||||
' command: dep ensure -v',
|
||||
'',
|
||||
' - run:',
|
||||
' name: Run unit tests',
|
||||
' command: |',
|
||||
' trap "go-junit-report <${TEST_RESULTS}/go-test.out > ${TEST_RESULTS}/go-test-report.xml" EXIT',
|
||||
' go test -v ./... | tee ${TEST_RESULTS}/go-test.out',
|
||||
'',
|
||||
' - run:',
|
||||
' name: Build',
|
||||
' command: go build -v',
|
||||
'',
|
||||
' - save_cache:',
|
||||
' key: v1-pkg-cache',
|
||||
' paths:',
|
||||
' - "/go/pkg"',
|
||||
'',
|
||||
' - store_artifacts:',
|
||||
' path: /tmp/test-results',
|
||||
' destination: raw-test-output',
|
||||
'',
|
||||
' - store_test_results:',
|
||||
' path: /tmp/test-results',
|
||||
]);
|
||||
});
|
||||
|
||||
test('issue #249040: 4-space indent should win over 2-space when predominant', () => {
|
||||
// File with mostly 4-space indents but some 2-space indents should detect as 4 spaces
|
||||
assertGuess(true, 4, [
|
||||
'function foo() {',
|
||||
' let a = 1;',
|
||||
' let b = 2;',
|
||||
' if (true) {',
|
||||
' console.log(a);',
|
||||
' console.log(b);',
|
||||
' }',
|
||||
' const obj = {',
|
||||
' x: 1,', // 2-space indent here
|
||||
' y: 2', // 2-space indent here
|
||||
' };',
|
||||
' return obj;',
|
||||
'}',
|
||||
]);
|
||||
});
|
||||
|
||||
test('validatePosition', () => {
|
||||
|
||||
const m = createTextModel('line one\nline two');
|
||||
|
||||
@ -40,3 +40,11 @@ export const luaIndentationRules = {
|
||||
decreaseIndentPattern: /^\s*((\b(elseif|else|end|until)\b)|(\})|(\)))/,
|
||||
increaseIndentPattern: /^((?!(\-\-)).)*((\b(else|function|then|do|repeat)\b((?!\b(end|until)\b).)*)|(\{\s*))$/,
|
||||
};
|
||||
|
||||
export const vbIndentationRules = {
|
||||
// Decrease indent when line starts with End <keyword>, Else, ElseIf, Case, Catch, Finally, Loop, Next, Wend, Until
|
||||
decreaseIndentPattern: /^\s*((End\s+(If|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Select|Try|While|For|Property|Get|Set|SyncLock|Using|AddHandler|RaiseEvent|RemoveHandler|Event|Operator))|Else|ElseIf|Case|Catch|Finally|Loop|Next|Wend|Until)\b/i,
|
||||
// Increase indent after lines ending with Then, or lines starting with If/While/For/Do/Select/Sub/Function/Class/etc (block-starting keywords)
|
||||
// The pattern matches lines that start block structures but excludes lines that also end them (like single-line If...Then...End If)
|
||||
increaseIndentPattern: /^\s*((If|ElseIf).*Then(?!\s+(End\s+If))\s*(('|REM).*)?$)|\b(Else|While|For|Do|Select\s+Case|Case|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Try|Catch|Finally|SyncLock|Using|Property|Get|Set|AddHandler|RaiseEvent|RemoveHandler|Event|Operator)\b(?!.*\bEnd\s+(If|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Select|Try|While|For|Property|Get|Set|SyncLock|Using|AddHandler|RaiseEvent|RemoveHandler|Event|Operator)\b).*(('|REM).*)?$/i,
|
||||
};
|
||||
|
||||
3
src/vs/monaco.d.ts
vendored
3
src/vs/monaco.d.ts
vendored
@ -771,6 +771,9 @@ declare namespace monaco {
|
||||
* Moves the range by the given amount of lines.
|
||||
*/
|
||||
delta(lineCount: number): Range;
|
||||
/**
|
||||
* Test if this range starts and ends on the same line.
|
||||
*/
|
||||
isSingleLine(): boolean;
|
||||
static fromPositions(start: IPosition, end?: IPosition): Range;
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user