turbo-editors/turbo-corepublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

root.go · 76 lines · 2.3 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 4h ago1// Where a project starts: the directory the language server is given as its
2// workspace root.
3
4package app
5
6import (
7 "os"
8 "path/filepath"
9
10 "codeberg.org/turbo-editors/turbo-core/profile"
11)
12
13// ProjectRoot returns the directory the language server should work in: the
14// project root above the first file named on the command line, or the working
15// directory when none was.
16//
17// "Project root" means the nearest directory at or above the file holding one
18// of the profile's RootMarkers — go.mod for Go, Cargo.toml for Rust. A
19// language server given the wrong root loads the wrong package set and then
20// answers nothing at all, with no error, which is the single most confusing way
21// completion can fail.
22//
23// This is deliberately **not** the rule the editor uses for "the project"
24// elsewhere: settings and the project tree are rooted in the working directory
25// with no walk up, because a module has a real boundary and "the project" does
26// not. A language server is the one thing that genuinely needs the module's
27// boundary, so it is the one thing that walks.
28//
29// root := app.ProjectRoot(golang.Profile(), []string{"internal/app/app.go"})
30// editor.StartLanguageServer(ctx, root)
31func ProjectRoot(p profile.Profile, files []string) string {
32 start, err := os.Getwd()
33 if err != nil {
34 start = "."
35 }
36 if len(files) > 0 {
37 if absolute, err := filepath.Abs(files[0]); err == nil {
38 start = filepath.Dir(absolute)
39 }
40 }
41 return markedRoot(start, p.RootMarkers)
42}
43
44// markedRoot walks up from a directory looking for one of the markers, and
45// returns the directory it started from when there is none.
46//
47// Returning the starting directory rather than the filesystem root is what
48// keeps a file edited outside any project working: the server is given
49// somewhere plausible instead of somewhere absurd.
50func markedRoot(start string, markers []string) string {
51 if len(markers) == 0 {
52 return start
53 }
54
55 directory := start
56 for {
57 if holdsAny(directory, markers) {
58 return directory
59 }
60 parent := filepath.Dir(directory)
61 if parent == directory {
62 return start
63 }
64 directory = parent
65 }
66}
67
68// holdsAny reports whether a directory holds one of the marker files.
69func holdsAny(directory string, markers []string) bool {
70 for _, marker := range markers {
71 if _, err := os.Stat(filepath.Join(directory, marker)); err == nil {
72 return true
73 }
74 }
75 return false
76}