Skip to content

Commit

Permalink
Add refresh-config and open-config command (helix-editor#1803)
Browse files Browse the repository at this point in the history
* Add refresh-config and open-config command

* clippy

* Use dynamic dispatch for editor config

* Refactor Result::Ok to Ok

* Remove unused import

* cargo fmt

* Modify config error handling

* cargo xtask docgen

* impl display for ConfigLoadError

* cargo fmt

* Put keymaps behind dyn access, refactor config.load()

* Update command names

* Update helix-term/src/application.rs

Co-authored-by: Blaž Hrastnik <blaz@mxxn.io>

* Switch to unbounded_channel

* Remove --edit-config command

* Update configuration docs

* Revert "Put keymaps behind dyn access", too hard

This reverts commit 06bad8c.

* Add refresh for keys

* Refactor default_keymaps, fix config default, add test

* swap -> store, remove unneeded clone

* cargo fmt

* Rename default_keymaps to default

Co-authored-by: Blaž Hrastnik <blaz@mxxn.io>
  • Loading branch information
jharrilim and archseer authored Mar 25, 2022
1 parent 309f2c2 commit bee05dd
Show file tree
Hide file tree
Showing 19 changed files with 797 additions and 581 deletions.
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>>,

#[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));
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

0 comments on commit bee05dd

Please sign in to comment.