CalebLarsen 2025-06-13 15:34:49 -05:00 committed by GitHub
commit 43853e96a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 100 additions and 21 deletions

View File

@ -1,3 +1,4 @@
//! Functions for working with the host environment.
use std::{ use std::{
borrow::Cow, borrow::Cow,
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
@ -10,9 +11,9 @@ use once_cell::sync::Lazy;
// We keep the CWD as a static so that we can access it in places where we don't have access to the Editor // We keep the CWD as a static so that we can access it in places where we don't have access to the Editor
static CWD: RwLock<Option<PathBuf>> = RwLock::new(None); static CWD: RwLock<Option<PathBuf>> = RwLock::new(None);
// Get the current working directory. /// Get the current working directory.
// This information is managed internally as the call to std::env::current_dir /// This information is managed internally as the call to std::env::current_dir
// might fail if the cwd has been deleted. /// might fail if the cwd has been deleted.
pub fn current_working_dir() -> PathBuf { pub fn current_working_dir() -> PathBuf {
if let Some(path) = &*CWD.read().unwrap() { if let Some(path) = &*CWD.read().unwrap() {
return path.clone(); return path.clone();
@ -37,6 +38,7 @@ pub fn current_working_dir() -> PathBuf {
cwd cwd
} }
/// Update the current working directory.
pub fn set_current_working_dir(path: impl AsRef<Path>) -> std::io::Result<Option<PathBuf>> { pub fn set_current_working_dir(path: impl AsRef<Path>) -> std::io::Result<Option<PathBuf>> {
let path = crate::path::canonicalize(path); let path = crate::path::canonicalize(path);
std::env::set_current_dir(&path)?; std::env::set_current_dir(&path)?;
@ -45,14 +47,17 @@ pub fn set_current_working_dir(path: impl AsRef<Path>) -> std::io::Result<Option
Ok(cwd.replace(path)) Ok(cwd.replace(path))
} }
/// Checks if the given environment variable is set.
pub fn env_var_is_set(env_var_name: &str) -> bool { pub fn env_var_is_set(env_var_name: &str) -> bool {
std::env::var_os(env_var_name).is_some() std::env::var_os(env_var_name).is_some()
} }
/// Checks if a binary with the given name exists.
pub fn binary_exists<T: AsRef<OsStr>>(binary_name: T) -> bool { pub fn binary_exists<T: AsRef<OsStr>>(binary_name: T) -> bool {
which::which(binary_name).is_ok() which::which(binary_name).is_ok()
} }
/// Attempts to find a binary of the given name. See [which](https://linux.die.net/man/1/which).
pub fn which<T: AsRef<OsStr>>( pub fn which<T: AsRef<OsStr>>(
binary_name: T, binary_name: T,
) -> Result<std::path::PathBuf, ExecutableNotFoundError> { ) -> Result<std::path::PathBuf, ExecutableNotFoundError> {

View File

@ -1,3 +1,4 @@
//! Functions for managine file metadata.
//! From <https://github.com/Freaky/faccess> //! From <https://github.com/Freaky/faccess>
use std::io; use std::io;

View File

@ -1,3 +1,6 @@
//! Extensions to the standard library. A collection of helper functions
//! used throughout helix.
pub mod env; pub mod env;
pub mod faccess; pub mod faccess;
pub mod path; pub mod path;

View File

@ -1,3 +1,5 @@
//! Functions for working with [Path].
pub use etcetera::home_dir; pub use etcetera::home_dir;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex_cursor::{engines::meta::Regex, Input}; use regex_cursor::{engines::meta::Regex, Input};
@ -140,6 +142,7 @@ pub fn canonicalize(path: impl AsRef<Path>) -> PathBuf {
normalize(path) normalize(path)
} }
/// Convert path into a relative path
pub fn get_relative_path<'a, P>(path: P) -> Cow<'a, Path> pub fn get_relative_path<'a, P>(path: P) -> Cow<'a, Path>
where where
P: Into<Cow<'a, Path>>, P: Into<Cow<'a, Path>>,

View File

@ -1,3 +1,5 @@
//! Provides [Range] type expanding on [RangeBounds].
use std::ops::{self, RangeBounds}; use std::ops::{self, RangeBounds};
/// A range of `char`s within the text. /// A range of `char`s within the text.
@ -66,6 +68,7 @@ pub fn is_subset<const ALLOW_EMPTY: bool>(
} }
} }
/// Similar to is_subset but requires each element of `super_set` to be matched
pub fn is_exact_subset( pub fn is_exact_subset(
mut super_set: impl Iterator<Item = Range>, mut super_set: impl Iterator<Item = Range>,
mut sub_set: impl Iterator<Item = Range>, mut sub_set: impl Iterator<Item = Range>,

View File

@ -1,3 +1,4 @@
//! Functions and types for working with [RopeSlice]
use std::fmt; use std::fmt;
use std::ops::{Bound, RangeBounds}; use std::ops::{Bound, RangeBounds};
@ -8,6 +9,7 @@ use ropey::iter::Chunks;
use ropey::RopeSlice; use ropey::RopeSlice;
use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete}; use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
/// Additional utility functions for [RopeSlice]
pub trait RopeSliceExt<'a>: Sized { pub trait RopeSliceExt<'a>: Sized {
fn ends_with(self, text: &str) -> bool; fn ends_with(self, text: &str) -> bool;
fn starts_with(self, text: &str) -> bool; fn starts_with(self, text: &str) -> bool;

View File

@ -95,6 +95,7 @@ impl Capabilities {
} }
} }
/// Terminal backend supporting a wide variety of terminals
pub struct CrosstermBackend<W: Write> { pub struct CrosstermBackend<W: Write> {
buffer: W, buffer: W,
capabilities: Capabilities, capabilities: Capabilities,

View File

@ -1,3 +1,5 @@
//! Provides interface for controlling the terminal
use std::io; use std::io;
use crate::{buffer::Cell, terminal::Config}; use crate::{buffer::Cell, terminal::Config};
@ -12,19 +14,32 @@ pub use self::crossterm::CrosstermBackend;
mod test; mod test;
pub use self::test::TestBackend; pub use self::test::TestBackend;
/// Representation of a terminal backend.
pub trait Backend { pub trait Backend {
/// Claims the terminal for TUI use.
fn claim(&mut self, config: Config) -> Result<(), io::Error>; fn claim(&mut self, config: Config) -> Result<(), io::Error>;
/// Update terminal configuration.
fn reconfigure(&mut self, config: Config) -> Result<(), io::Error>; fn reconfigure(&mut self, config: Config) -> Result<(), io::Error>;
/// Restores the terminal to a normal state, undoes `claim`
fn restore(&mut self, config: Config) -> Result<(), io::Error>; fn restore(&mut self, config: Config) -> Result<(), io::Error>;
/// Forcibly resets the terminal, ignoring errors and configuration
fn force_restore() -> Result<(), io::Error>; fn force_restore() -> Result<(), io::Error>;
/// Draws styled text to the terminal
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error> fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
where where
I: Iterator<Item = (u16, u16, &'a Cell)>; I: Iterator<Item = (u16, u16, &'a Cell)>;
/// Hides the cursor
fn hide_cursor(&mut self) -> Result<(), io::Error>; fn hide_cursor(&mut self) -> Result<(), io::Error>;
/// Sets the cursor to the given shape
fn show_cursor(&mut self, kind: CursorKind) -> Result<(), io::Error>; fn show_cursor(&mut self, kind: CursorKind) -> Result<(), io::Error>;
/// Gets the current position of the cursor
fn get_cursor(&mut self) -> Result<(u16, u16), io::Error>; fn get_cursor(&mut self) -> Result<(u16, u16), io::Error>;
/// Sets the cursor to the given position
fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), io::Error>; fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), io::Error>;
/// Clears the terminal
fn clear(&mut self) -> Result<(), io::Error>; fn clear(&mut self) -> Result<(), io::Error>;
/// Gets the size of the terminal in cells
fn size(&self) -> Result<Rect, io::Error>; fn size(&self) -> Result<Rect, io::Error>;
/// Flushes the terminal buffer
fn flush(&mut self) -> Result<(), io::Error>; fn flush(&mut self) -> Result<(), io::Error>;
} }

View File

@ -1,3 +1,4 @@
//! Contents of a terminal screen. A [Buffer] is made up of [Cell]s.
use crate::text::{Span, Spans}; use crate::text::{Span, Spans};
use helix_core::unicode::width::UnicodeWidthStr; use helix_core::unicode::width::UnicodeWidthStr;
use std::cmp::min; use std::cmp::min;
@ -5,7 +6,7 @@ use unicode_segmentation::UnicodeSegmentation;
use helix_view::graphics::{Color, Modifier, Rect, Style, UnderlineStyle}; use helix_view::graphics::{Color, Modifier, Rect, Style, UnderlineStyle};
/// A buffer cell /// One cell of the terminal. Contains one stylized grapheme.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cell { pub struct Cell {
pub symbol: String, pub symbol: String,
@ -17,28 +18,33 @@ pub struct Cell {
} }
impl Cell { impl Cell {
/// Set the cell's grapheme
pub fn set_symbol(&mut self, symbol: &str) -> &mut Cell { pub fn set_symbol(&mut self, symbol: &str) -> &mut Cell {
self.symbol.clear(); self.symbol.clear();
self.symbol.push_str(symbol); self.symbol.push_str(symbol);
self self
} }
/// Set the cell's grapheme to a [char]
pub fn set_char(&mut self, ch: char) -> &mut Cell { pub fn set_char(&mut self, ch: char) -> &mut Cell {
self.symbol.clear(); self.symbol.clear();
self.symbol.push(ch); self.symbol.push(ch);
self self
} }
/// Set the foreground [Color]
pub fn set_fg(&mut self, color: Color) -> &mut Cell { pub fn set_fg(&mut self, color: Color) -> &mut Cell {
self.fg = color; self.fg = color;
self self
} }
/// Set the background [Color]
pub fn set_bg(&mut self, color: Color) -> &mut Cell { pub fn set_bg(&mut self, color: Color) -> &mut Cell {
self.bg = color; self.bg = color;
self self
} }
/// Set the [Style] of the cell
pub fn set_style(&mut self, style: Style) -> &mut Cell { pub fn set_style(&mut self, style: Style) -> &mut Cell {
if let Some(c) = style.fg { if let Some(c) = style.fg {
self.fg = c; self.fg = c;
@ -58,6 +64,7 @@ impl Cell {
self self
} }
/// Returns the current style of the cell
pub fn style(&self) -> Style { pub fn style(&self) -> Style {
Style::default() Style::default()
.fg(self.fg) .fg(self.fg)
@ -67,6 +74,7 @@ impl Cell {
.add_modifier(self.modifier) .add_modifier(self.modifier)
} }
/// Resets the cell to a default blank state
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.symbol.clear(); self.symbol.clear();
self.symbol.push(' '); self.symbol.push(' ');
@ -494,6 +502,8 @@ impl Buffer {
(x_offset as u16, y) (x_offset as u16, y)
} }
/// Print at most the first `width` characters of a [Spans] if enough space is available
/// until the end of the line. Appends a `…` at the end of truncated lines.
pub fn set_spans_truncated(&mut self, x: u16, y: u16, spans: &Spans, width: u16) -> (u16, u16) { pub fn set_spans_truncated(&mut self, x: u16, y: u16, spans: &Spans, width: u16) -> (u16, u16) {
// prevent panic if out of range // prevent panic if out of range
if !self.in_bounds(x, y) || width == 0 { if !self.in_bounds(x, y) || width == 0 {
@ -535,6 +545,8 @@ impl Buffer {
(x_offset as u16, y) (x_offset as u16, y)
} }
/// Print at most the first `width` characters of a [Spans] if enough space is available
/// until the end of the line
pub fn set_spans(&mut self, x: u16, y: u16, spans: &Spans, width: u16) -> (u16, u16) { pub fn set_spans(&mut self, x: u16, y: u16, spans: &Spans, width: u16) -> (u16, u16) {
let mut remaining_width = width; let mut remaining_width = width;
let mut x = x; let mut x = x;
@ -556,6 +568,8 @@ impl Buffer {
(x, y) (x, y)
} }
/// Print at most the first `width` characters of a [Span] if enough space is available
/// until the end of the line
pub fn set_span(&mut self, x: u16, y: u16, span: &Span, width: u16) -> (u16, u16) { pub fn set_span(&mut self, x: u16, y: u16, span: &Span, width: u16) -> (u16, u16) {
self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style) self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style)
} }
@ -572,6 +586,7 @@ impl Buffer {
} }
} }
/// Set all cells in the [area](Rect) to the given [Style]
pub fn set_style(&mut self, area: Rect, style: Style) { pub fn set_style(&mut self, area: Rect, style: Style) {
for y in area.top()..area.bottom() { for y in area.top()..area.bottom() {
for x in area.left()..area.right() { for x in area.left()..area.right() {

View File

@ -1,3 +1,5 @@
//! Layout engine for terminal
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
@ -7,6 +9,7 @@ use cassowary::{Constraint as CassowaryConstraint, Expression, Solver, Variable}
use helix_view::graphics::{Margin, Rect}; use helix_view::graphics::{Margin, Rect};
/// Enum of all corners
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
pub enum Corner { pub enum Corner {
TopLeft, TopLeft,
@ -15,12 +18,14 @@ pub enum Corner {
BottomLeft, BottomLeft,
} }
/// Direction a [Rect] should be split
#[derive(Debug, Hash, Clone, PartialEq, Eq)] #[derive(Debug, Hash, Clone, PartialEq, Eq)]
pub enum Direction { pub enum Direction {
Horizontal, Horizontal,
Vertical, Vertical,
} }
/// Describes requirements of a [Rect] to be split
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Constraint { pub enum Constraint {
// TODO: enforce range 0 - 100 // TODO: enforce range 0 - 100
@ -46,6 +51,7 @@ impl Constraint {
} }
} }
/// How content should be aligned
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Alignment { pub enum Alignment {
Left, Left,
@ -53,6 +59,7 @@ pub enum Alignment {
Right, Right,
} }
/// Description of a how a [Rect] should be split
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Layout { pub struct Layout {
direction: Direction, direction: Direction,
@ -75,6 +82,7 @@ impl Default for Layout {
} }
impl Layout { impl Layout {
/// Returns a layout with the given [Constraint]s.
pub fn constraints<C>(mut self, constraints: C) -> Layout pub fn constraints<C>(mut self, constraints: C) -> Layout
where where
C: Into<Vec<Constraint>>, C: Into<Vec<Constraint>>,
@ -83,21 +91,25 @@ impl Layout {
self self
} }
/// Returns a layout wit the given margins on all sides.
pub const fn margin(mut self, margin: u16) -> Layout { pub const fn margin(mut self, margin: u16) -> Layout {
self.margin = Margin::all(margin); self.margin = Margin::all(margin);
self self
} }
/// Returns a layout with the given horizontal margins.
pub const fn horizontal_margin(mut self, horizontal: u16) -> Layout { pub const fn horizontal_margin(mut self, horizontal: u16) -> Layout {
self.margin.horizontal = horizontal; self.margin.horizontal = horizontal;
self self
} }
/// Returns a layout with the given vertical margins.
pub const fn vertical_margin(mut self, vertical: u16) -> Layout { pub const fn vertical_margin(mut self, vertical: u16) -> Layout {
self.margin.vertical = vertical; self.margin.vertical = vertical;
self self
} }
/// Returns a layout with the given [Direction].
pub const fn direction(mut self, direction: Direction) -> Layout { pub const fn direction(mut self, direction: Direction) -> Layout {
self.direction = direction; self.direction = direction;
self self

View File

@ -1,3 +1,5 @@
//! Common TUI symbols including blocks, bars, braille, lines
pub mod block { pub mod block {
pub const FULL: &str = ""; pub const FULL: &str = "";
pub const SEVEN_EIGHTHS: &str = ""; pub const SEVEN_EIGHTHS: &str = "";

View File

@ -1,3 +1,6 @@
//! Terminal interface provided through the [Terminal] type.
//! Frontend for [Backend]
use crate::{backend::Backend, buffer::Buffer}; use crate::{backend::Backend, buffer::Buffer};
use helix_view::editor::Config as EditorConfig; use helix_view::editor::Config as EditorConfig;
use helix_view::graphics::{CursorKind, Rect}; use helix_view::graphics::{CursorKind, Rect};
@ -17,6 +20,7 @@ pub struct Viewport {
resize_behavior: ResizeBehavior, resize_behavior: ResizeBehavior,
} }
/// Terminal configuration
#[derive(Debug)] #[derive(Debug)]
pub struct Config { pub struct Config {
pub enable_mouse_capture: bool, pub enable_mouse_capture: bool,
@ -47,7 +51,7 @@ pub struct TerminalOptions {
pub viewport: Viewport, pub viewport: Viewport,
} }
/// Interface to the terminal backed by Termion /// Interface to the terminal backed by crossterm
#[derive(Debug)] #[derive(Debug)]
pub struct Terminal<B> pub struct Terminal<B>
where where

View File

@ -331,6 +331,7 @@ impl<'a> Table<'a> {
} }
} }
/// Track [Table] scroll offset and selection
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct TableState { pub struct TableState {
pub offset: usize, pub offset: usize,

View File

@ -9,8 +9,6 @@ categories.workspace = true
repository.workspace = true repository.workspace = true
homepage.workspace = true homepage.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
helix-core = { path = "../helix-core" } helix-core = { path = "../helix-core" }
helix-event = { path = "../helix-event" } helix-event = { path = "../helix-event" }

View File

@ -34,6 +34,7 @@ struct DiffInner {
hunks: Vec<Hunk>, hunks: Vec<Hunk>,
} }
/// Representation of a diff that can be updated.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DiffHandle { pub struct DiffHandle {
channel: UnboundedSender<Event>, channel: UnboundedSender<Event>,
@ -64,10 +65,12 @@ impl DiffHandle {
(differ, handle) (differ, handle)
} }
/// Switch base and modified texts' roles
pub fn invert(&mut self) { pub fn invert(&mut self) {
self.inverted = !self.inverted; self.inverted = !self.inverted;
} }
/// Load the actual diff
pub fn load(&self) -> Diff { pub fn load(&self) -> Diff {
Diff { Diff {
diff: self.diff.read(), diff: self.diff.read(),
@ -88,6 +91,7 @@ impl DiffHandle {
self.update_document_impl(doc, self.inverted, Some(RenderLock { lock, timeout })) self.update_document_impl(doc, self.inverted, Some(RenderLock { lock, timeout }))
} }
/// Updates the base text of the diff. Returns if the update was successful.
pub fn update_diff_base(&self, diff_base: Rope) -> bool { pub fn update_diff_base(&self, diff_base: Rope) -> bool {
self.update_document_impl(diff_base, !self.inverted, None) self.update_document_impl(diff_base, !self.inverted, None)
} }
@ -143,7 +147,7 @@ impl Hunk {
after: u32::MAX..u32::MAX, after: u32::MAX..u32::MAX,
}; };
/// Inverts a change so that `before` /// Inverts a change so that `before` and `after` are swapped.
pub fn invert(&self) -> Hunk { pub fn invert(&self) -> Hunk {
Hunk { Hunk {
before: self.after.clone(), before: self.after.clone(),
@ -151,10 +155,12 @@ impl Hunk {
} }
} }
/// Is this hunk only adding to the document
pub fn is_pure_insertion(&self) -> bool { pub fn is_pure_insertion(&self) -> bool {
self.before.is_empty() self.before.is_empty()
} }
/// Is this hunk only removing from the document
pub fn is_pure_removal(&self) -> bool { pub fn is_pure_removal(&self) -> bool {
self.after.is_empty() self.after.is_empty()
} }

View File

@ -1,3 +1,7 @@
//! `helix_vcs` provides types for working with diffs from a Version Control System (VCS).
//! Currently `git` is the only supported provider for diffs, but this architecture allows
//! for other providers to be added in the future.
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use std::{ use std::{
@ -16,12 +20,16 @@ mod status;
pub use status::FileChange; pub use status::FileChange;
/// Contains all active diff providers. Diff providers are compiled in via features. Currently
/// only `git` is supported.
#[derive(Clone)] #[derive(Clone)]
pub struct DiffProviderRegistry { pub struct DiffProviderRegistry {
providers: Vec<DiffProvider>, providers: Vec<DiffProvider>,
} }
impl DiffProviderRegistry { impl DiffProviderRegistry {
/// Get the given file from the VCS. This provides the unedited document as a "base"
/// for a diff to be created.
pub fn get_diff_base(&self, file: &Path) -> Option<Vec<u8>> { pub fn get_diff_base(&self, file: &Path) -> Option<Vec<u8>> {
self.providers self.providers
.iter() .iter()
@ -35,6 +43,7 @@ impl DiffProviderRegistry {
}) })
} }
/// Get the current name of the current [HEAD](https://stackoverflow.com/questions/2304087/what-is-head-in-git).
pub fn get_current_head_name(&self, file: &Path) -> Option<Arc<ArcSwap<Box<str>>>> { pub fn get_current_head_name(&self, file: &Path) -> Option<Arc<ArcSwap<Box<str>>>> {
self.providers self.providers
.iter() .iter()
@ -75,6 +84,7 @@ impl Default for DiffProviderRegistry {
let providers = vec![ let providers = vec![
#[cfg(feature = "git")] #[cfg(feature = "git")]
DiffProvider::Git, DiffProvider::Git,
DiffProvider::None,
]; ];
DiffProviderRegistry { providers } DiffProviderRegistry { providers }
} }
@ -85,7 +95,7 @@ impl Default for DiffProviderRegistry {
/// ///
/// `Copy` is simply to ensure the `clone()` call is the simplest it can be. /// `Copy` is simply to ensure the `clone()` call is the simplest it can be.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum DiffProvider { enum DiffProvider {
#[cfg(feature = "git")] #[cfg(feature = "git")]
Git, Git,
None, None,

View File

@ -1,18 +1,16 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
/// States for a file having been changed.
pub enum FileChange { pub enum FileChange {
Untracked { /// Not tracked by the VCS.
path: PathBuf, Untracked { path: PathBuf },
}, /// File has been modified.
Modified { Modified { path: PathBuf },
path: PathBuf, /// File modification is in conflict with a different update.
}, Conflict { path: PathBuf },
Conflict { /// File has been deleted.
path: PathBuf, Deleted { path: PathBuf },
}, /// File has been renamed.
Deleted {
path: PathBuf,
},
Renamed { Renamed {
from_path: PathBuf, from_path: PathBuf,
to_path: PathBuf, to_path: PathBuf,