diff --git a/effekt/jvm/src/main/scala/effekt/Server.scala b/effekt/jvm/src/main/scala/effekt/Server.scala index fafeda280..40f4aff9e 100644 --- a/effekt/jvm/src/main/scala/effekt/Server.scala +++ b/effekt/jvm/src/main/scala/effekt/Server.scala @@ -10,7 +10,8 @@ import effekt.util.messages.EffektError import kiama.util.{ Filenames, Position, Services, Source } import kiama.output.PrettyPrinterTypes.Document -import org.eclipse.lsp4j.{ Diagnostic, DocumentSymbol, SymbolKind, ExecuteCommandParams } +import scala.collection.mutable.HashMap +import org.eclipse.lsp4j.{ Diagnostic, DocumentSymbol, CompletionItem, CompletionItemKind, InsertTextFormat, SymbolKind, ExecuteCommandParams } /** * effekt.Intelligence <--- gathers information -- LSPServer --- provides LSP interface ---> kiama.Server @@ -132,7 +133,6 @@ trait LSPServer extends kiama.util.Server[Tree, EffektConfig, EffektError] with } override def getSymbols(source: Source): Option[Vector[DocumentSymbol]] = - context.compiler.runFrontend(source)(using context) val documentSymbols = for { @@ -152,6 +152,25 @@ trait LSPServer extends kiama.util.Server[Tree, EffektConfig, EffektError] with allRefs = if (includeDecl) tree :: refs else refs } yield allRefs.toVector + override def getCompletion(position: Position): Option[Vector[CompletionItem]] = for { + suggestions <- getCompletionsAt(position)(context) + items = suggestions.map { + case Left(sym) => + val item = CompletionItem(sym.name.name) + item.setKind(getCompletionKind(sym)) + getInfoOf(sym)(context).map { detail => + item.setDetail(detail.header) + item.setDocumentation(detail.description.getOrElse("")) + } + item + case Right(name, detail) => + val item = CompletionItem(name) + item.setKind(CompletionItemKind.Keyword) + item.setDetail(detail) + item + } + } yield items + // settings might be null override def setSettings(settings: Object): Unit = { import com.google.gson.JsonObject @@ -177,6 +196,25 @@ trait LSPServer extends kiama.util.Server[Tree, EffektConfig, EffektError] with None } + // for custom icons and intelligence of autocomplete + def getCompletionKind(sym: Symbol): CompletionItemKind = + sym match { + case _: Module => + CompletionItemKind.Module + case _: Interface | _: ExternInterface => + CompletionItemKind.Interface + case _: DataType | _: ExternType | _: TypeAlias => + CompletionItemKind.Enum + case _: Callable => + CompletionItemKind.Method + case _: Param | _: ValBinder | _: VarBinder => + CompletionItemKind.Variable + case _: TypeSymbol => + CompletionItemKind.TypeParameter + case _ => + CompletionItemKind.Text + } + override def getCodeActions(position: Position): Option[Vector[TreeAction]] = Some(for { trees <- getTreesAt(position)(context).toVector diff --git a/effekt/shared/src/main/scala/effekt/Intelligence.scala b/effekt/shared/src/main/scala/effekt/Intelligence.scala index 813906b27..0a11fa6af 100644 --- a/effekt/shared/src/main/scala/effekt/Intelligence.scala +++ b/effekt/shared/src/main/scala/effekt/Intelligence.scala @@ -2,7 +2,7 @@ package effekt import effekt.context.{ Annotations, Context } import effekt.source.{ FunDef, ModuleDecl, Tree } -import kiama.util.{ Position, Source } +import kiama.util.{ Position, Positions, Source } trait Intelligence { @@ -80,6 +80,39 @@ trait Intelligence { case u => C.definitionTreeOption(u) } + type CompletionSuggestion = Either[Symbol, (String, String)] + + def getCompletableItems(position: Position, prefix: String)(implicit C: Context): Option[Vector[CompletionSuggestion]] = { + def isPrefix(s: String) = s.startsWith(prefix) // TODO: add more filters (e.g. Levenshtein distance?) + def filter(it: Iterable[String]) = it.filter(isPrefix) + + val keywords = filter(EffektLexers(new Positions).keywordStrings).map { keyword => Right(keyword, "Keyword") } + val builtinTypes = filter(builtins.rootTypes.keys).map { key => Left(builtins.rootTypes.get(key).get) } + val builtinTerms = filter(builtins.rootTerms.keys).map { key => Left(builtins.rootTerms.get(key).get) } + val builtinCaptures = filter(builtins.rootCaptures.keys).map { key => Left(builtins.rootCaptures.get(key).get) } + + // TODO: only return symbols within scope of current position and include library symbols + // "dumb" function for now to just get all symbols in AST recursively + def getAllSymbols(product: Product): Iterator[String] = product.productIterator.flatMap { + case p: Product => getAllSymbols(p) + case s: String => List(s) + case _ => List() + }.distinct + + val allSymbols = C.compiler.getAST(position.source) match + case Some(ast) => getAllSymbols(ast).map { s => Right(s, "Symbol in scope") } + case None => List() + + Some((keywords ++ builtinTypes ++ builtinTerms ++ builtinCaptures ++ allSymbols).toVector) + } + + def getCompletionsAt(position: Position)(implicit C: Context): Option[Vector[CompletionSuggestion]] = for { + line <- position.source.optLineContents(position.line) + beforeCaret = line.take(position.column - 1) + word = beforeCaret.drop(beforeCaret.lastIndexOf(" ") + 1) + syms <- getCompletableItems(position, word) + } yield syms + // For now, only show the first call target def resolveCallTarget(sym: Symbol): Symbol = sym match { case t: CallTarget => t.symbols.flatten.headOption.getOrElse(sym)