/// <summary> /// Return an enumerable of suitable edits to add open directives for all given namespaces for which no open directive already exists. /// Returns an edit for opening a given namespace even if an alias is already defined for that namespace. /// Returns an empty enumerable if suitable edits could not be determined. /// </summary> private static IEnumerable <TextEdit> OpenDirectiveSuggestions(this FileContentManager file, int lineNr, IEnumerable <string> namespaces) { // determine the first fragment in the containing namespace var nsElements = file?.NamespaceDeclarationTokens() .TakeWhile(t => t.Line <= lineNr).LastOrDefault() // going by line here is fine - inaccuracies if someone has multiple namespace and callable declarations on the same line seem acceptable... ?.GetChildren(deep: false); var firstInNs = nsElements?.FirstOrDefault()?.GetFragment(); if (file is null || nsElements is null || firstInNs?.Kind == null) { return(Enumerable.Empty <TextEdit>()); } // determine what open directives already exist var insertOpenDirAt = firstInNs.Range.Start; var openDirs = nsElements.SelectNotNull(t => t.GetFragment().Kind?.OpenedNamespace()) .TakeWhile(opened => opened.IsValue) .Select(opened => ( opened.Item.Item1.Symbol.AsDeclarationName(null), opened.Item.Item2.IsValue ? opened.Item.Item2.Item.Symbol.AsDeclarationName("") : null)) .Where(opened => opened.Item1 != null) .GroupBy(opened => opened.Item1, opened => opened.Item2) // in case there are duplicate open directives... .ToImmutableDictionary(opened => opened.Key, opened => opened.First()); // range and whitespace info for inserting open directives var additionalLinesAfterOpenDir = firstInNs.Kind.OpenedNamespace().IsNull ? $"{Environment.NewLine}{Environment.NewLine}" : ""; var indentationAfterOpenDir = file.GetLine(insertOpenDirAt.Line).Text.Substring(0, insertOpenDirAt.Column); var whitespaceAfterOpenDir = $"{Environment.NewLine}{additionalLinesAfterOpenDir}{(string.IsNullOrWhiteSpace(indentationAfterOpenDir) ? indentationAfterOpenDir : " ")}"; // construct a suitable edit return(namespaces.Distinct().Where(ns => !openDirs.Contains(ns, null)).Select(suggestedNS => // filter all namespaces that are already open { var directive = $"{Keywords.importDirectiveHeader.id} {suggestedNS}"; return new TextEdit { Range = new Lsp.Range { Start = insertOpenDirAt.ToLsp(), End = insertOpenDirAt.ToLsp() }, NewText = $"{directive};{whitespaceAfterOpenDir}" }; })); }