111 lines
4.3 KiB
Forth
111 lines
4.3 KiB
Forth
|
|
/// 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
|