Re: [Nitra] Autocompletion
От: VladD2 Российская Империя www.nemerle.org
Дата: 11.06.15 19:13
Оценка:
Здравствуйте, 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
}


Правда, пришлось кое что поменять и отрефакторить. Но, все равно, это демонстрирует гибкость выбранного подхода.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.