Merge remote-tracking branch 'origin/master' into debug

Contains type fix on helix-term/src/ui/editor.rs:752:13
pull/574/head
Dmitry Sharshakov 3 years ago
commit 5b20f6020a

48
Cargo.lock generated

@ -19,9 +19,9 @@ checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
version = "1.3.1" version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34a23efe54373080cf871532e2d01076be41c4c896d32ef63af1b2dded924b03" checksum = "b5ab7d9e73059c86c36473f459b52adbd99c3554a4fec492caef460806006f00"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
@ -232,15 +232,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.16" version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99" checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.16" version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c" checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@ -249,15 +249,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.16" version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2" checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.16" version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78" checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"futures-core", "futures-core",
@ -809,18 +809,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.129" version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.129" version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3" checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -829,9 +829,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.66" version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -905,9 +905,9 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
[[package]] [[package]]
name = "slotmap" name = "slotmap"
version = "1.0.5" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a952280edbecfb1d4bd3cf2dbc309dc6ab523e53487c438ae21a6df09fe84bc4" checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
dependencies = [ dependencies = [
"version_check", "version_check",
] ]
@ -948,18 +948,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.26" version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" checksum = "283d5230e63df9608ac7d9691adc1dfb6e701225436eb64d0b9a7f0a5a04f6ec"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.26" version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" checksum = "fa3884228611f5cd3608e2d409bf7dce832e4eb3135e3f11addbd7e41bd68e71"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1001,9 +1001,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.10.0" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cf844b23c6131f624accf65ce0e4e9956a8bb329400ea5bcc26ae3a5c20b0b" checksum = "92036be488bb6594459f2e03b60e42df6f937fe6ca5c5ffdcb539c6b84dc40f5"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes", "bytes",

@ -43,7 +43,7 @@ cargo install --path helix-term
This will install the `hx` binary to `$HOME/.cargo/bin`. This will install the `hx` binary to `$HOME/.cargo/bin`.
Helix also needs it's runtime files so make sure to copy/symlink the `runtime/` directory into the Helix also needs its runtime files so make sure to copy/symlink the `runtime/` directory into the
config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overriden config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overriden
via the `HELIX_RUNTIME` environment variable. via the `HELIX_RUNTIME` environment variable.

@ -122,6 +122,16 @@ in reverse, or searching via smartcase.
| `[D` | Go to first diagnostic in document | `goto_first_diag` | | `[D` | Go to first diagnostic in document | `goto_first_diag` |
| `]D` | Go to last diagnostic in document | `goto_last_diag` | | `]D` | Go to last diagnostic in document | `goto_last_diag` |
### Shell
| Key | Description | Command |
| ------ | ----------- | ------- |
| `\|` | Pipe each selection through shell command, replacing with output | `shell_pipe` |
| `A-\|` | Pipe each selection into shell command, ignoring output | `shell_pipe_to` |
| `!` | Run shell command, inserting output before each selection | `shell_insert_output` |
| `A-!` | Run shell command, appending output after each selection | `shell_append_output` |
| `$` | Pipe each selection into shell command, removing if the command exits >0 | `shell_keep_pipe` |
## Select / extend mode ## Select / extend mode
I'm still pondering whether to keep this mode or not. It changes movement I'm still pondering whether to keep this mode or not. It changes movement

@ -23,5 +23,5 @@ lsp-types = { version = "0.89", features = ["proposed"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
thiserror = "1.0" thiserror = "1.0"
tokio = { version = "1.9", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] } tokio = { version = "1.10", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
tokio-stream = "0.1" tokio-stream = "0.1"

@ -1,7 +1,7 @@
use crate::Result; use crate::Result;
use anyhow::Context; use anyhow::Context;
use jsonrpc_core as jsonrpc; use jsonrpc_core as jsonrpc;
use log::{error, info}; use log::{debug, error, info, warn};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
@ -83,19 +83,20 @@ impl Transport {
break; break;
} }
let mut parts = header.split(": "); debug!("<- header {}", header);
match (parts.next(), parts.next(), parts.next()) { let parts = header.split_once(": ");
(Some("Content-Length"), Some(value), None) => {
match parts {
Some(("Content-Length", value)) => {
content_length = Some(value.parse().context("invalid content length")?); content_length = Some(value.parse().context("invalid content length")?);
} }
(Some(_), Some(_), None) => {} Some((_, _)) => {}
_ => { None => {
return Err(std::io::Error::new( // Workaround: Some non-conformant language servers will output logging and other garbage
std::io::ErrorKind::Other, // into the same stream as JSON-RPC messages. This can also happen from shell scripts that spawn
"Failed to parse header", // the server. Skip such lines and log a warning.
) warn!("Failed to parse header: {:?}", header);
.into());
} }
} }
} }

@ -135,7 +135,7 @@ pub struct Command {
} }
macro_rules! commands { macro_rules! commands {
( $($name:ident, $doc:literal),* ) => { ( $($name:ident, $doc:literal,)* ) => {
$( $(
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
pub const $name: Self = Self { pub const $name: Self = Self {
@ -317,7 +317,12 @@ impl Command {
dap_variables, "List variables", dap_variables, "List variables",
dap_terminate, "End debug session", dap_terminate, "End debug session",
dap_switch_thread, "Switch current thread", dap_switch_thread, "Switch current thread",
suspend, "Suspend" shell_pipe, "Pipe selections through shell command",
shell_pipe_to, "Pipe selections into shell command, ignoring command output",
shell_insert_output, "Insert output of shell command before each selection",
shell_append_output, "Append output of shell command after each selection",
shell_keep_pipe, "Filter selections with shell predicate",
suspend, "Suspend",
); );
} }
@ -1089,7 +1094,7 @@ fn select_all(cx: &mut Context) {
} }
fn select_regex(cx: &mut Context) { fn select_regex(cx: &mut Context) {
let prompt = ui::regex_prompt(cx, "select:".to_string(), move |view, doc, _, regex| { let prompt = ui::regex_prompt(cx, "select:".into(), move |view, doc, _, regex| {
let text = doc.text().slice(..); let text = doc.text().slice(..);
if let Some(selection) = selection::select_on_matches(text, doc.selection(view.id), &regex) if let Some(selection) = selection::select_on_matches(text, doc.selection(view.id), &regex)
{ {
@ -1101,7 +1106,7 @@ fn select_regex(cx: &mut Context) {
} }
fn split_selection(cx: &mut Context) { fn split_selection(cx: &mut Context) {
let prompt = ui::regex_prompt(cx, "split:".to_string(), move |view, doc, _, regex| { let prompt = ui::regex_prompt(cx, "split:".into(), move |view, doc, _, regex| {
let text = doc.text().slice(..); let text = doc.text().slice(..);
let selection = selection::split_on_matches(text, doc.selection(view.id), &regex); let selection = selection::split_on_matches(text, doc.selection(view.id), &regex);
doc.set_selection(view.id, selection); doc.set_selection(view.id, selection);
@ -1167,15 +1172,11 @@ fn search(cx: &mut Context) {
// feed chunks into the regex yet // feed chunks into the regex yet
let contents = doc.text().slice(..).to_string(); let contents = doc.text().slice(..).to_string();
let prompt = ui::regex_prompt( let prompt = ui::regex_prompt(cx, "search:".into(), move |view, doc, registers, regex| {
cx, search_impl(doc, view, &contents, &regex, false);
"search:".to_string(), // TODO: only store on enter (accept), not update
move |view, doc, registers, regex| { registers.write('/', vec![regex.as_str().to_string()]);
search_impl(doc, view, &contents, &regex, false); });
// TODO: only store on enter (accept), not update
registers.write('/', vec![regex.as_str().to_string()]);
},
);
cx.push_layer(Box::new(prompt)); cx.push_layer(Box::new(prompt));
} }
@ -2400,7 +2401,7 @@ mod cmd {
fn command_mode(cx: &mut Context) { fn command_mode(cx: &mut Context) {
let mut prompt = Prompt::new( let mut prompt = Prompt::new(
":".to_owned(), ":".into(),
Some(':'), Some(':'),
|input: &str| { |input: &str| {
// we use .this over split_whitespace() because we care about empty segments // we use .this over split_whitespace() because we care about empty segments
@ -4009,7 +4010,7 @@ fn join_selections(cx: &mut Context) {
fn keep_selections(cx: &mut Context) { fn keep_selections(cx: &mut Context) {
// keep selections matching regex // keep selections matching regex
let prompt = ui::regex_prompt(cx, "keep:".to_string(), move |view, doc, _, regex| { let prompt = ui::regex_prompt(cx, "keep:".into(), move |view, doc, _, regex| {
let text = doc.text().slice(..); let text = doc.text().slice(..);
if let Some(selection) = selection::keep_matches(text, doc.selection(view.id), &regex) { if let Some(selection) = selection::keep_matches(text, doc.selection(view.id), &regex) {
@ -4487,6 +4488,128 @@ fn surround_delete(cx: &mut Context) {
}) })
} }
#[derive(Eq, PartialEq)]
enum ShellBehavior {
Replace,
Ignore,
Insert,
Append,
Filter,
}
fn shell_pipe(cx: &mut Context) {
shell(cx, "pipe:".into(), ShellBehavior::Replace);
}
fn shell_pipe_to(cx: &mut Context) {
shell(cx, "pipe-to:".into(), ShellBehavior::Ignore);
}
fn shell_insert_output(cx: &mut Context) {
shell(cx, "insert-output:".into(), ShellBehavior::Insert);
}
fn shell_append_output(cx: &mut Context) {
shell(cx, "append-output:".into(), ShellBehavior::Append);
}
fn shell_keep_pipe(cx: &mut Context) {
shell(cx, "keep-pipe:".into(), ShellBehavior::Filter);
}
fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
use std::io::Write;
use std::process::{Command, Stdio};
if cx.editor.config.shell.is_empty() {
cx.editor.set_error("No shell set".to_owned());
return;
}
let pipe = match behavior {
ShellBehavior::Replace | ShellBehavior::Ignore | ShellBehavior::Filter => true,
ShellBehavior::Insert | ShellBehavior::Append => false,
};
let prompt = Prompt::new(
prompt,
Some('|'),
|_input: &str| Vec::new(),
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
if event != PromptEvent::Validate {
return;
}
let shell = &cx.editor.config.shell;
let (view, doc) = current!(cx.editor);
let selection = doc.selection(view.id);
let mut changes = Vec::with_capacity(selection.len());
for range in selection.ranges() {
let mut process = match Command::new(&shell[0])
.args(&shell[1..])
.arg(input)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
Ok(process) => process,
Err(e) => {
log::error!("Failed to start shell: {}", e);
cx.editor.set_error("Failed to start shell".to_owned());
return;
}
};
if pipe {
let stdin = process.stdin.as_mut().unwrap();
let fragment = range.fragment(doc.text().slice(..));
stdin.write_all(fragment.as_bytes()).unwrap();
}
let output = process.wait_with_output().unwrap();
if behavior != ShellBehavior::Filter {
if !output.status.success() {
if !output.stderr.is_empty() {
log::error!("Shell error: {}", String::from_utf8_lossy(&output.stderr));
}
cx.editor.set_error("Command failed".to_owned());
return;
}
let tendril = match Tendril::try_from_byte_slice(&output.stdout) {
Ok(tendril) => tendril,
Err(_) => {
cx.editor
.set_error("Process did not output valid UTF-8".to_owned());
return;
}
};
let (from, to) = match behavior {
ShellBehavior::Replace => (range.from(), range.to()),
ShellBehavior::Insert => (range.from(), range.from()),
ShellBehavior::Append => (range.to(), range.to()),
_ => (range.from(), range.from()),
};
changes.push((from, to, Some(tendril)));
} else {
// if the process exits successfully, keep the selection, otherwise delete it.
let keep = output.status.success();
changes.push((
range.from(),
if keep { range.from() } else { range.to() },
None,
));
}
}
if behavior != ShellBehavior::Ignore {
let transaction = Transaction::change(doc.text(), changes.into_iter());
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view.id);
}
},
);
cx.push_layer(Box::new(prompt));
}
fn suspend(_cx: &mut Context) { fn suspend(_cx: &mut Context) {
#[cfg(not(windows))] #[cfg(not(windows))]
signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP).unwrap(); signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP).unwrap();

@ -525,6 +525,11 @@ impl Default for Keymaps {
}, },
"\"" => select_register, "\"" => select_register,
"|" => shell_pipe,
"A-|" => shell_pipe_to,
"!" => shell_insert_output,
"A-!" => shell_append_output,
"$" => shell_keep_pipe,
"C-z" => suspend, "C-z" => suspend,
}); });
let mut select = normal.clone(); let mut select = normal.clone();

@ -155,21 +155,21 @@ impl EditorView {
syntax syntax
.highlight_iter(text.slice(..), Some(range), None, |language| { .highlight_iter(text.slice(..), Some(range), None, |language| {
loader loader
.language_config_for_scope(&format!("source.{}", language)) .language_config_for_scope(&format!("source.{}", language))
.and_then(|language_config| { .and_then(|language_config| {
let config = language_config.highlight_config(scopes)?; let config = language_config.highlight_config(scopes)?;
let config_ref = config.as_ref(); let config_ref = config.as_ref();
// SAFETY: the referenced `HighlightConfiguration` behind // SAFETY: the referenced `HighlightConfiguration` behind
// the `Arc` is guaranteed to remain valid throughout the // the `Arc` is guaranteed to remain valid throughout the
// duration of the highlight. // duration of the highlight.
let config_ref = unsafe { let config_ref = unsafe {
std::mem::transmute::< std::mem::transmute::<
_, _,
&'static syntax::HighlightConfiguration, &'static syntax::HighlightConfiguration,
>(config_ref) >(config_ref)
}; };
Some(config_ref) Some(config_ref)
}) })
}) })
.map(|event| event.unwrap()) .map(|event| event.unwrap())
.collect() // TODO: we collect here to avoid holding the lock, fix later .collect() // TODO: we collect here to avoid holding the lock, fix later
@ -435,7 +435,7 @@ impl EditorView {
let current_line = doc let current_line = doc
.text() .text()
.char_to_line(doc.selection(view.id).primary().anchor); .char_to_line(doc.selection(view.id).primary().cursor(text));
// it's used inside an iterator so the collect isn't needless: // it's used inside an iterator so the collect isn't needless:
// https://github.com/rust-lang/rust-clippy/issues/6164 // https://github.com/rust-lang/rust-clippy/issues/6164
@ -749,7 +749,7 @@ impl EditorView {
_ => noop, _ => noop,
}; };
Prompt::new( Prompt::new(
format!("{}: ", name), format!("{}: ", name).into(),
None, None,
completer, completer,
move |cx: &mut crate::compositor::Context, input: &str, event: PromptEvent| { move |cx: &mut crate::compositor::Context, input: &str, event: PromptEvent| {

@ -27,7 +27,7 @@ use std::path::PathBuf;
pub fn regex_prompt( pub fn regex_prompt(
cx: &mut crate::commands::Context, cx: &mut crate::commands::Context,
prompt: String, prompt: std::borrow::Cow<'static, str>,
fun: impl Fn(&mut View, &mut Document, &mut Registers, Regex) + 'static, fun: impl Fn(&mut View, &mut Document, &mut Registers, Regex) + 'static,
) -> Prompt { ) -> Prompt {
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);

@ -202,7 +202,7 @@ impl<T> Picker<T> {
callback_fn: impl Fn(&mut Editor, &T, Action) + 'static, callback_fn: impl Fn(&mut Editor, &T, Action) + 'static,
) -> Self { ) -> Self {
let prompt = Prompt::new( let prompt = Prompt::new(
"".to_string(), "".into(),
None, None,
|_pattern: &str| Vec::new(), |_pattern: &str| Vec::new(),
|_editor: &mut Context, _pattern: &str, _event: PromptEvent| { |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {

@ -15,7 +15,7 @@ use helix_view::{
pub type Completion = (RangeFrom<usize>, Cow<'static, str>); pub type Completion = (RangeFrom<usize>, Cow<'static, str>);
pub struct Prompt { pub struct Prompt {
prompt: String, prompt: Cow<'static, str>,
pub line: String, pub line: String,
cursor: usize, cursor: usize,
completion: Vec<Completion>, completion: Vec<Completion>,
@ -55,7 +55,7 @@ pub enum Movement {
impl Prompt { impl Prompt {
pub fn new( pub fn new(
prompt: String, prompt: Cow<'static, str>,
history_register: Option<char>, history_register: Option<char>,
mut completion_fn: impl FnMut(&str) -> Vec<Completion> + 'static, mut completion_fn: impl FnMut(&str) -> Vec<Completion> + 'static,
callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static, callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static,

@ -36,6 +36,8 @@ pub struct Config {
pub scroll_lines: isize, pub scroll_lines: isize,
/// Mouse support. Defaults to true. /// Mouse support. Defaults to true.
pub mouse: bool, pub mouse: bool,
/// Shell to use for shell commands. Defaults to ["cmd", "/C"] on Windows and ["sh", "-c"] otherwise.
pub shell: Vec<String>,
/// Line number mode. /// Line number mode.
pub line_number: LineNumber, pub line_number: LineNumber,
/// Middle click paste support. Defaults to true /// Middle click paste support. Defaults to true
@ -58,6 +60,11 @@ impl Default for Config {
scrolloff: 5, scrolloff: 5,
scroll_lines: 3, scroll_lines: 3,
mouse: true, mouse: true,
shell: if cfg!(windows) {
vec!["cmd".to_owned(), "/C".to_owned()]
} else {
vec!["sh".to_owned(), "-c".to_owned()]
},
line_number: LineNumber::Absolute, line_number: LineNumber::Absolute,
middle_click_paste: true, middle_click_paste: true,
} }

Loading…
Cancel
Save