65b9a2b0546970959d4318d4de762f8624602aba94ed7ff18856c81806ad3b4d0d38716a459e32c9b0fe8385d333583f07dd51278ccec8799f864baf7a16a1 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  6. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  7. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  8. 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;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. var __param = (this && this.__param) || function (paramIndex, decorator) {
  12. return function (target, key) { decorator(target, key, paramIndex); }
  13. };
  14. import { normalizeDriveLetter } from '../../../../base/common/labels.js';
  15. import * as path from '../../../../base/common/path.js';
  16. import { dirname } from '../../../../base/common/resources.js';
  17. import { commonPrefixLength, getLeadingWhitespace, isFalsyOrWhitespace, splitLines } from '../../../../base/common/strings.js';
  18. import { generateUuid } from '../../../../base/common/uuid.js';
  19. import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
  20. import { Text } from './snippetParser.js';
  21. import * as nls from '../../../../nls.js';
  22. import { WORKSPACE_EXTENSION, isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js';
  23. export const KnownSnippetVariableNames = Object.freeze({
  24. 'CURRENT_YEAR': true,
  25. 'CURRENT_YEAR_SHORT': true,
  26. 'CURRENT_MONTH': true,
  27. 'CURRENT_DATE': true,
  28. 'CURRENT_HOUR': true,
  29. 'CURRENT_MINUTE': true,
  30. 'CURRENT_SECOND': true,
  31. 'CURRENT_DAY_NAME': true,
  32. 'CURRENT_DAY_NAME_SHORT': true,
  33. 'CURRENT_MONTH_NAME': true,
  34. 'CURRENT_MONTH_NAME_SHORT': true,
  35. 'CURRENT_SECONDS_UNIX': true,
  36. 'SELECTION': true,
  37. 'CLIPBOARD': true,
  38. 'TM_SELECTED_TEXT': true,
  39. 'TM_CURRENT_LINE': true,
  40. 'TM_CURRENT_WORD': true,
  41. 'TM_LINE_INDEX': true,
  42. 'TM_LINE_NUMBER': true,
  43. 'TM_FILENAME': true,
  44. 'TM_FILENAME_BASE': true,
  45. 'TM_DIRECTORY': true,
  46. 'TM_FILEPATH': true,
  47. 'CURSOR_INDEX': true,
  48. 'CURSOR_NUMBER': true,
  49. 'RELATIVE_FILEPATH': true,
  50. 'BLOCK_COMMENT_START': true,
  51. 'BLOCK_COMMENT_END': true,
  52. 'LINE_COMMENT': true,
  53. 'WORKSPACE_NAME': true,
  54. 'WORKSPACE_FOLDER': true,
  55. 'RANDOM': true,
  56. 'RANDOM_HEX': true,
  57. 'UUID': true
  58. });
  59. export class CompositeSnippetVariableResolver {
  60. constructor(_delegates) {
  61. this._delegates = _delegates;
  62. //
  63. }
  64. resolve(variable) {
  65. for (const delegate of this._delegates) {
  66. const value = delegate.resolve(variable);
  67. if (value !== undefined) {
  68. return value;
  69. }
  70. }
  71. return undefined;
  72. }
  73. }
  74. export class SelectionBasedVariableResolver {
  75. constructor(_model, _selection, _selectionIdx, _overtypingCapturer) {
  76. this._model = _model;
  77. this._selection = _selection;
  78. this._selectionIdx = _selectionIdx;
  79. this._overtypingCapturer = _overtypingCapturer;
  80. //
  81. }
  82. resolve(variable) {
  83. const { name } = variable;
  84. if (name === 'SELECTION' || name === 'TM_SELECTED_TEXT') {
  85. let value = this._model.getValueInRange(this._selection) || undefined;
  86. let isMultiline = this._selection.startLineNumber !== this._selection.endLineNumber;
  87. // If there was no selected text, try to get last overtyped text
  88. if (!value && this._overtypingCapturer) {
  89. const info = this._overtypingCapturer.getLastOvertypedInfo(this._selectionIdx);
  90. if (info) {
  91. value = info.value;
  92. isMultiline = info.multiline;
  93. }
  94. }
  95. if (value && isMultiline && variable.snippet) {
  96. // Selection is a multiline string which we indentation we now
  97. // need to adjust. We compare the indentation of this variable
  98. // with the indentation at the editor position and add potential
  99. // extra indentation to the value
  100. const line = this._model.getLineContent(this._selection.startLineNumber);
  101. const lineLeadingWhitespace = getLeadingWhitespace(line, 0, this._selection.startColumn - 1);
  102. let varLeadingWhitespace = lineLeadingWhitespace;
  103. variable.snippet.walk(marker => {
  104. if (marker === variable) {
  105. return false;
  106. }
  107. if (marker instanceof Text) {
  108. varLeadingWhitespace = getLeadingWhitespace(splitLines(marker.value).pop());
  109. }
  110. return true;
  111. });
  112. const whitespaceCommonLength = commonPrefixLength(varLeadingWhitespace, lineLeadingWhitespace);
  113. value = value.replace(/(\r\n|\r|\n)(.*)/g, (m, newline, rest) => `${newline}${varLeadingWhitespace.substr(whitespaceCommonLength)}${rest}`);
  114. }
  115. return value;
  116. }
  117. else if (name === 'TM_CURRENT_LINE') {
  118. return this._model.getLineContent(this._selection.positionLineNumber);
  119. }
  120. else if (name === 'TM_CURRENT_WORD') {
  121. const info = this._model.getWordAtPosition({
  122. lineNumber: this._selection.positionLineNumber,
  123. column: this._selection.positionColumn
  124. });
  125. return info && info.word || undefined;
  126. }
  127. else if (name === 'TM_LINE_INDEX') {
  128. return String(this._selection.positionLineNumber - 1);
  129. }
  130. else if (name === 'TM_LINE_NUMBER') {
  131. return String(this._selection.positionLineNumber);
  132. }
  133. else if (name === 'CURSOR_INDEX') {
  134. return String(this._selectionIdx);
  135. }
  136. else if (name === 'CURSOR_NUMBER') {
  137. return String(this._selectionIdx + 1);
  138. }
  139. return undefined;
  140. }
  141. }
  142. export class ModelBasedVariableResolver {
  143. constructor(_labelService, _model) {
  144. this._labelService = _labelService;
  145. this._model = _model;
  146. //
  147. }
  148. resolve(variable) {
  149. const { name } = variable;
  150. if (name === 'TM_FILENAME') {
  151. return path.basename(this._model.uri.fsPath);
  152. }
  153. else if (name === 'TM_FILENAME_BASE') {
  154. const name = path.basename(this._model.uri.fsPath);
  155. const idx = name.lastIndexOf('.');
  156. if (idx <= 0) {
  157. return name;
  158. }
  159. else {
  160. return name.slice(0, idx);
  161. }
  162. }
  163. else if (name === 'TM_DIRECTORY') {
  164. if (path.dirname(this._model.uri.fsPath) === '.') {
  165. return '';
  166. }
  167. return this._labelService.getUriLabel(dirname(this._model.uri));
  168. }
  169. else if (name === 'TM_FILEPATH') {
  170. return this._labelService.getUriLabel(this._model.uri);
  171. }
  172. else if (name === 'RELATIVE_FILEPATH') {
  173. return this._labelService.getUriLabel(this._model.uri, { relative: true, noPrefix: true });
  174. }
  175. return undefined;
  176. }
  177. }
  178. export class ClipboardBasedVariableResolver {
  179. constructor(_readClipboardText, _selectionIdx, _selectionCount, _spread) {
  180. this._readClipboardText = _readClipboardText;
  181. this._selectionIdx = _selectionIdx;
  182. this._selectionCount = _selectionCount;
  183. this._spread = _spread;
  184. //
  185. }
  186. resolve(variable) {
  187. if (variable.name !== 'CLIPBOARD') {
  188. return undefined;
  189. }
  190. const clipboardText = this._readClipboardText();
  191. if (!clipboardText) {
  192. return undefined;
  193. }
  194. // `spread` is assigning each cursor a line of the clipboard
  195. // text whenever there the line count equals the cursor count
  196. // and when enabled
  197. if (this._spread) {
  198. const lines = clipboardText.split(/\r\n|\n|\r/).filter(s => !isFalsyOrWhitespace(s));
  199. if (lines.length === this._selectionCount) {
  200. return lines[this._selectionIdx];
  201. }
  202. }
  203. return clipboardText;
  204. }
  205. }
  206. let CommentBasedVariableResolver = class CommentBasedVariableResolver {
  207. constructor(_model, _selection, _languageConfigurationService) {
  208. this._model = _model;
  209. this._selection = _selection;
  210. this._languageConfigurationService = _languageConfigurationService;
  211. //
  212. }
  213. resolve(variable) {
  214. const { name } = variable;
  215. const langId = this._model.getLanguageIdAtPosition(this._selection.selectionStartLineNumber, this._selection.selectionStartColumn);
  216. const config = this._languageConfigurationService.getLanguageConfiguration(langId).comments;
  217. if (!config) {
  218. return undefined;
  219. }
  220. if (name === 'LINE_COMMENT') {
  221. return config.lineCommentToken || undefined;
  222. }
  223. else if (name === 'BLOCK_COMMENT_START') {
  224. return config.blockCommentStartToken || undefined;
  225. }
  226. else if (name === 'BLOCK_COMMENT_END') {
  227. return config.blockCommentEndToken || undefined;
  228. }
  229. return undefined;
  230. }
  231. };
  232. CommentBasedVariableResolver = __decorate([
  233. __param(2, ILanguageConfigurationService)
  234. ], CommentBasedVariableResolver);
  235. export { CommentBasedVariableResolver };
  236. export class TimeBasedVariableResolver {
  237. constructor() {
  238. this._date = new Date();
  239. }
  240. resolve(variable) {
  241. const { name } = variable;
  242. if (name === 'CURRENT_YEAR') {
  243. return String(this._date.getFullYear());
  244. }
  245. else if (name === 'CURRENT_YEAR_SHORT') {
  246. return String(this._date.getFullYear()).slice(-2);
  247. }
  248. else if (name === 'CURRENT_MONTH') {
  249. return String(this._date.getMonth().valueOf() + 1).padStart(2, '0');
  250. }
  251. else if (name === 'CURRENT_DATE') {
  252. return String(this._date.getDate().valueOf()).padStart(2, '0');
  253. }
  254. else if (name === 'CURRENT_HOUR') {
  255. return String(this._date.getHours().valueOf()).padStart(2, '0');
  256. }
  257. else if (name === 'CURRENT_MINUTE') {
  258. return String(this._date.getMinutes().valueOf()).padStart(2, '0');
  259. }
  260. else if (name === 'CURRENT_SECOND') {
  261. return String(this._date.getSeconds().valueOf()).padStart(2, '0');
  262. }
  263. else if (name === 'CURRENT_DAY_NAME') {
  264. return TimeBasedVariableResolver.dayNames[this._date.getDay()];
  265. }
  266. else if (name === 'CURRENT_DAY_NAME_SHORT') {
  267. return TimeBasedVariableResolver.dayNamesShort[this._date.getDay()];
  268. }
  269. else if (name === 'CURRENT_MONTH_NAME') {
  270. return TimeBasedVariableResolver.monthNames[this._date.getMonth()];
  271. }
  272. else if (name === 'CURRENT_MONTH_NAME_SHORT') {
  273. return TimeBasedVariableResolver.monthNamesShort[this._date.getMonth()];
  274. }
  275. else if (name === 'CURRENT_SECONDS_UNIX') {
  276. return String(Math.floor(this._date.getTime() / 1000));
  277. }
  278. return undefined;
  279. }
  280. }
  281. TimeBasedVariableResolver.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")];
  282. TimeBasedVariableResolver.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")];
  283. TimeBasedVariableResolver.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")];
  284. TimeBasedVariableResolver.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")];
  285. export class WorkspaceBasedVariableResolver {
  286. constructor(_workspaceService) {
  287. this._workspaceService = _workspaceService;
  288. //
  289. }
  290. resolve(variable) {
  291. if (!this._workspaceService) {
  292. return undefined;
  293. }
  294. const workspaceIdentifier = toWorkspaceIdentifier(this._workspaceService.getWorkspace());
  295. if (!workspaceIdentifier) {
  296. return undefined;
  297. }
  298. if (variable.name === 'WORKSPACE_NAME') {
  299. return this._resolveWorkspaceName(workspaceIdentifier);
  300. }
  301. else if (variable.name === 'WORKSPACE_FOLDER') {
  302. return this._resoveWorkspacePath(workspaceIdentifier);
  303. }
  304. return undefined;
  305. }
  306. _resolveWorkspaceName(workspaceIdentifier) {
  307. if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) {
  308. return path.basename(workspaceIdentifier.uri.path);
  309. }
  310. let filename = path.basename(workspaceIdentifier.configPath.path);
  311. if (filename.endsWith(WORKSPACE_EXTENSION)) {
  312. filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
  313. }
  314. return filename;
  315. }
  316. _resoveWorkspacePath(workspaceIdentifier) {
  317. if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) {
  318. return normalizeDriveLetter(workspaceIdentifier.uri.fsPath);
  319. }
  320. const filename = path.basename(workspaceIdentifier.configPath.path);
  321. let folderpath = workspaceIdentifier.configPath.fsPath;
  322. if (folderpath.endsWith(filename)) {
  323. folderpath = folderpath.substr(0, folderpath.length - filename.length - 1);
  324. }
  325. return (folderpath ? normalizeDriveLetter(folderpath) : '/');
  326. }
  327. }
  328. export class RandomBasedVariableResolver {
  329. resolve(variable) {
  330. const { name } = variable;
  331. if (name === 'RANDOM') {
  332. return Math.random().toString().slice(-6);
  333. }
  334. else if (name === 'RANDOM_HEX') {
  335. return Math.random().toString(16).slice(-6);
  336. }
  337. else if (name === 'UUID') {
  338. return generateUuid();
  339. }
  340. return undefined;
  341. }
  342. }