cssWorker.ts 14.2 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

import nls = require('vs/nls');
8
import URI from 'vs/base/common/uri';
E
Erich Gamma 已提交
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
import _severity from 'vs/base/common/severity';
import strings = require('vs/base/common/strings');
import winjs = require('vs/base/common/winjs.base');
import languageService = require('vs/languages/css/common/services/cssLanguageService');
import languageFacts = require('vs/languages/css/common/services/languageFacts');
import occurrences = require('./services/occurrences');
import cssIntellisense = require('vs/languages/css/common/services/intelliSense');
import EditorCommon = require('vs/editor/common/editorCommon');
import Modes = require('vs/editor/common/modes');
import nodes = require('vs/languages/css/common/parser/cssNodes');
import _level = require('vs/languages/css/common/level');
import parser = require('vs/languages/css/common/parser/cssParser');
import selectorPrinting = require('vs/languages/css/common/services/selectorPrinting');
import lint = require('vs/languages/css/common/services/lint');
import lintRules = require('vs/languages/css/common/services/lintRules');
import {IMarker, IMarkerData} from 'vs/platform/markers/common/markers';
import {IMarkerService} from 'vs/platform/markers/common/markers';
import {IResourceService} from 'vs/editor/common/services/resourceService';
27
import {filterSuggestions} from 'vs/editor/common/modes/supports/suggestSupport';
28
import {ValidationHelper} from 'vs/editor/common/worker/validationHelper';
E
Erich Gamma 已提交
29

A
Alex Dima 已提交
30
export class CSSWorker {
E
Erich Gamma 已提交
31 32

	public languageService: languageService.ILanguageService;
A
Alex Dima 已提交
33 34 35
	private resourceService:IResourceService;
	private markerService: IMarkerService;
	private _modeId: string;
E
Erich Gamma 已提交
36 37
	private validationEnabled : boolean;
	private lintSettings : lintRules.IConfigurationSettings;
38
	private _validationHelper: ValidationHelper;
E
Erich Gamma 已提交
39

A
Alex Dima 已提交
40 41 42 43 44
	constructor(
		modeId: string,
		@IResourceService resourceService: IResourceService,
		@IMarkerService markerService: IMarkerService
	) {
E
Erich Gamma 已提交
45

A
Alex Dima 已提交
46 47 48
		this._modeId = modeId;
		this.resourceService = resourceService;
		this.markerService = markerService;
49 50 51

		this._validationHelper = new ValidationHelper(
			this.resourceService,
A
Alex Dima 已提交
52
			this._modeId,
53 54 55
			(toValidate) => this.doValidate(toValidate)
		);

A
Alex Dima 已提交
56
		this.languageService = this.createLanguageService(resourceService, modeId);
E
Erich Gamma 已提交
57 58 59 60
		this.lintSettings = {};
		this.validationEnabled = true;
	}

61 62
	public navigateValueSet(resource:URI, range:EditorCommon.IRange, up:boolean):winjs.TPromise<Modes.IInplaceReplaceSupportResult> {
		return this.languageService.join().then(() => {
63

64 65 66
			let model = this.resourceService.get(resource);
			let offset = model.getOffsetFromPosition({ lineNumber: range.startLineNumber, column: range.startColumn });
			let styleSheet = this.languageService.getStylesheet(resource);
67

68 69 70 71 72 73 74 75
			let node = nodes.getNodeAtOffset(styleSheet, offset);
			if (!node) {
				return;
			}
			let declaration = nodes.getParentDeclaration(node);
			if (!declaration) {
				return;
			}
76

77 78 79 80
			let entry: languageFacts.IEntry = languageFacts.getProperties()[declaration.getFullPropertyName()];
			if (!entry || !entry.values) {
				return;
			}
81

82
			let values = entry.values.filter(value => languageFacts.isCommonValue(value)).map(v => v.name);
83

84 85 86 87
			let isColor = (entry.restrictions.indexOf('color') >= 0);
			if (isColor) {
				values = values.concat(Object.getOwnPropertyNames(languageFacts.colors), Object.getOwnPropertyNames(languageFacts.colorKeywords));
			}
88

89 90 91 92 93 94 95 96 97 98
			let text = node.getText();
			for (let i = 0, len = values.length; i < len; i++) {
				if (strings.equalsIgnoreCase(values[i], text)) {
					let nextIdx = i;
					if(up) {
						nextIdx = (i + 1) % len;
					} else {
						nextIdx =  i - 1;
						if(nextIdx < 0) {
							nextIdx = len - 1;
99 100
						}
					}
101 102 103 104 105 106 107 108 109 110 111 112 113 114
					let result:Modes.IInplaceReplaceSupportResult = {
						value: values[nextIdx],
						range: this._range(node, model)
					};
					return result;
				}
			}
			// if none matches, take the first one
			if (values.length > 0) {
				let result:Modes.IInplaceReplaceSupportResult = {
					value: values[0],
					range: this._range(node, model)
				};
				return result;
115
			}
116 117

			return null;
118
		});
E
Erich Gamma 已提交
119 120 121 122 123 124 125 126 127 128
	}

	public createLanguageService(resourceService:IResourceService, modeId:string): languageService.CSSLanguageService {
		return new languageService.CSSLanguageService(resourceService, this.createParser.bind(this), modeId);
	}

	public createParser() : parser.Parser {
		return new parser.Parser();
	}

129
	_doConfigure(raw:any): winjs.TPromise<void> {
E
Erich Gamma 已提交
130 131 132 133 134 135 136
		if (raw) {
			this.validationEnabled = raw.validate;
			if (raw.lint) {
				this.lintSettings = lintRules.sanitize(raw.lint);
			} else {
				this.lintSettings = {};
			}
137 138
			this._validationHelper.triggerDueToConfigurationChange();
		}
139 140

		return winjs.TPromise.as(void 0);
141 142 143 144 145 146 147 148 149 150
	}

	public enableValidator(): winjs.TPromise<void> {
		this._validationHelper.enable();
		return winjs.TPromise.as(null);
	}

	public doValidate(resources: URI[]):void {
		for (var i = 0; i < resources.length; i++) {
			this.doValidate1(resources[i]);
E
Erich Gamma 已提交
151 152 153
		}
	}

154
	private doValidate1(resource: URI):void {
E
Erich Gamma 已提交
155
		if (!this.validationEnabled) {
A
Alex Dima 已提交
156
			this.markerService.changeOne(this._modeId, resource, []);
E
Erich Gamma 已提交
157 158 159 160 161
			return;
		}

		this.languageService.join().then(() => {

162
			let modelMirror = this.resourceService.get(resource),
E
Erich Gamma 已提交
163 164 165 166 167 168
				node = this.languageService.getStylesheet(resource),
				entries: nodes.IMarker[] = [];

			entries.push.apply(entries, nodes.ParseErrorCollector.entries(node));
			entries.push.apply(entries, this.collectLintEntries(node));

169
			let markerData = entries
E
Erich Gamma 已提交
170 171 172
				.filter(entry => entry.getLevel() !== _level.Level.Ignore)
				.map(entry => this._createMarkerData(modelMirror, entry));

A
Alex Dima 已提交
173
			this.markerService.changeOne(this._modeId, resource, markerData);
E
Erich Gamma 已提交
174 175 176 177
		});
	}

	private _createMarkerData(model: EditorCommon.IMirrorModel, marker: nodes.IMarker): IMarkerData {
178
		let range = model.getRangeFromOffsetAndLength(marker.getOffset(), marker.getLength());
E
Erich Gamma 已提交
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
		return <IMarkerData> {
			code: marker.getRule().id,
			message: marker.getMessage(),
			severity: marker.getLevel() === _level.Level.Warning ? _severity.Warning : _severity.Error,
			startLineNumber: range.startLineNumber,
			startColumn: range.startColumn,
			endLineNumber: range.endLineNumber,
			endColumn: range.endColumn
		};
	}

	public collectLintEntries(stylesheet:nodes.Stylesheet):nodes.IMarker[] {
		return lint.LintVisitor.entries(stylesheet, this.lintSettings);
	}

	public createIntellisense(): cssIntellisense.CSSIntellisense {
		return new cssIntellisense.CSSIntellisense();
	}

198 199 200 201 202
	public suggest(resource:URI, position:EditorCommon.IPosition):winjs.TPromise<Modes.ISuggestResult[]> {
		return this.doSuggest(resource, position).then(value => filterSuggestions(value));
	}

	private doSuggest(resource:URI, position:EditorCommon.IPosition):winjs.TPromise<Modes.ISuggestResult> {
E
Erich Gamma 已提交
203 204 205

		return this.languageService.join().then(() => {

206 207
			let model = this.resourceService.get(resource);
			let result = this.createIntellisense().getCompletionsAtPosition(this.languageService, model, resource, position);
E
Erich Gamma 已提交
208 209 210 211 212
			return result;
		});

	}

213
	public getOutline(resource:URI):winjs.TPromise<Modes.IOutlineEntry[]> {
E
Erich Gamma 已提交
214 215 216

		return this.languageService.join().then(() => {

217
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
218 219 220 221 222
				stylesheet = this.languageService.getStylesheet(resource),
				result:Modes.IOutlineEntry[] = [];

			stylesheet.accept((node) => {

223
				let entry:Modes.IOutlineEntry = {
E
Erich Gamma 已提交
224 225 226 227 228 229 230 231 232 233
					label: null,
					type: 'rule',
					range: null,
					children: []
				};

				if(node instanceof nodes.Selector) {
					entry.label = node.getText();
				} else if(node instanceof nodes.VariableDeclaration) {
					entry.label = (<nodes.VariableDeclaration> node).getName();
234
					entry.type = 'letiable';
E
Erich Gamma 已提交
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
				} else if(node instanceof nodes.MixinDeclaration) {
					entry.label = (<nodes.MixinDeclaration> node).getName();
					entry.type = 'method';
				} else if(node instanceof nodes.FunctionDeclaration) {
					entry.label = (<nodes.FunctionDeclaration> node).getName();
					entry.type = 'function';
				} else if(node instanceof nodes.Keyframe) {
					entry.label = nls.localize('literal.keyframes', "@keyframes {0}", (<nodes.Keyframe> node).getName());
				} else if(node instanceof nodes.FontFace) {
					entry.label = nls.localize('literal.fontface', "@font-face");
				}

				if(entry.label) {
					entry.range = this._range(node, model, true);
					result.push(entry);
				}

				return true;
			});

			return result;
		});
	}

259
	public computeInfo(resource:URI, position:EditorCommon.IPosition): winjs.TPromise<Modes.IComputeExtraInfoResult> {
E
Erich Gamma 已提交
260 261 262

		return this.languageService.join().then(() => {

263
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
264 265 266 267
				offset = model.getOffsetFromPosition(position),
				stylesheet = this.languageService.getStylesheet(resource),
				nodepath = nodes.getNodePath(stylesheet, offset);

268 269 270
			for (let i = 0; i < nodepath.length; i++) {
				let node = nodepath[i];
				if (node instanceof nodes.Selector) {
E
Erich Gamma 已提交
271 272 273 274 275
					return {
						htmlContent: [selectorPrinting.selectorToHtml(<nodes.Selector> node)],
						range: this._range(node, model)
					};
				}
276
				if (node instanceof nodes.SimpleSelector) {
E
Erich Gamma 已提交
277 278 279 280 281
					return {
						htmlContent: [selectorPrinting.simpleSelectorToHtml(<nodes.SimpleSelector> node)],
						range: this._range(node, model)
					};
				}
282 283 284 285 286 287 288 289 290 291
				if (node instanceof nodes.Declaration) {
					let propertyName = node.getFullPropertyName();
					let entry = languageFacts.getProperties()[propertyName];
					if (entry) {
						return {
							htmlContent: [{ text: entry.description }],
							range: this._range(node, model)
						};
					}
				}
E
Erich Gamma 已提交
292 293 294 295 296 297
			}

			return null;
		});
	}

298
	public findDeclaration(resource:URI, position:EditorCommon.IPosition):winjs.TPromise<Modes.IReference> {
E
Erich Gamma 已提交
299 300 301

		return this.languageService.join().then(() => {

302
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
303 304 305 306 307 308 309 310 311 312 313 314 315 316
				offset = model.getOffsetFromPosition(position),
				node = occurrences.findDeclaration(this.languageService.getStylesheet(resource), offset);

			if (!node) {
				return null;
			}

			return <Modes.IReference> {
				resource: resource,
				range: this._range(node, model, true)
			};
		});
	}

317
	public findOccurrences(resource:URI, position:EditorCommon.IPosition, strict?:boolean):winjs.TPromise<Modes.IOccurence[]> {
E
Erich Gamma 已提交
318 319 320

		return this.languageService.join().then(() => {

321
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
322 323 324 325 326 327 328 329 330 331 332 333
				offset = model.getOffsetFromPosition(position),
				nodes = occurrences.findOccurrences(this.languageService.getStylesheet(resource), offset);

			return nodes.map((occurrence) => {
				return {
					range: this._range(occurrence.node, model),
					kind: occurrence.kind
				};
			});
		});
	}

334
	public findReferences(resource:URI, position:EditorCommon.IPosition):winjs.TPromise<Modes.IReference[]> {
E
Erich Gamma 已提交
335 336

		return this.languageService.join().then(() => {
337
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
338 339 340 341 342 343 344 345 346 347 348 349
				offset = model.getOffsetFromPosition(position),
				nodes = occurrences.findOccurrences(this.languageService.getStylesheet(resource), offset);

			return nodes.map((occurrence) => {
				return <Modes.IReference> {
					resource: model.getAssociatedResource(),
					range: this._range(occurrence.node, model)
				};
			});
		});
	}

350
	public findColorDeclarations(resource:URI):winjs.Promise {
E
Erich Gamma 已提交
351 352 353

		return this.languageService.join().then(() => {

354
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
				styleSheet = this.languageService.getStylesheet(resource),
				result:{range:EditorCommon.IRange; value:string; }[] = [];

			styleSheet.accept((node) => {
				if (languageFacts.isColorValue(node)) {
					result.push({
						range: this._range(node, model),
						value: node.getText()
					});
				}
				return true;
			});

			return result;
		});
	}

	_range(node:{offset:number; length:number;}, model:EditorCommon.IMirrorModel, empty:boolean = false):EditorCommon.IRange {
373 374
		if (empty) {
			let position = model.getPositionFromOffset(node.offset);
E
Erich Gamma 已提交
375 376 377 378 379 380 381 382 383 384 385 386 387
			return {
				startLineNumber: position.lineNumber,
				startColumn: position.column,
				endLineNumber: position.lineNumber,
				endColumn: position.column
			};
		} else {
			return model.getRangeFromOffsetAndLength(node.offset, node.length);
		}
	}

	private getFixesForUnknownProperty(property: nodes.Property) : Modes.IQuickFix[] {

388 389 390 391
		let propertyName = property.getName();
		let result: Modes.IQuickFix[] = [];
		for (let p in languageFacts.getProperties()) {
			let score = strings.difference(propertyName, p);
E
Erich Gamma 已提交
392 393
			if (score >= propertyName.length / 2 /*score_lim*/) {
				result.push({
394 395 396 397 398 399
					command: {
						id: 'css.renameProptery',
						title: nls.localize('css.quickfix.rename', "Rename to '{0}'", p),
						arguments: [{ type: 'rename', name: p }]
					},
					score
E
Erich Gamma 已提交
400 401 402 403 404 405 406 407 408 409 410 411
				});
			}
		}

		// Sort in descending order.
		result.sort((a, b) => {
			return b.score - a.score;
		});

		return result.slice(0, 3 /*max_result*/);
	}

412
	public getQuickFixes(resource: URI, marker: IMarker | EditorCommon.IRange): winjs.TPromise<Modes.IQuickFix[]> {
E
Erich Gamma 已提交
413 414 415 416 417 418
		if ((<IMarker> marker).code !== lintRules.Rules.UnknownProperty.id) {
			return winjs.TPromise.as([]);
		}

		return this.languageService.join().then(() => {

419
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
420 421 422 423
				offset = model.getOffsetFromPosition({ column: marker.startColumn, lineNumber: marker.startLineNumber }),
				stylesheet = this.languageService.getStylesheet(resource),
				nodepath = nodes.getNodePath(stylesheet, offset);

424 425
			for (let i = nodepath.length - 1; i >= 0; i--) {
				let node = nodepath[i];
E
Erich Gamma 已提交
426
				if (node instanceof nodes.Declaration) {
427
					let property = (<nodes.Declaration> node).getProperty();
E
Erich Gamma 已提交
428 429 430 431 432 433 434 435 436
					if (property && property.offset === offset && property.length === marker.endColumn - marker.startColumn) {
						return this.getFixesForUnknownProperty(property);
					}
				}
			}
			return [];
		});
	}

437 438 439
	public runQuickFixAction(resource: URI, range: EditorCommon.IRange, quickFix: Modes.IQuickFix): winjs.TPromise<Modes.IQuickFixResult>{
		let [{type, name}] = quickFix.command.arguments;
		switch (type) {
E
Erich Gamma 已提交
440 441
			case 'rename': {
				return winjs.TPromise.as({
442
					edits: [{ resource, range, newText: name }]
E
Erich Gamma 已提交
443 444 445 446 447 448
				});
			}
		}
		return null;
	}
}