Skip to content

Commit

Permalink
Move expand_args() to helix_view::editor
Browse files Browse the repository at this point in the history
  • Loading branch information
ksdrar committed May 10, 2023
1 parent 35afdc7 commit 957413e
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 106 deletions.
6 changes: 3 additions & 3 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,18 +192,18 @@ impl MappableCommand {
};
if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) {
let args = args.join(" ");
let args = match typed::expand_args(cx.editor, &args) {
let args = match helix_view::editor::expand_args(cx.editor, &args) {
Ok(a) => a,
Err(e) => {
cx.editor.set_error(format!("{}", e));
cx.editor.set_error(format!("{e}"));
return;
}
};

let args: Vec<Cow<str>> = args.split_whitespace().map(Cow::from).collect();

if let Err(e) = (command.fun)(&mut cx, &args[..], PromptEvent::Validate) {
cx.editor.set_error(format!("{}", e));
cx.editor.set_error(format!("{e}"));
}
}
}
Expand Down
103 changes: 1 addition & 102 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2205,7 +2205,7 @@ pub fn process_cmd(
event: PromptEvent,
) -> anyhow::Result<()> {
let input: Cow<str> = if event == PromptEvent::Validate {
expand_args(cx.editor, input)?
helix_view::editor::expand_args(cx.editor, input)?
} else {
Cow::Borrowed(input)
};
Expand Down Expand Up @@ -2899,107 +2899,6 @@ pub(super) fn command_mode(cx: &mut Context) {
cx.push_layer(Box::new(prompt));
}

static EXPAND_ARGS_REGEXP: Lazy<Regex> =
Lazy::new(|| Regex::new(r"%(\w+)\{([^{}]*(\{[^{}]*\}[^{}]*)*)\}").unwrap());

pub fn expand_args<'a>(editor: &Editor, args: &'a str) -> anyhow::Result<Cow<'a, str>> {
let (view, doc) = current_ref!(editor);
let shell = &editor.config().shell;

replace_all(
Lazy::force(&EXPAND_ARGS_REGEXP),
Cow::Borrowed(args),
move |keyword, body| match keyword.trim() {
"val" => match body.trim() {
"filename" => Ok((match doc.path() {
Some(p) => p.to_str().unwrap(),
None => SCRATCH_BUFFER_NAME,
})
.to_owned()),
"dirname" => doc
.path()
.and_then(|p| p.parent())
.and_then(|p| p.to_str())
.map_or(
Err(anyhow::anyhow!("Current buffer has no path or parent")),
|v| Ok(v.to_owned()),
),
"line_number" => Ok((doc
.selection(view.id)
.primary()
.cursor_line(doc.text().slice(..))
+ 1)
.to_string()),
_ => anyhow::bail!("Unknown variable: {body}"),
},
"sh" => {
let result = tokio::task::block_in_place(move || {
helix_lsp::block_on(async move {
let args = &expand_args(editor, body)?[..];

let mut command = tokio::process::Command::new(&shell[0]);
command.args(&shell[1..]).arg(args);

let output = command
.output()
.await
.map_err(|_| anyhow::anyhow!("Shell command failed: {args}"))?;

if output.status.success() {
String::from_utf8(output.stdout)
.map_err(|_| anyhow::anyhow!("Process did not output valid UTF-8"))
} else if output.stderr.is_empty() {
Err(anyhow::anyhow!("Shell command failed: {args}"))
} else {
let stderr = String::from_utf8_lossy(&output.stderr);

Err(anyhow::anyhow!("{stderr}"))
}
})
});

result.map_err(|it| {
log::error!("{}", it.to_string());

it
})
}
_ => anyhow::bail!("Unknown keyword {keyword}"),
},
)
}

// Copy of regex::Regex::replace_all to allow using result in the replacer function
fn replace_all<'a>(
regex: &regex::Regex,
text: Cow<'a, str>,
matcher: impl Fn(&str, &str) -> anyhow::Result<String>,
) -> anyhow::Result<Cow<'a, str>> {
let mut it = regex.captures_iter(&text).peekable();

if it.peek().is_none() {
return Ok(text);
}

let mut new = String::with_capacity(text.len());
let mut last_match = 0;

for cap in it {
let m = cap.get(0).unwrap();
new.push_str(&text[last_match..m.start()]);

let replace = matcher(cap.get(1).unwrap().as_str(), cap.get(2).unwrap().as_str())?;

new.push_str(&replace);

last_match = m.end();
}

new.push_str(&text[last_match..]);

replace_all(regex, Cow::Owned(new), matcher)
}

fn argument_number_of(shellwords: &Shellwords) -> usize {
if shellwords.ends_with_whitespace() {
shellwords.words().len().saturating_sub(1)
Expand Down
105 changes: 104 additions & 1 deletion helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ pub struct WhitespaceCharacters {
impl Default for WhitespaceCharacters {
fn default() -> Self {
Self {
space: '·', // U+00B7
space: '·', // U+00B7
nbsp: '⍽', // U+237D
tab: '→', // U+2192
newline: '⏎', // U+23CE
Expand Down Expand Up @@ -1715,3 +1715,106 @@ fn try_restore_indent(doc: &mut Document, view: &mut View) {
doc.apply(&transaction, view.id);
}
}

static EXPAND_ARGS_REGEXP: once_cell::sync::Lazy<helix_core::regex::Regex> =
once_cell::sync::Lazy::new(|| {
helix_core::regex::Regex::new(r"%(\w+)\{([^{}]*(\{[^{}]*\}[^{}]*)*)\}").unwrap()
});

pub fn expand_args<'a>(editor: &Editor, args: &'a str) -> anyhow::Result<Cow<'a, str>> {
let (view, doc) = current_ref!(editor);
let shell = &editor.config().shell;

replace_all(
once_cell::sync::Lazy::force(&EXPAND_ARGS_REGEXP),
Cow::Borrowed(args),
move |keyword, body| match keyword.trim() {
"val" => match body.trim() {
"filename" => Ok(doc
.path()
.and_then(|it| it.to_str())
.unwrap_or(crate::document::SCRATCH_BUFFER_NAME)
.to_string()),
"dirname" => doc
.path()
.and_then(|p| p.parent())
.and_then(std::path::Path::to_str)
.map_or(
Err(anyhow::anyhow!("Current buffer has no path or parent")),
|v| Ok(v.to_string()),
),
"line_number" => Ok((doc
.selection(view.id)
.primary()
.cursor_line(doc.text().slice(..))
+ 1)
.to_string()),
_ => anyhow::bail!("Unknown variable: {body}"),
},
"sh" => {
let result = tokio::task::block_in_place(move || {
helix_lsp::block_on(async move {
let args = &expand_args(editor, body)?[..];

let mut command = tokio::process::Command::new(&shell[0]);
command.args(&shell[1..]).arg(args);

let output = command
.output()
.await
.map_err(|_| anyhow::anyhow!("Shell command failed: {args}"))?;

if output.status.success() {
String::from_utf8(output.stdout)
.map_err(|_| anyhow::anyhow!("Process did not output valid UTF-8"))
} else if output.stderr.is_empty() {
Err(anyhow::anyhow!("Shell command failed: {args}"))
} else {
let stderr = String::from_utf8_lossy(&output.stderr);

Err(anyhow::anyhow!("{stderr}"))
}
})
});

result.map_err(|it| {
log::error!("{}", it.to_string());

it
})
}
_ => anyhow::bail!("Unknown keyword {keyword}"),
},
)
}

// Copy of regex::Regex::replace_all to allow using result in the replacer function
fn replace_all<'a>(
regex: &helix_core::regex::Regex,
text: Cow<'a, str>,
matcher: impl Fn(&str, &str) -> anyhow::Result<String>,
) -> anyhow::Result<Cow<'a, str>> {
let mut it = regex.captures_iter(&text).peekable();

if it.peek().is_none() {
return Ok(text);
}

let mut new = String::with_capacity(text.len());
let mut last_match = 0;

for cap in it {
let m = cap.get(0).unwrap();
new.push_str(&text[last_match..m.start()]);

let replace = matcher(cap.get(1).unwrap().as_str(), cap.get(2).unwrap().as_str())?;

new.push_str(&replace);

last_match = m.end();
}

new.push_str(&text[last_match..]);

replace_all(regex, Cow::Owned(new), matcher)
}

0 comments on commit 957413e

Please sign in to comment.