lsp-bjolang/Completion.fs

111 lines
4.3 KiB
Forth
Raw Normal View History

/// What can be written at a position: the top-level names of every module
/// the file sees, nearest first, the compiler's builtins, and the special
/// forms. The client filters them as the word is typed; the doc of one is
/// sent when the client asks for it, through `completionItem/resolve`.
module BjolangLsp.Completion
open System
open Bjolang
open BjolangLsp.Symbols
/// The heads the parsers recognise themselves, which no module defines. A
/// copy of the REPL's list in `Repl.fs`, which is private to it.
let private specialForms =
[ "->"; "and"; "begin"; "bjo"; "bjoroutine"; "by-colour"; "case"; "cast"; "def"; "def*"; "def/hash-extend"
"def/macro"; "def/mutable"; "def/pattern"; "def/trait"; "defbjo"; "defbjouble"; "defun"; "dyn"
"export"; "fun"; "if"; "impl"; "impl/extern"; "import"; "import/class"; "import/extern"; "include"
"let"; "let/mono"; "letrec"; "loop"; "match"; "module"; "not"; "or"; "parameterize"; "re-export"
"record"; "record-ref"; "record-set"; "record-set!"; "seq"; "seql"; "set!"; "spawn"; "spawn-evt"
"spawn/daemon"; "spawn/detached"; "struct"; "struct-ref"; "struct-set"; "struct-set!"; "task->event"
"try"; "type"; "type-rec"; "unless"; "when"; "with-open"; "with-return"; "yield"; "yield-from" ]
/// LSP's `CompletionItemKind`, the members of it used here.
type ItemKind =
| Method = 2
| Function = 3
| Field = 5
| Variable = 6
| Class = 7
| Interface = 8
| Keyword = 14
| EnumMember = 20
| Struct = 22
type Item =
{ Label: string
Kind: ItemKind
/// Where it comes from: a module's file name, `builtin`, `special form`.
From: string }
let private itemKind (k: Kind) =
match k with
| Kind.Function -> ItemKind.Function
| Kind.Variable -> ItemKind.Variable
| Kind.Class -> ItemKind.Class
| Kind.Interface -> ItemKind.Interface
| Kind.Method -> ItemKind.Method
| Kind.Field -> ItemKind.Field
| Kind.EnumMember -> ItemKind.EnumMember
| Kind.Struct -> ItemKind.Struct
| _ -> ItemKind.Variable
/// The word the cursor ends, as the start column of it on its line. A word
/// is what the lexer reads as one symbol, so `list-` and `string->` are
/// words and the item replaces all of it.
let wordStart (line: string) (column: int) : int =
let mutable i = min column line.Length
while i > 0 && Lexer.isSymbolChar line[i - 1] do
i <- i - 1
i
/// Whether `column` of `line` is in a comment or a string, where nothing is
/// offered. Read off the line alone, so a string that began on an earlier
/// line is not seen.
let inText (line: string) (column: int) : bool =
let mutable inString = false
let mutable inComment = false
let mutable i = 0
while i < min column line.Length && not inComment do
match line[i] with
| '\\' when inString -> i <- i + 1
| '#' when not inString && i + 1 < line.Length && line[i + 1] = '\\' -> i <- i + 2
| '"' -> inString <- not inString
| ';' when not inString -> inComment <- true
| _ -> ()
i <- i + 1
inString || inComment
/// Everything that can be written in the file of `text`, each name once, the
/// nearest definition of it winning.
let items (path: string) (text: string) : Item list =
let seen = Collections.Generic.HashSet<string>()
let fromModules =
[ for m in visible path text do
let from = IO.Path.GetFileName m.File
for s in m.Symbols do
// An `impl` is not a name, and a field is read with
// `record-ref` rather than written on its own.
if s.Kind <> Kind.Module && s.Kind <> Kind.Field && seen.Add s.Name then
yield { Label = s.Name; Kind = itemKind s.Kind; From = from } ]
let builtins =
[ for name in Prelude.builtinNames do
if seen.Add name then
// `Cons`, `None`, `Err`: the builtin constructors.
let kind = if Char.IsUpper name[0] then ItemKind.EnumMember else ItemKind.Function
yield { Label = name; Kind = kind; From = "builtin" } ]
let special =
[ for name in specialForms do
if seen.Add name then
yield { Label = name; Kind = ItemKind.Keyword; From = "special form" } ]
fromModules @ builtins @ special