cssWorker.ts 15.0 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 27
import _severity from 'vs/base/common/severity';
import strings = require('vs/base/common/strings');
import winjs = require('vs/base/common/winjs.base');
import {AbstractModeWorker} from 'vs/editor/common/modes/abstractModeWorker';
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';
28
import {filterSuggestions} from 'vs/editor/common/modes/supports/suggestSupport';
29
import {ValidationHelper} from 'vs/editor/common/worker/validationHelper';
E
Erich Gamma 已提交
30 31 32 33 34 35 36

export class CSSWorker extends AbstractModeWorker {

	public languageService: languageService.ILanguageService;

	private validationEnabled : boolean;
	private lintSettings : lintRules.IConfigurationSettings;
37
	private _validationHelper: ValidationHelper;
E
Erich Gamma 已提交
38

A
Alex Dima 已提交
39
	constructor(modeId: string, participants: Modes.IWorkerParticipant[], @IResourceService resourceService: IResourceService,
E
Erich Gamma 已提交
40 41
		@IMarkerService markerService: IMarkerService) {

A
Alex Dima 已提交
42
		super(modeId, participants, resourceService, markerService);
43 44 45

		this._validationHelper = new ValidationHelper(
			this.resourceService,
A
Alex Dima 已提交
46
			this._getModeId(),
47 48 49
			(toValidate) => this.doValidate(toValidate)
		);

A
Alex Dima 已提交
50
		this.languageService = this.createLanguageService(resourceService, modeId);
E
Erich Gamma 已提交
51 52 53 54
		this.lintSettings = {};
		this.validationEnabled = true;
	}

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

58 59 60
			let model = this.resourceService.get(resource);
			let offset = model.getOffsetFromPosition({ lineNumber: range.startLineNumber, column: range.startColumn });
			let styleSheet = this.languageService.getStylesheet(resource);
61

62 63 64 65 66 67 68 69
			let node = nodes.getNodeAtOffset(styleSheet, offset);
			if (!node) {
				return;
			}
			let declaration = nodes.getParentDeclaration(node);
			if (!declaration) {
				return;
			}
70

71 72 73 74
			let entry: languageFacts.IEntry = languageFacts.getProperties()[declaration.getFullPropertyName()];
			if (!entry || !entry.values) {
				return;
			}
75

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

78 79 80 81
			let isColor = (entry.restrictions.indexOf('color') >= 0);
			if (isColor) {
				values = values.concat(Object.getOwnPropertyNames(languageFacts.colors), Object.getOwnPropertyNames(languageFacts.colorKeywords));
			}
82

83 84 85 86 87 88 89 90 91 92
			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;
93 94
						}
					}
95 96 97 98 99 100 101 102 103 104 105 106 107 108
					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;
109
			}
110 111

			return null;
112
		});
E
Erich Gamma 已提交
113 114 115 116 117 118 119 120 121 122
	}

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

123
	_doConfigure(raw:any): winjs.TPromise<void> {
E
Erich Gamma 已提交
124 125 126 127 128 129 130
		if (raw) {
			this.validationEnabled = raw.validate;
			if (raw.lint) {
				this.lintSettings = lintRules.sanitize(raw.lint);
			} else {
				this.lintSettings = {};
			}
131 132
			this._validationHelper.triggerDueToConfigurationChange();
		}
133 134

		return winjs.TPromise.as(void 0);
135 136 137 138 139 140 141 142 143 144
	}

	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 已提交
145 146 147
		}
	}

148
	private doValidate1(resource: URI):void {
E
Erich Gamma 已提交
149
		if (!this.validationEnabled) {
A
Alex Dima 已提交
150
			this.markerService.changeOne(this._getModeId(), resource, []);
E
Erich Gamma 已提交
151 152 153 154 155
			return;
		}

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

156
			let modelMirror = this.resourceService.get(resource),
E
Erich Gamma 已提交
157 158 159 160 161 162
				node = this.languageService.getStylesheet(resource),
				entries: nodes.IMarker[] = [];

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

163
			let markerData = entries
E
Erich Gamma 已提交
164 165 166
				.filter(entry => entry.getLevel() !== _level.Level.Ignore)
				.map(entry => this._createMarkerData(modelMirror, entry));

A
Alex Dima 已提交
167
			this.markerService.changeOne(this._getModeId(), resource, markerData);
E
Erich Gamma 已提交
168 169 170 171
		});
	}

	private _createMarkerData(model: EditorCommon.IMirrorModel, marker: nodes.IMarker): IMarkerData {
172
		let range = model.getRangeFromOffsetAndLength(marker.getOffset(), marker.getLength());
E
Erich Gamma 已提交
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
		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();
	}

192 193 194 195 196
	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 已提交
197 198 199

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

200 201
			let model = this.resourceService.get(resource);
			let result = this.createIntellisense().getCompletionsAtPosition(this.languageService, model, resource, position);
E
Erich Gamma 已提交
202 203 204 205 206
			return result;
		});

	}

207
	public getRangesToPosition(resource:URI, position:EditorCommon.IPosition):winjs.TPromise<Modes.ILogicalSelectionEntry[]> {
E
Erich Gamma 已提交
208 209 210

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

211
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
212 213 214 215 216
				offset = model.getOffsetFromPosition(position),
				styleSheet = this.languageService.getStylesheet(resource),
				path = nodes.getNodePath(styleSheet, offset),
				result: Modes.ILogicalSelectionEntry[] = [];

217 218
			for (let i = 0; i < path.length; i++) {
				let node = path[i];
E
Erich Gamma 已提交
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
				if(node.offset === -1 || node.length === -1) {
					continue;
				}
				if(node.parent && node.parent.offset === node.offset && node.parent.length === node.length) {
					continue;
				}
				result.push({
					type: 'node',
					range: this._range(node, model)
				});
			}

			return result;
		});
	}

235
	public getOutline(resource:URI):winjs.TPromise<Modes.IOutlineEntry[]> {
E
Erich Gamma 已提交
236 237 238

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

239
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
240 241 242 243 244
				stylesheet = this.languageService.getStylesheet(resource),
				result:Modes.IOutlineEntry[] = [];

			stylesheet.accept((node) => {

245
				let entry:Modes.IOutlineEntry = {
E
Erich Gamma 已提交
246 247 248 249 250 251 252 253 254 255
					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();
256
					entry.type = 'letiable';
E
Erich Gamma 已提交
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
				} 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;
		});
	}

281
	public computeInfo(resource:URI, position:EditorCommon.IPosition): winjs.TPromise<Modes.IComputeExtraInfoResult> {
E
Erich Gamma 已提交
282 283 284

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

285
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
286 287 288 289
				offset = model.getOffsetFromPosition(position),
				stylesheet = this.languageService.getStylesheet(resource),
				nodepath = nodes.getNodePath(stylesheet, offset);

290 291 292
			for (let i = 0; i < nodepath.length; i++) {
				let node = nodepath[i];
				if (node instanceof nodes.Selector) {
E
Erich Gamma 已提交
293 294 295 296 297
					return {
						htmlContent: [selectorPrinting.selectorToHtml(<nodes.Selector> node)],
						range: this._range(node, model)
					};
				}
298
				if (node instanceof nodes.SimpleSelector) {
E
Erich Gamma 已提交
299 300 301 302 303
					return {
						htmlContent: [selectorPrinting.simpleSelectorToHtml(<nodes.SimpleSelector> node)],
						range: this._range(node, model)
					};
				}
304 305 306 307 308 309 310 311 312 313
				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 已提交
314 315 316 317 318 319
			}

			return null;
		});
	}

320
	public findDeclaration(resource:URI, position:EditorCommon.IPosition):winjs.TPromise<Modes.IReference> {
E
Erich Gamma 已提交
321 322 323

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

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

339
	public findOccurrences(resource:URI, position:EditorCommon.IPosition, strict?:boolean):winjs.TPromise<Modes.IOccurence[]> {
E
Erich Gamma 已提交
340 341 342

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

343
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
344 345 346 347 348 349 350 351 352 353 354 355
				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
				};
			});
		});
	}

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

		return this.languageService.join().then(() => {
359
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
360 361 362 363 364 365 366 367 368 369 370 371
				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)
				};
			});
		});
	}

372
	public findColorDeclarations(resource:URI):winjs.Promise {
E
Erich Gamma 已提交
373 374 375

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

376
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
				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 {
395 396
		if (empty) {
			let position = model.getPositionFromOffset(node.offset);
E
Erich Gamma 已提交
397 398 399 400 401 402 403 404 405 406 407 408 409
			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[] {

410 411 412 413
		let propertyName = property.getName();
		let result: Modes.IQuickFix[] = [];
		for (let p in languageFacts.getProperties()) {
			let score = strings.difference(propertyName, p);
E
Erich Gamma 已提交
414 415
			if (score >= propertyName.length / 2 /*score_lim*/) {
				result.push({
416 417 418 419 420 421
					command: {
						id: 'css.renameProptery',
						title: nls.localize('css.quickfix.rename', "Rename to '{0}'", p),
						arguments: [{ type: 'rename', name: p }]
					},
					score
E
Erich Gamma 已提交
422 423 424 425 426 427 428 429 430 431 432 433
				});
			}
		}

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

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

434
	public getQuickFixes(resource: URI, marker: IMarker | EditorCommon.IRange): winjs.TPromise<Modes.IQuickFix[]> {
E
Erich Gamma 已提交
435 436 437 438 439 440
		if ((<IMarker> marker).code !== lintRules.Rules.UnknownProperty.id) {
			return winjs.TPromise.as([]);
		}

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

441
			let model = this.resourceService.get(resource),
E
Erich Gamma 已提交
442 443 444 445
				offset = model.getOffsetFromPosition({ column: marker.startColumn, lineNumber: marker.startLineNumber }),
				stylesheet = this.languageService.getStylesheet(resource),
				nodepath = nodes.getNodePath(stylesheet, offset);

446 447
			for (let i = nodepath.length - 1; i >= 0; i--) {
				let node = nodepath[i];
E
Erich Gamma 已提交
448
				if (node instanceof nodes.Declaration) {
449
					let property = (<nodes.Declaration> node).getProperty();
E
Erich Gamma 已提交
450 451 452 453 454 455 456 457 458
					if (property && property.offset === offset && property.length === marker.endColumn - marker.startColumn) {
						return this.getFixesForUnknownProperty(property);
					}
				}
			}
			return [];
		});
	}

459 460 461
	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 已提交
462 463
			case 'rename': {
				return winjs.TPromise.as({
464
					edits: [{ resource, range, newText: name }]
E
Erich Gamma 已提交
465 466 467 468 469 470
				});
			}
		}
		return null;
	}
}