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

Add refresh-config and open-config command #1803

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
03bade9
Add refresh-config and open-config command
jharrilim Mar 13, 2022
4a0eff1
clippy
jharrilim Mar 13, 2022
209112b
Use dynamic dispatch for editor config
jharrilim Mar 13, 2022
f1b21af
Refactor Result::Ok to Ok
jharrilim Mar 13, 2022
124ea23
Remove unused import
jharrilim Mar 13, 2022
3676e2e
cargo fmt
jharrilim Mar 13, 2022
7b74c43
Modify config error handling
jharrilim Mar 13, 2022
6e4da94
cargo xtask docgen
jharrilim Mar 13, 2022
d7e552b
impl display for ConfigLoadError
jharrilim Mar 13, 2022
a41c215
cargo fmt
jharrilim Mar 13, 2022
06bad8c
Put keymaps behind dyn access, refactor config.load()
jharrilim Mar 18, 2022
ea39201
Update command names
jharrilim Mar 18, 2022
0bf7beb
Update helix-term/src/application.rs
jharrilim Mar 18, 2022
161f9b8
Merge branch 'master' into feat/add-refresh-config-command
jharrilim Mar 18, 2022
b459895
Switch to unbounded_channel
jharrilim Mar 18, 2022
f930c77
Remove --edit-config command
jharrilim Mar 18, 2022
e6a2a60
Update configuration docs
jharrilim Mar 18, 2022
c5d7242
Revert "Put keymaps behind dyn access", too hard
jharrilim Mar 19, 2022
dbbea48
Merge branch 'master' into feat/add-refresh-config-command
jharrilim Mar 22, 2022
7df2f0c
Add refresh for keys
jharrilim Mar 23, 2022
681b8c8
Refactor default_keymaps, fix config default, add test
jharrilim Mar 23, 2022
ad5dc91
swap -> store, remove unneeded clone
jharrilim Mar 23, 2022
23e96a6
cargo fmt
jharrilim Mar 23, 2022
252d746
Rename default_keymaps to default
jharrilim Mar 25, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ To override global configuration parameters, create a `config.toml` file located
* Linux and Mac: `~/.config/helix/config.toml`
* Windows: `%AppData%\helix\config.toml`

> Note: You may use `hx --edit-config` to create and edit the `config.toml` file.
> Hint: You can easily open the config file by typing `:config-open` within Helix normal mode.

Example config:

Expand Down
2 changes: 2 additions & 0 deletions book/src/generated/typable-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,5 @@
| `:sort` | Sort ranges in selection. |
| `:rsort` | Sort ranges in selection in reverse order. |
| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
| `:config-reload` | Refreshes helix's config. |
| `:config-open` | Open the helix config.toml file. |
1 change: 1 addition & 0 deletions helix-term/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ crossterm = { version = "0.23", features = ["event-stream"] }
signal-hook = "0.3"
tokio-stream = "0.1"
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
arc-swap = { version = "1.5.0" }

# Logging
fern = "0.6"
Expand Down
84 changes: 72 additions & 12 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use arc_swap::{access::Map, ArcSwap};
use helix_core::{
config::{default_syntax_loader, user_syntax_loader},
pos_at_coords, syntax, Selection,
};
use helix_dap::{self as dap, Payload, Request};
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
use helix_view::{editor::Breakpoint, theme, Editor};
use helix_view::{
editor::{Breakpoint, ConfigEvent},
theme, Editor,
};
use serde_json::json;

use crate::{
Expand All @@ -13,6 +17,7 @@ use crate::{
compositor::Compositor,
config::Config,
job::Jobs,
keymap::Keymaps,
ui::{self, overlay::overlayed},
};

Expand Down Expand Up @@ -42,8 +47,7 @@ pub struct Application {
compositor: Compositor,
editor: Editor,

// TODO: share an ArcSwap with Editor?
config: Config,
config: Arc<ArcSwap<Config>>,
archseer marked this conversation as resolved.
Show resolved Hide resolved

#[allow(dead_code)]
theme_loader: Arc<theme::Loader>,
Expand All @@ -56,7 +60,7 @@ pub struct Application {
}

impl Application {
pub fn new(args: Args, mut config: Config) -> Result<Self, Error> {
pub fn new(args: Args, config: Config) -> Result<Self, Error> {
use helix_view::editor::Action;
let mut compositor = Compositor::new()?;
let size = compositor.size();
Expand Down Expand Up @@ -98,30 +102,33 @@ impl Application {
});
let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf));

let config = Arc::new(ArcSwap::from_pointee(config));
archseer marked this conversation as resolved.
Show resolved Hide resolved
let mut editor = Editor::new(
size,
theme_loader.clone(),
syn_loader.clone(),
config.editor.clone(),
Box::new(Map::new(Arc::clone(&config), |config: &Config| {
&config.editor
})),
);

let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys)));
let keys = Box::new(Map::new(Arc::clone(&config), |config: &Config| {
&config.keys
}));
let editor_view = Box::new(ui::EditorView::new(Keymaps::new(keys)));
compositor.push(editor_view);

if args.load_tutor {
let path = helix_loader::runtime_dir().join("tutor.txt");
editor.open(path, Action::VerticalSplit)?;
// Unset path to prevent accidentally saving to the original tutor file.
doc_mut!(editor).set_path(None)?;
} else if args.edit_config {
let path = conf_dir.join("config.toml");
editor.open(path, Action::VerticalSplit)?;
} else if !args.files.is_empty() {
let first = &args.files[0].0; // we know it's not empty
if first.is_dir() {
std::env::set_current_dir(&first)?;
editor.new_file(Action::VerticalSplit);
let picker = ui::file_picker(".".into(), &config.editor);
let picker = ui::file_picker(".".into(), &config.load().editor);
compositor.push(Box::new(overlayed(picker)));
} else {
let nr_of_files = args.files.len();
Expand Down Expand Up @@ -228,6 +235,10 @@ impl Application {
Some(payload) = self.editor.debugger_events.next() => {
self.handle_debugger_message(payload).await;
}
Some(config_event) = self.editor.config_events.1.recv() => {
self.handle_config_events(config_event);
self.render();
}
Some(callback) = self.jobs.futures.next() => {
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
self.render();
Expand All @@ -245,6 +256,55 @@ impl Application {
}
}

pub fn handle_config_events(&mut self, config_event: ConfigEvent) {
match config_event {
ConfigEvent::Refresh => self.refresh_config(),

// Since only the Application can make changes to Editor's config,
// the Editor must send up a new copy of a modified config so that
// the Application can apply it.
ConfigEvent::Update(editor_config) => {
let mut app_config = (*self.config.load().clone()).clone();
app_config.editor = editor_config;
self.config.store(Arc::new(app_config));
}
}
}

fn refresh_config(&mut self) {
let config = Config::load(helix_loader::config_file()).unwrap_or_else(|err| {
self.editor.set_error(err.to_string());
Config::default()
});

// Refresh theme
if let Some(theme) = config.theme.clone() {
let true_color = self.true_color();
self.editor.set_theme(
self.theme_loader
.load(&theme)
.map_err(|e| {
log::warn!("failed to load theme `{}` - {}", theme, e);
e
})
.ok()
.filter(|theme| (true_color || theme.is_16_color()))
.unwrap_or_else(|| {
if true_color {
self.theme_loader.default()
} else {
self.theme_loader.base16_default()
}
}),
);
}
self.config.store(Arc::new(config));
}

fn true_color(&self) -> bool {
self.config.load().editor.true_color || crate::true_color()
}

#[cfg(windows)]
// no signal handling available on windows
pub async fn handle_signals(&mut self, _signal: ()) {}
Expand Down Expand Up @@ -700,7 +760,7 @@ impl Application {
self.lsp_progress.update(server_id, token, work);
}

if self.config.lsp.display_messages {
if self.config.load().lsp.display_messages {
self.editor.set_status(status);
}
}
Expand Down Expand Up @@ -809,7 +869,7 @@ impl Application {
terminal::enable_raw_mode()?;
let mut stdout = stdout();
execute!(stdout, terminal::EnterAlternateScreen)?;
if self.config.editor.mouse {
if self.config.load().editor.mouse {
execute!(stdout, EnableMouseCapture)?;
}
Ok(())
Expand Down
2 changes: 0 additions & 2 deletions helix-term/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ pub struct Args {
pub build_grammars: bool,
pub verbosity: u64,
pub files: Vec<(PathBuf, Position)>,
pub edit_config: bool,
}

impl Args {
Expand All @@ -29,7 +28,6 @@ impl Args {
"--version" => args.display_version = true,
"--help" => args.display_help = true,
"--tutor" => args.load_tutor = true,
"--edit-config" => args.edit_config = true,
"--health" => {
args.health = true;
args.health_arg = argv.next_if(|opt| !opt.starts_with('-'));
Expand Down
35 changes: 21 additions & 14 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@ fn align_selections(cx: &mut Context) {

fn goto_window(cx: &mut Context, align: Align) {
let count = cx.count() - 1;
let config = cx.editor.config();
let (view, doc) = current!(cx.editor);

let height = view.inner_area().height as usize;
Expand All @@ -850,7 +851,7 @@ fn goto_window(cx: &mut Context, align: Align) {
// - 1 so we have at least one gap in the middle.
// a height of 6 with padding of 3 on each side will keep shifting the view back and forth
// as we type
let scrolloff = cx.editor.config.scrolloff.min(height.saturating_sub(1) / 2);
let scrolloff = config.scrolloff.min(height.saturating_sub(1) / 2);

let last_line = view.last_line(doc);

Expand Down Expand Up @@ -1274,6 +1275,7 @@ fn switch_to_lowercase(cx: &mut Context) {

pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
use Direction::*;
let config = cx.editor.config();
let (view, doc) = current!(cx.editor);

let range = doc.selection(view.id).primary();
Expand All @@ -1292,7 +1294,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {

let height = view.inner_area().height;

let scrolloff = cx.editor.config.scrolloff.min(height as usize / 2);
let scrolloff = config.scrolloff.min(height as usize / 2);

view.offset.row = match direction {
Forward => view.offset.row + offset,
Expand Down Expand Up @@ -1585,8 +1587,9 @@ fn rsearch(cx: &mut Context) {

fn searcher(cx: &mut Context, direction: Direction) {
let reg = cx.register.unwrap_or('/');
let scrolloff = cx.editor.config.scrolloff;
let wrap_around = cx.editor.config.search.wrap_around;
let config = cx.editor.config();
let scrolloff = config.scrolloff;
let wrap_around = config.search.wrap_around;

let doc = doc!(cx.editor);

Expand Down Expand Up @@ -1629,13 +1632,14 @@ fn searcher(cx: &mut Context, direction: Direction) {
}

fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
let scrolloff = cx.editor.config.scrolloff;
let config = cx.editor.config();
let scrolloff = config.scrolloff;
let (view, doc) = current!(cx.editor);
let registers = &cx.editor.registers;
if let Some(query) = registers.read('/') {
let query = query.last().unwrap();
let contents = doc.text().slice(..).to_string();
let search_config = &cx.editor.config.search;
let search_config = &config.search;
let case_insensitive = if search_config.smart_case {
!query.chars().any(char::is_uppercase)
} else {
Expand Down Expand Up @@ -1695,8 +1699,9 @@ fn search_selection(cx: &mut Context) {
fn global_search(cx: &mut Context) {
let (all_matches_sx, all_matches_rx) =
tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>();
let smart_case = cx.editor.config.search.smart_case;
let file_picker_config = cx.editor.config.file_picker.clone();
let config = cx.editor.config();
let smart_case = config.search.smart_case;
let file_picker_config = config.file_picker.clone();

let completions = search_completions(cx, None);
let prompt = ui::regex_prompt(
Expand Down Expand Up @@ -2028,7 +2033,7 @@ fn append_mode(cx: &mut Context) {
fn file_picker(cx: &mut Context) {
// We don't specify language markers, root will be the root of the current git repo
let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./"));
let picker = ui::file_picker(root, &cx.editor.config);
let picker = ui::file_picker(root, &cx.editor.config());
cx.push_layer(Box::new(overlayed(picker)));
}

Expand Down Expand Up @@ -2105,7 +2110,7 @@ pub fn command_palette(cx: &mut Context) {
move |compositor: &mut Compositor, cx: &mut compositor::Context| {
let doc = doc_mut!(cx.editor);
let keymap =
compositor.find::<ui::EditorView>().unwrap().keymaps.map[&doc.mode].reverse_map();
compositor.find::<ui::EditorView>().unwrap().keymaps.map()[&doc.mode].reverse_map();

let mut commands: Vec<MappableCommand> = MappableCommand::STATIC_COMMAND_LIST.into();
commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| {
Expand Down Expand Up @@ -2571,14 +2576,15 @@ pub mod insert {
// It trigger completion when idle timer reaches deadline
// Only trigger completion if the word under cursor is longer than n characters
pub fn idle_completion(cx: &mut Context) {
let config = cx.editor.config();
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
let cursor = doc.selection(view.id).primary().cursor(text);

use helix_core::chars::char_is_word;
let mut iter = text.chars_at(cursor);
iter.reverse();
for _ in 0..cx.editor.config.completion_trigger_len {
for _ in 0..config.completion_trigger_len {
match iter.next() {
Some(c) if char_is_word(c) => {}
_ => return,
Expand Down Expand Up @@ -4154,7 +4160,7 @@ fn shell_keep_pipe(cx: &mut Context) {
Some('|'),
ui::completers::none,
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
let shell = &cx.editor.config.shell;
let shell = &cx.editor.config().shell;
if event != PromptEvent::Validate {
return;
}
Expand Down Expand Up @@ -4250,7 +4256,8 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
Some('|'),
ui::completers::none,
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
let shell = &cx.editor.config.shell;
let config = cx.editor.config();
let shell = &config.shell;
if event != PromptEvent::Validate {
return;
}
Expand Down Expand Up @@ -4295,7 +4302,7 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {

// after replace cursor may be out of bounds, do this to
// make sure cursor is in view and update scroll as well
view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff);
view.ensure_cursor_in_view(doc, config.scrolloff);
},
);

Expand Down
Loading