void ICompletionSource.AugmentCompletionSession(ICompletionSession session, IList <CompletionSet> completionSets) { ObjectTree objectTree = new ObjectTree(this.language, this.textBuffer.CurrentSnapshot); // In the case of virtual spaces, VS sends SnapshotPoint of the last physical // location in the line. Unfortunately, we really need to know the virtual // location! As far as I know, there's no way to actually get this. Sadly, // that means that we might provide the wrong context. SnapshotPoint point = session.GetTriggerPoint(this.textBuffer.CurrentSnapshot).Value; Position pos = point.ToSwixPosition(); StatementNode currentNode; StatementNode parentNode; if (!objectTree.TryFindContextNodes(pos, out currentNode, out parentNode)) { return; } // Get compilation context for value completions (IDs, file names, etc.) IEnumerable <PackageItem> contextItems = SourceFileCompiler.Instance.GetCompilationContext(this.textBuffer); List <Completion> completions = this.CreateCompletions(objectTree, currentNode, parentNode, null, pos, contextItems); ITrackingSpan applicableSpan = this.FindApplicableSpan(session); completionSets.Add(new CompletionSet( "Swix Terms", // the non-localized title of the tab "Swix Terms", // the display title of the tab applicableSpan, completions, null)); }
private void AddObjectCompletions(ObjectTree objectTree, StatementNode parentNode, List <Completion> completions) { SpewDiagnostics("----- Adding Objects for {0}", parentNode.Statement); var parentOit = this.GetTypeFromObjectStatement(parentNode); if (parentOit == null) { return; } // Add all of the types that are allowable as children of the parent object. foreach (var oit in this.typeCache.GetTypes().Where(t => t.Type.IsOrDerivesFrom(parentOit.GetChildrenTypes()))) { var type = oit.Type; // Check for [Obsolete] objects... object[] attributes = type.GetCustomAttributes(typeof(ObsoleteAttribute), true); // Some attributes have a enabled/disabled flag, but [Obsolete] is always set if present. if (attributes == null || attributes.Length == 0) { var prefix = this.typeCache.GetPrefixForClrNamespace(type.Namespace); if (!string.IsNullOrEmpty(prefix)) { prefix = string.Concat(prefix, this.prefixSeparator); } string objectName = this.FormatName(type.Name); string replacement = string.Concat(prefix, objectName); completions.Add(new Completion( replacement, string.Concat(replacement, " "), // added space makes continued completions worthwhile... string.Format("{0}\nobject", type.Name), this.GetGlyph(StandardGlyphGroup.GlyphGroupClass, StandardGlyphItem.GlyphItemPublic), "")); } } }
private void AddValueCompletions(ObjectTree objectTree, StatementNode currentNode, Token <ParserTokenType> token, IEnumerable <PackageItem> contextItems, List <Completion> completions) { if (token == null) { return; } SpewDiagnostics("----- Adding Values for {0}", currentNode.Statement); var oit = this.GetTypeFromObjectStatement(currentNode); if (oit == null) { return; } // Determine the type of the property or attached property we need... // Look at the tokens prior to the context token. var tokens = currentNode.Statement.AllTokens; var lookup = tokens.Take(tokens.IndexOf(token)).Reverse().ToList(); var propToken = lookup.FirstOrDefault(t => t.TokenType == ParserTokenType.PropertyName); if (propToken == null) { return; } string propName = propToken.Value; string attachedObjectName = string.Empty; string prefix = string.Empty; var propIndex = lookup.IndexOf(propToken); var attachedObjectToken = lookup.Skip(propIndex).ElementAtOrDefault(2); if (attachedObjectToken != null && attachedObjectToken.TokenType == ParserTokenType.AttachedPropertyObject) { attachedObjectName = attachedObjectToken.Value; var prefixToken = lookup.Skip(propIndex).ElementAtOrDefault(4); if (prefixToken != null && prefixToken.TokenType == ParserTokenType.NamespacePrefix) { prefix = prefixToken.Value; } } Type type = null; if (string.IsNullOrEmpty(attachedObjectName)) { // normal property... PropertyInstanceType prop; if (oit.TryGetProperty(propName, out prop)) { type = prop.ValueType; } } else { // attached property... string clrNamespace; AttachedPropertyObjectType attachedObj; AttachedPropertySetterType setter; if (this.typeCache.TryGetNamespaceByPrefix(prefix, out clrNamespace) && this.typeCache.TryGetAttachedPropertyType(clrNamespace, attachedObjectName, out attachedObj) && attachedObj.TryGetPropertySetter(propName, out setter)) { type = setter.ValueType; } } if (type == null) { return; } if (type == typeof(string)) { // We don't show any completions for strings... we can't! } else if (type.IsValueType) { if (type.IsEnum) { foreach (string name in type.GetEnumNames()) { string valueName = this.FormatName(name); completions.Add(new Completion( valueName, valueName, string.Format("{0}\n{1} enumeration value", name, type.Name), this.GetGlyph(StandardGlyphGroup.GlyphGroupValueType, StandardGlyphItem.GlyphItemPublic), "")); } } else { // simple type... if (type == typeof(bool)) { completions.Add(new Completion("true", "true", null, this.GetGlyph(StandardGlyphGroup.GlyphGroupValueType, StandardGlyphItem.GlyphItemPublic), "")); completions.Add(new Completion("false", "false", null, this.GetGlyph(StandardGlyphGroup.GlyphGroupValueType, StandardGlyphItem.GlyphItemPublic), "")); } else { // ?? non-class value types / strings?? // We have to have more than one placeholder, or else VS will auto-fill for us! string info = string.Format("TODO: Determine values for {0}", type.FullName); completions.Add(new Completion("fake 1", "???", info, this.GetGlyph(StandardGlyphGroup.GlyphGroupValueType, StandardGlyphItem.GlyphItemPublic), "")); completions.Add(new Completion("fake 2", "???", info, this.GetGlyph(StandardGlyphGroup.GlyphGroupValueType, StandardGlyphItem.GlyphItemPublic), "")); } } } else { // Handle other classes/interfaces... // Find all the context items that could fulfill this type... List <string> usedValues = new List <string>(); foreach (PackageItem item in contextItems) { var itemType = item.GetType(); if (itemType.IsOrDerivesFrom(type)) { // By convention, we know that the first entry is the shortest possible reference, // and the last one is the longest/most-explicit. List <string> referenceNames = item.GetReferenceNames().ToList(); // While editing, it's possible to end up with incomplete items that have no names // at all. In that case, just skip them! if (referenceNames.Count == 0) { continue; } string shortest = referenceNames[0]; string longest = referenceNames[referenceNames.Count - 1]; string info = string.Format( "Reference to {0} object\nshortest reference: {1}\nlongest reference: {2}", this.FormatName(itemType.Name), shortest, longest); foreach (string referenceName in referenceNames) { if (!usedValues.Contains(referenceName)) { usedValues.Add(referenceName); // Add quotes if there are any non-token characters in the value. string insertionText = referenceName; if (!referenceName.All(c => CompletionSource.IsTokenTermCharacter(c) && c != '"')) { insertionText = string.Concat('"', referenceName, '"'); } completions.Add(new Completion( referenceName, insertionText, info, this.GetGlyph(StandardGlyphGroup.GlyphGroupValueType, StandardGlyphItem.GlyphItemPublic), "")); } } } } } }
private void AddMemberCompletions(ObjectTree objectTree, StatementNode currentNode, string currentText, List <Completion> completions) { SpewDiagnostics("----- Adding Members for {0}", currentNode.Statement); var oit = this.GetTypeFromObjectStatement(currentNode); if (oit == null) { return; } // We want to add all the members that are publically writable, and aren't already used // on the object *unless* it's the current token context. var propsUsed = currentNode.Statement.Tokens.Where(t => t.TokenType == ParserTokenType.PropertyName); var propsToExclude = propsUsed.Select(t => t.Value).Where(s => !string.Equals(s, currentText)).ToList(); foreach (var prop in oit.Properties) { // Check for [Obsolete] members... object[] attributes = prop.PropertyInfo.GetCustomAttributes(typeof(ObsoleteAttribute), true); // Some attributes have a enabled/disabled flag, but [Obsolete] is always set if present. if (attributes == null || attributes.Length == 0) { string propName = this.FormatName(prop.Name); if (propsToExclude.Contains(propName)) { continue; } completions.Add(new Completion( propName, string.Concat(propName, "="), string.Format("{0}.{1}\nattribute", oit.Type.Name, prop.Name), this.GetGlyph(StandardGlyphGroup.GlyphGroupField, StandardGlyphItem.GlyphItemPublic), "")); } } // In addition to any properties on the object itself, we also need to list any // attached properties that work for this object. foreach (var prop in this.typeCache.GetAttachedProperties()) { var prefix = this.typeCache.GetPrefixForClrNamespace(prop.Type.Namespace); if (!string.IsNullOrEmpty(prefix)) { prefix = string.Concat(prefix, this.prefixSeparator); } var attachedObjectName = this.FormatName(prop.Type.Name); foreach (var setter in prop.Setters) { // TODO: Check for [Obsolete] members... ////object[] attributes = setter.GetCustomAttributes(typeof(ObsoleteAttribute), true); ////// Some attributes have a enabled/disabled flag, but [Obsolete] is always set if present. ////if (attributes == null || attributes.Length == 0) ////{ if (oit.Type.IsOrDerivesFrom(setter.ObjectType)) { var propName = this.FormatName(setter.Name); var replacement = string.Concat(prefix, attachedObjectName, ".", propName); completions.Add(new Completion( replacement, string.Concat(replacement, "="), string.Format("{0}.{1}\nextension attribute\nfrom \"{2}\"", prop.Type.Name, setter.Name, prop.Type.Namespace), this.GetGlyph(StandardGlyphGroup.GlyphExtensionMethod, StandardGlyphItem.GlyphItemPublic), "")); } ////} } } }
private List <Completion> CreateCompletions(ObjectTree objectTree, StatementNode currentNode, StatementNode parentNode, string filterText, Position position, IEnumerable <PackageItem> contextItems) { List <Completion> completions = new List <Completion>(); // Figure out what kind of completion we need... bool addObjectCompletions = false; bool addMemberCompletions = false; bool addValueCompletions = false; string currentText = string.Empty; Token <ParserTokenType> token = null; if (currentNode == null) { addObjectCompletions = true; } else { // Determine the completions based on the node! token = currentNode.Statement.AllTokens.FirstOrDefault(t => t.Range.End >= position); SpewDiagnostics("----- Finding completions for statement: [{0}]", currentNode.Statement); SpewDiagnostics("----- Position: {0}", position); SpewDiagnostics("----- Token: {0}", token); var tokenType = ParserTokenType.Unknown; if (token == null) { // Need to find a surrogate token? } else { if (token.Range.End < position) { // we're actually *after* that token, so figure out what that means... } tokenType = token.TokenType; currentText = token.Value; } switch (tokenType) { ////case ParserTokenType.Unknown: //// break; case ParserTokenType.Whitespace: // Depends on what the previous/next tokens are... addMemberCompletions = true; break; ////case ParserTokenType.Comment: //// break; case ParserTokenType.UseKeyword: break; case ParserTokenType.Object: addObjectCompletions = true; break; case ParserTokenType.PropertyName: addMemberCompletions = true; break; case ParserTokenType.Equals: addValueCompletions = true; break; case ParserTokenType.PropertyValue: addValueCompletions = true; break; case ParserTokenType.DoubleQuote: addValueCompletions = true; break; case ParserTokenType.SingleQuote: addValueCompletions = true; break; case ParserTokenType.Period: break; case ParserTokenType.NamespacePrefix: break; case ParserTokenType.NamespacePrefixDeclaration: break; case ParserTokenType.NamespaceDeclaration: break; case ParserTokenType.AttachedPropertyObject: break; default: break; } } if (addObjectCompletions) { this.AddObjectCompletions(objectTree, parentNode, completions); } if (addMemberCompletions) { this.AddMemberCompletions(objectTree, currentNode, currentText, completions); } if (addValueCompletions) { this.AddValueCompletions(objectTree, currentNode, token, contextItems, completions); } // sort the completions... completions.Sort((a, b) => { return(a.DisplayText.CompareTo(b.DisplayText)); }); return(completions); }