/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt VS Tools. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ /// This file contains the classification of the syntax elements recognized by the QML parser. using System; using System.Collections.Generic; using System.ComponentModel.Composition; using Microsoft.VisualStudio.Language.StandardClassification; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.Utilities; namespace QtVsTools.Qml.Classification { using Syntax; using VisualStudio.Text.Extensions; /// /// Represents a classification tag that can be mapped onto future versions of the source code /// public class TrackingTag : ITag { public ITextSnapshot Snapshot { get; } public int Start { get; } private int Length { get; } public ITrackingSpan Span { get; } public TrackingTag(ITextSnapshot snapshot, int start, int length) { Snapshot = snapshot; Start = start; Length = length; Span = snapshot.CreateTrackingSpan(start, length, SpanTrackingMode.EdgeExclusive); } public ITagSpan MapToSnapshot(ITextSnapshot snapshot) { return new TagSpan(Span.GetSpan(snapshot), this); } } /// /// Represents the classification of a QML syntax element /// public class QmlSyntaxTag : TrackingTag { public const string Keyword = "keyword.qml"; public const string Numeric = "numeric.qml"; public const string String = "string.qml"; public const string Comment = "comment.qml"; public const string TypeName = "typename.qml"; public const string Binding = "binding.qml"; private SyntaxElement SyntaxElement { get; } private SourceLocation SourceLocation { get; } public IClassificationType ClassificationType { get; } private QmlSyntaxTag(ITextSnapshot snapshot, SourceLocation location) : base(snapshot, location.Offset, location.Length) { SourceLocation = location; } public QmlSyntaxTag( ITextSnapshot snapshot, SyntaxElement element, string classificationType, SourceLocation location) : this(snapshot, location) { SyntaxElement = element; ClassificationType = QmlClassificationType.Get(classificationType); } static QmlSyntaxTag GetClassificationTag( ITextSnapshot snapshot, AstNode parentNode, string classificationType, UiQualifiedId qualifiedId) { var firstName = qualifiedId.IdentifierToken; var lastName = qualifiedId.IdentifierToken; while (qualifiedId.Next != null) { qualifiedId = qualifiedId.Next; lastName = qualifiedId.IdentifierToken; } var fullNameLocation = new SourceLocation { Offset = firstName.Offset, Length = lastName.Offset + lastName.Length - firstName.Offset }; return new QmlSyntaxTag(snapshot, parentNode, classificationType, fullNameLocation); } private static readonly HashSet QmlBasicTypes = new HashSet { "bool", "double", "enumeration", "int", "list", "real", "string", "url", "var", "date", "point", "rect", "size", "alias" }; public static IEnumerable GetClassification( ITextSnapshot snapshot, SyntaxElement element) { var tags = new List(); if (element is KeywordToken) { var token = element as KeywordToken; tags.Add(new QmlSyntaxTag(snapshot, token, Keyword, token.Location)); } else if (element is NumberToken) { var token = element as NumberToken; tags.Add(new QmlSyntaxTag(snapshot, token, Numeric, token.Location)); } else if (element is StringToken) { var token = element as StringToken; tags.Add(new QmlSyntaxTag(snapshot, token, String, token.Location)); } else if (element is CommentToken) { var token = element as CommentToken; // QML parser does not report the initial/final tokens of comments var commentStart = snapshot.GetText(token.Location.Offset - 2, 2); var commentLocation = token.Location; if (commentStart == "//") { commentLocation.Offset -= 2; commentLocation.Length += 2; } else { commentLocation.Offset -= 2; commentLocation.Length += 4; } tags.Add(new QmlSyntaxTag(snapshot, token, Comment, commentLocation)); } else if (element is UiImport) { var node = element as UiImport; if (node.ImportIdToken.Length > 0) tags.Add(new QmlSyntaxTag(snapshot, node, TypeName, node.ImportIdToken)); } else if (element is UiObjectDefinition) { var node = element as UiObjectDefinition; if (node.QualifiedTypeNameId != null) { var name = snapshot.GetText(node.QualifiedTypeNameId.IdentifierToken); // an UiObjectDefinition may be used to group property bindings // think anchors { ... } bool isGroupedBinding = !string.IsNullOrEmpty(name) && char.IsLower(name[0]); if (!isGroupedBinding) { tags.Add(GetClassificationTag( snapshot, node, TypeName, node.QualifiedTypeNameId)); } else { tags.Add(GetClassificationTag( snapshot, node, Binding, node.QualifiedTypeNameId)); } } } else if (element is UiObjectBinding) { var node = element as UiObjectBinding; if (node.QualifiedId != null) { tags.Add(GetClassificationTag( snapshot, node, Binding, node.QualifiedId)); } if (node.QualifiedTypeNameId != null) { tags.Add(GetClassificationTag( snapshot, node, TypeName, node.QualifiedTypeNameId)); } } else if (element is UiScriptBinding) { var node = element as UiScriptBinding; var qualifiedId = node.QualifiedId; while (qualifiedId != null) { tags.Add(GetClassificationTag(snapshot, node, Binding, qualifiedId)); qualifiedId = qualifiedId.Next; } } else if (element is UiArrayBinding) { var node = element as UiArrayBinding; var qualifiedId = node.QualifiedId; while (qualifiedId != null) { tags.Add(GetClassificationTag(snapshot, node, Binding, qualifiedId)); qualifiedId = qualifiedId.Next; } } else if (element is UiPublicMember) { var node = element as UiPublicMember; if (node.Type == UiPublicMemberType.Property && node.TypeToken.Length > 0) { var typeName = snapshot.GetText(node.TypeToken); if (QmlBasicTypes.Contains(typeName)) tags.Add(new QmlSyntaxTag(snapshot, node, Keyword, node.TypeToken)); else tags.Add(new QmlSyntaxTag(snapshot, node, TypeName, node.TypeToken)); } if (node.IdentifierToken.Length > 0) tags.Add(new QmlSyntaxTag(snapshot, node, Binding, node.IdentifierToken)); } return tags; } } public class QmlDiagnosticsTag : TrackingTag { private DiagnosticMessage DiagnosticMessage { get; } public QmlDiagnosticsTag(ITextSnapshot snapshot, DiagnosticMessage diagnosticMessage) : base(snapshot, diagnosticMessage.Location.Offset, diagnosticMessage.Location.Length) { DiagnosticMessage = diagnosticMessage; } } internal static class QmlClassificationType { [Export(typeof(ClassificationTypeDefinition))] [Name(QmlSyntaxTag.Keyword)] internal static ClassificationTypeDefinition qmlKeyword = null; [Export(typeof(ClassificationTypeDefinition))] [Name(QmlSyntaxTag.Numeric)] internal static ClassificationTypeDefinition qmlNumber = null; [Export(typeof(ClassificationTypeDefinition))] [Name(QmlSyntaxTag.String)] internal static ClassificationTypeDefinition qmlString = null; [Export(typeof(ClassificationTypeDefinition))] [Name(QmlSyntaxTag.Comment)] internal static ClassificationTypeDefinition qmlComment = null; [Export(typeof(ClassificationTypeDefinition))] [Name(QmlSyntaxTag.TypeName)] internal static ClassificationTypeDefinition qmlTypeName = null; [Export(typeof(ClassificationTypeDefinition))] [Name(QmlSyntaxTag.Binding)] internal static ClassificationTypeDefinition qmlBinding = null; public static IDictionary ClassificationTypes { get; private set; } public static void InitClassificationTypes(IClassificationTypeRegistryService typeService) { if (ClassificationTypes != null) return; ClassificationTypes = new Dictionary { { QmlSyntaxTag.Keyword, typeService.GetClassificationType(QmlSyntaxTag.Keyword) }, { QmlSyntaxTag.Numeric, typeService.GetClassificationType(QmlSyntaxTag.Numeric) }, { QmlSyntaxTag.String, typeService.GetClassificationType(QmlSyntaxTag.String) }, { QmlSyntaxTag.TypeName, typeService.GetClassificationType(QmlSyntaxTag.TypeName) }, { QmlSyntaxTag.Binding, typeService.GetClassificationType(QmlSyntaxTag.Binding) }, // QML comments are mapped to the Visual Studio pre-defined comment classification { QmlSyntaxTag.Comment, typeService.GetClassificationType(PredefinedClassificationTypeNames.Comment) } }; } public static IClassificationType Get(string classificationType) { if (ClassificationTypes == null) return null; return ClassificationTypes[classificationType]; } } namespace VisualStudio.Text.Extensions { public static class TextSnapshotExtensions { public static string GetText(this ITextSnapshot _this, SourceLocation sourceLocation) { if (sourceLocation.Length == 0) return string.Empty; if (_this.Length < sourceLocation.Offset + sourceLocation.Length) return string.Empty; try { return _this.GetText(sourceLocation.Offset, sourceLocation.Length); } catch (Exception) { return string.Empty; } } } } }