Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/expansion.rs')
-rw-r--r--helix-view/src/expansion.rs275
1 files changed, 0 insertions, 275 deletions
diff --git a/helix-view/src/expansion.rs b/helix-view/src/expansion.rs
deleted file mode 100644
index 6a41ae43..00000000
--- a/helix-view/src/expansion.rs
+++ /dev/null
@@ -1,275 +0,0 @@
-use std::borrow::Cow;
-
-use helix_core::command_line::{ExpansionKind, Token, TokenKind, Tokenizer};
-
-use anyhow::{anyhow, bail, Result};
-
-use crate::Editor;
-
-/// Variables that can be expanded in the command mode (`:`) via the expansion syntax.
-///
-/// For example `%{cursor_line}`.
-//
-// To add a new variable follow these steps:
-//
-// * Add the new enum member to `Variable` below.
-// * Add an item to the `VARIANTS` constant - this enables completion.
-// * Add a branch in `Variable::as_str`, converting the name from TitleCase to snake_case.
-// * Add a branch in `Variable::from_name` with the reverse association.
-// * Add a branch in the `expand_variable` function to read the value from the editor.
-// * Add the new variable to the documentation in `book/src/command-line.md`.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Variable {
- /// The one-indexed line number of the primary cursor in the currently focused document.
- CursorLine,
- /// The one-indexed column number of the primary cursor in the currently focused document.
- ///
- /// Note that this is the count of grapheme clusters from the start of the line (regardless of
- /// softwrap) - the same as the `position` element in the statusline.
- CursorColumn,
- /// The display name of the currently focused document.
- ///
- /// This corresponds to `crate::Document::display_name`.
- BufferName,
- /// A string containing the line-ending of the currently focused document.
- LineEnding,
- /// Curreng working directory
- CurrentWorkingDirectory,
- /// Nearest ancestor directory of the current working directory that contains `.git`, `.svn`, `jj` or `.helix`
- WorkspaceDirectory,
- // The name of current buffers language as set in `languages.toml`
- Language,
- // Primary selection
- Selection,
- // The one-indexed line number of the start of the primary selection in the currently focused document.
- SelectionLineStart,
- // The one-indexed line number of the end of the primary selection in the currently focused document.
- SelectionLineEnd,
-}
-
-impl Variable {
- pub const VARIANTS: &'static [Self] = &[
- Self::CursorLine,
- Self::CursorColumn,
- Self::BufferName,
- Self::LineEnding,
- Self::CurrentWorkingDirectory,
- Self::WorkspaceDirectory,
- Self::Language,
- Self::Selection,
- Self::SelectionLineStart,
- Self::SelectionLineEnd,
- ];
-
- pub const fn as_str(&self) -> &'static str {
- match self {
- Self::CursorLine => "cursor_line",
- Self::CursorColumn => "cursor_column",
- Self::BufferName => "buffer_name",
- Self::LineEnding => "line_ending",
- Self::CurrentWorkingDirectory => "current_working_directory",
- Self::WorkspaceDirectory => "workspace_directory",
- Self::Language => "language",
- Self::Selection => "selection",
- Self::SelectionLineStart => "selection_line_start",
- Self::SelectionLineEnd => "selection_line_end",
- }
- }
-
- pub fn from_name(s: &str) -> Option<Self> {
- match s {
- "cursor_line" => Some(Self::CursorLine),
- "cursor_column" => Some(Self::CursorColumn),
- "buffer_name" => Some(Self::BufferName),
- "line_ending" => Some(Self::LineEnding),
- "workspace_directory" => Some(Self::WorkspaceDirectory),
- "current_working_directory" => Some(Self::CurrentWorkingDirectory),
- "language" => Some(Self::Language),
- "selection" => Some(Self::Selection),
- "selection_line_start" => Some(Self::SelectionLineStart),
- "selection_line_end" => Some(Self::SelectionLineEnd),
- _ => None,
- }
- }
-}
-
-/// Expands the given command line token.
-///
-/// Note that the lifetime of the expanded variable is only bound to the input token and not the
-/// `Editor`. See `expand_variable` below for more discussion of lifetimes.
-pub fn expand<'a>(editor: &Editor, token: Token<'a>) -> Result<Cow<'a, str>> {
- // Note: see the `TokenKind` documentation for more details on how each branch should expand.
- match token.kind {
- TokenKind::Unquoted | TokenKind::Quoted(_) => Ok(token.content),
- TokenKind::Expansion(ExpansionKind::Variable) => {
- let var = Variable::from_name(&token.content)
- .ok_or_else(|| anyhow!("unknown variable '{}'", token.content))?;
-
- expand_variable(editor, var)
- }
- TokenKind::Expansion(ExpansionKind::Unicode) => {
- if let Some(ch) = u32::from_str_radix(token.content.as_ref(), 16)
- .ok()
- .and_then(char::from_u32)
- {
- Ok(Cow::Owned(ch.to_string()))
- } else {
- Err(anyhow!(
- "could not interpret '{}' as a Unicode character code",
- token.content
- ))
- }
- }
- TokenKind::Expand => expand_inner(editor, token.content),
- TokenKind::Expansion(ExpansionKind::Shell) => expand_shell(editor, token.content),
- // Note: see the docs for this variant.
- TokenKind::ExpansionKind => unreachable!(
- "expansion name tokens cannot be emitted when command line validation is enabled"
- ),
- }
-}
-
-/// Expand a shell command.
-pub fn expand_shell<'a>(editor: &Editor, content: Cow<'a, str>) -> Result<Cow<'a, str>> {
- use std::process::{Command, Stdio};
-
- // Recursively expand the expansion's content before executing the shell command.
- let content = expand_inner(editor, content)?;
-
- let config = editor.config();
- let shell = &config.shell;
- let mut process = Command::new(&shell[0]);
- process
- .args(&shell[1..])
- .arg(content.as_ref())
- .stdin(Stdio::null())
- .stdout(Stdio::piped())
- .stderr(Stdio::piped());
-
- // TODO: there is no protection here against a shell command taking a long time.
- // Ideally you should be able to hit `<ret>` in command mode and then be able to
- // cancel the invocation (for example with `<C-c>`) if it takes longer than you'd
- // like.
- let output = match process.spawn() {
- Ok(process) => process.wait_with_output()?,
- Err(err) => {
- bail!("Failed to start shell: {err}");
- }
- };
-
- let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
-
- if !output.stderr.is_empty() {
- log::warn!(
- "Shell expansion command `{content}` failed: {}",
- String::from_utf8_lossy(&output.stderr)
- );
- }
-
- // Trim exactly one trailing line ending if it exists.
- if text.ends_with('\n') {
- text.pop();
- if text.ends_with('\r') {
- text.pop();
- }
- }
-
- Ok(Cow::Owned(text))
-}
-
-/// Expand a token's contents recursively.
-fn expand_inner<'a>(editor: &Editor, content: Cow<'a, str>) -> Result<Cow<'a, str>> {
- let mut escaped = String::new();
- let mut start = 0;
-
- while let Some(offset) = content[start..].find('%') {
- let idx = start + offset;
- if content.as_bytes().get(idx + '%'.len_utf8()).copied() == Some(b'%') {
- // Treat two percents in a row as an escaped percent.
- escaped.push_str(&content[start..=idx]);
- // Skip over both percents.
- start = idx + ('%'.len_utf8() * 2);
- } else {
- // Otherwise interpret the percent as an expansion. Push up to (but not
- // including) the percent token.
- escaped.push_str(&content[start..idx]);
- // Then parse the expansion,
- let mut tokenizer = Tokenizer::new(&content[idx..], true);
- let token = tokenizer
- .parse_percent_token()
- .unwrap()
- .map_err(|err| anyhow!("{err}"))?;
- // expand it (this is the recursive part),
- let expanded = expand(editor, token)?;
- escaped.push_str(expanded.as_ref());
- // and move forward to the end of the expansion.
- start = idx + tokenizer.pos();
- }
- }
-
- if escaped.is_empty() {
- Ok(content)
- } else {
- escaped.push_str(&content[start..]);
- Ok(Cow::Owned(escaped))
- }
-}
-
-// Note: the lifetime of the expanded variable (the `Cow`) must not be tied to the lifetime of
-// the borrow of `Editor`. That would prevent commands from mutating the `Editor` until the
-// command consumed or cloned all arguments - this is poor ergonomics. A sensible thing for this
-// function to return then, instead, would normally be a `String`. We can return some statically
-// known strings like the scratch buffer name or line ending strings though, so this function
-// returns a `Cow<'static, str>` instead.
-fn expand_variable(editor: &Editor, variable: Variable) -> Result<Cow<'static, str>> {
- let (view, doc) = current_ref!(editor);
- let text = doc.text().slice(..);
-
- match variable {
- Variable::CursorLine => {
- let cursor_line = doc.selection(view.id).primary().cursor_line(text);
- Ok(Cow::Owned((cursor_line + 1).to_string()))
- }
- Variable::CursorColumn => {
- let cursor = doc.selection(view.id).primary().cursor(text);
- let position = helix_core::coords_at_pos(text, cursor);
- Ok(Cow::Owned((position.col + 1).to_string()))
- }
- Variable::BufferName => {
- // Note: usually we would use `Document::display_name` but we can statically borrow
- // the scratch buffer name by partially reimplementing `display_name`.
- if let Some(path) = doc.relative_path() {
- Ok(Cow::Owned(path.to_string_lossy().into_owned()))
- } else {
- Ok(Cow::Borrowed(crate::document::SCRATCH_BUFFER_NAME))
- }
- }
- Variable::LineEnding => Ok(Cow::Borrowed(doc.line_ending.as_str())),
- Variable::CurrentWorkingDirectory => Ok(std::borrow::Cow::Owned(
- helix_stdx::env::current_working_dir()
- .to_string_lossy()
- .to_string(),
- )),
- Variable::WorkspaceDirectory => Ok(std::borrow::Cow::Owned(
- helix_loader::find_workspace()
- .0
- .to_string_lossy()
- .to_string(),
- )),
- Variable::Language => Ok(match doc.language_name() {
- Some(lang) => Cow::Owned(lang.to_owned()),
- None => Cow::Borrowed("text"),
- }),
- Variable::Selection => Ok(Cow::Owned(
- doc.selection(view.id).primary().fragment(text).to_string(),
- )),
- Variable::SelectionLineStart => {
- let start_line = doc.selection(view.id).primary().line_range(text).0;
- Ok(Cow::Owned((start_line + 1).to_string()))
- }
- Variable::SelectionLineEnd => {
- let end_line = doc.selection(view.id).primary().line_range(text).1;
- Ok(Cow::Owned((end_line + 1).to_string()))
- }
- }
-}