protected override async Task <CompletionDataList> GetAttributeCompletions(
            IAttributedXObject attributedOb,
            Dictionary <string, string> existingAtts, CancellationToken token)
        {
            var list = (await base.GetAttributeCompletions(attributedOb, existingAtts, token)) ?? new CompletionDataList();

            if (attributedOb is XElement)
            {
                if (!existingAtts.ContainsKey("runat"))
                {
                    list.Add("runat=\"server\"", "md-literal",
                             GettextCatalog.GetString("Required for ASP.NET controls.\n") +
                             GettextCatalog.GetString(
                                 "Indicates that this tag should be able to be\n" +
                                 "manipulated programmatically on the web server."));
                }

                if (!existingAtts.ContainsKey("id"))
                {
                    list.Add("id", "md-literal",
                             GettextCatalog.GetString("Unique identifier.\n") +
                             GettextCatalog.GetString(
                                 "An identifier that is unique within the document.\n" +
                                 "If the tag is a server control, this will be used \n" +
                                 "for the corresponding variable name in the CodeBehind."));
                }

                existingAtts["ID"] = "";
                if (attributedOb.Name.HasPrefix)
                {
                    await refman.CreateCompilation(token);

                    AddAspAttributeCompletionData(list, attributedOb.Name, existingAtts);
                }
            }
            else if (attributedOb is WebFormsDirective)
            {
                return(WebFormsDirectiveCompletion.GetAttributes(project, attributedOb.Name.FullName, existingAtts));
            }
            return(list.Count > 0? list : null);
        }
        protected override async Task <CompletionDataList> GetAttributeValueCompletions(IAttributedXObject ob, XAttribute att, CancellationToken token)
        {
            var list = (await base.GetAttributeValueCompletions(ob, att, token)) ?? new CompletionDataList();

            if (ob is XElement)
            {
                if (ob.Name.HasPrefix)
                {
                    string id = ob.GetId();
                    if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(id.Trim()))
                    {
                        id = null;
                    }
                    AddAspAttributeValueCompletionData(list, ob.Name, att.Name, id);
                }
            }
            else if (ob is WebFormsDirective)
            {
                return(WebFormsDirectiveCompletion.GetAttributeValues(project, DocumentContext.Name, ob.Name.FullName, att.Name.FullName));
            }
            return(list.Count > 0? list : null);
        }
        protected override Task <ICompletionDataList> HandleCodeCompletion(
            CodeCompletionContext completionContext, bool forced, CancellationToken token)
        {
            // completionChar may be a space even if the current char isn't, when ctrl-space is fired t
            char currentChar = completionContext.TriggerOffset < 1? ' ' : Editor.GetCharAt(completionContext.TriggerOffset - 1);

            //char previousChar = completionContext.TriggerOffset < 2? ' ' : buf.GetCharAt (completionContext.TriggerOffset - 2);

            //directive names
            if (Tracker.Engine.CurrentState is WebFormsDirectiveState)
            {
                var directive = Tracker.Engine.Nodes.Peek() as WebFormsDirective;
                if (HasDoc && directive != null && directive.Region.BeginLine == completionContext.TriggerLine &&
                    directive.Region.BeginColumn + 3 == completionContext.TriggerLineOffset)
                {
                    return(Task.FromResult((ICompletionDataList)WebFormsDirectiveCompletion.GetDirectives(aspDoc.Type)));
                }
                return(null);
            }
            if (Tracker.Engine.CurrentState is XmlNameState && Tracker.Engine.CurrentState.Parent is WebFormsDirectiveState)
            {
                var directive = Tracker.Engine.Nodes.Peek() as WebFormsDirective;
                if (HasDoc && directive != null && directive.Region.BeginLine == completionContext.TriggerLine &&
                    directive.Region.BeginColumn + 4 == completionContext.TriggerLineOffset && char.IsLetter(currentChar))
                {
                    completionContext.TriggerWordLength = 1;
                    completionContext.TriggerOffset    -= 1;
                    return(Task.FromResult((ICompletionDataList)WebFormsDirectiveCompletion.GetDirectives(aspDoc.Type)));
                }
                return(null);
            }

            bool isAspExprState = Tracker.Engine.CurrentState is WebFormsExpressionState;

            //non-xml tag completion
            if (currentChar == '<' && !(isAspExprState || Tracker.Engine.CurrentState is XmlRootState))
            {
                var list = new CompletionDataList();
                AddAspBeginExpressions(list);
                return(Task.FromResult((ICompletionDataList)list));
            }

            if (!HasDoc || aspDoc.Info.DocType == null)
            {
                //FIXME: get doctype from master page
                DocType = null;
            }
            else
            {
                DocType = new XDocType(DocumentLocation.Empty);
                var matches = DocTypeRegex.Match(aspDoc.Info.DocType);
                DocType.PublicFpi = matches.Groups["fpi"].Value;
                DocType.Uri       = matches.Groups["uri"].Value;
            }

            if (Tracker.Engine.CurrentState is HtmlScriptBodyState)
            {
                var el = Tracker.Engine.Nodes.Peek() as XElement;
                if (el != null)
                {
                    if (el.IsRunatServer())
                    {
                        if (documentBuilder != null)
                        {
                            // TODO: C# completion
                        }
                    }

                    /*
                     * else {
                     *      att = GetHtmlAtt (el, "language");
                     *      if (att == null || "javascript".Equals (att.Value, StringComparison.OrdinalIgnoreCase)) {
                     *          att = GetHtmlAtt (el, "type");
                     *              if (att == null || "text/javascript".Equals (att.Value, StringComparison.OrdinalIgnoreCase)) {
                     *                      // TODO: JS completion
                     *              }
                     *      }
                     * }*/
                }
            }

            return(base.HandleCodeCompletion(completionContext, forced, token));
        }