settingsDocumentHelper.ts 11.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import { getLocation, Location } from 'jsonc-parser';
import * as nls from 'vscode-nls';

const localize = nls.loadMessageBundle();

export class SettingsDocument {

	constructor(private document: vscode.TextDocument) { }

16
	public provideCompletionItems(position: vscode.Position, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.CompletionItem[]> {
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
		const location = getLocation(this.document.getText(), this.document.offsetAt(position));
		const range = this.document.getWordRangeAtPosition(position) || new vscode.Range(position, position);

		// window.title
		if (location.path[0] === 'window.title') {
			return this.provideWindowTitleCompletionItems(location, range);
		}

		// files.association
		if (location.path[0] === 'files.associations') {
			return this.provideFilesAssociationsCompletionItems(location, range);
		}

		// files.exclude, search.exclude
		if (location.path[0] === 'files.exclude' || location.path[0] === 'search.exclude') {
			return this.provideExcludeCompletionItems(location, range);
		}

35 36
		// files.defaultLanguage
		if (location.path[0] === 'files.defaultLanguage') {
37
			return this.provideLanguageCompletionItems(location, range);
38 39
		}

40 41 42
		return this.provideLanguageOverridesCompletionItems(location, position);
	}

43
	private provideWindowTitleCompletionItems(_location: Location, range: vscode.Range): vscode.ProviderResult<vscode.CompletionItem[]> {
44 45
		const completions: vscode.CompletionItem[] = [];

46
		completions.push(this.newSimpleCompletionItem('${activeEditorShort}', range, localize('activeEditorShort', "the file name (e.g. myFile.txt)")));
47 48 49 50 51
		completions.push(this.newSimpleCompletionItem('${activeEditorMedium}', range, localize('activeEditorMedium', "the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)")));
		completions.push(this.newSimpleCompletionItem('${activeEditorLong}', range, localize('activeEditorLong', "the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt)")));
		completions.push(this.newSimpleCompletionItem('${activeFolderShort}', range, localize('activeFolderShort', "the name of the folder the file is contained in (e.g. myFileFolder)")));
		completions.push(this.newSimpleCompletionItem('${activeFolderMedium}', range, localize('activeFolderMedium', "the path of the folder the file is contained in, relative to the workspace folder (e.g. myFolder/myFileFolder)")));
		completions.push(this.newSimpleCompletionItem('${activeFolderLong}', range, localize('activeFolderLong', "the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)")));
52 53 54 55
		completions.push(this.newSimpleCompletionItem('${rootName}', range, localize('rootName', "name of the workspace (e.g. myFolder or myWorkspace)")));
		completions.push(this.newSimpleCompletionItem('${rootPath}', range, localize('rootPath', "file path of the workspace (e.g. /Users/Development/myWorkspace)")));
		completions.push(this.newSimpleCompletionItem('${folderName}', range, localize('folderName', "name of the workspace folder the file is contained in (e.g. myFolder)")));
		completions.push(this.newSimpleCompletionItem('${folderPath}', range, localize('folderPath', "file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)")));
56
		completions.push(this.newSimpleCompletionItem('${appName}', range, localize('appName', "e.g. VS Code")));
57
		completions.push(this.newSimpleCompletionItem('${remoteName}', range, localize('remoteName', "e.g. SSH")));
58 59 60 61 62 63 64 65 66
		completions.push(this.newSimpleCompletionItem('${dirty}', range, localize('dirty', "a dirty indicator if the active editor is dirty")));
		completions.push(this.newSimpleCompletionItem('${separator}', range, localize('separator', "a conditional separator (' - ') that only shows when surrounded by variables with values")));

		return Promise.resolve(completions);
	}

	private provideFilesAssociationsCompletionItems(location: Location, range: vscode.Range): vscode.ProviderResult<vscode.CompletionItem[]> {
		const completions: vscode.CompletionItem[] = [];

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
		if (location.path.length === 2) {
			// Key
			if (!location.isAtPropertyKey || location.path[1] === '') {
				completions.push(this.newSnippetCompletionItem({
					label: localize('assocLabelFile', "Files with Extension"),
					documentation: localize('assocDescriptionFile', "Map all files matching the glob pattern in their filename to the language with the given identifier."),
					snippet: location.isAtPropertyKey ? '"*.${1:extension}": "${2:language}"' : '{ "*.${1:extension}": "${2:language}" }',
					range
				}));

				completions.push(this.newSnippetCompletionItem({
					label: localize('assocLabelPath', "Files with Path"),
					documentation: localize('assocDescriptionPath', "Map all files matching the absolute path glob pattern in their path to the language with the given identifier."),
					snippet: location.isAtPropertyKey ? '"/${1:path to file}/*.${2:extension}": "${3:language}"' : '{ "/${1:path to file}/*.${2:extension}": "${3:language}" }',
					range
				}));
			} else {
				// Value
				return this.provideLanguageCompletionItems(location, range);
			}
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
		}

		return Promise.resolve(completions);
	}

	private provideExcludeCompletionItems(location: Location, range: vscode.Range): vscode.ProviderResult<vscode.CompletionItem[]> {
		const completions: vscode.CompletionItem[] = [];

		// Key
		if (location.path.length === 1) {
			completions.push(this.newSnippetCompletionItem({
				label: localize('fileLabel', "Files by Extension"),
				documentation: localize('fileDescription', "Match all files of a specific file extension."),
				snippet: location.isAtPropertyKey ? '"**/*.${1:extension}": true' : '{ "**/*.${1:extension}": true }',
				range
			}));

			completions.push(this.newSnippetCompletionItem({
				label: localize('filesLabel', "Files with Multiple Extensions"),
				documentation: localize('filesDescription', "Match all files with any of the file extensions."),
				snippet: location.isAtPropertyKey ? '"**/*.{ext1,ext2,ext3}": true' : '{ "**/*.{ext1,ext2,ext3}": true }',
				range
			}));

			completions.push(this.newSnippetCompletionItem({
				label: localize('derivedLabel', "Files with Siblings by Name"),
				documentation: localize('derivedDescription', "Match files that have siblings with the same name but a different extension."),
				snippet: location.isAtPropertyKey ? '"**/*.${1:source-extension}": { "when": "$(basename).${2:target-extension}" }' : '{ "**/*.${1:source-extension}": { "when": "$(basename).${2:target-extension}" } }',
				range
			}));

			completions.push(this.newSnippetCompletionItem({
				label: localize('topFolderLabel', "Folder by Name (Top Level)"),
				documentation: localize('topFolderDescription', "Match a top level folder with a specific name."),
				snippet: location.isAtPropertyKey ? '"${1:name}": true' : '{ "${1:name}": true }',
				range
			}));

			completions.push(this.newSnippetCompletionItem({
				label: localize('topFoldersLabel', "Folders with Multiple Names (Top Level)"),
				documentation: localize('topFoldersDescription', "Match multiple top level folders."),
				snippet: location.isAtPropertyKey ? '"{folder1,folder2,folder3}": true' : '{ "{folder1,folder2,folder3}": true }',
				range
			}));

			completions.push(this.newSnippetCompletionItem({
				label: localize('folderLabel', "Folder by Name (Any Location)"),
				documentation: localize('folderDescription', "Match a folder with a specific name in any location."),
				snippet: location.isAtPropertyKey ? '"**/${1:name}": true' : '{ "**/${1:name}": true }',
				range
			}));
		}

		// Value
		else {
			completions.push(this.newSimpleCompletionItem('false', range, localize('falseDescription', "Disable the pattern.")));
			completions.push(this.newSimpleCompletionItem('true', range, localize('trueDescription', "Enable the pattern.")));

			completions.push(this.newSnippetCompletionItem({
				label: localize('derivedLabel', "Files with Siblings by Name"),
				documentation: localize('siblingsDescription', "Match files that have siblings with the same name but a different extension."),
				snippet: '{ "when": "$(basename).${1:extension}" }',
				range
			}));
		}

		return Promise.resolve(completions);
	}

156
	private provideLanguageCompletionItems(_location: Location, range: vscode.Range, formatFunc: (string: string) => string = (l) => JSON.stringify(l)): vscode.ProviderResult<vscode.CompletionItem[]> {
157
		return vscode.languages.getLanguages().then(languages => {
S
Sandeep Somavarapu 已提交
158 159 160 161 162 163 164 165 166 167 168 169
			const completionItems = [];
			const configuration = vscode.workspace.getConfiguration();
			for (const language of languages) {
				const inspect = configuration.inspect(`[${language}]`);
				if (!inspect || !inspect.defaultValue) {
					const item = new vscode.CompletionItem(formatFunc(language));
					item.kind = vscode.CompletionItemKind.Property;
					item.range = range;
					completionItems.push(item);
				}
			}
			return completionItems;
170 171 172 173 174 175 176
		});
	}

	private provideLanguageOverridesCompletionItems(location: Location, position: vscode.Position): vscode.ProviderResult<vscode.CompletionItem[]> {

		if (location.path.length === 0) {

S
Sandeep Somavarapu 已提交
177 178 179 180 181 182 183 184 185
			let range = this.document.getWordRangeAtPosition(position, /^\s*\[.*]?/) || new vscode.Range(position, position);
			let text = this.document.getText(range);
			if (text && text.trim().startsWith('[')) {
				range = new vscode.Range(new vscode.Position(range.start.line, range.start.character + text.indexOf('[')), range.end);
				return this.provideLanguageCompletionItems(location, range, language => `"[${language}]"`);
			}

			range = this.document.getWordRangeAtPosition(position) || new vscode.Range(position, position);
			text = this.document.getText(range);
186 187 188 189 190
			let snippet = '"[${1:language}]": {\n\t"$0"\n}';

			// Suggestion model word matching includes quotes,
			// hence exclude the starting quote from the snippet and the range
			// ending quote gets replaced
S
Sandeep Somavarapu 已提交
191
			if (text && text.startsWith('"')) {
192 193 194 195 196 197 198 199 200 201 202 203
				range = new vscode.Range(new vscode.Position(range.start.line, range.start.character + 1), range.end);
				snippet = snippet.substring(1);
			}

			return Promise.resolve([this.newSnippetCompletionItem({
				label: localize('languageSpecificEditorSettings', "Language specific editor settings"),
				documentation: localize('languageSpecificEditorSettingsDescription', "Override editor settings for language"),
				snippet,
				range
			})]);
		}

S
Sandeep Somavarapu 已提交
204
		if (location.path.length === 1 && location.previousNode && typeof location.previousNode.value === 'string' && location.previousNode.value.startsWith('[')) {
205 206
			// Suggestion model word matching includes closed sqaure bracket and ending quote
			// Hence include them in the proposal to replace
S
Sandeep Somavarapu 已提交
207
			let range = this.document.getWordRangeAtPosition(position) || new vscode.Range(position, position);
208
			return this.provideLanguageCompletionItems(location, range, language => `"[${language}]"`);
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
		}
		return Promise.resolve([]);
	}

	private newSimpleCompletionItem(text: string, range: vscode.Range, description?: string, insertText?: string): vscode.CompletionItem {
		const item = new vscode.CompletionItem(text);
		item.kind = vscode.CompletionItemKind.Value;
		item.detail = description;
		item.insertText = insertText ? insertText : text;
		item.range = range;
		return item;
	}

	private newSnippetCompletionItem(o: { label: string; documentation?: string; snippet: string; range: vscode.Range; }): vscode.CompletionItem {
		const item = new vscode.CompletionItem(o.label);
		item.kind = vscode.CompletionItemKind.Value;
		item.documentation = o.documentation;
		item.insertText = new vscode.SnippetString(o.snippet);
		item.range = o.range;
		return item;
	}
}