Здравствуйте, VladD2, Вы писали:
VD>Прикрутили к Nitra автодополенине на базе символов.
Самое забавное, что в итоге, весь код поддержки автодополнения вылился в пару простых функций.
MakeComletionList в Scope:
public MakeComletionList(prefix : string) : Seq[Symbol2]
{
match (this)
{
| Table as s => if (string.IsNullOrEmpty(prefix)) s.Symbols else s.Symbols.Where(s => prefix.IsPrefixOf(s.Name.Text))
| Union as s => s.Scopes.SelectMany(s => s.MakeComletionList(prefix))
| Filter as s => s.Scope.MakeComletionList(prefix).Where(s.Predicate)
| Nil => Enumerable.Empty()
| Hide as s =>
def hiding = s.Scope.MakeComletionList(prefix).ToDictionary(s => s.Id);
def result = List(hiding.Values);
foreach (symbol in s.Hidden.MakeComletionList(prefix))
when (!hiding.ContainsKey(symbol.Id))
result.Add(symbol);
result
}
}
И статической CompleteWord:
public CompleteWord(pos : int, parseResult : IParseResult, astRoot : IAst, replacementSpan : out NSpan) : Seq[object]
{
def source = parseResult.SourceSnapshot;
def completionList = List.[object]();
// Получаем информацию о пробельных ветках дерева разбора в области курсора
def carretSpan = NSpan(pos, pos);
def spans = HashSet();
def spasesWalker = VoidRuleWalker(carretSpan);
spasesWalker.Walk(parseResult, spans);
foreach (spanInfo when spanInfo.Span.Contains(carretSpan) && spanInfo.SpanClass != SpanClass.Default in spans)
return []; // выходим, если комплит идет в комментарии или грязи
// вычисляем начало и конец пробельных правил
mutable spacesStart = pos;
mutable spacesEnd = pos;
when (spans.Count != 0)
{
spacesStart = spans.Min(s => s.Span.StartPos);
spacesEnd = spans.Max(s => s.Span.EndPos);
}
// находим цепочку ветвей AST ведущую к позиции комплита
def visitor = FindNodeAstVisitor(NSpan(spacesStart, spacesEnd));
astRoot.Accept(visitor);
// Вычисляем префикс комплита (если есть). Он будет использоваться для фильтрации списка и будет заменен выбранным словом
def firstAstNode = visitor.Stack.Peek();
def span = firstAstNode.Span;
def start = span.StartPos;
def prefix = if (span.EndPos == pos) source.Text.Substring(span.StartPos, span.Length) else "";
replacementSpan = span;
// Находим первую ветку AST в которой есть зависимое свойство Scope и вычисляем по этому Scope список автодополнения
// TODO: заменить рефлексию на интерфейсы
foreach (curr in visitor.Stack)
{
def scopeProp = curr.GetType().GetProperty("Scope");
when (scopeProp != null && late (curr.IsScopeEvaluated) :> bool)
{
completionList.AddRange(late (curr.Scope.MakeComletionList(prefix)) :> Seq[Symbol2]);
break;
}
}
// Комплишен по литералам (в соответствии с грамматикой).
def text = source.Text.Substring(0, start) + '\xFFFF';
def parseSession = parseResult.ParseSession;
try
{
// при задании этих свойств, парсер вернет список литералов в исключении LiteralCompletionException
parseSession.CompletionStartPos = start;
parseSession.CompletionPrefix = prefix;
parseSession.OnRecovery = ParseSession.SmartRecovery;
_ = parseSession.Parse(text);
}
catch
{
| ex is LiteralCompletionException => completionList.AddRange(ex.Literals);
| _ => ()
}
finally
{
parseSession.CompletionStartPos = -1;
parseSession.CompletionPrefix = null;
}
completionList
}
Правда, пришлось кое что поменять и отрефакторить. Но, все равно, это демонстрирует гибкость выбранного подхода.