/// 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() 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