Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalised to multiple runtime directories with priorities #5411

Merged
merged 12 commits into from
Mar 9, 2023

Conversation

paul-scott
Copy link
Contributor

@paul-scott paul-scott commented Jan 5, 2023

This is an implementation for #3346
See commit message for an overview.

Here are some thing to consider when reviewing.

Depending on how helix is packaged, it still might not be possible for some users to conveniently override runtime files. E.g. on AUR helix-git, /usr/bin/hx is a shell script that sets HELIX_RUNTIME to /usr/lib/helix/runtime before calling /usr/lib/helix/hx. Setting this env var is not necessary because helix already automatically checks for a runtime directory in the same directory as the executable. Setting it prevents ~/.config/helix/runtime from getting priority from the default runtime installation.

The docs suggest that the HELIX_RUNTIME is intended as an option to override a ~/.config/helix/runtime, which I agree could be useful and why I've initially kept this behaviour. However, if it looks like packager's and build-from-source installers want to use this env var for their default runtime install, then we might want to rethink the priority order as outlined in the commit message.

For consistencies sake you might want to consider if ~/.config/helix/themes should be deprecated since this commit makes it so that ~/.config/helix/runtime/themes can be used to the same effect. Before and after this commit the former always has the higher priority.

It just remembered that I haven't touched the user doc / book. I'll have to have a look and see where the new behaviour can be easily explained.

Closes #3346

@paul-scott paul-scott marked this pull request as ready for review January 8, 2023 12:39
@kirawi kirawi added A-helix-term Area: Helix term improvements S-waiting-on-review Status: Awaiting review from a maintainer. labels Jan 9, 2023
@archseer archseer mentioned this pull request Jan 11, 2023
helix-loader/src/lib.rs Outdated Show resolved Hide resolved
@archseer
Copy link
Member

archseer commented Jan 16, 2023

Priority should be (highest to lowest):

  1. ~/.config/helix/runtime (user's configuration)
  2. HELIX_RUNTIME (indicates the system's runtime, will be usually be under /usr/ for package installs, not writable)
  3. current executable's directory (if all else fails, it might just be someone running it from a tarball)

The docs recommend setting HELIX_RUNTIME just because I felt it's simpler to use an env var than to copy files over and then forget to update them in the future.

This means that for grammar fetch & build we probably want to write into the first dir? Except on CI \cc @the-mikedavis

Comment on lines 69 to 89
writeln!(stdout, "Number of Runtime directories: {}", rt_dirs.len())?;
for (i, rt_dir) in rt_dirs.iter().enumerate() {
writeln!(stdout, "Runtime directory {}: {}", i + 1, rt_dir.display())?;
if let Ok(path) = std::fs::read_link(&rt_dir) {
let msg = format!("Runtime directory is symlinked to {}", path.display());
writeln!(stdout, "{}", msg.yellow())?;
}
if !rt_dir.exists() {
writeln!(stdout, "{}", "Runtime directory does not exist.".red())?;
}
if rt_dir.read_dir().ok().map(|it| it.count()) == Some(0) {
writeln!(stdout, "{}", "Runtime directory is empty.".red())?;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd just output this similar to PATH or GOPATH: a bunch of directories separated by ;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the runtime paths, the original behaviour conditionally reports for each runtime directory:

  1. the destination if a runtime directory is symlinked
  2. a red warning if the runtime directory doesn't exist
  3. a red warning if the runtime directory is empty

I assume you still want this info displayed? How should we clearly display this using a ;-separated list? For 2 and 3, we can just filter on the non-existent / empty directories and join them on ;. Should we just do the same thing for 1, and leave it up to the user to work out which runtime directory is the symlink?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've change the implementation so that a ;-separated list of runtime directories is presented. For the warnings / errors, changed them all to warnings (yellow) because now it might be more common for a runtime directory to be missing or empty and it not be a problem. I didn't join these paths on ; in the warnings, instead I just report a warning per case.

One thing is that windows / linux use ; / : for this separator in PATH and GOPATH. I'm not sure we want to go to the effort to make this OS aware, so I just went with your suggestion for the semicolon. This seems like it would cause the least confusion given windows using : after drive letter in path C:\\ etc.

Let me know what you think.

@paul-scott
Copy link
Contributor Author

Priority should be (highest to lowest):

1. `~/.config/helix/runtime` (user's configuration)

2. `HELIX_RUNTIME` (indicates the system's runtime, will be usually be under `/usr/` for package installs, not writable)

3. current executable's directory (if all else fails, it might just be someone running it from a tarball)

The docs recommend setting HELIX_RUNTIME just because I felt it's simpler to use an env var than to copy files over and then forget to update them in the future.

This means that for grammar fetch & build we probably want to write into the first dir? Except on CI \cc @the-mikedavis

In that case how about:

  1. sibling runtime directory to $CARGO_MANIFEST_DIR
  2. subdirectory of user config directory (~/.config/helix/runtime)
  3. $HELIX_RUNTIME
  4. subdirectory of path to helix executable

Compared to the original behaviour this is just swapping 2 and 3.

I assume my 1 here is what the CI uses. In addition it seems like helix-git on AUR fetches and builds the grammars under 1, and then packages them up from there so they get installed under /usr/lib/helix/runtime.

I think it is best if this kind of packaging behaviour remains. It would be a pain if on updating a packager started putting (and overriding) the grammars under ~/.config/helix/runtime. A user might intentionally be trying to keep grammars in there to override the defaults.

@archseer
Copy link
Member

Oh I agree, I forgot to list it above :) Just wanted to clarify the intended use of HELIX_RUNTIME

@gibbz00
Copy link
Contributor

gibbz00 commented Jan 16, 2023

Should we also make use of XDG_RUNTIME_DIR? I'm personally not of fan of placing runtime files the .config folder.

@archseer
Copy link
Member

It's orthogonal to this PR. #2135 implements that but it should be rebased after this is merged

@paul-scott
Copy link
Contributor Author

paul-scott commented Jan 18, 2023

Oh I agree, I forgot to list it above :) Just wanted to clarify the intended use of HELIX_RUNTIME

I've now moved HELIX_RUNTIME down in priority below the user config runtime.

In terms of fetch and build, the behaviour is now that a cargo build will always put grammars in the runtime directory within the helix repo being built. HELIX_RUNTIME can no longer be used to change where they will automatically be fetched / built.

When a user calls hx -g fetch / build this is now always put in ~/.config/helix/runtime/grammars/.

None of this is hard-coded, it is just the way it works with the new priority ordering and how the implementation of fetch and build always grab the highest priority runtime directory.

I've modified the build from source instructions in the book to reflect some of this but not in that much detail.

Let me know if you would like to further tweak this.

paul-scott and others added 4 commits January 18, 2023 22:40
This is an implementation for helix-editor#3346.

Previously, one of the following runtime directories were used:

1. `$HELIX_RUNTIME`
2. sibling directory to `$CARGO_MANIFEST_DIR`
3. subdirectory of user config directory
4. subdirectory of path to helix executable

The first directory provided / found to exist in this order was used as a
root for all runtime file searches (grammars, themes, queries).

This change lowers the priority of `$HELIX_RUNTIME` so that the user
config runtime has higher priority. More significantly, all of these
directories are now searched for runtime files, enabling a user to override
default or system-level runtime files. If the same file name appears
in multiple runtime directories, the following priority is now used:

1. sibling directory to `$CARGO_MANIFEST_DIR`
2. subdirectory of user config directory
3. `$HELIX_RUNTIME`
4. subdirectory of path to helix executable

One exception to this rule is that a user can have a `themes`
directory directly in the user config directory that has higher piority
to `themes` directories in runtime directories. That behaviour has been
preserved.

As part of implementing this feature `theme::Loader` was simplified
and the cycle detection logic of the theme inheritance was improved to
cover more cases and to be more explicit.
@gibbz00 gibbz00 mentioned this pull request Jan 24, 2023
5 tasks
gibbz00 added a commit to gibbz00/helix that referenced this pull request Jan 24, 2023
Reduced comment some verbosity, the method
signatures should speak for themselves.

runtime_file -> get_runtime_file
gibbz00 added a commit to gibbz00/helix that referenced this pull request Jan 24, 2023
@archseer archseer added this to the next milestone Feb 16, 2023
Copy link
Member

@pascalkuthe pascalkuthe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! Just some minor nits

Comment on lines 111 to 113
config directory (for example `~/.config/helix/runtime` on Linux/macOS). An alternative runtime directory can
be used by setting the `HELIX_RUNTIME` environment variable. Both runtime directories can be used at the same
time, with the files residing under the config runtime directory given priority.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be expanded a bit, ideally we would have bullet points here that exactly lists what directories are searched in what order (and where grammars are installed by the cli)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I've also moved it down slightly in the document to try and make the flow make a bit more sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a conflict with master. I've now merged in latest master and resolved the conflict.

BTW, what is the preferred process to keep these PRs up to date / conflict free with master? Should we be periodically rebasing and force pushing to our PR branch, or doing a regular merge? I was doing the former to try and keep things cleaner but then I see archseer at some point decided to do a regular merge.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase is preferred but if it's a small PR that we'll squash then it'll result in a single commit anyway

helix-loader/src/lib.rs Show resolved Hide resolved
Comment on lines 79 to 82
if let Ok(path) = std::fs::read_link(&rt_dir) {
let msg = format!("Runtime directory is symlinked to: {}", path.display());
writeln!(stdout, "{}", msg.yellow())?;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this should show what runtime directory is symlinked, this may not always be obvious when multiple runtime dirs are symlinked.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have added both paths to output.

Comment on lines 181 to 185
let mut names = Vec::new();
for dir in &self.theme_dirs {
names.extend(Self::read_names(dir));
}
names
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to use a set here or sort and deadup, otherwise we could endup with duplicate theme names

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is actually no longer used anywhere . I'm not sure if this is due to change in master being merged or if I made the change. Anyway I've removed the method.

Comment on lines 170 to 175
if path.exists() && !visited_paths.contains(&path) {
visited_paths.insert(path.clone());
Some(path)
} else {
None
}
Copy link
Member

@pascalkuthe pascalkuthe Mar 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should track whether there is a cycle here (simply set a bool to true if we return None because visited_paths already contains path) so we can create a separate error message for that case. Having the same error message for both cases seems confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep I'm doing that now. This method now returns a Result which distinguishes between the two cases.

The health display of runtime symlinks now prints both ends of the
link.

Separate errors are given when theme file is not found and when the
only theme file found would form an inheritence cycle.
book/src/install.md Outdated Show resolved Hide resolved
&helix_loader::config_dir().join("themes"),
));
let mut names = theme::Loader::read_names(&helix_loader::config_dir().join("themes"));
for rt_dir in helix_loader::runtime_dirs() {
Copy link
Member

@pascalkuthe pascalkuthe Mar 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the function I commented in earlier has been inlined here (maybe by the merge from master?). This function has the same issue where we might ensup with the same theme name multiple times. I think an (unstable) sort and deadup would be a good solution

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look down a couple of lines to 289 and 290 and it looks like this being done already.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah you are right, sorry about that 😅

Also gave markdown headings to subsections.

Fixed a error with table indentation not building
table that also appears present on master.
Copy link
Member

@pascalkuthe pascalkuthe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now

@archseer archseer merged commit ce1fb9e into helix-editor:master Mar 9, 2023
@archseer
Copy link
Member

archseer commented Mar 9, 2023

Thanks! This was a much needed improvement 🎉

@lazytanuki lazytanuki mentioned this pull request Mar 10, 2023
15 tasks
sagnibak pushed a commit to sagnibak/helix that referenced this pull request Mar 21, 2023
…itor#5411)

* Generalised to multiple runtime directories with priorities

This is an implementation for helix-editor#3346.

Previously, one of the following runtime directories were used:

1. `$HELIX_RUNTIME`
2. sibling directory to `$CARGO_MANIFEST_DIR`
3. subdirectory of user config directory
4. subdirectory of path to helix executable

The first directory provided / found to exist in this order was used as a
root for all runtime file searches (grammars, themes, queries).

This change lowers the priority of `$HELIX_RUNTIME` so that the user
config runtime has higher priority. More significantly, all of these
directories are now searched for runtime files, enabling a user to override
default or system-level runtime files. If the same file name appears
in multiple runtime directories, the following priority is now used:

1. sibling directory to `$CARGO_MANIFEST_DIR`
2. subdirectory of user config directory
3. `$HELIX_RUNTIME`
4. subdirectory of path to helix executable

One exception to this rule is that a user can have a `themes`
directory directly in the user config directory that has higher piority
to `themes` directories in runtime directories. That behaviour has been
preserved.

As part of implementing this feature `theme::Loader` was simplified
and the cycle detection logic of the theme inheritance was improved to
cover more cases and to be more explicit.

* Removed AsRef usage to avoid binary growth

* Health displaying ;-separated runtime dirs

* Changed HELIX_RUNTIME build from src instructions

* Updated doc for more detail on runtime directories

* Improved health symlink printing and theme cycle errors

The health display of runtime symlinks now prints both ends of the
link.

Separate errors are given when theme file is not found and when the
only theme file found would form an inheritence cycle.

* Satisfied clippy on passing Path

* Clarified highest priority runtime directory purpose

* Further clarified multiple runtime details in book

Also gave markdown headings to subsections.

Fixed a error with table indentation not building
table that also appears present on master.

---------

Co-authored-by: Paul Scott <[email protected]>
Co-authored-by: Blaž Hrastnik <[email protected]>

1. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
developing and testing helix only).
2. `runtime/` subdirectory of OS-dependent helix user config directory.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, shouldn't this be ~/.local/state/helix/runtime (XDG_STATE_HOME)?

This is clearly not config, but state, and we don't want backup programs to back up compiled tree-sitter .so grammars…

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's #584

This PR kept it to ~/.config for backwards compatibility

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#5636 🤷

@flokli flokli mentioned this pull request Mar 29, 2023
@paul-scott paul-scott deleted the multi-runtime branch April 1, 2023 09:51
wes-adams pushed a commit to wes-adams/helix that referenced this pull request Jul 4, 2023
…itor#5411)

* Generalised to multiple runtime directories with priorities

This is an implementation for helix-editor#3346.

Previously, one of the following runtime directories were used:

1. `$HELIX_RUNTIME`
2. sibling directory to `$CARGO_MANIFEST_DIR`
3. subdirectory of user config directory
4. subdirectory of path to helix executable

The first directory provided / found to exist in this order was used as a
root for all runtime file searches (grammars, themes, queries).

This change lowers the priority of `$HELIX_RUNTIME` so that the user
config runtime has higher priority. More significantly, all of these
directories are now searched for runtime files, enabling a user to override
default or system-level runtime files. If the same file name appears
in multiple runtime directories, the following priority is now used:

1. sibling directory to `$CARGO_MANIFEST_DIR`
2. subdirectory of user config directory
3. `$HELIX_RUNTIME`
4. subdirectory of path to helix executable

One exception to this rule is that a user can have a `themes`
directory directly in the user config directory that has higher piority
to `themes` directories in runtime directories. That behaviour has been
preserved.

As part of implementing this feature `theme::Loader` was simplified
and the cycle detection logic of the theme inheritance was improved to
cover more cases and to be more explicit.

* Removed AsRef usage to avoid binary growth

* Health displaying ;-separated runtime dirs

* Changed HELIX_RUNTIME build from src instructions

* Updated doc for more detail on runtime directories

* Improved health symlink printing and theme cycle errors

The health display of runtime symlinks now prints both ends of the
link.

Separate errors are given when theme file is not found and when the
only theme file found would form an inheritence cycle.

* Satisfied clippy on passing Path

* Clarified highest priority runtime directory purpose

* Further clarified multiple runtime details in book

Also gave markdown headings to subsections.

Fixed a error with table indentation not building
table that also appears present on master.

---------

Co-authored-by: Paul Scott <[email protected]>
Co-authored-by: Blaž Hrastnik <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-helix-term Area: Helix term improvements S-waiting-on-review Status: Awaiting review from a maintainer.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Treat runtime files from installation as immutable
7 participants