monaco-editor-core
Version:
A browser based code editor
353 lines (352 loc) • 16 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { normalizeDriveLetter } from '../../../../base/common/labels.js';
import * as path from '../../../../base/common/path.js';
import { dirname } from '../../../../base/common/resources.js';
import { commonPrefixLength, getLeadingWhitespace, isFalsyOrWhitespace, splitLines } from '../../../../base/common/strings.js';
import { generateUuid } from '../../../../base/common/uuid.js';
import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
import { Text } from './snippetParser.js';
import * as nls from '../../../../nls.js';
import { WORKSPACE_EXTENSION, isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier, isEmptyWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js';
export const KnownSnippetVariableNames = Object.freeze({
'CURRENT_YEAR': true,
'CURRENT_YEAR_SHORT': true,
'CURRENT_MONTH': true,
'CURRENT_DATE': true,
'CURRENT_HOUR': true,
'CURRENT_MINUTE': true,
'CURRENT_SECOND': true,
'CURRENT_DAY_NAME': true,
'CURRENT_DAY_NAME_SHORT': true,
'CURRENT_MONTH_NAME': true,
'CURRENT_MONTH_NAME_SHORT': true,
'CURRENT_SECONDS_UNIX': true,
'CURRENT_TIMEZONE_OFFSET': true,
'SELECTION': true,
'CLIPBOARD': true,
'TM_SELECTED_TEXT': true,
'TM_CURRENT_LINE': true,
'TM_CURRENT_WORD': true,
'TM_LINE_INDEX': true,
'TM_LINE_NUMBER': true,
'TM_FILENAME': true,
'TM_FILENAME_BASE': true,
'TM_DIRECTORY': true,
'TM_FILEPATH': true,
'CURSOR_INDEX': true, // 0-offset
'CURSOR_NUMBER': true, // 1-offset
'RELATIVE_FILEPATH': true,
'BLOCK_COMMENT_START': true,
'BLOCK_COMMENT_END': true,
'LINE_COMMENT': true,
'WORKSPACE_NAME': true,
'WORKSPACE_FOLDER': true,
'RANDOM': true,
'RANDOM_HEX': true,
'UUID': true
});
export class CompositeSnippetVariableResolver {
constructor(_delegates) {
this._delegates = _delegates;
//
}
resolve(variable) {
for (const delegate of this._delegates) {
const value = delegate.resolve(variable);
if (value !== undefined) {
return value;
}
}
return undefined;
}
}
export class SelectionBasedVariableResolver {
constructor(_model, _selection, _selectionIdx, _overtypingCapturer) {
this._model = _model;
this._selection = _selection;
this._selectionIdx = _selectionIdx;
this._overtypingCapturer = _overtypingCapturer;
//
}
resolve(variable) {
const { name } = variable;
if (name === 'SELECTION' || name === 'TM_SELECTED_TEXT') {
let value = this._model.getValueInRange(this._selection) || undefined;
let isMultiline = this._selection.startLineNumber !== this._selection.endLineNumber;
// If there was no selected text, try to get last overtyped text
if (!value && this._overtypingCapturer) {
const info = this._overtypingCapturer.getLastOvertypedInfo(this._selectionIdx);
if (info) {
value = info.value;
isMultiline = info.multiline;
}
}
if (value && isMultiline && variable.snippet) {
// Selection is a multiline string which we indentation we now
// need to adjust. We compare the indentation of this variable
// with the indentation at the editor position and add potential
// extra indentation to the value
const line = this._model.getLineContent(this._selection.startLineNumber);
const lineLeadingWhitespace = getLeadingWhitespace(line, 0, this._selection.startColumn - 1);
let varLeadingWhitespace = lineLeadingWhitespace;
variable.snippet.walk(marker => {
if (marker === variable) {
return false;
}
if (marker instanceof Text) {
varLeadingWhitespace = getLeadingWhitespace(splitLines(marker.value).pop());
}
return true;
});
const whitespaceCommonLength = commonPrefixLength(varLeadingWhitespace, lineLeadingWhitespace);
value = value.replace(/(\r\n|\r|\n)(.*)/g, (m, newline, rest) => `${newline}${varLeadingWhitespace.substr(whitespaceCommonLength)}${rest}`);
}
return value;
}
else if (name === 'TM_CURRENT_LINE') {
return this._model.getLineContent(this._selection.positionLineNumber);
}
else if (name === 'TM_CURRENT_WORD') {
const info = this._model.getWordAtPosition({
lineNumber: this._selection.positionLineNumber,
column: this._selection.positionColumn
});
return info && info.word || undefined;
}
else if (name === 'TM_LINE_INDEX') {
return String(this._selection.positionLineNumber - 1);
}
else if (name === 'TM_LINE_NUMBER') {
return String(this._selection.positionLineNumber);
}
else if (name === 'CURSOR_INDEX') {
return String(this._selectionIdx);
}
else if (name === 'CURSOR_NUMBER') {
return String(this._selectionIdx + 1);
}
return undefined;
}
}
export class ModelBasedVariableResolver {
constructor(_labelService, _model) {
this._labelService = _labelService;
this._model = _model;
//
}
resolve(variable) {
const { name } = variable;
if (name === 'TM_FILENAME') {
return path.basename(this._model.uri.fsPath);
}
else if (name === 'TM_FILENAME_BASE') {
const name = path.basename(this._model.uri.fsPath);
const idx = name.lastIndexOf('.');
if (idx <= 0) {
return name;
}
else {
return name.slice(0, idx);
}
}
else if (name === 'TM_DIRECTORY') {
if (path.dirname(this._model.uri.fsPath) === '.') {
return '';
}
return this._labelService.getUriLabel(dirname(this._model.uri));
}
else if (name === 'TM_FILEPATH') {
return this._labelService.getUriLabel(this._model.uri);
}
else if (name === 'RELATIVE_FILEPATH') {
return this._labelService.getUriLabel(this._model.uri, { relative: true, noPrefix: true });
}
return undefined;
}
}
export class ClipboardBasedVariableResolver {
constructor(_readClipboardText, _selectionIdx, _selectionCount, _spread) {
this._readClipboardText = _readClipboardText;
this._selectionIdx = _selectionIdx;
this._selectionCount = _selectionCount;
this._spread = _spread;
//
}
resolve(variable) {
if (variable.name !== 'CLIPBOARD') {
return undefined;
}
const clipboardText = this._readClipboardText();
if (!clipboardText) {
return undefined;
}
// `spread` is assigning each cursor a line of the clipboard
// text whenever there the line count equals the cursor count
// and when enabled
if (this._spread) {
const lines = clipboardText.split(/\r\n|\n|\r/).filter(s => !isFalsyOrWhitespace(s));
if (lines.length === this._selectionCount) {
return lines[this._selectionIdx];
}
}
return clipboardText;
}
}
let CommentBasedVariableResolver = class CommentBasedVariableResolver {
constructor(_model, _selection, _languageConfigurationService) {
this._model = _model;
this._selection = _selection;
this._languageConfigurationService = _languageConfigurationService;
//
}
resolve(variable) {
const { name } = variable;
const langId = this._model.getLanguageIdAtPosition(this._selection.selectionStartLineNumber, this._selection.selectionStartColumn);
const config = this._languageConfigurationService.getLanguageConfiguration(langId).comments;
if (!config) {
return undefined;
}
if (name === 'LINE_COMMENT') {
return config.lineCommentToken || undefined;
}
else if (name === 'BLOCK_COMMENT_START') {
return config.blockCommentStartToken || undefined;
}
else if (name === 'BLOCK_COMMENT_END') {
return config.blockCommentEndToken || undefined;
}
return undefined;
}
};
CommentBasedVariableResolver = __decorate([
__param(2, ILanguageConfigurationService)
], CommentBasedVariableResolver);
export { CommentBasedVariableResolver };
export class TimeBasedVariableResolver {
constructor() {
this._date = new Date();
}
static { this.dayNames = [nls.localize('Sunday', "Sunday"), nls.localize('Monday', "Monday"), nls.localize('Tuesday', "Tuesday"), nls.localize('Wednesday', "Wednesday"), nls.localize('Thursday', "Thursday"), nls.localize('Friday', "Friday"), nls.localize('Saturday', "Saturday")]; }
static { this.dayNamesShort = [nls.localize('SundayShort', "Sun"), nls.localize('MondayShort', "Mon"), nls.localize('TuesdayShort', "Tue"), nls.localize('WednesdayShort', "Wed"), nls.localize('ThursdayShort', "Thu"), nls.localize('FridayShort', "Fri"), nls.localize('SaturdayShort', "Sat")]; }
static { this.monthNames = [nls.localize('January', "January"), nls.localize('February', "February"), nls.localize('March', "March"), nls.localize('April', "April"), nls.localize('May', "May"), nls.localize('June', "June"), nls.localize('July', "July"), nls.localize('August', "August"), nls.localize('September', "September"), nls.localize('October', "October"), nls.localize('November', "November"), nls.localize('December', "December")]; }
static { this.monthNamesShort = [nls.localize('JanuaryShort', "Jan"), nls.localize('FebruaryShort', "Feb"), nls.localize('MarchShort', "Mar"), nls.localize('AprilShort', "Apr"), nls.localize('MayShort', "May"), nls.localize('JuneShort', "Jun"), nls.localize('JulyShort', "Jul"), nls.localize('AugustShort', "Aug"), nls.localize('SeptemberShort', "Sep"), nls.localize('OctoberShort', "Oct"), nls.localize('NovemberShort', "Nov"), nls.localize('DecemberShort', "Dec")]; }
resolve(variable) {
const { name } = variable;
if (name === 'CURRENT_YEAR') {
return String(this._date.getFullYear());
}
else if (name === 'CURRENT_YEAR_SHORT') {
return String(this._date.getFullYear()).slice(-2);
}
else if (name === 'CURRENT_MONTH') {
return String(this._date.getMonth().valueOf() + 1).padStart(2, '0');
}
else if (name === 'CURRENT_DATE') {
return String(this._date.getDate().valueOf()).padStart(2, '0');
}
else if (name === 'CURRENT_HOUR') {
return String(this._date.getHours().valueOf()).padStart(2, '0');
}
else if (name === 'CURRENT_MINUTE') {
return String(this._date.getMinutes().valueOf()).padStart(2, '0');
}
else if (name === 'CURRENT_SECOND') {
return String(this._date.getSeconds().valueOf()).padStart(2, '0');
}
else if (name === 'CURRENT_DAY_NAME') {
return TimeBasedVariableResolver.dayNames[this._date.getDay()];
}
else if (name === 'CURRENT_DAY_NAME_SHORT') {
return TimeBasedVariableResolver.dayNamesShort[this._date.getDay()];
}
else if (name === 'CURRENT_MONTH_NAME') {
return TimeBasedVariableResolver.monthNames[this._date.getMonth()];
}
else if (name === 'CURRENT_MONTH_NAME_SHORT') {
return TimeBasedVariableResolver.monthNamesShort[this._date.getMonth()];
}
else if (name === 'CURRENT_SECONDS_UNIX') {
return String(Math.floor(this._date.getTime() / 1000));
}
else if (name === 'CURRENT_TIMEZONE_OFFSET') {
const rawTimeOffset = this._date.getTimezoneOffset();
const sign = rawTimeOffset > 0 ? '-' : '+';
const hours = Math.trunc(Math.abs(rawTimeOffset / 60));
const hoursString = (hours < 10 ? '0' + hours : hours);
const minutes = Math.abs(rawTimeOffset) - hours * 60;
const minutesString = (minutes < 10 ? '0' + minutes : minutes);
return sign + hoursString + ':' + minutesString;
}
return undefined;
}
}
export class WorkspaceBasedVariableResolver {
constructor(_workspaceService) {
this._workspaceService = _workspaceService;
//
}
resolve(variable) {
if (!this._workspaceService) {
return undefined;
}
const workspaceIdentifier = toWorkspaceIdentifier(this._workspaceService.getWorkspace());
if (isEmptyWorkspaceIdentifier(workspaceIdentifier)) {
return undefined;
}
if (variable.name === 'WORKSPACE_NAME') {
return this._resolveWorkspaceName(workspaceIdentifier);
}
else if (variable.name === 'WORKSPACE_FOLDER') {
return this._resoveWorkspacePath(workspaceIdentifier);
}
return undefined;
}
_resolveWorkspaceName(workspaceIdentifier) {
if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) {
return path.basename(workspaceIdentifier.uri.path);
}
let filename = path.basename(workspaceIdentifier.configPath.path);
if (filename.endsWith(WORKSPACE_EXTENSION)) {
filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
}
return filename;
}
_resoveWorkspacePath(workspaceIdentifier) {
if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) {
return normalizeDriveLetter(workspaceIdentifier.uri.fsPath);
}
const filename = path.basename(workspaceIdentifier.configPath.path);
let folderpath = workspaceIdentifier.configPath.fsPath;
if (folderpath.endsWith(filename)) {
folderpath = folderpath.substr(0, folderpath.length - filename.length - 1);
}
return (folderpath ? normalizeDriveLetter(folderpath) : '/');
}
}
export class RandomBasedVariableResolver {
resolve(variable) {
const { name } = variable;
if (name === 'RANDOM') {
return Math.random().toString().slice(-6);
}
else if (name === 'RANDOM_HEX') {
return Math.random().toString(16).slice(-6);
}
else if (name === 'UUID') {
return generateUuid();
}
return undefined;
}
}