protected virtual async Task <ICompletionDataList> HandleCodeCompletion( CodeCompletionContext completionContext, bool forced, CancellationToken token) { var buf = this.Editor; // completionChar may be a space even if the current char isn't, when ctrl-space is fired t var currentLocation = new DocumentLocation(completionContext.TriggerLine, completionContext.TriggerLineOffset); char currentChar = completionContext.TriggerOffset < 1? ' ' : buf.GetCharAt(completionContext.TriggerOffset - 1); char previousChar = completionContext.TriggerOffset < 2? ' ' : buf.GetCharAt(completionContext.TriggerOffset - 2); LoggingService.LogDebug("Attempting completion for state '{0}'x{1}, previousChar='{2}'," + " currentChar='{3}', forced='{4}'", tracker.Engine.CurrentState, tracker.Engine.CurrentStateLength, previousChar, currentChar, forced); //closing tag completion if (tracker.Engine.CurrentState is XmlRootState && currentChar == '>') { return(ClosingTagCompletion(buf, currentLocation)); } // Auto insert '>' when '/' is typed inside tag state (for quick tag closing) //FIXME: avoid doing this when the next non-whitespace char is ">" or ignore the next ">" typed if (XmlEditorOptions.AutoInsertFragments && tracker.Engine.CurrentState is XmlTagState && currentChar == '/') { buf.InsertAtCaret(">"); return(null); } //entity completion if (currentChar == '&' && (tracker.Engine.CurrentState is XmlRootState || tracker.Engine.CurrentState is XmlAttributeValueState)) { var list = new CompletionDataList(); //TODO: need to tweak semicolon insertion list.Add(new BaseXmlCompletionData("apos", "'")); list.Add(new BaseXmlCompletionData("quot", "\"")); list.Add(new BaseXmlCompletionData("lt", "<")); list.Add(new BaseXmlCompletionData("gt", ">")); list.Add(new BaseXmlCompletionData("amp", "&")); //not sure about these "completions". they're more like //shortcuts than completions but they're pretty useful list.Add(new BaseXmlCompletionData("'") { CompletionText = "apos;" }); list.Add(new BaseXmlCompletionData("\"") { CompletionText = "quot;" }); list.Add(new BaseXmlCompletionData("<") { CompletionText = "lt;" }); list.Add(new BaseXmlCompletionData(">") { CompletionText = "gt;" }); list.Add(new BaseXmlCompletionData("&") { CompletionText = "amp;" }); var ecList = await GetEntityCompletions(token); list.AddRange(ecList); return(list); } //doctype completion if (tracker.Engine.CurrentState is XmlDocTypeState) { if (tracker.Engine.CurrentStateLength == 1) { CompletionDataList list = await GetDocTypeCompletions(token); if (list != null && list.Count > 0) { return(list); } } return(null); } //attribute value completion //determine whether to trigger completion within attribute values quotes if ((Tracker.Engine.CurrentState is XmlAttributeValueState) //trigger on the opening quote && ((Tracker.Engine.CurrentStateLength == 1 && (currentChar == '\'' || currentChar == '"')) //or trigger on first letter of value, if unforced || (forced || Tracker.Engine.CurrentStateLength == 2))) { var att = (XAttribute)Tracker.Engine.Nodes.Peek(); if (att.IsNamed) { var attributedOb = Tracker.Engine.Nodes.Peek(1) as IAttributedXObject; if (attributedOb == null) { return(null); } //if triggered by first letter of value or forced, grab those letters var result = await GetAttributeValueCompletions(attributedOb, att, token); if (result != null) { result.TriggerWordLength = Tracker.Engine.CurrentStateLength - 1; return(result); } return(null); } } //attribute name completion if ((forced && Tracker.Engine.Nodes.Peek() is IAttributedXObject && !tracker.Engine.Nodes.Peek().IsEnded) || ((Tracker.Engine.CurrentState is XmlNameState && Tracker.Engine.CurrentState.Parent is XmlAttributeState) || Tracker.Engine.CurrentState is XmlTagState)) { IAttributedXObject attributedOb = (Tracker.Engine.Nodes.Peek() as IAttributedXObject) ?? Tracker.Engine.Nodes.Peek(1) as IAttributedXObject; if (attributedOb == null || !attributedOb.Name.IsValid) { return(null); } var currentIsNameStart = XmlNameState.IsValidNameStart(currentChar); var currentIsWhiteSpace = char.IsWhiteSpace(currentChar); var previousIsWhiteSpace = char.IsWhiteSpace(previousChar); bool shouldTriggerAttributeCompletion = forced || (currentIsNameStart && previousIsWhiteSpace) || currentIsWhiteSpace; if (!shouldTriggerAttributeCompletion) { return(null); } var existingAtts = new Dictionary <string, string> (StringComparer.OrdinalIgnoreCase); foreach (XAttribute att in attributedOb.Attributes) { existingAtts [att.Name.FullName] = att.Value ?? string.Empty; } var result = await GetAttributeCompletions(attributedOb, existingAtts, token); if (result != null) { if (!forced && currentIsNameStart) { result.TriggerWordLength = 1; } result.AutoSelect = !currentIsWhiteSpace; result.AddKeyHandler(new AttributeKeyHandler()); return(result); } } //element completion if (currentChar == '<' && tracker.Engine.CurrentState is XmlRootState || (tracker.Engine.CurrentState is XmlNameState && forced)) { var list = await GetElementCompletions(token); if (completionContext.TriggerLine == 1 && completionContext.TriggerOffset == 1) { var encoding = Editor.Encoding.WebName; list.Add(new BaseXmlCompletionData($"?xml version=\"1.0\" encoding=\"{encoding}\" ?>")); } AddCloseTag(list, Tracker.Engine.Nodes); return(list.Count > 0 ? list : null); } if (forced && Tracker.Engine.CurrentState is XmlRootState) { var list = new CompletionDataList(); MonoDevelop.Ide.CodeTemplates.CodeTemplateService.AddCompletionDataForFileName(DocumentContext.Name, list); return(list.Count > 0? list : null); } return(null); }