on master help sign in
Merge remote-tracking branch 'origin/master' into debug

Contains type fix on helix-term/src/ui/editor.rs:752:13

commit5b20f6020a4acd74bc545fc27c473f0198354e5d authorDmitry Sharshakov <d3dx12.xx@gmail.com> dateTue 31 Aug 2021, 21:29 (5 years ago) parent6265e19a3bd80a
12 files changed+219−73
Cargo.lock+24−24 README.md+1−1 book/src/keymap.md+10 helix-lsp/Cargo.toml+1−1 helix-lsp/src/transport.rs+12−11 helix-term/src/commands.rs+138−15 helix-term/src/keymap.rs+5 helix-term/src/ui/editor.rs+17−17 helix-term/src/ui/mod.rs+1−1 helix-term/src/ui/picker.rs+1−1 helix-term/src/ui/prompt.rs+2−2 helix-view/src/editor.rs+7
Cargo.lock+24−24
@@ -19,9 +19,9 @@ checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
1919
2020[[package]]
2121name = "arc-swap"
22−version = "1.3.1"
22+version = "1.3.2"
2323source = "registry+https://github.com/rust-lang/crates.io-index"
24−checksum = "34a23efe54373080cf871532e2d01076be41c4c896d32ef63af1b2dded924b03"
24+checksum = "b5ab7d9e73059c86c36473f459b52adbd99c3554a4fec492caef460806006f00"
2525
2626[[package]]
2727name = "autocfg"
@@ -232,15 +232,15 @@ dependencies = [
232232
233233[[package]]
234234name = "futures-core"
235−version = "0.3.16"
235+version = "0.3.17"
236236source = "registry+https://github.com/rust-lang/crates.io-index"
237−checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99"
237+checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
238238
239239[[package]]
240240name = "futures-executor"
241−version = "0.3.16"
241+version = "0.3.17"
242242source = "registry+https://github.com/rust-lang/crates.io-index"
243−checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c"
243+checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c"
244244dependencies = [
245245 "futures-core",
246246 "futures-task",
@@ -249,15 +249,15 @@ dependencies = [
249249
250250[[package]]
251251name = "futures-task"
252−version = "0.3.16"
252+version = "0.3.17"
253253source = "registry+https://github.com/rust-lang/crates.io-index"
254−checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2"
254+checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
255255
256256[[package]]
257257name = "futures-util"
258−version = "0.3.16"
258+version = "0.3.17"
259259source = "registry+https://github.com/rust-lang/crates.io-index"
260−checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78"
260+checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
261261dependencies = [
262262 "autocfg",
263263 "futures-core",
@@ -809,18 +809,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
809809
810810[[package]]
811811name = "serde"
812−version = "1.0.129"
812+version = "1.0.130"
813813source = "registry+https://github.com/rust-lang/crates.io-index"
814−checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1"
814+checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
815815dependencies = [
816816 "serde_derive",
817817]
818818
819819[[package]]
820820name = "serde_derive"
821−version = "1.0.129"
821+version = "1.0.130"
822822source = "registry+https://github.com/rust-lang/crates.io-index"
823−checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3"
823+checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
824824dependencies = [
825825 "proc-macro2",
826826 "quote",
@@ -829,9 +829,9 @@ dependencies = [
829829
830830[[package]]
831831name = "serde_json"
832−version = "1.0.66"
832+version = "1.0.67"
833833source = "registry+https://github.com/rust-lang/crates.io-index"
834−checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
834+checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950"
835835dependencies = [
836836 "itoa",
837837 "ryu",
@@ -905,9 +905,9 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
905905
906906[[package]]
907907name = "slotmap"
908−version = "1.0.5"
908+version = "1.0.6"
909909source = "registry+https://github.com/rust-lang/crates.io-index"
910−checksum = "a952280edbecfb1d4bd3cf2dbc309dc6ab523e53487c438ae21a6df09fe84bc4"
910+checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
911911dependencies = [
912912 "version_check",
913913]
@@ -948,18 +948,18 @@ dependencies = [
948948
949949[[package]]
950950name = "thiserror"
951−version = "1.0.26"
951+version = "1.0.28"
952952source = "registry+https://github.com/rust-lang/crates.io-index"
953−checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
953+checksum = "283d5230e63df9608ac7d9691adc1dfb6e701225436eb64d0b9a7f0a5a04f6ec"
954954dependencies = [
955955 "thiserror-impl",
956956]
957957
958958[[package]]
959959name = "thiserror-impl"
960−version = "1.0.26"
960+version = "1.0.28"
961961source = "registry+https://github.com/rust-lang/crates.io-index"
962−checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
962+checksum = "fa3884228611f5cd3608e2d409bf7dce832e4eb3135e3f11addbd7e41bd68e71"
963963dependencies = [
964964 "proc-macro2",
965965 "quote",
@@ -1001,9 +1001,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
10011001
10021002[[package]]
10031003name = "tokio"
1004−version = "1.10.0"
1004+version = "1.10.1"
10051005source = "registry+https://github.com/rust-lang/crates.io-index"
1006−checksum = "01cf844b23c6131f624accf65ce0e4e9956a8bb329400ea5bcc26ae3a5c20b0b"
1006+checksum = "92036be488bb6594459f2e03b60e42df6f937fe6ca5c5ffdcb539c6b84dc40f5"
10071007dependencies = [
10081008 "autocfg",
10091009 "bytes",
README.md+1−1
@@ -43,7 +43,7 @@ cargo install --path helix-term
4343
4444This will install the `hx` binary to `$HOME/.cargo/bin`.
4545
46−Helix also needs it's runtime files so make sure to copy/symlink the `runtime/` directory into the
46+Helix also needs its runtime files so make sure to copy/symlink the `runtime/` directory into the
4747config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overriden
4848via the `HELIX_RUNTIME` environment variable.
4949
book/src/keymap.md+10
@@ -122,6 +122,16 @@ in reverse, or searching via smartcase.
122122| `[D` | Go to first diagnostic in document | `goto_first_diag` |
123123| `]D` | Go to last diagnostic in document | `goto_last_diag` |
124124
125+### Shell
126+
127+| Key | Description | Command |
128+| ------ | ----------- | ------- |
129+| `\|` | Pipe each selection through shell command, replacing with output | `shell_pipe` |
130+| `A-\|` | Pipe each selection into shell command, ignoring output | `shell_pipe_to` |
131+| `!` | Run shell command, inserting output before each selection | `shell_insert_output` |
132+| `A-!` | Run shell command, appending output after each selection | `shell_append_output` |
133+| `$` | Pipe each selection into shell command, removing if the command exits >0 | `shell_keep_pipe` |
134+
125135## Select / extend mode
126136
127137I'm still pondering whether to keep this mode or not. It changes movement
helix-lsp/Cargo.toml+1−1
@@ -23,5 +23,5 @@ lsp-types = { version = "0.89", features = ["proposed"] }
2323serde = { version = "1.0", features = ["derive"] }
2424serde_json = "1.0"
2525thiserror = "1.0"
26−tokio = { version = "1.9", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
26+tokio = { version = "1.10", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
2727tokio-stream = "0.1"
helix-lsp/src/transport.rs+12−11
@@ -1,7 +1,7 @@
11use crate::Result;
22use anyhow::Context;
33use jsonrpc_core as jsonrpc;
4−use log::{error, info};
4+use log::{debug, error, info, warn};
55use serde::{Deserialize, Serialize};
66use serde_json::Value;
77use std::collections::HashMap;
@@ -83,19 +83,20 @@ impl Transport {
8383 break;
8484 }
8585
86− let mut parts = header.split(": ");
86+ debug!("<- header {}", header);
8787
88− match (parts.next(), parts.next(), parts.next()) {
89− (Some("Content-Length"), Some(value), None) => {
88+ let parts = header.split_once(": ");
89+
90+ match parts {
91+ Some(("Content-Length", value)) => {
9092 content_length = Some(value.parse().context("invalid content length")?);
9193 }
92− (Some(_), Some(_), None) => {}
93− _ => {
94− return Err(std::io::Error::new(
95− std::io::ErrorKind::Other,
96− "Failed to parse header",
97− )
98− .into());
94+ Some((_, _)) => {}
95+ None => {
96+ // Workaround: Some non-conformant language servers will output logging and other garbage
97+ // into the same stream as JSON-RPC messages. This can also happen from shell scripts that spawn
98+ // the server. Skip such lines and log a warning.
99+ warn!("Failed to parse header: {:?}", header);
99100 }
100101 }
101102 }
helix-term/src/commands.rs+138−15
@@ -135,7 +135,7 @@ pub struct Command {
135135}
136136
137137macro_rules! commands {
138− ( $($name:ident, $doc:literal),* ) => {
138+ ( $($name:ident, $doc:literal,)* ) => {
139139 $(
140140 #[allow(non_upper_case_globals)]
141141 pub const $name: Self = Self {
@@ -317,7 +317,12 @@ impl Command {
317317 dap_variables, "List variables",
318318 dap_terminate, "End debug session",
319319 dap_switch_thread, "Switch current thread",
320− suspend, "Suspend"
320+ shell_pipe, "Pipe selections through shell command",
321+ shell_pipe_to, "Pipe selections into shell command, ignoring command output",
322+ shell_insert_output, "Insert output of shell command before each selection",
323+ shell_append_output, "Append output of shell command after each selection",
324+ shell_keep_pipe, "Filter selections with shell predicate",
325+ suspend, "Suspend",
321326 );
322327}
323328
@@ -1089,7 +1094,7 @@ fn select_all(cx: &mut Context) {
10891094}
10901095
10911096fn select_regex(cx: &mut Context) {
1092− let prompt = ui::regex_prompt(cx, "select:".to_string(), move |view, doc, _, regex| {
1097+ let prompt = ui::regex_prompt(cx, "select:".into(), move |view, doc, _, regex| {
10931098 let text = doc.text().slice(..);
10941099 if let Some(selection) = selection::select_on_matches(text, doc.selection(view.id), &regex)
10951100 {
@@ -1101,7 +1106,7 @@ fn select_regex(cx: &mut Context) {
11011106}
11021107
11031108fn split_selection(cx: &mut Context) {
1104− let prompt = ui::regex_prompt(cx, "split:".to_string(), move |view, doc, _, regex| {
1109+ let prompt = ui::regex_prompt(cx, "split:".into(), move |view, doc, _, regex| {
11051110 let text = doc.text().slice(..);
11061111 let selection = selection::split_on_matches(text, doc.selection(view.id), &regex);
11071112 doc.set_selection(view.id, selection);
@@ -1167,15 +1172,11 @@ fn search(cx: &mut Context) {
11671172 // feed chunks into the regex yet
11681173 let contents = doc.text().slice(..).to_string();
11691174
1170− let prompt = ui::regex_prompt(
1171− cx,
1172− "search:".to_string(),
1173− move |view, doc, registers, regex| {
1174− search_impl(doc, view, &contents, &regex, false);
1175− // TODO: only store on enter (accept), not update
1176− registers.write('/', vec![regex.as_str().to_string()]);
1177− },
1178− );
1175+ let prompt = ui::regex_prompt(cx, "search:".into(), move |view, doc, registers, regex| {
1176+ search_impl(doc, view, &contents, &regex, false);
1177+ // TODO: only store on enter (accept), not update
1178+ registers.write('/', vec![regex.as_str().to_string()]);
1179+ });
11791180
11801181 cx.push_layer(Box::new(prompt));
11811182}
@@ -2400,7 +2401,7 @@ mod cmd {
24002401
24012402fn command_mode(cx: &mut Context) {
24022403 let mut prompt = Prompt::new(
2403− ":".to_owned(),
2404+ ":".into(),
24042405 Some(':'),
24052406 |input: &str| {
24062407 // we use .this over split_whitespace() because we care about empty segments
@@ -4009,7 +4010,7 @@ fn join_selections(cx: &mut Context) {
40094010
40104011fn keep_selections(cx: &mut Context) {
40114012 // keep selections matching regex
4012− let prompt = ui::regex_prompt(cx, "keep:".to_string(), move |view, doc, _, regex| {
4013+ let prompt = ui::regex_prompt(cx, "keep:".into(), move |view, doc, _, regex| {
40134014 let text = doc.text().slice(..);
40144015
40154016 if let Some(selection) = selection::keep_matches(text, doc.selection(view.id), &regex) {
@@ -4487,6 +4488,128 @@ fn surround_delete(cx: &mut Context) {
44874488 })
44884489}
44894490
4491+#[derive(Eq, PartialEq)]
4492+enum ShellBehavior {
4493+ Replace,
4494+ Ignore,
4495+ Insert,
4496+ Append,
4497+ Filter,
4498+}
4499+
4500+fn shell_pipe(cx: &mut Context) {
4501+ shell(cx, "pipe:".into(), ShellBehavior::Replace);
4502+}
4503+
4504+fn shell_pipe_to(cx: &mut Context) {
4505+ shell(cx, "pipe-to:".into(), ShellBehavior::Ignore);
4506+}
4507+
4508+fn shell_insert_output(cx: &mut Context) {
4509+ shell(cx, "insert-output:".into(), ShellBehavior::Insert);
4510+}
4511+
4512+fn shell_append_output(cx: &mut Context) {
4513+ shell(cx, "append-output:".into(), ShellBehavior::Append);
4514+}
4515+
4516+fn shell_keep_pipe(cx: &mut Context) {
4517+ shell(cx, "keep-pipe:".into(), ShellBehavior::Filter);
4518+}
4519+
4520+fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
4521+ use std::io::Write;
4522+ use std::process::{Command, Stdio};
4523+ if cx.editor.config.shell.is_empty() {
4524+ cx.editor.set_error("No shell set".to_owned());
4525+ return;
4526+ }
4527+ let pipe = match behavior {
4528+ ShellBehavior::Replace | ShellBehavior::Ignore | ShellBehavior::Filter => true,
4529+ ShellBehavior::Insert | ShellBehavior::Append => false,
4530+ };
4531+ let prompt = Prompt::new(
4532+ prompt,
4533+ Some('|'),
4534+ |_input: &str| Vec::new(),
4535+ move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
4536+ if event != PromptEvent::Validate {
4537+ return;
4538+ }
4539+ let shell = &cx.editor.config.shell;
4540+ let (view, doc) = current!(cx.editor);
4541+ let selection = doc.selection(view.id);
4542+
4543+ let mut changes = Vec::with_capacity(selection.len());
4544+
4545+ for range in selection.ranges() {
4546+ let mut process = match Command::new(&shell[0])
4547+ .args(&shell[1..])
4548+ .arg(input)
4549+ .stdin(Stdio::piped())
4550+ .stdout(Stdio::piped())
4551+ .stderr(Stdio::piped())
4552+ .spawn()
4553+ {
4554+ Ok(process) => process,
4555+ Err(e) => {
4556+ log::error!("Failed to start shell: {}", e);
4557+ cx.editor.set_error("Failed to start shell".to_owned());
4558+ return;
4559+ }
4560+ };
4561+ if pipe {
4562+ let stdin = process.stdin.as_mut().unwrap();
4563+ let fragment = range.fragment(doc.text().slice(..));
4564+ stdin.write_all(fragment.as_bytes()).unwrap();
4565+ }
4566+ let output = process.wait_with_output().unwrap();
4567+
4568+ if behavior != ShellBehavior::Filter {
4569+ if !output.status.success() {
4570+ if !output.stderr.is_empty() {
4571+ log::error!("Shell error: {}", String::from_utf8_lossy(&output.stderr));
4572+ }
4573+ cx.editor.set_error("Command failed".to_owned());
4574+ return;
4575+ }
4576+ let tendril = match Tendril::try_from_byte_slice(&output.stdout) {
4577+ Ok(tendril) => tendril,
4578+ Err(_) => {
4579+ cx.editor
4580+ .set_error("Process did not output valid UTF-8".to_owned());
4581+ return;
4582+ }
4583+ };
4584+ let (from, to) = match behavior {
4585+ ShellBehavior::Replace => (range.from(), range.to()),
4586+ ShellBehavior::Insert => (range.from(), range.from()),
4587+ ShellBehavior::Append => (range.to(), range.to()),
4588+ _ => (range.from(), range.from()),
4589+ };
4590+ changes.push((from, to, Some(tendril)));
4591+ } else {
4592+ // if the process exits successfully, keep the selection, otherwise delete it.
4593+ let keep = output.status.success();
4594+ changes.push((
4595+ range.from(),
4596+ if keep { range.from() } else { range.to() },
4597+ None,
4598+ ));
4599+ }
4600+ }
4601+
4602+ if behavior != ShellBehavior::Ignore {
4603+ let transaction = Transaction::change(doc.text(), changes.into_iter());
4604+ doc.apply(&transaction, view.id);
4605+ doc.append_changes_to_history(view.id);
4606+ }
4607+ },
4608+ );
4609+
4610+ cx.push_layer(Box::new(prompt));
4611+}
4612+
44904613fn suspend(_cx: &mut Context) {
44914614 #[cfg(not(windows))]
44924615 signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP).unwrap();
helix-term/src/keymap.rs+5
@@ -525,6 +525,11 @@ impl Default for Keymaps {
525525 },
526526
527527 "\"" => select_register,
528+ "|" => shell_pipe,
529+ "A-|" => shell_pipe_to,
530+ "!" => shell_insert_output,
531+ "A-!" => shell_append_output,
532+ "$" => shell_keep_pipe,
528533 "C-z" => suspend,
529534 });
530535 let mut select = normal.clone();
helix-term/src/ui/editor.rs+17−17
@@ -155,21 +155,21 @@ impl EditorView {
155155 syntax
156156 .highlight_iter(text.slice(..), Some(range), None, |language| {
157157 loader
158− .language_config_for_scope(&format!("source.{}", language))
159− .and_then(|language_config| {
160− let config = language_config.highlight_config(scopes)?;
161− let config_ref = config.as_ref();
162− // SAFETY: the referenced `HighlightConfiguration` behind
163− // the `Arc` is guaranteed to remain valid throughout the
164− // duration of the highlight.
165− let config_ref = unsafe {
166− std::mem::transmute::<
167− _,
168− &'static syntax::HighlightConfiguration,
169− >(config_ref)
170− };
171− Some(config_ref)
172− })
158+ .language_config_for_scope(&format!("source.{}", language))
159+ .and_then(|language_config| {
160+ let config = language_config.highlight_config(scopes)?;
161+ let config_ref = config.as_ref();
162+ // SAFETY: the referenced `HighlightConfiguration` behind
163+ // the `Arc` is guaranteed to remain valid throughout the
164+ // duration of the highlight.
165+ let config_ref = unsafe {
166+ std::mem::transmute::<
167+ _,
168+ &'static syntax::HighlightConfiguration,
169+ >(config_ref)
170+ };
171+ Some(config_ref)
172+ })
173173 })
174174 .map(|event| event.unwrap())
175175 .collect() // TODO: we collect here to avoid holding the lock, fix later
@@ -435,7 +435,7 @@ impl EditorView {
435435
436436 let current_line = doc
437437 .text()
438− .char_to_line(doc.selection(view.id).primary().anchor);
438+ .char_to_line(doc.selection(view.id).primary().cursor(text));
439439
440440 // it's used inside an iterator so the collect isn't needless:
441441 // https://github.com/rust-lang/rust-clippy/issues/6164
@@ -749,7 +749,7 @@ impl EditorView {
749749 _ => noop,
750750 };
751751 Prompt::new(
752− format!("{}: ", name),
752+ format!("{}: ", name).into(),
753753 None,
754754 completer,
755755 move |cx: &mut crate::compositor::Context, input: &str, event: PromptEvent| {
helix-term/src/ui/mod.rs+1−1
@@ -27,7 +27,7 @@ use std::path::PathBuf;
2727
2828pub fn regex_prompt(
2929 cx: &mut crate::commands::Context,
30− prompt: String,
30+ prompt: std::borrow::Cow<'static, str>,
3131 fun: impl Fn(&mut View, &mut Document, &mut Registers, Regex) + 'static,
3232) -> Prompt {
3333 let (view, doc) = current!(cx.editor);
helix-term/src/ui/picker.rs+1−1
@@ -202,7 +202,7 @@ impl<T> Picker<T> {
202202 callback_fn: impl Fn(&mut Editor, &T, Action) + 'static,
203203 ) -> Self {
204204 let prompt = Prompt::new(
205− "".to_string(),
205+ "".into(),
206206 None,
207207 |_pattern: &str| Vec::new(),
208208 |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {
helix-term/src/ui/prompt.rs+2−2
@@ -15,7 +15,7 @@ use helix_view::{
1515pub type Completion = (RangeFrom<usize>, Cow<'static, str>);
1616
1717pub struct Prompt {
18− prompt: String,
18+ prompt: Cow<'static, str>,
1919 pub line: String,
2020 cursor: usize,
2121 completion: Vec<Completion>,
@@ -55,7 +55,7 @@ pub enum Movement {
5555
5656impl Prompt {
5757 pub fn new(
58− prompt: String,
58+ prompt: Cow<'static, str>,
5959 history_register: Option<char>,
6060 mut completion_fn: impl FnMut(&str) -> Vec<Completion> + 'static,
6161 callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static,
helix-view/src/editor.rs+7
@@ -36,6 +36,8 @@ pub struct Config {
3636 pub scroll_lines: isize,
3737 /// Mouse support. Defaults to true.
3838 pub mouse: bool,
39+ /// Shell to use for shell commands. Defaults to ["cmd", "/C"] on Windows and ["sh", "-c"] otherwise.
40+ pub shell: Vec<String>,
3941 /// Line number mode.
4042 pub line_number: LineNumber,
4143 /// Middle click paste support. Defaults to true
@@ -58,6 +60,11 @@ impl Default for Config {
5860 scrolloff: 5,
5961 scroll_lines: 3,
6062 mouse: true,
63+ shell: if cfg!(windows) {
64+ vec!["cmd".to_owned(), "/C".to_owned()]
65+ } else {
66+ vec!["sh".to_owned(), "-c".to_owned()]
67+ },
6168 line_number: LineNumber::Absolute,
6269 middle_click_paste: true,
6370 }