Add refresh-config and open-config command

pull/1803/head
Joseph Harrison-Lim 3 years ago
parent 6fdf5d0920
commit 03bade90d2
No known key found for this signature in database
GPG Key ID: 5DAB214D5E62987D

2
Cargo.lock generated

@ -435,6 +435,7 @@ name = "helix-term"
version = "0.6.0" version = "0.6.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap",
"chrono", "chrono",
"content_inspector", "content_inspector",
"crossterm", "crossterm",
@ -483,6 +484,7 @@ name = "helix-view"
version = "0.6.0" version = "0.6.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap",
"bitflags", "bitflags",
"chardetng", "chardetng",
"clipboard-win", "clipboard-win",

@ -40,6 +40,7 @@ crossterm = { version = "0.23", features = ["event-stream"] }
signal-hook = "0.3" signal-hook = "0.3"
tokio-stream = "0.1" tokio-stream = "0.1"
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
arc-swap = { version = "1.5.0" }
# Logging # Logging
fern = "0.6" fern = "0.6"

@ -1,10 +1,11 @@
use arc_swap::{access::Map, ArcSwap};
use helix_core::{ use helix_core::{
config::{default_syntax_loader, user_syntax_loader}, config::{default_syntax_loader, user_syntax_loader},
pos_at_coords, syntax, Selection, pos_at_coords, syntax, Selection,
}; };
use helix_dap::{self as dap, Payload, Request}; use helix_dap::{self as dap, Payload, Request};
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; 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 serde_json::json;
use crate::{ use crate::{
@ -42,8 +43,7 @@ pub struct Application {
compositor: Compositor, compositor: Compositor,
editor: Editor, editor: Editor,
// TODO: share an ArcSwap with Editor? config: Arc<ArcSwap<Config>>,
config: Config,
#[allow(dead_code)] #[allow(dead_code)]
theme_loader: Arc<theme::Loader>, theme_loader: Arc<theme::Loader>,
@ -56,7 +56,7 @@ pub struct Application {
} }
impl 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; use helix_view::editor::Action;
let mut compositor = Compositor::new()?; let mut compositor = Compositor::new()?;
let size = compositor.size(); let size = compositor.size();
@ -98,14 +98,16 @@ impl Application {
}); });
let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf)); 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( let mut editor = Editor::new(
size, size,
theme_loader.clone(), theme_loader.clone(),
syn_loader.clone(), syn_loader.clone(),
config.editor.clone(), ArcSwap::from_pointee(config.load().editor.clone())
// config.clone(),
); );
let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys))); let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.load().keys.clone())));
compositor.push(editor_view); compositor.push(editor_view);
if args.load_tutor { if args.load_tutor {
@ -121,7 +123,7 @@ impl Application {
if first.is_dir() { if first.is_dir() {
std::env::set_current_dir(&first)?; std::env::set_current_dir(&first)?;
editor.new_file(Action::VerticalSplit); 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))); compositor.push(Box::new(overlayed(picker)));
} else { } else {
let nr_of_files = args.files.len(); let nr_of_files = args.files.len();
@ -228,6 +230,10 @@ impl Application {
Some(payload) = self.editor.debugger_events.next() => { Some(payload) = self.editor.debugger_events.next() => {
self.handle_debugger_message(payload).await; self.handle_debugger_message(payload).await;
} }
Some(ConfigEvent) = self.editor.config_events.next() => {
self.refresh_config();
self.render();
}
Some(callback) = self.jobs.futures.next() => { Some(callback) = self.jobs.futures.next() => {
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
self.render(); self.render();
@ -245,6 +251,39 @@ impl Application {
} }
} }
pub fn refresh_config(&mut self) {
let config = Config::load(helix_loader::config_file()).unwrap();
// Just an example to start; Some config properties like "theme" are a bit more involved and require a reload
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()
}
})
.clone(),
);
}
self.config.store(Arc::new(config));
// Is it possible to not do this manually? Presumably I've completely butchered using ArcSwap?
self.editor.config.store(Arc::new(self.config.load().editor.clone()));
}
fn true_color(&self) -> bool {
self.config.load().editor.true_color || crate::true_color()
}
#[cfg(windows)] #[cfg(windows)]
// no signal handling available on windows // no signal handling available on windows
pub async fn handle_signals(&mut self, _signal: ()) {} pub async fn handle_signals(&mut self, _signal: ()) {}
@ -700,7 +739,7 @@ impl Application {
self.lsp_progress.update(server_id, token, work); 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); self.editor.set_status(status);
} }
} }
@ -809,7 +848,7 @@ impl Application {
terminal::enable_raw_mode()?; terminal::enable_raw_mode()?;
let mut stdout = stdout(); let mut stdout = stdout();
execute!(stdout, terminal::EnterAlternateScreen)?; execute!(stdout, terminal::EnterAlternateScreen)?;
if self.config.editor.mouse { if self.config.load().editor.mouse {
execute!(stdout, EnableMouseCapture)?; execute!(stdout, EnableMouseCapture)?;
} }
Ok(()) Ok(())

@ -848,7 +848,7 @@ fn goto_window(cx: &mut Context, align: Align) {
// - 1 so we have at least one gap in the middle. // - 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 // a height of 6 with padding of 3 on each side will keep shifting the view back and forth
// as we type // as we type
let scrolloff = cx.editor.config.scrolloff.min(height.saturating_sub(1) / 2); let scrolloff = cx.editor.config.load().scrolloff.min(height.saturating_sub(1) / 2);
let last_line = view.last_line(doc); let last_line = view.last_line(doc);
@ -1290,7 +1290,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
let height = view.inner_area().height; let height = view.inner_area().height;
let scrolloff = cx.editor.config.scrolloff.min(height as usize / 2); let scrolloff = cx.editor.config.load().scrolloff.min(height as usize / 2);
view.offset.row = match direction { view.offset.row = match direction {
Forward => view.offset.row + offset, Forward => view.offset.row + offset,
@ -1583,8 +1583,8 @@ fn rsearch(cx: &mut Context) {
fn searcher(cx: &mut Context, direction: Direction) { fn searcher(cx: &mut Context, direction: Direction) {
let reg = cx.register.unwrap_or('/'); let reg = cx.register.unwrap_or('/');
let scrolloff = cx.editor.config.scrolloff; let scrolloff = cx.editor.config.load().scrolloff;
let wrap_around = cx.editor.config.search.wrap_around; let wrap_around = cx.editor.config.load().search.wrap_around;
let doc = doc!(cx.editor); let doc = doc!(cx.editor);
@ -1627,13 +1627,13 @@ fn searcher(cx: &mut Context, direction: Direction) {
} }
fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) { fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
let scrolloff = cx.editor.config.scrolloff; let scrolloff = cx.editor.config.load().scrolloff;
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
let registers = &cx.editor.registers; let registers = &cx.editor.registers;
if let Some(query) = registers.read('/') { if let Some(query) = registers.read('/') {
let query = query.last().unwrap(); let query = query.last().unwrap();
let contents = doc.text().slice(..).to_string(); let contents = doc.text().slice(..).to_string();
let search_config = &cx.editor.config.search; let search_config = &cx.editor.config.load().search;
let case_insensitive = if search_config.smart_case { let case_insensitive = if search_config.smart_case {
!query.chars().any(char::is_uppercase) !query.chars().any(char::is_uppercase)
} else { } else {
@ -1693,8 +1693,8 @@ fn search_selection(cx: &mut Context) {
fn global_search(cx: &mut Context) { fn global_search(cx: &mut Context) {
let (all_matches_sx, all_matches_rx) = let (all_matches_sx, all_matches_rx) =
tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>(); tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>();
let smart_case = cx.editor.config.search.smart_case; let smart_case = cx.editor.config.load().search.smart_case;
let file_picker_config = cx.editor.config.file_picker.clone(); let file_picker_config = cx.editor.config.load().file_picker.clone();
let completions = search_completions(cx, None); let completions = search_completions(cx, None);
let prompt = ui::regex_prompt( let prompt = ui::regex_prompt(
@ -2023,7 +2023,7 @@ fn append_mode(cx: &mut Context) {
fn file_picker(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 // 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 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.load());
cx.push_layer(Box::new(overlayed(picker))); cx.push_layer(Box::new(overlayed(picker)));
} }
@ -2573,7 +2573,7 @@ pub mod insert {
use helix_core::chars::char_is_word; use helix_core::chars::char_is_word;
let mut iter = text.chars_at(cursor); let mut iter = text.chars_at(cursor);
iter.reverse(); iter.reverse();
for _ in 0..cx.editor.config.completion_trigger_len { for _ in 0..cx.editor.config.load().completion_trigger_len {
match iter.next() { match iter.next() {
Some(c) if char_is_word(c) => {} Some(c) if char_is_word(c) => {}
_ => return, _ => return,
@ -4136,7 +4136,7 @@ fn shell_keep_pipe(cx: &mut Context) {
Some('|'), Some('|'),
ui::completers::none, ui::completers::none,
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
let shell = &cx.editor.config.shell; let shell = &cx.editor.config.load().shell;
if event != PromptEvent::Validate { if event != PromptEvent::Validate {
return; return;
} }
@ -4232,7 +4232,7 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
Some('|'), Some('|'),
ui::completers::none, ui::completers::none,
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
let shell = &cx.editor.config.shell; let shell = &cx.editor.config.load().shell;
if event != PromptEvent::Validate { if event != PromptEvent::Validate {
return; return;
} }
@ -4277,7 +4277,7 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
// after replace cursor may be out of bounds, do this to // after replace cursor may be out of bounds, do this to
// make sure cursor is in view and update scroll as well // 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, cx.editor.config.load().scrolloff);
}, },
); );

@ -1,6 +1,8 @@
use std::{borrow::BorrowMut, sync::Arc};
use super::*; use super::*;
use helix_view::editor::Action; use helix_view::editor::{Action, ConfigEvent};
use ui::completers::{self, Completer}; use ui::completers::{self, Completer};
#[derive(Clone)] #[derive(Clone)]
@ -533,7 +535,7 @@ fn theme(
.theme_loader .theme_loader
.load(theme) .load(theme)
.with_context(|| format!("Failed setting theme {}", theme))?; .with_context(|| format!("Failed setting theme {}", theme))?;
let true_color = cx.editor.config.true_color || crate::true_color(); let true_color = cx.editor.config.load().true_color || crate::true_color();
if !(true_color || theme.is_16_color()) { if !(true_color || theme.is_16_color()) {
bail!("Unsupported theme: theme requires true color support"); bail!("Unsupported theme: theme requires true color support");
} }
@ -857,28 +859,28 @@ fn setting(
args: &[Cow<str>], args: &[Cow<str>],
_event: PromptEvent, _event: PromptEvent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let runtime_config = &mut cx.editor.config;
if args.len() != 2 { if args.len() != 2 {
anyhow::bail!("Bad arguments. Usage: `:set key field`"); anyhow::bail!("Bad arguments. Usage: `:set key field`");
} }
let (key, arg) = (&args[0].to_lowercase(), &args[1]); let (key, arg) = (&args[0].to_lowercase(), &args[1]);
match key.as_ref() { if let Ok(runtime_config) = &mut std::sync::Arc::try_unwrap(cx.editor.config.load().clone()) {
"scrolloff" => runtime_config.scrolloff = arg.parse()?, match key.as_ref() {
"scroll-lines" => runtime_config.scroll_lines = arg.parse()?, "scrolloff" => runtime_config.scrolloff = arg.parse()?,
"mouse" => runtime_config.mouse = arg.parse()?, "scroll-lines" => runtime_config.scroll_lines = arg.parse()?,
"line-number" => runtime_config.line_number = arg.parse()?, "mouse" => runtime_config.mouse = arg.parse()?,
"middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?, "line-number" => runtime_config.line_number = arg.parse()?,
"auto-pairs" => runtime_config.auto_pairs = arg.parse()?, "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?,
"auto-completion" => runtime_config.auto_completion = arg.parse()?, "auto-pairs" => runtime_config.auto_pairs = arg.parse()?,
"completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?, "auto-completion" => runtime_config.auto_completion = arg.parse()?,
"auto-info" => runtime_config.auto_info = arg.parse()?, "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?,
"true-color" => runtime_config.true_color = arg.parse()?, "auto-info" => runtime_config.auto_info = arg.parse()?,
"search.smart-case" => runtime_config.search.smart_case = arg.parse()?, "true-color" => runtime_config.true_color = arg.parse()?,
"search.wrap-around" => runtime_config.search.wrap_around = arg.parse()?, "search.smart-case" => runtime_config.search.smart_case = arg.parse()?,
_ => anyhow::bail!("Unknown key `{}`.", args[0]), "search.wrap-around" => runtime_config.search.wrap_around = arg.parse()?,
_ => anyhow::bail!("Unknown key `{}`.", args[0]),
}
cx.editor.config.store(Arc::new(runtime_config.clone()));
} }
Ok(()) Ok(())
@ -970,6 +972,24 @@ fn tree_sitter_subtree(
Ok(()) Ok(())
} }
fn open_config(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
cx.editor.open(helix_loader::config_file(), Action::Replace)?;
Ok(())
}
fn refresh_config(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
cx.editor.config_events.push(tokio_stream::once(ConfigEvent));
Ok(())
}
pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand { TypableCommand {
name: "quit", name: "quit",
@ -1342,6 +1362,20 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: tree_sitter_subtree, fun: tree_sitter_subtree,
completer: None, completer: None,
}, },
TypableCommand {
name: "refresh-config",
aliases: &[],
doc: "Refreshes helix's config.",
fun: refresh_config,
completer: None,
},
TypableCommand {
name: "open-config",
aliases: &[],
doc: "Open the helix config.toml file.",
fun: open_config,
completer: None,
},
]; ];
pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> = pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =

@ -1,6 +1,7 @@
use crate::keymap::{merge_keys, Keymaps};
use anyhow::{Error, Result};
use serde::Deserialize; use serde::Deserialize;
use std::path::PathBuf;
use crate::keymap::Keymaps;
#[derive(Debug, Default, Clone, PartialEq, Deserialize)] #[derive(Debug, Default, Clone, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
@ -20,6 +21,25 @@ pub struct LspConfig {
pub display_messages: bool, pub display_messages: bool,
} }
impl Config {
pub fn load(config_path: PathBuf) -> Result<Config, Error> {
match std::fs::read_to_string(config_path) {
Ok(config) => Result::Ok(toml::from_str(&config)
.map(merge_keys)
.unwrap_or_else(|err| {
eprintln!("Bad config: {}", err);
eprintln!("Press <ENTER> to continue with default config");
use std::io::Read;
// This waits for an enter press.
let _ = std::io::stdin().read(&mut []);
Config::default()
})),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Result::Ok(Config::default()),
Err(err) => return Err(Error::new(err)),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

@ -2,7 +2,6 @@ use anyhow::{Context, Error, Result};
use helix_term::application::Application; use helix_term::application::Application;
use helix_term::args::Args; use helix_term::args::Args;
use helix_term::config::Config; use helix_term::config::Config;
use helix_term::keymap::merge_keys;
use std::path::PathBuf; use std::path::PathBuf;
fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> { fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
@ -118,20 +117,7 @@ FLAGS:
std::fs::create_dir_all(&conf_dir).ok(); std::fs::create_dir_all(&conf_dir).ok();
} }
let config = match std::fs::read_to_string(helix_loader::config_file()) { let config = Config::load(helix_loader::config_file())?;
Ok(config) => toml::from_str(&config)
.map(merge_keys)
.unwrap_or_else(|err| {
eprintln!("Bad config: {}", err);
eprintln!("Press <ENTER> to continue with default config");
use std::io::Read;
// This waits for an enter press.
let _ = std::io::stdin().read(&mut []);
Config::default()
}),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(),
Err(err) => return Err(Error::new(err)),
};
setup_logging(logpath, args.verbosity).context("failed to initialize logging")?; setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;

@ -118,7 +118,7 @@ impl EditorView {
let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused { let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
Box::new(syntax::merge( Box::new(syntax::merge(
highlights, highlights,
Self::doc_selection_highlights(doc, view, theme, &editor.config.cursor_shape), Self::doc_selection_highlights(doc, view, theme, &editor.config.load().cursor_shape),
)) ))
} else { } else {
Box::new(highlights) Box::new(highlights)
@ -846,7 +846,7 @@ impl EditorView {
pub fn handle_idle_timeout(&mut self, cx: &mut crate::compositor::Context) -> EventResult { pub fn handle_idle_timeout(&mut self, cx: &mut crate::compositor::Context) -> EventResult {
if self.completion.is_some() if self.completion.is_some()
|| !cx.editor.config.auto_completion || !cx.editor.config.load().auto_completion
|| doc!(cx.editor).mode != Mode::Insert || doc!(cx.editor).mode != Mode::Insert
{ {
return EventResult::Ignored(None); return EventResult::Ignored(None);
@ -872,6 +872,7 @@ impl EditorView {
event: MouseEvent, event: MouseEvent,
cxt: &mut commands::Context, cxt: &mut commands::Context,
) -> EventResult { ) -> EventResult {
let config = cxt.editor.config.load();
match event { match event {
MouseEvent { MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left), kind: MouseEventKind::Down(MouseButton::Left),
@ -972,7 +973,7 @@ impl EditorView {
None => return EventResult::Ignored(None), None => return EventResult::Ignored(None),
} }
let offset = cxt.editor.config.scroll_lines.abs() as usize; let offset = config.scroll_lines.abs() as usize;
commands::scroll(cxt, offset, direction); commands::scroll(cxt, offset, direction);
cxt.editor.tree.focus = current_view; cxt.editor.tree.focus = current_view;
@ -984,7 +985,7 @@ impl EditorView {
kind: MouseEventKind::Up(MouseButton::Left), kind: MouseEventKind::Up(MouseButton::Left),
.. ..
} => { } => {
if !cxt.editor.config.middle_click_paste { if !config.middle_click_paste {
return EventResult::Ignored(None); return EventResult::Ignored(None);
} }
@ -1040,7 +1041,7 @@ impl EditorView {
.. ..
} => { } => {
let editor = &mut cxt.editor; let editor = &mut cxt.editor;
if !editor.config.middle_click_paste { if !config.middle_click_paste {
return EventResult::Ignored(None); return EventResult::Ignored(None);
} }
@ -1166,7 +1167,7 @@ impl Component for EditorView {
} }
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff); view.ensure_cursor_in_view(doc, cx.editor.config.load().scrolloff);
// Store a history state if not in insert mode. This also takes care of // Store a history state if not in insert mode. This also takes care of
// commiting changes when leaving insert mode. // commiting changes when leaving insert mode.
@ -1217,7 +1218,7 @@ impl Component for EditorView {
self.render_view(cx.editor, doc, view, area, surface, is_focused); self.render_view(cx.editor, doc, view, area, surface, is_focused);
} }
if cx.editor.config.auto_info { if cx.editor.config.load().auto_info {
if let Some(mut info) = cx.editor.autoinfo.take() { if let Some(mut info) = cx.editor.autoinfo.take() {
info.render(area, surface, cx); info.render(area, surface, cx);
cx.editor.autoinfo = Some(info) cx.editor.autoinfo = Some(info)

@ -37,6 +37,7 @@ pub fn regex_prompt(
let doc_id = view.doc; let doc_id = view.doc;
let snapshot = doc.selection(view.id).clone(); let snapshot = doc.selection(view.id).clone();
let offset_snapshot = view.offset; let offset_snapshot = view.offset;
let config = cx.editor.config.load();
let mut prompt = Prompt::new( let mut prompt = Prompt::new(
prompt, prompt,
@ -65,7 +66,7 @@ pub fn regex_prompt(
return; return;
} }
let case_insensitive = if cx.editor.config.search.smart_case { let case_insensitive = if config.search.smart_case {
!input.chars().any(char::is_uppercase) !input.chars().any(char::is_uppercase)
} else { } else {
false false
@ -84,7 +85,7 @@ pub fn regex_prompt(
fun(view, doc, regex, event); fun(view, doc, regex, event);
view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff); view.ensure_cursor_in_view(doc, config.scrolloff);
} }
Err(_err) => (), // TODO: mark command line as error Err(_err) => (), // TODO: mark command line as error
} }

@ -25,6 +25,8 @@ crossterm = { version = "0.23", optional = true }
once_cell = "1.10" once_cell = "1.10"
url = "2" url = "2"
arc-swap = { version = "1.5.0" }
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
tokio-stream = "0.1" tokio-stream = "0.1"
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }

@ -13,7 +13,6 @@ use futures_util::future;
use futures_util::stream::select_all::SelectAll; use futures_util::stream::select_all::SelectAll;
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
use log::debug;
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
@ -40,6 +39,8 @@ use helix_dap as dap;
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize}; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize};
use arc_swap::{access::{DynAccess}, ArcSwap};
fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error> fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
@ -271,6 +272,8 @@ pub struct Breakpoint {
pub log_message: Option<String>, pub log_message: Option<String>,
} }
pub trait DynAccessDebug<T>: DynAccess<T> + std::fmt::Debug {}
#[derive(Debug)] #[derive(Debug)]
pub struct Editor { pub struct Editor {
pub tree: Tree, pub tree: Tree,
@ -295,7 +298,7 @@ pub struct Editor {
pub status_msg: Option<(Cow<'static, str>, Severity)>, pub status_msg: Option<(Cow<'static, str>, Severity)>,
pub autoinfo: Option<Info>, pub autoinfo: Option<Info>,
pub config: Config, pub config: ArcSwap<Config>,
pub auto_pairs: Option<AutoPairs>, pub auto_pairs: Option<AutoPairs>,
pub idle_timer: Pin<Box<Sleep>>, pub idle_timer: Pin<Box<Sleep>>,
@ -305,8 +308,13 @@ pub struct Editor {
pub last_completion: Option<CompleteAction>, pub last_completion: Option<CompleteAction>,
pub exit_code: i32, pub exit_code: i32,
pub config_events: SelectAll<tokio_stream::Once<ConfigEvent>>,
} }
#[derive(Debug)]
pub struct ConfigEvent;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CompleteAction { pub struct CompleteAction {
pub trigger_offset: usize, pub trigger_offset: usize,
@ -326,12 +334,11 @@ impl Editor {
mut area: Rect, mut area: Rect,
theme_loader: Arc<theme::Loader>, theme_loader: Arc<theme::Loader>,
syn_loader: Arc<syntax::Loader>, syn_loader: Arc<syntax::Loader>,
config: Config, config: ArcSwap<Config>,
) -> Self { ) -> Self {
let language_servers = helix_lsp::Registry::new(); let language_servers = helix_lsp::Registry::new();
let auto_pairs = (&config.auto_pairs).into(); let conf = config.load();
let auto_pairs = (&conf.auto_pairs).into();
debug!("Editor config: {config:#?}");
// HAXX: offset the render area height by 1 to account for prompt/commandline // HAXX: offset the render area height by 1 to account for prompt/commandline
area.height -= 1; area.height -= 1;
@ -354,13 +361,14 @@ impl Editor {
clipboard_provider: get_clipboard_provider(), clipboard_provider: get_clipboard_provider(),
status_msg: None, status_msg: None,
autoinfo: None, autoinfo: None,
idle_timer: Box::pin(sleep(config.idle_timeout)), idle_timer: Box::pin(sleep(conf.idle_timeout)),
last_motion: None, last_motion: None,
last_completion: None, last_completion: None,
pseudo_pending: None, pseudo_pending: None,
config, config,
auto_pairs, auto_pairs,
exit_code: 0, exit_code: 0,
config_events: SelectAll::new(),
} }
} }
@ -374,7 +382,7 @@ impl Editor {
pub fn reset_idle_timer(&mut self) { pub fn reset_idle_timer(&mut self) {
self.idle_timer self.idle_timer
.as_mut() .as_mut()
.reset(Instant::now() + self.config.idle_timeout); .reset(Instant::now() + self.config.load().idle_timeout);
} }
pub fn clear_status(&mut self) { pub fn clear_status(&mut self) {
@ -452,7 +460,7 @@ impl Editor {
fn _refresh(&mut self) { fn _refresh(&mut self) {
for (view, _) in self.tree.views_mut() { for (view, _) in self.tree.views_mut() {
let doc = &self.documents[&view.doc]; let doc = &self.documents[&view.doc];
view.ensure_cursor_in_view(doc, self.config.scrolloff) view.ensure_cursor_in_view(doc, self.config.load().scrolloff)
} }
} }
@ -702,7 +710,7 @@ impl Editor {
pub fn ensure_cursor_in_view(&mut self, id: ViewId) { pub fn ensure_cursor_in_view(&mut self, id: ViewId) {
let view = self.tree.get_mut(id); let view = self.tree.get_mut(id);
let doc = &self.documents[&view.doc]; let doc = &self.documents[&view.doc];
view.ensure_cursor_in_view(doc, self.config.scrolloff) view.ensure_cursor_in_view(doc, self.config.load().scrolloff)
} }
#[inline] #[inline]
@ -745,7 +753,7 @@ impl Editor {
let inner = view.inner_area(); let inner = view.inner_area();
pos.col += inner.x as usize; pos.col += inner.x as usize;
pos.row += inner.y as usize; pos.row += inner.y as usize;
let cursorkind = self.config.cursor_shape.from_mode(doc.mode()); let cursorkind = self.config.load().cursor_shape.from_mode(doc.mode());
(Some(pos), cursorkind) (Some(pos), cursorkind)
} else { } else {
(None, CursorKind::default()) (None, CursorKind::default())

@ -60,7 +60,7 @@ pub fn line_number<'doc>(
.text() .text()
.char_to_line(doc.selection(view.id).primary().cursor(text)); .char_to_line(doc.selection(view.id).primary().cursor(text));
let config = editor.config.line_number; let config = editor.config.load().line_number;
let mode = doc.mode; let mode = doc.mode;
Box::new(move |line: usize, selected: bool, out: &mut String| { Box::new(move |line: usize, selected: bool, out: &mut String| {

Loading…
Cancel
Save