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
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');
16 17
import editorCommon = require('vs/editor/common/editorCommon');
import modes = require('vs/editor/common/modes');
E
Erich Gamma 已提交
18 19 20 21 22 23 24 25
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';
26
import {Range} from 'vs/editor/common/core/range';
E
Erich Gamma 已提交
27
import {IResourceService} from 'vs/editor/common/services/resourceService';
28
import {filterSuggestions} from 'vs/editor/common/modes/supports/suggestSupport';
29
import {ValidationHelper} from 'vs/editor/common/worker/validationHelper';
E
Erich Gamma 已提交
30

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

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

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

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

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

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

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

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

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

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

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

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

90 91 92 93 94 95 96 97 98 99
			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;
100 101
						}
					}
102
					let result:modes.IInplaceReplaceSupportResult = {
103 104 105 106 107 108 109 110
						value: values[nextIdx],
						range: this._range(node, model)
					};
					return result;
				}
			}
			// if none matches, take the first one
			if (values.length > 0) {
111
				let result:modes.IInplaceReplaceSupportResult = {
112 113 114 115
					value: values[0],
					range: this._range(node, model)
				};
				return result;
116
			}
117 118

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

	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();
	}

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

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

	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 已提交
152 153 154
		}
	}

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

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

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

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

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

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

178
	private _createMarkerData(model: editorCommon.IMirrorModel, marker: nodes.IMarker): IMarkerData {
179
		let range = model.getRangeFromOffsetAndLength(marker.getOffset(), marker.getLength());
E
Erich Gamma 已提交
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
		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();
	}

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

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

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

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

	}

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

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

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

			stylesheet.accept((node) => {

224
				let entry:modes.IOutlineEntry = {
E
Erich Gamma 已提交
225 226 227 228 229 230 231 232 233 234
					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();
235
					entry.type = 'letiable';
E
Erich Gamma 已提交
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
				} 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;
		});
	}

260
	public provideHover(resource:URI, position:editorCommon.IPosition): winjs.TPromise<modes.Hover> {
E
Erich Gamma 已提交
261 262 263

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

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

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

			return null;
		});
	}

299
	public provideDefinition(resource:URI, position:editorCommon.IPosition):winjs.TPromise<modes.Location> {
E
Erich Gamma 已提交
300 301 302

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

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

			if (!node) {
				return null;
			}

311 312
			return {
				uri: resource,
E
Erich Gamma 已提交
313 314 315 316 317
				range: this._range(node, model, true)
			};
		});
	}

318
	public provideDocumentHighlights(resource:URI, position:editorCommon.IPosition):winjs.TPromise<modes.DocumentHighlight[]> {
E
Erich Gamma 已提交
319 320 321

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

322
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
323 324 325 326 327 328 329 330 331 332 333 334
				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
				};
			});
		});
	}

335
	public findReferences(resource:URI, position:editorCommon.IPosition):winjs.TPromise<modes.Location[]> {
E
Erich Gamma 已提交
336 337

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

			return nodes.map((occurrence) => {
343 344
				return {
					uri: model.getAssociatedResource(),
E
Erich Gamma 已提交
345 346 347 348 349 350
					range: this._range(occurrence.node, model)
				};
			});
		});
	}

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

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

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

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

			return result;
		});
	}

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

387
	private getFixesForUnknownProperty(property: nodes.Property, marker: IMarker) : modes.IQuickFix[] {
E
Erich Gamma 已提交
388

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

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

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

413
	private appendFixesForMarker(bucket: modes.IQuickFix[], marker: IMarker): void {
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431

		if ((<IMarker>marker).code !== lintRules.Rules.UnknownProperty.id) {
			return;
		}
		let model = this.resourceService.get(marker.resource),
			offset = model.getOffsetFromPosition({ column: marker.startColumn, lineNumber: marker.startLineNumber }),
			stylesheet = this.languageService.getStylesheet(marker.resource),
			nodepath = nodes.getNodePath(stylesheet, offset);

		for (let i = nodepath.length - 1; i >= 0; i--) {
			let node = nodepath[i];
			if (node instanceof nodes.Declaration) {
				let property = (<nodes.Declaration> node).getProperty();
				if (property && property.offset === offset && property.length === marker.endColumn - marker.startColumn) {
					bucket.push(...this.getFixesForUnknownProperty(property, marker));
					return;
				}
			}
E
Erich Gamma 已提交
432
		}
433 434
	}

435
	public getQuickFixes(resource: URI, range: editorCommon.IRange): winjs.TPromise<modes.IQuickFix[]> {
E
Erich Gamma 已提交
436 437

		return this.languageService.join().then(() => {
438
			const result: modes.IQuickFix[] = [];
E
Erich Gamma 已提交
439

440 441 442
			this.markerService.read({ resource })
				.filter(marker => Range.containsRange(range, marker))
				.forEach(marker => this.appendFixesForMarker(result, marker));
E
Erich Gamma 已提交
443

444
			return result;
E
Erich Gamma 已提交
445 446 447 448
		});
	}

}