# lsp/nav: from a name to the item it names. A name `Alias.rest`, where Alias # is an import of the document, is `rest` in that file; any other name is an # item of the document, or else of Base. Open documents are read from the # store (what the editor shows), everything else through the files service. import Base import ../../syntax/outline.bend as Outline import ../../syntax/word.bend as Word import ../../syntax/bind.bend as Bind import ./files/service.bend as F import ./docs.bend as Docs import ./path.bend as Path import ./proto.bend as Proto # what a name names: an item and the document it lives in, or a binder of # the document itself (a parameter, a local, a field, ..) type Found is Data: Found{uri: String, item: Outline.Item} Here{uri: String, bind: Bind.Bind} # an item, when there is one, in a document def found(m: Maybe<&2, Outline.Item>, uri: String) -> Maybe<&2, Found>: match m: case None{}: None{} case Some{item}: Some{Found{uri, item}} def load.miss(~fs: F.Files, open: Maybe<&2, String>, path: String) -> IO(String): match open: case Some{text}: IO.pure(String, text) case None{}: do IO: r : Maybe<&2, String> <- F.read(fs, path) return Maybe.default(&2, String, r, "") # a document's text: the open one, or the file's; "" when there is neither def load(~fs: F.Files, docs: List<&2, Docs.Doc>, uri: String, path: String) -> IO(String): load.miss(~fs, Docs.get(docs, uri), path) # the item of a name in a file def in_file(~fs: F.Files, docs: List<&2, Docs.Doc>, +path: String, name: String) -> IO(Maybe<&2, Found>): do IO>: text : String <- load(~fs, docs, "file://" ++ path, path) return found(Outline.find(Outline.items(text), name), "file://" ++ path) # the item of a name in Base def in_base(~fs: F.Files, docs: List<&2, Docs.Doc>, name: String) -> IO(Maybe<&2, Found>): do IO>: path : String <- F.base_path(fs) in_file(~fs, docs, path, name) # an item of the document, else of Base def own(~fs: F.Files, m: Maybe<&2, Outline.Item>, docs: List<&2, Docs.Doc>, uri: String, name: String) -> IO(Maybe<&2, Found>): match m: case Some{item}: IO.pure(Maybe<&2, Found>, Some{Found{uri, item}}) case None{}: in_base(~fs, docs, name) # through an import alias when the name has one, else the document's own def via(~fs: F.Files, imp: Maybe<&2, String>, docs: List<&2, Docs.Doc>, +uri: String, +name: String, items: List<&2, Outline.Item>) -> IO(Maybe<&2, Found>): match imp: case Some{rel}: in_file(~fs, docs, Path.join(Path.dir(Proto.path(uri)), rel), Word.rest(name)) case None{}: own(~fs, Outline.find(items, name), docs, uri, name) # a name against a document's items def among(~fs: F.Files, +items: List<&2, Outline.Item>, docs: List<&2, Docs.Doc>, uri: String, +name: String) -> IO(Maybe<&2, Found>): via(~fs, Outline.import_path(items, Word.head(name)), docs, uri, name, items) # the item a name in a document names def resolve(~fs: F.Files, +docs: List<&2, Docs.Doc>, +uri: String, name: String) -> IO(Maybe<&2, Found>): do IO>: text : String <- load(~fs, docs, uri, Proto.path(uri)) among(~fs, Outline.items(text), docs, uri, name) # a binder, when there is one, in a document def here(m: Maybe<&2, Bind.Bind>, uri: String) -> Maybe<&2, Found>: match m: case None{}: None{} case Some{b}: Some{Here{uri, b}} # a binder of the document, or a use of one, is answered from the document; # a use of anything else is looked up as an item; nothing at all falls back # to the word under the cursor (a half-typed name) def at.site(~fs: F.Files, site: Maybe<&2, Bind.Site>, +binds: List<&2, Bind.Bind>, docs: List<&2, Docs.Doc>, uri: String, +name: String) -> IO(Maybe<&2, Found>): match site: case Some{Bind.SBind{b}}: IO.pure(Maybe<&2, Found>, Some{Here{uri, b}}) case Some{Bind.SUse{Bind.Use{n, l, c, Bind.TLocal{tl, tc}}}}: IO.pure(Maybe<&2, Found>, here(Bind.binder(binds, tl, tc), uri)) case Some{Bind.SUse{Bind.Use{n, l, c, tg}}}: resolve(~fs, docs, uri, n) case None{}: resolve(~fs, docs, uri, name) def at.bound(~fs: F.Files, b: Bind.Bound, docs: List<&2, Docs.Doc>, uri: String, name: String, +line: U32, +col: U32) -> IO(Maybe<&2, Found>): Bind.Bound{+binds, uses, scopes} = b at.site(~fs, Bind.at(Bind.Bound{binds, uses, scopes}, line, col), binds, docs, uri, name) def at.in(~fs: F.Files, +text: String, docs: List<&2, Docs.Doc>, uri: String, +line: U32, +col: U32) -> IO(Maybe<&2, Found>): at.bound(~fs, Bind.bound(text), docs, uri, Word.at(text, line, col), line, col) # what the name under a position of a document names def at(~fs: F.Files, +docs: List<&2, Docs.Doc>, +uri: String, line: U32, col: U32) -> IO(Maybe<&2, Found>): do IO>: text : String <- load(~fs, docs, uri, Proto.path(uri)) at.in(~fs, text, docs, uri, line, col) # references and rename # --------------------- # the positions a name is at in its document, and whether the name is the # document's own (a binder, or an item of it) -- only those can be renamed type Sites is Data: Sites{mine: Bool, at: List<&2, Bind.Pos>} def sites.of(site: Maybe<&2, Bind.Site>, +b: Bind.Bound) -> Sites: match site: case None{}: Sites{False{}, []} case Some{Bind.SBind{Bind.Bind{name, l, c, Bind.KItem{}, note}}}: Sites{True{}, Bind.item_sites(b, name)} case Some{Bind.SBind{Bind.Bind{name, l, c, Bind.KCtor{}, note}}}: Sites{True{}, Bind.item_sites(b, name)} case Some{Bind.SBind{Bind.Bind{name, l, c, k, note}}}: Sites{True{}, Bind.sites(b, l, c)} case Some{Bind.SUse{Bind.Use{n, l, c, Bind.TLocal{tl, tc}}}}: Sites{True{}, Bind.sites(b, tl, tc)} case Some{Bind.SUse{Bind.Use{n, l, c, Bind.TItem{name}}}}: Sites{True{}, Bind.item_sites(b, name)} case Some{Bind.SUse{Bind.Use{n, l, c, other}}}: Sites{False{}, Bind.named_sites(b, n)} def sites.in(+text: String, +line: U32, +col: U32) -> Sites: +b = Bind.bound(text) sites.of(Bind.at(b, line, col), b) # the positions of the name at a position of a document def sites(~fs: F.Files, docs: List<&2, Docs.Doc>, +uri: String, line: U32, col: U32) -> IO(Sites): do IO: text : String <- load(~fs, docs, uri, Proto.path(uri)) return sites.in(text, line, col) # the items of a document def outline(~fs: F.Files, docs: List<&2, Docs.Doc>, +uri: String) -> IO(List<&2, Outline.Item>): do IO>: text : String <- load(~fs, docs, uri, Proto.path(uri)) return Outline.items(text) # completion # ---------- # the items of a file that start with a prefix, renamed for insertion def starting_in(~fs: F.Files, docs: List<&2, Docs.Doc>, +path: String, prefix: String, qual: String) -> IO(List<&2, Outline.Item>): do IO>: text : String <- load(~fs, docs, "file://" ++ path, path) return Outline.starting(Outline.items(text), prefix, qual) # Base is big: it is offered once something is typed def from_base(~fs: F.Files, typed: Bool, docs: List<&2, Docs.Doc>, prefix: String) -> IO(List<&2, Outline.Item>): match typed: case False{}: IO.pure(List<&2, Outline.Item>, []) case True{}: do IO>: path : String <- F.base_path(fs) starting_in(~fs, docs, path, prefix, "") # a binder as a candidate: its note stands for a signature def as_items(ls: List<&2, Bind.Bind>) -> List<&2, Outline.Item>: match ls: case Nil{}: Nil{} case Con{Bind.Bind{name, line, col, kind, note}, t}: Outline.Item{Outline.ILocal{}, name, line, note, Proto.kind_name(kind), ""} <> as_items(t) # candidates for a plain prefix: visible names, the document's items, its # aliases, then Base def unqualified(~fs: F.Files, +items: List<&2, Outline.Item>, docs: List<&2, Docs.Doc>, +prefix: String, locals: List<&2, Outline.Item>) -> IO(List<&2, Outline.Item>): do IO>: base : List<&2, Outline.Item> <- from_base(~fs, Bool.not(String.is_empty(prefix)), docs, prefix) return List.append(&2, Outline.Item, Outline.starting(locals, prefix, ""), List.append(&2, Outline.Item, Outline.starting(items, prefix, ""), List.append(&2, Outline.Item, Outline.aliases(items, prefix), base))) # `Alias.re` completes from the file behind the alias; anything else from the # document, its aliases and Base def candidates.via(~fs: F.Files, imp: Maybe<&2, String>, items: List<&2, Outline.Item>, docs: List<&2, Docs.Doc>, uri: String, +prefix: String, locals: List<&2, Outline.Item>) -> IO(List<&2, Outline.Item>): match imp: case Some{rel}: starting_in(~fs, docs, Path.join(Path.dir(Proto.path(uri)), rel), Word.rest(prefix), Word.head(prefix) ++ ".") case None{}: unqualified(~fs, items, docs, prefix, locals) def candidates.among(~fs: F.Files, +items: List<&2, Outline.Item>, docs: List<&2, Docs.Doc>, uri: String, +prefix: String, locals: List<&2, Outline.Item>) -> IO(List<&2, Outline.Item>): candidates.via(~fs, Outline.import_path(items, Word.head(prefix)), items, docs, uri, prefix, locals) def candidates.in(~fs: F.Files, +text: String, docs: List<&2, Docs.Doc>, uri: String, prefix: String, line: U32) -> IO(List<&2, Outline.Item>): candidates.among(~fs, Outline.items(text), docs, uri, prefix, as_items(Bind.visible(Bind.bound(text), line))) # what could complete the prefix typed at a line of a document: the locals # visible there, then items, each named by the text to insert def candidates(~fs: F.Files, +docs: List<&2, Docs.Doc>, +uri: String, prefix: String, line: U32) -> IO(List<&2, Outline.Item>): do IO>: text : String <- load(~fs, docs, uri, Proto.path(uri)) candidates.in(~fs, text, docs, uri, prefix, line) # a document's text def text_of(~fs: F.Files, docs: List<&2, Docs.Doc>, +uri: String) -> IO(String): load(~fs, docs, uri, Proto.path(uri))