mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 17:36:19 +04:00
Merge remote-tracking branch 'origin/master' into debug
This commit is contained in:
commit
c7759a5aa0
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -19,9 +19,9 @@ checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "1.3.0"
|
version = "1.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e906254e445520903e7fc9da4f709886c84ae4bc4ddaf0e093188d66df4dc820"
|
checksum = "34a23efe54373080cf871532e2d01076be41c4c896d32ef63af1b2dded924b03"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
@ -114,9 +114,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.20.0"
|
version = "0.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d"
|
checksum = "486d44227f71a1ef39554c0dc47e44b9f4139927c75043312690c3f476d1d788"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
@ -810,18 +810,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.127"
|
version = "1.0.129"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8"
|
checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.127"
|
version = "1.0.129"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc"
|
checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
pub mod match_brackets;
|
pub mod match_brackets;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
pub mod object;
|
pub mod object;
|
||||||
|
pub mod path;
|
||||||
mod position;
|
mod position;
|
||||||
pub mod register;
|
pub mod register;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
92
helix-core/src/path.rs
Normal file
92
helix-core/src/path.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
|
||||||
|
/// Replaces users home directory from `path` with tilde `~` if the directory
|
||||||
|
/// is available, otherwise returns the path unchanged.
|
||||||
|
pub fn fold_home_dir(path: &Path) -> PathBuf {
|
||||||
|
if let Ok(home) = super::home_dir() {
|
||||||
|
if path.starts_with(&home) {
|
||||||
|
// it's ok to unwrap, the path starts with home dir
|
||||||
|
return PathBuf::from("~").join(path.strip_prefix(&home).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path.to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands tilde `~` into users home directory if avilable, otherwise returns the path
|
||||||
|
/// unchanged. The tilde will only be expanded when present as the first component of the path
|
||||||
|
/// and only slash follows it.
|
||||||
|
pub fn expand_tilde(path: &Path) -> PathBuf {
|
||||||
|
let mut components = path.components().peekable();
|
||||||
|
if let Some(Component::Normal(c)) = components.peek() {
|
||||||
|
if c == &"~" {
|
||||||
|
if let Ok(home) = super::home_dir() {
|
||||||
|
// it's ok to unwrap, the path starts with `~`
|
||||||
|
return home.join(path.strip_prefix("~").unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path.to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalize a path, removing things like `.` and `..`.
|
||||||
|
///
|
||||||
|
/// CAUTION: This does not resolve symlinks (unlike
|
||||||
|
/// [`std::fs::canonicalize`]). This may cause incorrect or surprising
|
||||||
|
/// behavior at times. This should be used carefully. Unfortunately,
|
||||||
|
/// [`std::fs::canonicalize`] can be hard to use correctly, since it can often
|
||||||
|
/// fail, or on Windows returns annoying device paths. This is a problem Cargo
|
||||||
|
/// needs to improve on.
|
||||||
|
/// Copied from cargo: <https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81>
|
||||||
|
pub fn get_normalized_path(path: &Path) -> PathBuf {
|
||||||
|
let path = expand_tilde(path);
|
||||||
|
let mut components = path.components().peekable();
|
||||||
|
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
|
||||||
|
components.next();
|
||||||
|
PathBuf::from(c.as_os_str())
|
||||||
|
} else {
|
||||||
|
PathBuf::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
for component in components {
|
||||||
|
match component {
|
||||||
|
Component::Prefix(..) => unreachable!(),
|
||||||
|
Component::RootDir => {
|
||||||
|
ret.push(component.as_os_str());
|
||||||
|
}
|
||||||
|
Component::CurDir => {}
|
||||||
|
Component::ParentDir => {
|
||||||
|
ret.pop();
|
||||||
|
}
|
||||||
|
Component::Normal(c) => {
|
||||||
|
ret.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the canonical, absolute form of a path with all intermediate components normalized.
|
||||||
|
///
|
||||||
|
/// This function is used instead of `std::fs::canonicalize` because we don't want to verify
|
||||||
|
/// here if the path exists, just normalize it's components.
|
||||||
|
pub fn get_canonicalized_path(path: &Path) -> std::io::Result<PathBuf> {
|
||||||
|
let path = if path.is_relative() {
|
||||||
|
std::env::current_dir().map(|current_dir| current_dir.join(path))?
|
||||||
|
} else {
|
||||||
|
path.to_path_buf()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(get_normalized_path(path.as_path()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_relative_path(path: &Path) -> PathBuf {
|
||||||
|
let path = if path.is_absolute() {
|
||||||
|
let cwdir = std::env::current_dir().expect("couldn't determine current directory");
|
||||||
|
path.strip_prefix(cwdir).unwrap_or(path)
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
};
|
||||||
|
fold_home_dir(path)
|
||||||
|
}
|
@ -222,10 +222,13 @@ async fn recv(
|
|||||||
loop {
|
loop {
|
||||||
match Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await {
|
match Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await {
|
||||||
Ok(msg) => {
|
Ok(msg) => {
|
||||||
transport
|
match transport.process_server_message(&client_tx, msg).await {
|
||||||
.process_server_message(&client_tx, msg)
|
Ok(_) => {}
|
||||||
.await
|
Err(err) => {
|
||||||
.unwrap();
|
error!("err: <- {:?}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("err: <- {:?}", err);
|
error!("err: <- {:?}", err);
|
||||||
@ -254,10 +257,15 @@ async fn send(
|
|||||||
mut client_rx: UnboundedReceiver<Payload>,
|
mut client_rx: UnboundedReceiver<Payload>,
|
||||||
) {
|
) {
|
||||||
while let Some(msg) = client_rx.recv().await {
|
while let Some(msg) = client_rx.recv().await {
|
||||||
transport
|
match transport
|
||||||
.send_payload_to_server(&mut server_stdin, msg)
|
.send_payload_to_server(&mut server_stdin, msg)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
error!("err: <- {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ once_cell = "1.8"
|
|||||||
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"] }
|
||||||
num_cpus = "1"
|
num_cpus = "1"
|
||||||
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
|
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
|
||||||
crossterm = { version = "0.20", features = ["event-stream"] }
|
crossterm = { version = "0.21", 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 }
|
||||||
|
@ -2006,7 +2006,24 @@ fn debug_breakpoint_condition(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_set_logpoint(
|
fn vsplit(
|
||||||
|
cx: &mut compositor::Context,
|
||||||
|
args: &[&str],
|
||||||
|
_event: PromptEvent,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let (_, doc) = current!(cx.editor);
|
||||||
|
let id = doc.id();
|
||||||
|
|
||||||
|
if let Some(path) = args.get(0) {
|
||||||
|
cx.editor.open(path.into(), Action::VerticalSplit)?;
|
||||||
|
} else {
|
||||||
|
cx.editor.switch(id, Action::VerticalSplit);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hsplit(
|
||||||
cx: &mut compositor::Context,
|
cx: &mut compositor::Context,
|
||||||
args: &[&str],
|
args: &[&str],
|
||||||
_event: PromptEvent,
|
_event: PromptEvent,
|
||||||
@ -2051,6 +2068,24 @@ fn debug_remote(
|
|||||||
_ => Some(args.remove(0)),
|
_ => Some(args.remove(0)),
|
||||||
};
|
};
|
||||||
dap_start_impl(&mut cx.editor, name, address, Some(args));
|
dap_start_impl(&mut cx.editor, name, address, Some(args));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_set_logpoint(
|
||||||
|
cx: &mut compositor::Context,
|
||||||
|
args: &[&str],
|
||||||
|
_event: PromptEvent,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let (_, doc) = current!(cx.editor);
|
||||||
|
let id = doc.id();
|
||||||
|
|
||||||
|
if let Some(path) = args.get(0) {
|
||||||
|
cx.editor.open(path.into(), Action::HorizontalSplit)?;
|
||||||
|
} else {
|
||||||
|
cx.editor.switch(id, Action::HorizontalSplit);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2327,6 +2362,20 @@ fn debug_remote(
|
|||||||
doc: "Make current breakpoint a log point.",
|
doc: "Make current breakpoint a log point.",
|
||||||
fun: debug_set_logpoint,
|
fun: debug_set_logpoint,
|
||||||
completer: None,
|
completer: None,
|
||||||
|
},
|
||||||
|
TypableCommand {
|
||||||
|
name: "vsplit",
|
||||||
|
alias: Some("vsp"),
|
||||||
|
doc: "Open the file in a vertical split.",
|
||||||
|
fun: vsplit,
|
||||||
|
completer: Some(completers::filename),
|
||||||
|
},
|
||||||
|
TypableCommand {
|
||||||
|
name: "hsplit",
|
||||||
|
alias: Some("sp"),
|
||||||
|
doc: "Open the file in a horizontal split.",
|
||||||
|
fun: hsplit,
|
||||||
|
completer: Some(completers::filename),
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -2429,16 +2478,16 @@ fn buffer_picker(cx: &mut Context) {
|
|||||||
cx.editor
|
cx.editor
|
||||||
.documents
|
.documents
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, doc)| (id, doc.relative_path()))
|
.map(|(id, doc)| (id, doc.path().cloned()))
|
||||||
.collect(),
|
.collect(),
|
||||||
move |(id, path): &(DocumentId, Option<PathBuf>)| {
|
move |(id, path): &(DocumentId, Option<PathBuf>)| {
|
||||||
// format_fn
|
let path = path.as_deref().map(helix_core::path::get_relative_path);
|
||||||
match path.as_ref().and_then(|path| path.to_str()) {
|
match path.as_ref().and_then(|path| path.to_str()) {
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
if *id == current {
|
if *id == current {
|
||||||
format!("{} (*)", path).into()
|
format!("{} (*)", &path).into()
|
||||||
} else {
|
} else {
|
||||||
path.into()
|
path.to_owned().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => "[scratch buffer]".into(),
|
None => "[scratch buffer]".into(),
|
||||||
@ -2454,7 +2503,7 @@ fn buffer_picker(cx: &mut Context) {
|
|||||||
.selection(view_id)
|
.selection(view_id)
|
||||||
.primary()
|
.primary()
|
||||||
.cursor_line(doc.text().slice(..));
|
.cursor_line(doc.text().slice(..));
|
||||||
Some((path.clone()?, Some(line)))
|
Some((path.clone()?, Some((line, line))))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
cx.push_layer(Box::new(picker));
|
cx.push_layer(Box::new(picker));
|
||||||
@ -2519,13 +2568,18 @@ fn nested_to_flat(
|
|||||||
if let Some(range) =
|
if let Some(range) =
|
||||||
lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding)
|
lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding)
|
||||||
{
|
{
|
||||||
doc.set_selection(view.id, Selection::single(range.anchor, range.head));
|
// we flip the range so that the cursor sits on the start of the symbol
|
||||||
|
// (for example start of the function).
|
||||||
|
doc.set_selection(view.id, Selection::single(range.head, range.anchor));
|
||||||
align_view(doc, view, Align::Center);
|
align_view(doc, view, Align::Center);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
move |_editor, symbol| {
|
move |_editor, symbol| {
|
||||||
let path = symbol.location.uri.to_file_path().unwrap();
|
let path = symbol.location.uri.to_file_path().unwrap();
|
||||||
let line = Some(symbol.location.range.start.line as usize);
|
let line = Some((
|
||||||
|
symbol.location.range.start.line as usize,
|
||||||
|
symbol.location.range.end.line as usize,
|
||||||
|
));
|
||||||
Some((path, line))
|
Some((path, line))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -2958,7 +3012,10 @@ fn jump_to(
|
|||||||
},
|
},
|
||||||
|_editor, location| {
|
|_editor, location| {
|
||||||
let path = location.uri.to_file_path().unwrap();
|
let path = location.uri.to_file_path().unwrap();
|
||||||
let line = Some(location.range.start.line as usize);
|
let line = Some((
|
||||||
|
location.range.start.line as usize,
|
||||||
|
location.range.end.line as usize,
|
||||||
|
));
|
||||||
Some((path, line))
|
Some((path, line))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -112,13 +112,11 @@ pub fn render_view(
|
|||||||
|
|
||||||
self.render_diagnostics(doc, view, inner, surface, theme, debugger);
|
self.render_diagnostics(doc, view, inner, surface, theme, debugger);
|
||||||
|
|
||||||
let area = Rect::new(
|
let statusline_area = view
|
||||||
view.area.x,
|
.area
|
||||||
view.area.y + view.area.height.saturating_sub(1),
|
.clip_top(view.area.height.saturating_sub(1))
|
||||||
view.area.width,
|
.clip_bottom(1); // -1 from bottom to remove commandline
|
||||||
1,
|
self.render_statusline(doc, view, statusline_area, surface, theme, is_focused);
|
||||||
);
|
|
||||||
self.render_statusline(doc, view, area, surface, theme, is_focused);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get syntax highlights for a document in a view represented by the first line
|
/// Get syntax highlights for a document in a view represented by the first line
|
||||||
@ -201,7 +199,9 @@ pub fn doc_diagnostics_highlights(
|
|||||||
.find_scope_index("diagnostic")
|
.find_scope_index("diagnostic")
|
||||||
.or_else(|| theme.find_scope_index("ui.cursor"))
|
.or_else(|| theme.find_scope_index("ui.cursor"))
|
||||||
.or_else(|| theme.find_scope_index("ui.selection"))
|
.or_else(|| theme.find_scope_index("ui.selection"))
|
||||||
.expect("no selection scope found!");
|
.expect(
|
||||||
|
"at least one of the following scopes must be defined in the theme: `diagnostic`, `ui.cursor`, or `ui.selection`",
|
||||||
|
);
|
||||||
|
|
||||||
doc.diagnostics()
|
doc.diagnostics()
|
||||||
.iter()
|
.iter()
|
||||||
@ -226,7 +226,7 @@ pub fn doc_selection_highlights(
|
|||||||
|
|
||||||
let selection_scope = theme
|
let selection_scope = theme
|
||||||
.find_scope_index("ui.selection")
|
.find_scope_index("ui.selection")
|
||||||
.expect("no selection scope found!");
|
.expect("could not find `ui.selection` scope in the theme!");
|
||||||
let base_cursor_scope = theme
|
let base_cursor_scope = theme
|
||||||
.find_scope_index("ui.cursor")
|
.find_scope_index("ui.cursor")
|
||||||
.unwrap_or(selection_scope);
|
.unwrap_or(selection_scope);
|
||||||
@ -598,12 +598,7 @@ pub fn render_diagnostics(
|
|||||||
let width = 80.min(viewport.width);
|
let width = 80.min(viewport.width);
|
||||||
let height = 15.min(viewport.height);
|
let height = 15.min(viewport.height);
|
||||||
paragraph.render(
|
paragraph.render(
|
||||||
Rect::new(
|
Rect::new(viewport.right() - width, viewport.y + 1, width, height),
|
||||||
viewport.right() - width,
|
|
||||||
viewport.y as u16 + 1,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
),
|
|
||||||
surface,
|
surface,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -642,7 +637,7 @@ pub fn render_statusline(
|
|||||||
theme.get("ui.statusline.inactive")
|
theme.get("ui.statusline.inactive")
|
||||||
};
|
};
|
||||||
// statusline
|
// statusline
|
||||||
surface.set_style(Rect::new(viewport.x, viewport.y, viewport.width, 1), style);
|
surface.set_style(viewport.with_height(1), style);
|
||||||
if is_focused {
|
if is_focused {
|
||||||
surface.set_string(viewport.x + 1, viewport.y, mode, style);
|
surface.set_string(viewport.x + 1, viewport.y, mode, style);
|
||||||
}
|
}
|
||||||
@ -1071,8 +1066,7 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
|||||||
surface.set_style(area, cx.editor.theme.get("ui.background"));
|
surface.set_style(area, cx.editor.theme.get("ui.background"));
|
||||||
|
|
||||||
// if the terminal size suddenly changed, we need to trigger a resize
|
// if the terminal size suddenly changed, we need to trigger a resize
|
||||||
cx.editor
|
cx.editor.resize(area.clip_bottom(1)); // -1 from bottom for commandline
|
||||||
.resize(Rect::new(area.x, area.y, area.width, area.height - 1)); // - 1 to account for commandline
|
|
||||||
|
|
||||||
for (view, is_focused) in cx.editor.tree.views() {
|
for (view, is_focused) in cx.editor.tree.views() {
|
||||||
let doc = cx.editor.document(view.doc).unwrap();
|
let doc = cx.editor.document(view.doc).unwrap();
|
||||||
|
@ -6,8 +6,12 @@
|
|||||||
|
|
||||||
impl Component for Info {
|
impl Component for Info {
|
||||||
fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
|
fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
|
||||||
let text_style = cx.editor.theme.get("ui.text.focus");
|
let get_theme = |style, fallback| {
|
||||||
let popup_style = text_style.patch(cx.editor.theme.get("ui.popup"));
|
let theme = &cx.editor.theme;
|
||||||
|
theme.try_get(style).unwrap_or_else(|| theme.get(fallback))
|
||||||
|
};
|
||||||
|
let text_style = get_theme("ui.info.text", "ui.text");
|
||||||
|
let popup_style = text_style.patch(get_theme("ui.info", "ui.popup"));
|
||||||
|
|
||||||
// Calculate the area of the terminal to modify. Because we want to
|
// Calculate the area of the terminal to modify. Because we want to
|
||||||
// render at the bottom right, we use the viewport's width and height
|
// render at the bottom right, we use the viewport's width and height
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
Rope,
|
Rope,
|
||||||
};
|
};
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
graphics::{Color, Rect, Style},
|
graphics::{Color, Margin, Rect, Style},
|
||||||
Theme,
|
Theme,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -207,8 +207,11 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
|||||||
.wrap(Wrap { trim: false })
|
.wrap(Wrap { trim: false })
|
||||||
.scroll((cx.scroll.unwrap_or_default() as u16, 0));
|
.scroll((cx.scroll.unwrap_or_default() as u16, 0));
|
||||||
|
|
||||||
let area = Rect::new(area.x + 1, area.y + 1, area.width - 2, area.height - 2);
|
let margin = Margin {
|
||||||
par.render(area, surface);
|
vertical: 1,
|
||||||
|
horizontal: 1,
|
||||||
|
};
|
||||||
|
par.render(area.inner(&margin), surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
||||||
|
@ -304,14 +304,6 @@ const fn div_ceil(a: usize, b: usize) -> usize {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// // TODO: set bg for the whole row if selected
|
|
||||||
// if line == self.cursor {
|
|
||||||
// surface.set_style(
|
|
||||||
// Rect::new(area.x, area.y + i as u16, area.width - 1, 1),
|
|
||||||
// selected,
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
for (i, _) in (scroll..(scroll + win_height).min(len)).enumerate() {
|
for (i, _) in (scroll..(scroll + win_height).min(len)).enumerate() {
|
||||||
let is_marked = i >= scroll_line && i < scroll_line + scroll_height;
|
let is_marked = i >= scroll_line && i < scroll_line + scroll_height;
|
||||||
|
|
||||||
|
@ -208,7 +208,7 @@ fn filename_impl<F>(input: &str, filter_fn: F) -> Vec<Completion>
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
let is_tilde = input.starts_with('~') && input.len() == 1;
|
let is_tilde = input.starts_with('~') && input.len() == 1;
|
||||||
let path = helix_view::document::expand_tilde(Path::new(input));
|
let path = helix_core::path::expand_tilde(Path::new(input));
|
||||||
|
|
||||||
let (dir, file_name) = if input.ends_with('/') {
|
let (dir, file_name) = if input.ends_with('/') {
|
||||||
(path, None)
|
(path, None)
|
||||||
|
@ -17,16 +17,15 @@
|
|||||||
use crate::ui::{Prompt, PromptEvent};
|
use crate::ui::{Prompt, PromptEvent};
|
||||||
use helix_core::Position;
|
use helix_core::Position;
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
document::canonicalize_path,
|
|
||||||
editor::Action,
|
editor::Action,
|
||||||
graphics::{Color, CursorKind, Rect, Style},
|
graphics::{Color, CursorKind, Margin, Rect, Style},
|
||||||
Document, Editor,
|
Document, Editor,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const MIN_SCREEN_WIDTH_FOR_PREVIEW: u16 = 80;
|
pub const MIN_SCREEN_WIDTH_FOR_PREVIEW: u16 = 80;
|
||||||
|
|
||||||
/// File path and line number (used to align and highlight a line)
|
/// File path and line number (used to align and highlight a line)
|
||||||
type FileLocation = (PathBuf, Option<usize>);
|
type FileLocation = (PathBuf, Option<(usize, usize)>);
|
||||||
|
|
||||||
pub struct FilePicker<T> {
|
pub struct FilePicker<T> {
|
||||||
picker: Picker<T>,
|
picker: Picker<T>,
|
||||||
@ -54,7 +53,11 @@ fn current_file(&self, editor: &Editor) -> Option<FileLocation> {
|
|||||||
self.picker
|
self.picker
|
||||||
.selection()
|
.selection()
|
||||||
.and_then(|current| (self.file_fn)(editor, current))
|
.and_then(|current| (self.file_fn)(editor, current))
|
||||||
.and_then(|(path, line)| canonicalize_path(&path).ok().zip(Some(line)))
|
.and_then(|(path, line)| {
|
||||||
|
helix_core::path::get_canonicalized_path(&path)
|
||||||
|
.ok()
|
||||||
|
.zip(Some(line))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_preview(&mut self, editor: &Editor) {
|
fn calculate_preview(&mut self, editor: &Editor) {
|
||||||
@ -90,34 +93,40 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
|||||||
area.width
|
area.width
|
||||||
};
|
};
|
||||||
|
|
||||||
let picker_area = Rect::new(area.x, area.y, picker_width, area.height);
|
let picker_area = area.with_width(picker_width);
|
||||||
self.picker.render(picker_area, surface, cx);
|
self.picker.render(picker_area, surface, cx);
|
||||||
|
|
||||||
if !render_preview {
|
if !render_preview {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let preview_area = Rect::new(area.x + picker_width, area.y, area.width / 2, area.height);
|
let preview_area = area.clip_left(picker_width);
|
||||||
|
|
||||||
// don't like this but the lifetime sucks
|
// don't like this but the lifetime sucks
|
||||||
let block = Block::default().borders(Borders::ALL);
|
let block = Block::default().borders(Borders::ALL);
|
||||||
|
|
||||||
// calculate the inner area inside the box
|
// calculate the inner area inside the box
|
||||||
let mut inner = block.inner(preview_area);
|
let inner = block.inner(preview_area);
|
||||||
// 1 column gap on either side
|
// 1 column gap on either side
|
||||||
inner.x += 1;
|
let margin = Margin {
|
||||||
inner.width = inner.width.saturating_sub(2);
|
vertical: 0,
|
||||||
|
horizontal: 1,
|
||||||
|
};
|
||||||
|
let inner = inner.inner(&margin);
|
||||||
|
|
||||||
block.render(preview_area, surface);
|
block.render(preview_area, surface);
|
||||||
|
|
||||||
if let Some((doc, line)) = self.current_file(cx.editor).and_then(|(path, line)| {
|
if let Some((doc, line)) = self.current_file(cx.editor).and_then(|(path, range)| {
|
||||||
cx.editor
|
cx.editor
|
||||||
.document_by_path(&path)
|
.document_by_path(&path)
|
||||||
.or_else(|| self.preview_cache.get(&path))
|
.or_else(|| self.preview_cache.get(&path))
|
||||||
.zip(Some(line))
|
.zip(Some(range))
|
||||||
}) {
|
}) {
|
||||||
// align to middle
|
// align to middle
|
||||||
let first_line = line.unwrap_or(0).saturating_sub(inner.height as usize / 2);
|
let first_line = line
|
||||||
|
.map(|(start, _)| start)
|
||||||
|
.unwrap_or(0)
|
||||||
|
.saturating_sub(inner.height as usize / 2);
|
||||||
let offset = Position::new(first_line, 0);
|
let offset = Position::new(first_line, 0);
|
||||||
|
|
||||||
let highlights = EditorView::doc_syntax_highlights(
|
let highlights = EditorView::doc_syntax_highlights(
|
||||||
@ -137,12 +146,21 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// highlight the line
|
// highlight the line
|
||||||
if let Some(line) = line {
|
if let Some((start, end)) = line {
|
||||||
for x in inner.left()..inner.right() {
|
let offset = start.saturating_sub(first_line) as u16;
|
||||||
surface
|
surface.set_style(
|
||||||
.get_mut(x, inner.y + line.saturating_sub(first_line) as u16)
|
Rect::new(
|
||||||
.set_style(cx.editor.theme.get("ui.selection"));
|
inner.x,
|
||||||
}
|
inner.y + offset,
|
||||||
|
inner.width,
|
||||||
|
(end.saturating_sub(start) as u16 + 1)
|
||||||
|
.min(inner.height.saturating_sub(offset)),
|
||||||
|
),
|
||||||
|
cx.editor
|
||||||
|
.theme
|
||||||
|
.try_get("ui.highlight")
|
||||||
|
.unwrap_or_else(|| cx.editor.theme.get("ui.selection")),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,15 +300,11 @@ pub fn save_filter(&mut self) {
|
|||||||
// - score all the names in relation to input
|
// - score all the names in relation to input
|
||||||
|
|
||||||
fn inner_rect(area: Rect) -> Rect {
|
fn inner_rect(area: Rect) -> Rect {
|
||||||
let padding_vertical = area.height * 10 / 100;
|
let margin = Margin {
|
||||||
let padding_horizontal = area.width * 10 / 100;
|
vertical: area.height * 10 / 100,
|
||||||
|
horizontal: area.width * 10 / 100,
|
||||||
Rect::new(
|
};
|
||||||
area.x + padding_horizontal,
|
area.inner(&margin)
|
||||||
area.y + padding_vertical,
|
|
||||||
area.width - padding_horizontal * 2,
|
|
||||||
area.height - padding_vertical * 2,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> Component for Picker<T> {
|
impl<T: 'static> Component for Picker<T> {
|
||||||
@ -410,7 +424,7 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
|||||||
|
|
||||||
// -- Render the input bar:
|
// -- Render the input bar:
|
||||||
|
|
||||||
let area = Rect::new(inner.x + 1, inner.y, inner.width - 1, 1);
|
let area = inner.clip_left(1).with_height(1);
|
||||||
|
|
||||||
let count = format!("{}/{}", self.matches.len(), self.options.len());
|
let count = format!("{}/{}", self.matches.len(), self.options.len());
|
||||||
surface.set_stringn(
|
surface.set_stringn(
|
||||||
@ -434,8 +448,8 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -- Render the contents:
|
// -- Render the contents:
|
||||||
// subtract the area of the prompt (-2) and current item marker " > " (-3)
|
// subtract area of prompt from top and current item marker " > " from left
|
||||||
let inner = Rect::new(inner.x + 3, inner.y + 2, inner.width - 3, inner.height - 2);
|
let inner = inner.clip_top(2).clip_left(3);
|
||||||
|
|
||||||
let selected = cx.editor.theme.get("ui.text.focus");
|
let selected = cx.editor.theme.get("ui.text.focus");
|
||||||
|
|
||||||
@ -474,7 +488,7 @@ fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind)
|
|||||||
let inner = block.inner(area);
|
let inner = block.inner(area);
|
||||||
|
|
||||||
// prompt area
|
// prompt area
|
||||||
let area = Rect::new(inner.x + 1, inner.y, inner.width - 1, 1);
|
let area = inner.clip_left(1).with_height(1);
|
||||||
|
|
||||||
self.prompt.cursor(area, editor)
|
self.prompt.cursor(area, editor)
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ default = ["crossterm"]
|
|||||||
bitflags = "1.3"
|
bitflags = "1.3"
|
||||||
cassowary = "0.3"
|
cassowary = "0.3"
|
||||||
unicode-segmentation = "1.8"
|
unicode-segmentation = "1.8"
|
||||||
crossterm = { version = "0.20", optional = true }
|
crossterm = { version = "0.21", optional = true }
|
||||||
serde = { version = "1", "optional" = true, features = ["derive"]}
|
serde = { version = "1", "optional" = true, features = ["derive"]}
|
||||||
helix-view = { version = "0.4", path = "../helix-view", features = ["term"] }
|
helix-view = { version = "0.4", path = "../helix-view", features = ["term"] }
|
||||||
helix-core = { version = "0.4", path = "../helix-core" }
|
helix-core = { version = "0.4", path = "../helix-core" }
|
||||||
|
@ -19,7 +19,7 @@ anyhow = "1"
|
|||||||
helix-core = { version = "0.4", path = "../helix-core" }
|
helix-core = { version = "0.4", path = "../helix-core" }
|
||||||
helix-lsp = { version = "0.4", path = "../helix-lsp"}
|
helix-lsp = { version = "0.4", path = "../helix-lsp"}
|
||||||
helix-dap = { version = "0.4", path = "../helix-dap"}
|
helix-dap = { version = "0.4", path = "../helix-dap"}
|
||||||
crossterm = { version = "0.20", optional = true }
|
crossterm = { version = "0.21", optional = true }
|
||||||
|
|
||||||
# Conversion traits
|
# Conversion traits
|
||||||
once_cell = "1.8"
|
once_cell = "1.8"
|
||||||
|
@ -84,7 +84,7 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
|
|||||||
// FIXME: check performance of is_exit_success
|
// FIXME: check performance of is_exit_success
|
||||||
command_provider! {
|
command_provider! {
|
||||||
paste => "xsel", "-o", "-b";
|
paste => "xsel", "-o", "-b";
|
||||||
copy => "xsel", "--nodetach", "-i", "-b";
|
copy => "xsel", "-i", "-b";
|
||||||
primary_paste => "xsel", "-o";
|
primary_paste => "xsel", "-o";
|
||||||
primary_copy => "xsel", "-i";
|
primary_copy => "xsel", "-i";
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -317,87 +317,6 @@ fn take_with<T, F>(mut_ref: &mut T, closure: F)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expands tilde `~` into users home directory if avilable, otherwise returns the path
|
|
||||||
/// unchanged. The tilde will only be expanded when present as the first component of the path
|
|
||||||
/// and only slash follows it.
|
|
||||||
pub fn expand_tilde(path: &Path) -> PathBuf {
|
|
||||||
let mut components = path.components().peekable();
|
|
||||||
if let Some(Component::Normal(c)) = components.peek() {
|
|
||||||
if c == &"~" {
|
|
||||||
if let Ok(home) = helix_core::home_dir() {
|
|
||||||
// it's ok to unwrap, the path starts with `~`
|
|
||||||
return home.join(path.strip_prefix("~").unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
path.to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replaces users home directory from `path` with tilde `~` if the directory
|
|
||||||
/// is available, otherwise returns the path unchanged.
|
|
||||||
pub fn fold_home_dir(path: &Path) -> PathBuf {
|
|
||||||
if let Ok(home) = helix_core::home_dir() {
|
|
||||||
if path.starts_with(&home) {
|
|
||||||
// it's ok to unwrap, the path starts with home dir
|
|
||||||
return PathBuf::from("~").join(path.strip_prefix(&home).unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
path.to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Normalize a path, removing things like `.` and `..`.
|
|
||||||
///
|
|
||||||
/// CAUTION: This does not resolve symlinks (unlike
|
|
||||||
/// [`std::fs::canonicalize`]). This may cause incorrect or surprising
|
|
||||||
/// behavior at times. This should be used carefully. Unfortunately,
|
|
||||||
/// [`std::fs::canonicalize`] can be hard to use correctly, since it can often
|
|
||||||
/// fail, or on Windows returns annoying device paths. This is a problem Cargo
|
|
||||||
/// needs to improve on.
|
|
||||||
/// Copied from cargo: <https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81>
|
|
||||||
pub fn normalize_path(path: &Path) -> PathBuf {
|
|
||||||
let path = expand_tilde(path);
|
|
||||||
let mut components = path.components().peekable();
|
|
||||||
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
|
|
||||||
components.next();
|
|
||||||
PathBuf::from(c.as_os_str())
|
|
||||||
} else {
|
|
||||||
PathBuf::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
for component in components {
|
|
||||||
match component {
|
|
||||||
Component::Prefix(..) => unreachable!(),
|
|
||||||
Component::RootDir => {
|
|
||||||
ret.push(component.as_os_str());
|
|
||||||
}
|
|
||||||
Component::CurDir => {}
|
|
||||||
Component::ParentDir => {
|
|
||||||
ret.pop();
|
|
||||||
}
|
|
||||||
Component::Normal(c) => {
|
|
||||||
ret.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the canonical, absolute form of a path with all intermediate components normalized.
|
|
||||||
///
|
|
||||||
/// This function is used instead of `std::fs::canonicalize` because we don't want to verify
|
|
||||||
/// here if the path exists, just normalize it's components.
|
|
||||||
pub fn canonicalize_path(path: &Path) -> std::io::Result<PathBuf> {
|
|
||||||
let path = if path.is_relative() {
|
|
||||||
std::env::current_dir().map(|current_dir| current_dir.join(path))?
|
|
||||||
} else {
|
|
||||||
path.to_path_buf()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(normalize_path(&path))
|
|
||||||
}
|
|
||||||
|
|
||||||
use helix_lsp::lsp;
|
use helix_lsp::lsp;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@ -631,7 +550,7 @@ pub fn encoding(&self) -> &'static encoding_rs::Encoding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_path(&mut self, path: &Path) -> Result<(), std::io::Error> {
|
pub fn set_path(&mut self, path: &Path) -> Result<(), std::io::Error> {
|
||||||
let path = canonicalize_path(path)?;
|
let path = helix_core::path::get_canonicalized_path(path)?;
|
||||||
|
|
||||||
// if parent doesn't exist we still want to open the document
|
// if parent doesn't exist we still want to open the document
|
||||||
// and error out when document is saved
|
// and error out when document is saved
|
||||||
@ -936,15 +855,9 @@ pub fn selections(&self) -> &HashMap<ViewId, Selection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn relative_path(&self) -> Option<PathBuf> {
|
pub fn relative_path(&self) -> Option<PathBuf> {
|
||||||
let cwdir = std::env::current_dir().expect("couldn't determine current directory");
|
self.path
|
||||||
|
.as_deref()
|
||||||
self.path.as_ref().map(|path| {
|
.map(helix_core::path::get_relative_path)
|
||||||
let mut path = path.as_path();
|
|
||||||
if path.is_absolute() {
|
|
||||||
path = path.strip_prefix(cwdir).unwrap_or(path)
|
|
||||||
};
|
|
||||||
fold_home_dir(path)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn slice<R>(&self, range: R) -> RopeSlice where R: RangeBounds {
|
// pub fn slice<R>(&self, range: R) -> RopeSlice where R: RangeBounds {
|
||||||
|
@ -238,7 +238,7 @@ pub fn new_file(&mut self, action: Action) -> DocumentId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(&mut self, path: PathBuf, action: Action) -> Result<DocumentId, Error> {
|
pub fn open(&mut self, path: PathBuf, action: Action) -> Result<DocumentId, Error> {
|
||||||
let path = crate::document::canonicalize_path(&path)?;
|
let path = helix_core::path::get_canonicalized_path(&path)?;
|
||||||
|
|
||||||
let id = self
|
let id = self
|
||||||
.documents()
|
.documents()
|
||||||
|
@ -24,7 +24,7 @@ pub struct Margin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A simple rectangle used in the computation of the layout and to give widgets an hint about the
|
/// A simple rectangle used in the computation of the layout and to give widgets an hint about the
|
||||||
/// area they are supposed to render to.
|
/// area they are supposed to render to. (x, y) = (0, 0) is at the top left corner of the screen.
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
pub x: u16,
|
pub x: u16,
|
||||||
@ -92,6 +92,57 @@ pub fn bottom(self) -> u16 {
|
|||||||
self.y.saturating_add(self.height)
|
self.y.saturating_add(self.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a new Rect with width reduced from the left side.
|
||||||
|
// This changes the `x` coordinate and clamps it to the right
|
||||||
|
// edge of the original Rect.
|
||||||
|
pub fn clip_left(self, width: u16) -> Rect {
|
||||||
|
let width = std::cmp::min(width, self.width);
|
||||||
|
Rect {
|
||||||
|
x: self.x.saturating_add(width),
|
||||||
|
width: self.width.saturating_sub(width),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new Rect with width reduced from the right side.
|
||||||
|
// This does _not_ change the `x` coordinate.
|
||||||
|
pub fn clip_right(self, width: u16) -> Rect {
|
||||||
|
Rect {
|
||||||
|
width: self.width.saturating_sub(width),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new Rect with height reduced from the top.
|
||||||
|
// This changes the `y` coordinate and clamps it to the bottom
|
||||||
|
// edge of the original Rect.
|
||||||
|
pub fn clip_top(self, height: u16) -> Rect {
|
||||||
|
let height = std::cmp::min(height, self.height);
|
||||||
|
Rect {
|
||||||
|
y: self.y.saturating_add(height),
|
||||||
|
height: self.height.saturating_sub(height),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new Rect with height reduced from the bottom.
|
||||||
|
// This does _not_ change the `y` coordinate.
|
||||||
|
pub fn clip_bottom(self, height: u16) -> Rect {
|
||||||
|
Rect {
|
||||||
|
height: self.height.saturating_sub(height),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_height(self, height: u16) -> Rect {
|
||||||
|
// new height may make area > u16::max_value, so use new()
|
||||||
|
Self::new(self.x, self.y, self.width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_width(self, width: u16) -> Rect {
|
||||||
|
Self::new(self.x, self.y, width, self.height)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inner(self, margin: &Margin) -> Rect {
|
pub fn inner(self, margin: &Margin) -> Rect {
|
||||||
if self.width < 2 * margin.horizontal || self.height < 2 * margin.vertical {
|
if self.width < 2 * margin.horizontal || self.height < 2 * margin.vertical {
|
||||||
Rect::default()
|
Rect::default()
|
||||||
@ -495,6 +546,40 @@ fn test_rect_size_preservation() {
|
|||||||
assert_eq!(rect.height, 100);
|
assert_eq!(rect.height, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rect_chop_from_left() {
|
||||||
|
let rect = Rect::new(0, 0, 20, 30);
|
||||||
|
assert_eq!(Rect::new(10, 0, 10, 30), rect.clip_left(10));
|
||||||
|
assert_eq!(
|
||||||
|
Rect::new(20, 0, 0, 30),
|
||||||
|
rect.clip_left(40),
|
||||||
|
"x should be clamped to original width if new width is bigger"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rect_chop_from_right() {
|
||||||
|
let rect = Rect::new(0, 0, 20, 30);
|
||||||
|
assert_eq!(Rect::new(0, 0, 10, 30), rect.clip_right(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rect_chop_from_top() {
|
||||||
|
let rect = Rect::new(0, 0, 20, 30);
|
||||||
|
assert_eq!(Rect::new(0, 10, 20, 20), rect.clip_top(10));
|
||||||
|
assert_eq!(
|
||||||
|
Rect::new(0, 30, 20, 0),
|
||||||
|
rect.clip_top(50),
|
||||||
|
"y should be clamped to original height if new height is bigger"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rect_chop_from_bottom() {
|
||||||
|
let rect = Rect::new(0, 0, 20, 30);
|
||||||
|
assert_eq!(Rect::new(0, 0, 20, 20), rect.clip_bottom(10));
|
||||||
|
}
|
||||||
|
|
||||||
fn styles() -> Vec<Style> {
|
fn styles() -> Vec<Style> {
|
||||||
vec![
|
vec![
|
||||||
Style::default(),
|
Style::default(),
|
||||||
|
@ -83,12 +83,7 @@ pub fn new(doc: DocumentId) -> Self {
|
|||||||
pub fn inner_area(&self) -> Rect {
|
pub fn inner_area(&self) -> Rect {
|
||||||
// TODO: not ideal
|
// TODO: not ideal
|
||||||
const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
|
const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
|
||||||
Rect::new(
|
self.area.clip_left(OFFSET).clip_bottom(1) // -1 for statusline
|
||||||
self.area.x + OFFSET,
|
|
||||||
self.area.y,
|
|
||||||
self.area.width - OFFSET,
|
|
||||||
self.area.height.saturating_sub(1), // -1 for statusline
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) {
|
pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) {
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"to"
|
"to"
|
||||||
"stream"
|
"stream"
|
||||||
"extend"
|
"extend"
|
||||||
|
"optional"
|
||||||
] @keyword
|
] @keyword
|
||||||
|
|
||||||
[
|
[
|
||||||
@ -23,7 +24,6 @@
|
|||||||
|
|
||||||
[
|
[
|
||||||
(mapName)
|
(mapName)
|
||||||
(oneofName)
|
|
||||||
(enumName)
|
(enumName)
|
||||||
(messageName)
|
(messageName)
|
||||||
(extendName)
|
(extendName)
|
||||||
|
83
runtime/themes/monokai.toml
Normal file
83
runtime/themes/monokai.toml
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# Author: Shafkath Shuhan <shafkathshuhannyc@gmail.com>
|
||||||
|
|
||||||
|
"namespace" = { fg = "type" }
|
||||||
|
"module" = { fg = "type" }
|
||||||
|
|
||||||
|
"type" = { fg = "type" }
|
||||||
|
"type.builtin" = { fg = "#66D9EF" }
|
||||||
|
"type.enum.variant" = { fg = "text" }
|
||||||
|
"constructor" = { fg = "text" }
|
||||||
|
"property" = { fg = "variable" }
|
||||||
|
|
||||||
|
"keyword" = { fg = "keyword" }
|
||||||
|
"keyword.directive" = { fg = "keyword" }
|
||||||
|
"keyword.control" = { fg = "keyword" }
|
||||||
|
"label" = { fg = "keyword" }
|
||||||
|
|
||||||
|
"special" = { fg = "keyword" }
|
||||||
|
"operator" = { fg = "text" }
|
||||||
|
|
||||||
|
"punctuation" = { fg = "text" }
|
||||||
|
"punctuation.delimiter" = { fg = "text" }
|
||||||
|
|
||||||
|
"variable" = { fg = "variable" }
|
||||||
|
"variable.parameter" = { fg = "#fd971f" }
|
||||||
|
"variable.builtin" = { fg = "keyword" }
|
||||||
|
"constant" = { fg = "variable" }
|
||||||
|
"constant.builtin" = { fg = "#ae81ff" }
|
||||||
|
|
||||||
|
"function" = { fg = "fn_declaration" }
|
||||||
|
"function.builtin" = { fg = "fn_declaration" }
|
||||||
|
"function.macro" = { fg = "keyword" }
|
||||||
|
"attribute" = { fg = "fn_declaration" }
|
||||||
|
|
||||||
|
"comment" = { fg = "#88846F" }
|
||||||
|
|
||||||
|
"string" = { fg = "#e6db74" }
|
||||||
|
"number" = { fg = "#ae81ff" }
|
||||||
|
"escape" = { fg = "#ae81ff" }
|
||||||
|
|
||||||
|
"ui.background" = { fg = "text", bg = "background" }
|
||||||
|
|
||||||
|
"ui.window" = { bg = "widget" }
|
||||||
|
"ui.popup" = { bg = "widget" }
|
||||||
|
"ui.help" = { bg = "widget" }
|
||||||
|
"ui.menu.selected" = { bg = "widget" }
|
||||||
|
|
||||||
|
"ui.cursor" = { fg = "cursor", modifiers = ["reversed"] }
|
||||||
|
"ui.cursor.primary" = { fg = "cursor", modifiers = ["reversed"] }
|
||||||
|
"ui.cursor.match" = { fg = "#888888", modifiers = ["reversed"] }
|
||||||
|
|
||||||
|
"ui.selection" = { bg = "#878b91" }
|
||||||
|
"ui.selection.primary" = { bg = "#575b61" }
|
||||||
|
|
||||||
|
"ui.linenr" = { fg = "#90908a" }
|
||||||
|
"ui.linenr.selected" = { fg = "#c2c2bf" }
|
||||||
|
|
||||||
|
"ui.statusline" = { fg = "active_text", bg = "#75715e" }
|
||||||
|
"ui.statusline.inactive" = { fg = "active_text", bg = "#75715e" }
|
||||||
|
|
||||||
|
"ui.text" = { fg = "text", bg = "background" }
|
||||||
|
"ui.text.focus" = { fg = "active_text" }
|
||||||
|
|
||||||
|
"warning" = { fg = "#cca700" }
|
||||||
|
"error" = { fg = "#f48771" }
|
||||||
|
"info" = { fg = "#75beff" }
|
||||||
|
"hint" = { fg = "#eeeeeeb3" }
|
||||||
|
|
||||||
|
diagnostic = { modifiers = ["underlined"] }
|
||||||
|
|
||||||
|
[palette]
|
||||||
|
type = "#A6E22E"
|
||||||
|
keyword = "#F92672"
|
||||||
|
regex = "#CE9178"
|
||||||
|
special = "#C586C0"
|
||||||
|
variable = "#F8F8F2"
|
||||||
|
fn_declaration = "#A6E22E"
|
||||||
|
|
||||||
|
background = "#272822"
|
||||||
|
text = "#f8f8f2"
|
||||||
|
active_text = "#ffffff"
|
||||||
|
cursor = "#a6a6a6"
|
||||||
|
inactive_cursor = "#878b91"
|
||||||
|
widget = "#1e1f1c"
|
@ -51,6 +51,7 @@ module = "#ff0000"
|
|||||||
"ui.cursor.insert" = { bg = "white" }
|
"ui.cursor.insert" = { bg = "white" }
|
||||||
"ui.cursor.match" = { fg = "#212121", bg = "#6C6999" }
|
"ui.cursor.match" = { fg = "#212121", bg = "#6C6999" }
|
||||||
"ui.cursor" = { modifiers = ["reversed"] }
|
"ui.cursor" = { modifiers = ["reversed"] }
|
||||||
|
"ui.highlight" = { bg = "bossanova" }
|
||||||
|
|
||||||
"ui.menu.selected" = { fg = "revolver", bg = "white" }
|
"ui.menu.selected" = { fg = "revolver", bg = "white" }
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user