diff --git a/.github/osv-scanner.toml b/.github/osv-scanner.toml index d884235f6d5..972368ff44e 100644 --- a/.github/osv-scanner.toml +++ b/.github/osv-scanner.toml @@ -1 +1,3 @@ -IgnoredVulns = [] +[[IgnoredVulns]] +id = "RUSTSEC-2025-0141" +reason = "bincode is unmaintained but still widely used in the Rust ecosystem. It has no other open RUSTSECs so there is no real risk in using it. Gonna take some time for the ecosystem to transition." diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml index 1b9798f0514..5cb6e90940e 100644 --- a/.github/workflows/osv-scanner.yml +++ b/.github/workflows/osv-scanner.yml @@ -26,6 +26,7 @@ jobs: uses: google/osv-scanner-action/osv-scanner-action@c5996e0193a3df57d695c1b8a1dec2a4c62e8730 # v2.3.3 with: scan-args: |- + --config=.github/osv-scanner.toml --output=results.sarif --format=sarif -r @@ -95,6 +96,7 @@ jobs: uses: google/osv-scanner-action/osv-scanner-action@c5996e0193a3df57d695c1b8a1dec2a4c62e8730 # v2.3.3 with: scan-args: |- + --config=.github/osv-scanner.toml --format=json --output=old-results.json -r @@ -106,6 +108,7 @@ jobs: uses: google/osv-scanner-action/osv-scanner-action@c5996e0193a3df57d695c1b8a1dec2a4c62e8730 # v2.3.3 with: scan-args: |- + --config=.github/osv-scanner.toml --format=json --output=new-results.json -r diff --git a/Cargo.lock b/Cargo.lock index e2b2fa420f3..4451811e50b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,6 +186,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arbitrary" version = "1.4.2" @@ -598,6 +607,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -832,9 +850,11 @@ dependencies = [ "shell-words", "snapbox", "strum", + "syntect", "temp-env", "tempfile", "terminal_size", + "textwrap", "tokio", "tracing", "tracing-forest", @@ -1754,6 +1774,12 @@ dependencies = [ "uuid", ] +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + [[package]] name = "byte-unit" version = "5.2.0" @@ -3207,6 +3233,12 @@ dependencies = [ "regex", ] +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "faster-hex" version = "0.10.0" @@ -7527,6 +7559,28 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "onig" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" +dependencies = [ + "bitflags 2.11.0", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "open" version = "5.3.3" @@ -7697,6 +7751,30 @@ dependencies = [ "supports-color 3.0.2", ] +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf 0.11.3", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "pango" version = "0.18.3" @@ -8556,6 +8634,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" dependencies = [ "instability", + "palette", "ratatui-core", "ratatui-crossterm", "ratatui-macros", @@ -8576,6 +8655,7 @@ dependencies = [ "itertools", "kasuari", "lru", + "palette", "strum", "thiserror 2.0.18", "unicode-segmentation", @@ -9793,6 +9873,12 @@ dependencies = [ "serde", ] +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "snapbox" version = "0.6.24" @@ -10039,6 +10125,26 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "syntect" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" +dependencies = [ + "bincode", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror 2.0.18", + "walkdir", +] + [[package]] name = "sys-locale" version = "0.3.2" @@ -10764,6 +10870,8 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ + "smawk", + "unicode-linebreak", "unicode-width", ] @@ -11492,6 +11600,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.25" diff --git a/crates/but/Cargo.toml b/crates/but/Cargo.toml index 34372c806da..633d152f512 100644 --- a/crates/but/Cargo.toml +++ b/crates/but/Cargo.toml @@ -130,7 +130,7 @@ minus = { git = "https://github.com/arijit79/minus", rev = "57f1848f831d153a10d6 ] } unicode-width = "0.2" cfg-if = "1.0.4" -ratatui.workspace = true +ratatui = { workspace = true, features = ["unstable-rendered-line-info", "palette"] } crossterm.workspace = true sha2.workspace = true machine-uid = "0.5.4" @@ -142,6 +142,14 @@ rand.workspace = true ratatui-textarea = "0.8.0" arboard = { version = "3.6.1", default-features = false } ansi-to-tui = "8.0.1" +textwrap = "0.16.2" +syntect = { version = "5.3.0", default-features = false, features = [ + "default-syntaxes", + "default-themes", + "regex-onig", + "metadata", + "html", +] } [dev-dependencies] but-graph.workspace = true diff --git a/crates/but/assets/syntax-highlighting-themes/Monokai Extended License b/crates/but/assets/syntax-highlighting-themes/Monokai Extended License new file mode 100644 index 00000000000..d1cdd80d308 --- /dev/null +++ b/crates/but/assets/syntax-highlighting-themes/Monokai Extended License @@ -0,0 +1,25 @@ +Monokai Extended.tmTheme was copied from https://github.com/jonschlinkert/sublime-monokai-extended/tree/master under the following license + +------------ + +The MIT License (MIT) + +Copyright (c) 2013-2016, Jon Schlinkert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/crates/but/assets/syntax-highlighting-themes/Monokai Extended.tmTheme b/crates/but/assets/syntax-highlighting-themes/Monokai Extended.tmTheme new file mode 100644 index 00000000000..9c2aa3e4640 --- /dev/null +++ b/crates/but/assets/syntax-highlighting-themes/Monokai Extended.tmTheme @@ -0,0 +1,1878 @@ + + + + + author + github.com/jonschlinkert + colorSpaceName + sRGB + gutterSettings + + background + #073642 + divider + #586e75 + foreground + #839496 + selectionBackground + #586e75 + selectionForeground + #a6e22e + + name + Monokai Extended + semanticClass + theme.dark.monokai_extended + settings + + + settings + + activeGuide + #9d550fb0 + background + #222222 + bracketContentsForeground + #f8f8f2a5 + bracketContentsOptions + underline + bracketsForeground + #f8f8f2a5 + bracketsOptions + underline + caret + #f8f8f0 + findHighlight + #ffe792 + findHighlightForeground + #000000 + foreground + #f8f8f2 + invisibles + #3b3a32 + lineHighlight + #333333 + selection + #444444 + selectionBorder + #1c1c1c + tagsOptions + stippled_underline + + + + name + Comment + scope + comment + settings + + foreground + #75715e + + + + name + String + scope + string + settings + + foreground + #e6db74 + + + + name + Number + scope + constant.numeric + settings + + foreground + #be84ff + + + + name + Constant: Built-in + scope + constant.language, meta.preprocessor + settings + + foreground + #be84ff + + + + name + Constant: User-defined + scope + constant.character, constant.other + settings + + foreground + #be84ff + + + + name + Variable + scope + variable.language, variable.other + settings + + foreground + #ffffff + + + + name + Keyword + scope + keyword + settings + + foreground + #f92672 + + + + name + Storage + scope + storage + settings + + fontStyle + + foreground + #f92672 + + + + name + Storage type + scope + storage.type + settings + + fontStyle + italic + foreground + #66d9ef + + + + name + Class name + scope + entity.name.class + settings + + fontStyle + underline + foreground + #66D9EF + + + + name + Inherited class + scope + entity.other.inherited-class + settings + + fontStyle + italic underline + foreground + #a6e22e + + + + name + Function name + scope + entity.name.function + settings + + fontStyle + + foreground + #a6e22e + + + + name + Function argument + scope + variable.parameter + settings + + fontStyle + italic + foreground + #fd971f + + + + name + Tag name + scope + entity.name.tag + settings + + fontStyle + + foreground + #f92672 + + + + name + Tag attribute + scope + entity.other.attribute-name + settings + + fontStyle + + foreground + #a6e22e + + + + name + Library function + scope + support.function + settings + + fontStyle + + foreground + #66d9ef + + + + name + Library constant + scope + support.constant + settings + + fontStyle + + foreground + #66d9ef + + + + name + Library class/type + scope + support.type, support.class + settings + + fontStyle + italic + foreground + #A6E22E + + + + name + Library variable + scope + support.other.variable + settings + + fontStyle + + + + + name + String constant + scope + string constant + settings + + foreground + #66d9ef + + + + name + String.regexp + scope + string.regexp + settings + + foreground + #f6aa11 + + + + name + String variable + scope + string variable + settings + + foreground + #ffffff + + + + name + Variable: punctuation + scope + punctuation.definition.variable + settings + + foreground + #ffffff + + + + name + Entity + scope + entity + settings + + fontStyle + + foreground + #a6e22e + + + + name + HTML: Doctype/XML Processing + scope + meta.tag.sgml.doctype.xml, declaration.sgml.html declaration.doctype, declaration.sgml.html declaration.doctype entity, declaration.sgml.html declaration.doctype string, declaration.xml-processing, declaration.xml-processing entity, declaration.xml-processing string, doctype + settings + + foreground + #c8cecc + + + + name + HTML: Comment Block + scope + comment.block.html + settings + + fontStyle + + foreground + #7c7865 + + + + name + HTML: Script + scope + entity.name.tag.script.html + settings + + fontStyle + italic + + + + name + HTML: Attribute punctuation + scope + text.html.basic meta.tag.other.html, text.html.basic meta.tag.any.html, text.html.basic meta.tag.block.any, text.html.basic meta.tag.inline.any, text.html.basic meta.tag.structure.any.html, text.html.basic source.js.embedded.html, punctuation.separator.key-value.html + settings + + fontStyle + + foreground + #a6e22e + + + + name + HTML: Attributes + scope + text.html.basic entity.other.attribute-name.html + settings + + foreground + #a6e22e + + + + name + HTML: Quotation Marks + scope + text.html.basic meta.tag.structure.any.html punctuation.definition.string.begin.html, punctuation.definition.string.begin.html, punctuation.definition.string.end.html + settings + + fontStyle + + foreground + #ffffff + + + + name + HTML: Tags punctuation + scope + punctuation.definition.tag.end, punctuation.definition.tag.begin, punctuation.definition.tag + settings + + foreground + #ffffff + + + + name + Handlebars: Variable + scope + variable.parameter.handlebars + settings + + foreground + #f6aa11 + + + + name + Handlebars: Constant + scope + support.constant.handlebars, meta.function.block.start.handlebars + settings + + foreground + #66d9ef + + + + name + CSS: @at-rule + scope + meta.preprocessor.at-rule keyword.control.at-rule + settings + + foreground + #f6aa11 + + + + name + CSS: #Id + scope + meta.selector.css entity.other.attribute-name.id + settings + + foreground + #f6aa11 + + + + name + CSS: .class + scope + meta.selector.css entity.other.attribute-name.class + settings + + foreground + #a6e22e + + + + name + CSS: Property Name + scope + support.type.property-name.css + settings + + foreground + #66d9ef + + + + name + CSS: Constructor Argument + scope + meta.constructor.argument.css + settings + + foreground + #f6aa11 + + + + name + CSS: {} + scope + punctuation.section.property-list.css + settings + + foreground + #ffffff + + + + name + CSS: Tag Punctuation + scope + punctuation.definition.tag.css + settings + + foreground + #f92672 + + + + name + CSS: : , + scope + punctuation.separator.key-value.css, punctuation.terminator.rule.css + settings + + fontStyle + + foreground + #ffffff + + + + name + CSS :pseudo + scope + entity.other.attribute-name.pseudo-element.css, entity.other.attribute-name.pseudo-class.css, entity.other.attribute-name.pseudo-selector.css + settings + + fontStyle + + foreground + #a6e22e + + + + name + LESS variables + scope + variable.other.less + settings + + foreground + #ffffff + + + + name + LESS mixins + scope + entity.other.less.mixin + settings + + fontStyle + italic + foreground + #e0fdce + + + + name + LESS: Extend + scope + entity.other.attribute-name.pseudo-element.less + settings + + fontStyle + + foreground + #ff9117 + + + + name + JS: Instance constructor + scope + meta.instance.constructor meta.function-call.constructor.js + settings + + fontStyle + + foreground + #a6e22e + + + + name + JS: es6 template delimiters ${} + scope + meta.template.expression.js punctuation.definition.template-expression.begin.js, meta.template.expression.js punctuation.definition.template-expression.end.js, meta.template.expression.js punctuation.accessor + settings + + fontStyle + + foreground + #AFF132 + + + + name + JS: Function Name + scope + meta.function.js, entity.name.function.js, support.function.dom.js + settings + + fontStyle + + foreground + #a6e22e + + + + name + JS: Param commas + scope + source.js meta.function.js punctuation.separator.parameter.function.js + settings + + fontStyle + + foreground + #ffffff + + + + name + JS: Object dot notation + scope + meta.property.object.js, keyword.operator.accessor.js + settings + + fontStyle + + foreground + #ffffff + + + + name + JS: colons + scope + source.js meta.group.braces.curly constant.other.object.key.js punctuation.separator.key-value.js + settings + + fontStyle + + foreground + #ffffff + + + + name + JS: unquoted string + scope + source.js meta.group.braces.curly constant.other.object.key.js string.unquoted.label.js + settings + + fontStyle + + foreground + #ffffff + + + + name + JS: receiver + scope + support.type.object.module.js, source.js meta.function.declaration.js support.class.js + settings + + fontStyle + + foreground + #66d9ef + + + + name + JS: receiver property + scope + support.type.object.module.js support.type.object.module.js + settings + + fontStyle + + foreground + #a6e22e + + + + name + JS: Storage Type + scope + storage.type.js + settings + + fontStyle + italic + foreground + #66d9ef + + + + name + JS: Source + scope + text.html.basic source.js.embedded.html + settings + + fontStyle + + foreground + #ffffff + + + + name + JS: Function + scope + storage.type.function.js + settings + + fontStyle + italic + foreground + #66d9ef + + + + name + JS: Numeric Constant + scope + constant.numeric.js + settings + + foreground + #ae81ff + + + + name + JS: Literal language variable + scope + variable.language.arguments.js, variable.language.super.js, variable.language.this.js, variable.language.self.js, variable.language.proto.js, variable.language.constructor.js, variable.language.prototype.js + settings + + fontStyle + italic + foreground + #66d9ef + + + + name + JS: [] + scope + meta.brace.square.js + settings + + foreground + #ffffff + + + + name + JS: () + scope + meta.brace.round, punctuation.definition.parameters.begin.js, punctuation.definition.parameters.end.js, punctuation.definition.group + settings + + foreground + #ffffff + + + + name + JS: object literal {} + scope + meta.brace.curly.js, meta.object-literal.js + settings + + foreground + #ffffff + + + + name + JSON String + scope + meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #cfcfc2 + + + + name + CoffeeScript String Interpolated + scope + punctuation.section.embedded.coffee + settings + + foreground + #e69f66 + + + + name + PHP: [] + scope + keyword.operator.index-start.php, keyword.operator.index-end.php + settings + + foreground + #ffffff + + + + name + PHP: Array + scope + meta.array.php + settings + + foreground + #ffffff + + + + name + PHP: Array() + scope + meta.array.php support.function.construct.php, meta.array.empty.php support.function.construct.php + settings + + fontStyle + + foreground + #e42e70 + + + + name + PHP: Array Construct + scope + support.function.construct.php + settings + + foreground + #e42e70 + + + + name + PHP: Storage Type Function + scope + storage.type.function.php + settings + + foreground + #f92672dd + + + + name + PHP: Numeric Constant + scope + constant.numeric.php + settings + + foreground + #be84ff + + + + name + PHP: New + scope + keyword.other.new.php + settings + + foreground + #f6aa11 + + + + name + PHP: :: + scope + support.class.php + settings + + fontStyle + + foreground + #ffffff + + + + name + PHP: Other Property + scope + variable.other.property.php + settings + + foreground + #f6aa11 + + + + name + PHP: Class + scope + storage.modifier.extends.php, storage.type.class.php, keyword.operator.class.php + settings + + foreground + #a6e22e + + + + name + PHP: Inherited Class + scope + meta.other.inherited-class.php + settings + + fontStyle + + foreground + #a6e22e + + + + name + PHP: Storage Type + scope + storage.type.php + settings + + foreground + #66d9ef + + + + name + PHP: Function + scope + entity.name.function.php + settings + + foreground + #66d9ef + + + + name + PHP: Function Construct + scope + support.function.construct.php + settings + + foreground + #a6e22e + + + + name + PHP: Function Call + scope + entity.name.type.class.php, meta.function-call.php, meta.function-call.static.php, meta.function-call.object.php + settings + + foreground + #ffffff + + + + name + PHP: Comment + scope + keyword.other.phpdoc + settings + + fontStyle + + foreground + #7c7865 + + + + name + PHP: Source Emebedded + scope + source.php.embedded.block.html + settings + + foreground + #ffffff + + + + name + Invalid + scope + invalid + settings + + background + #f92672 + fontStyle + + foreground + #f8f8f0 + + + + name + Invalid deprecated + scope + invalid.deprecated + settings + + background + #ae81ff + foreground + #f8f8f0 + + + + name + diff.header + scope + meta.diff, meta.diff.header + settings + + foreground + #75715e + + + + name + diff.deleted + scope + markup.deleted + settings + + foreground + #f92672 + + + + name + diff.inserted + scope + markup.inserted + settings + + foreground + #a6e22e + + + + name + diff.changed + scope + markup.changed + settings + + foreground + #e6db74 + + + + name + diff.range + scope + meta.diff, meta.diff.range + settings + + foreground + #3bc0f0 + + + + name + Python: storage + scope + storage.type.class.python, storage.type.function.python, storage.modifier.global.python + settings + + fontStyle + + foreground + #a6e22e + + + + name + Python: import + scope + keyword.control.import.python, keyword.control.import.from.python + settings + + foreground + #f92672dd + + + + name + Python: Support.exception + scope + support.type.exception.python + settings + + foreground + #66d9ef + + + + name + Perl: variables + scope + punctuation.definition.variable.perl, variable.other.readwrite.global.perl, variable.other.predefined.perl, keyword.operator.comparison.perl + settings + + foreground + #e42e70 + + + + name + Perl: functions + scope + support.function.perl + settings + + foreground + #66d9ef + + + + name + Perl: comments + scope + comment.line.number-sign.perl + settings + + fontStyle + italic + foreground + #75715e + + + + name + Perl: quotes + scope + punctuation.definition.string.begin.perl, punctuation.definition.string.end.perl + settings + + foreground + #ffffff + + + + name + Perl: char + scope + constant.character.escape.perl + settings + + foreground + #dc322f + + + + name + Ruby: Constant + scope + constant.language.ruby, constant.numeric.ruby + settings + + foreground + #ae81ff + + + + name + Ruby: Variable definition + scope + punctuation.definition.variable.ruby + settings + + fontStyle + + foreground + #f6aa11 + + + + name + Ruby: Function Name + scope + meta.function.method.with-arguments.ruby + settings + + foreground + #a6e22e + + + + name + Ruby: Variable + scope + variable.language.ruby + settings + + foreground + #ffffff + + + + name + Ruby: Function + scope + entity.name.function.ruby + settings + + foreground + #f6aa11 + + + + name + Ruby: Keyword Control + scope + keyword.control.ruby, keyword.control.def.ruby + settings + + fontStyle + bold + foreground + #a6e22e + + + + name + Ruby: Class + scope + keyword.control.class.ruby, meta.class.ruby + settings + + foreground + #a6e22e + + + + name + Ruby: Class Name + scope + entity.name.type.class.ruby + settings + + fontStyle + + foreground + #66d9ef + + + + name + Ruby: Keyword + scope + keyword.control.ruby + settings + + fontStyle + + foreground + #a6e22e + + + + name + Ruby: Support Class + scope + support.class.ruby + settings + + fontStyle + + foreground + #66d9ef + + + + name + Ruby: Special Method + scope + keyword.other.special-method.ruby + settings + + foreground + #a6e22e + + + + name + Ruby: Constant Other + scope + variable.other.constant.ruby + settings + + fontStyle + + foreground + #66d9ef + + + + name + Ruby: :symbol + scope + constant.other.symbol.ruby + settings + + fontStyle + + foreground + #f6f080 + + + + name + Ruby: Punctuation Section + scope + punctuation.section.embedded.ruby, punctuation.definition.string.begin.ruby, punctuation.definition.string.end.ruby + settings + + foreground + #f92672 + + + + name + Ruby: Special Method + scope + keyword.other.special-method.ruby + settings + + foreground + #e42e70 + + + + name + Markdown: plain + scope + text.html.markdown + settings + + foreground + #ffffff + + + + name + Markup: raw inline + scope + text.html.markdown markup.raw.inline + settings + + foreground + #ec3533 + + + + name + Markdown: linebreak + scope + text.html.markdown meta.dummy.line-break + settings + + foreground + #e0eddd + + + + name + Markdown: heading + scope + markdown.heading, markup.heading | markup.heading entity.name, markup.heading.markdown punctuation.definition.heading.markdown + settings + + fontStyle + + foreground + #fd971f + + + + name + Markup: italic + scope + markup.italic + settings + + fontStyle + italic + foreground + #e42e70 + + + + name + Markup: bold + scope + markup.bold + settings + + fontStyle + bold + foreground + #f92672 + + + + name + Markup: underline + scope + markup.underline + settings + + fontStyle + underline + foreground + #a6e22e + + + + name + Markup: strike + scope + markup.strike + settings + + fontStyle + + foreground + #cc4273 + + + + name + Markdown: Blockquote + scope + markup.quote, punctuation.definition.blockquote.markdown + settings + + fontStyle + italic + foreground + #66d9ef + + + + name + Markup: Quote + scope + markup.quote + settings + + fontStyle + italic + foreground + #66d9ef + + + + name + Markdown: Link + scope + string.other.link.title.markdown + settings + + fontStyle + underline + foreground + #66d9ef + + + + name + Markup: Raw block + scope + markup.raw.block + settings + + foreground + #ae81ff + + + + name + Markdown: List Items Punctuation + scope + punctuation.definition.list_item.markdown + settings + + foreground + #777777 + + + + name + Markdown: Raw Block fenced + scope + markup.raw.block.fenced.markdown + settings + + background + #222 + foreground + #ffffff + + + + name + Markdown: Fenced Bode Block + scope + punctuation.definition.fenced.markdown, variable.language.fenced.markdown + settings + + background + #222222 + foreground + #636050 + + + + name + Markdown: Fenced Language + scope + variable.language.fenced.markdown + settings + + fontStyle + + foreground + #7c7865 + + + + name + Markdown: Separator + scope + meta.separator + settings + + background + #ffffff0f + fontStyle + bold + foreground + #ffffff33 + + + + name + Markup: table + scope + markup.table + settings + + background + #ff3a281a + foreground + #b42a1d + + + + name + LaTeX: Math Variables + scope + variable.other.math.tex + settings + + foreground + #e6db74 + + + + name + Other: Removal + scope + other.package.exclude, other.remove + settings + + fontStyle + + foreground + #d3201f + + + + name + Shell: builtin + scope + support.function.builtin.shell + settings + + foreground + #a6e22e + + + + name + Shell: variable + scope + variable.other.normal.shell + settings + + foreground + #66d9ef + + + + name + Shell: DOTFILES + scope + source.shell + settings + + fontStyle + + foreground + #ffffff + + + + name + Shell: meta scope in loop + scope + meta.scope.for-in-loop.shell, variable.other.loop.shell + settings + + fontStyle + + foreground + #fd971f + + + + name + Shell: Function name + scope + entity.name.function.shell + settings + + fontStyle + + foreground + #a6e22e + + + + name + Shell: Quotation Marks + scope + punctuation.definition.string.end.shell, punctuation.definition.string.begin.shell + settings + + fontStyle + + foreground + #ffffff + + + + name + Shell: Meta Block + scope + meta.scope.case-block.shell, meta.scope.case-body.shell + settings + + fontStyle + + foreground + #fd971f + + + + name + Shell: [] + scope + punctuation.definition.logical-expression.shell + settings + + fontStyle + + foreground + #ffffff + + + + name + Shell: Comment + scope + comment.line.number-sign.shell + settings + + fontStyle + italic + foreground + #7c7865 + + + + name + Makefile: Comment + scope + comment.line.number-sign.makefile + settings + + fontStyle + + foreground + #7c7865 + + + + name + Makefile: Comment punctuation + scope + punctuation.definition.comment.makefile + settings + + fontStyle + + foreground + #7c7865 + + + + name + Makefile: Variables + scope + variable.other.makefile + settings + + fontStyle + + foreground + #f92672 + + + + name + Makefile: Function name + scope + entity.name.function.makefile + settings + + fontStyle + + foreground + #a6e22e + + + + name + Makefile: Function + scope + meta.function.makefile + settings + + fontStyle + + foreground + #66d9ef + + + + name + GitGutter deleted + scope + markup.deleted.git_gutter + settings + + foreground + #F92672 + + + + name + GitGutter inserted + scope + markup.inserted.git_gutter + settings + + foreground + #A6E22E + + + + name + GitGutter changed + scope + markup.changed.git_gutter + settings + + foreground + #FC951E + + + + name + GitGutter ignored + scope + markup.ignored.git_gutter + settings + + foreground + #565656 + + + + name + GitGutter untracked + scope + markup.untracked.git_gutter + settings + + foreground + #565656 + + + + name + Nginx path + scope + string.other.path.nginx + settings + + foreground + #fc951e + + + + uuid + 1D07ACC0-832F-11E2-9E96-0800200C9A66 + + diff --git a/crates/but/src/command/legacy/diff/display.rs b/crates/but/src/command/legacy/diff/display.rs index 8ea4f348e50..ff4d12ba5a8 100644 --- a/crates/but/src/command/legacy/diff/display.rs +++ b/crates/but/src/command/legacy/diff/display.rs @@ -191,9 +191,9 @@ impl DiffDisplay for HunkAssignment { let content_width = short_id.as_ref().map_or(0, |s| s.len() + 1) + self.path.len(); // Render box-style header: - // ────────╮ + // ─────────╮ // file│ - // ────────╯ + // ─────────╯ output.push_str(&format!("{}╮\n", "─".repeat(content_width).dimmed())); if let Some(id) = &short_id { output.push_str(&format!("{} {}│\n", id.blue().bold(), self.path.bold())); diff --git a/crates/but/src/command/legacy/status/tui/details.rs b/crates/but/src/command/legacy/status/tui/details.rs new file mode 100644 index 00000000000..24989209169 --- /dev/null +++ b/crates/but/src/command/legacy/status/tui/details.rs @@ -0,0 +1,903 @@ +use std::{ + collections::BTreeMap, + iter::{once, repeat_n}, + sync::LazyLock, +}; + +use bstr::{BStr, ByteSlice}; +use but_core::{ + UnifiedPatch, + ui::{TreeChange, TreeStatus}, + unified_diff::DiffHunk, +}; +use but_ctx::{Context, OnDemand}; +use but_hunk_assignment::HunkAssignment; +use gitbutler_stack::StackId; +use gix::actor::Signature; +use ratatui::{ + Frame, + layout::{Constraint, Layout, Rect}, + palette::Hsl, + style::{Color, Style, Stylize}, + text::{Line, Span}, + widgets::{Block, Borders, List, ListItem, Widget}, +}; +use syntect::{ + easy::HighlightLines, + highlighting::{Theme, ThemeSet}, + parsing::SyntaxSet, +}; +use unicode_width::UnicodeWidthStr; + +use crate::{ + CliId, + command::legacy::status::tui::{ + CommandMessage, CommitMessage, DebugType, FilesMessage, Message, MoveMessage, + RewordMessage, RubMessage, + }, + id::UncommittedCliId, +}; + +use super::BranchMessage; + +// we don't currently compute word level diffs so MINUS_EMPH_BG and PLUS_EMPH_BG aren't used (in +// the diff lines themselves). Without that MINUS_BG and PLUS_BG are a little too hard to see, so +// this adjustment is applied to make them more clear. +const LIGHTNESS_ADJUSTMENT: f32 = 0.05; + +// colors from delta with slight adjustment +static MINUS_BG: LazyLock = + LazyLock::new(|| Color::from_hsl(Hsl::new(-0.952, 1.0, 0.123 + LIGHTNESS_ADJUSTMENT))); +static PLUS_BG: LazyLock = + LazyLock::new(|| Color::from_hsl(Hsl::new(120.0, 1.0, 0.078 + LIGHTNESS_ADJUSTMENT))); + +static MINUS_EMPH_BG: LazyLock = + LazyLock::new(|| Color::from_hsl(Hsl::new(-0.468, 0.8, 0.313))); +static PLUS_EMPH_BG: LazyLock = + LazyLock::new(|| Color::from_hsl(Hsl::new(120.0, 1.0, 0.188))); + +const MONOKAI_THEME: &[u8] = + include_bytes!("../../../../../assets/syntax-highlighting-themes/Monokai Extended.tmTheme"); + +#[derive(Debug, Default, Copy, Clone)] +pub(super) enum DetailsVisibility { + #[default] + Hidden, + VisibleVertical, +} + +#[derive(Debug, Clone)] +pub(super) enum DetailsMessage { + ScrollUp(usize), + ScrollDown(usize), + ToggleVisibility, +} + +#[derive(Debug)] +pub(super) struct Details { + is_dirty: bool, + scroll_top: usize, + widget: Option, + syntax_set: DebugType>, + dark_theme: DebugType>, + visibility: DetailsVisibility, +} + +impl Default for Details { + fn default() -> Self { + Self { + is_dirty: true, + widget: Default::default(), + scroll_top: Default::default(), + visibility: Default::default(), + syntax_set: OnDemand::new(|| Ok(SyntaxSet::load_defaults_newlines())).into(), + dark_theme: OnDemand::new(|| { + Ok(ThemeSet::load_from_reader(&mut std::io::Cursor::new(MONOKAI_THEME)).unwrap()) + }) + .into(), + } + } +} + +impl Details { + pub(super) fn mark_dirty(&mut self) { + self.is_dirty = true; + } + + pub(super) fn is_dirty(&self) -> bool { + self.is_dirty + } + + pub(super) fn visibility(&self) -> DetailsVisibility { + self.visibility + } + + pub(super) fn needs_update_after_message(&self, msg: &Message) -> bool { + match self.visibility { + DetailsVisibility::Hidden => return false, + DetailsVisibility::VisibleVertical => {} + } + + match msg { + Message::JustRender + | Message::CopySelection + | Message::Quit + | Message::ShowError(_) + | Message::ShowToast { .. } + | Message::Confirm(_) + | Message::EnterNormalMode => false, + + Message::MoveCursorUp + | Message::MoveCursorDown + | Message::MoveCursorPreviousSection + | Message::MoveCursorNextSection + | Message::Reload(_) + | Message::RunAfterConfirmation(_) => true, + + Message::Commit(commit_message) => match commit_message { + CommitMessage::Confirm { .. } | CommitMessage::CreateEmpty => true, + CommitMessage::Start | CommitMessage::SetInsertSide(_) => false, + }, + Message::Rub(rub_message) => match rub_message { + RubMessage::Start { .. } => false, + RubMessage::Confirm => true, + }, + Message::Reword(reword_message) => match reword_message { + RewordMessage::WithEditor | RewordMessage::InlineConfirm => true, + RewordMessage::InlineStart | RewordMessage::InlineInput(_) => false, + }, + Message::Command(command_message) => match command_message { + CommandMessage::Start | CommandMessage::Input(_) => false, + CommandMessage::Confirm => true, + }, + Message::Files(files_message) => match files_message { + FilesMessage::ToggleGlobalFilesList | FilesMessage::ToggleFilesForCommit => true, + }, + Message::Move(move_message) => match move_message { + MoveMessage::Start | MoveMessage::SetInsertSide(_) => false, + MoveMessage::Confirm => true, + }, + Message::Branch(branch_message) => match branch_message { + BranchMessage::Start => false, + BranchMessage::New => true, + }, + Message::Details(details_message) => match details_message { + DetailsMessage::ScrollUp(_) + | DetailsMessage::ScrollDown(_) + | DetailsMessage::ToggleVisibility => false, + }, + } + } + pub(super) fn try_handle_message( + &mut self, + msg: DetailsMessage, + viewport: Rect, + ) -> anyhow::Result<()> { + match msg { + DetailsMessage::ScrollUp(n) => { + self.scroll_top = self.scroll_top.saturating_sub(n); + } + DetailsMessage::ScrollDown(n) => { + self.scroll_top = self.scroll_top.saturating_add(n); + } + DetailsMessage::ToggleVisibility => { + self.visibility = match self.visibility { + DetailsVisibility::Hidden => DetailsVisibility::VisibleVertical, + DetailsVisibility::VisibleVertical => DetailsVisibility::Hidden, + }; + + match self.visibility { + DetailsVisibility::Hidden => { + self.scroll_top = 0; + } + DetailsVisibility::VisibleVertical => { + self.mark_dirty(); + } + } + } + } + + self.clamp_scroll_top(viewport); + + Ok(()) + } + + fn clamp_scroll_top(&mut self, viewport: Rect) { + let max_scroll_top = self + .widget + .as_ref() + .map(|diff| { + diff.total_rows(viewport.width) + .saturating_sub(viewport.height as usize) + }) + .unwrap_or(0); + + self.scroll_top = self.scroll_top.min(max_scroll_top); + } + + pub(super) fn update( + &mut self, + ctx: &mut Context, + selection: Option<&CliId>, + ) -> anyhow::Result<()> { + self.is_dirty = false; + self.scroll_top = 0; + + self.widget = self.update_widget(ctx, selection)?; + + Ok(()) + } + + fn update_widget( + &mut self, + ctx: &mut Context, + selection: Option<&CliId>, + ) -> anyhow::Result> { + let Some(selection) = selection else { + return Ok(None); + }; + + Ok(Some(match selection { + CliId::Commit { commit_id, .. } => DetailsAndDiffWidget::from_commit( + ctx, + *commit_id, + &*self.syntax_set.get()?, + &*self.dark_theme.get()?, + )?, + CliId::Branch { name, .. } => DetailsAndDiffWidget::from_branch( + ctx, + name.to_owned(), + &*self.syntax_set.get()?, + &*self.dark_theme.get()?, + )?, + CliId::Uncommitted(uncommitted) => DetailsAndDiffWidget::from_uncommitted( + uncommitted, + &*self.syntax_set.get()?, + &*self.dark_theme.get()?, + )?, + CliId::Unassigned { .. } => DetailsAndDiffWidget::from_unassigned( + ctx, + &*self.syntax_set.get()?, + &*self.dark_theme.get()?, + )?, + CliId::CommittedFile { + commit_id, path, .. + } => DetailsAndDiffWidget::from_committed_file( + ctx, + *commit_id, + path.as_ref(), + &*self.syntax_set.get()?, + &*self.dark_theme.get()?, + )?, + CliId::Stack { stack_id, .. } => DetailsAndDiffWidget::from_stack( + ctx, + *stack_id, + &*self.syntax_set.get()?, + &*self.dark_theme.get()?, + )?, + CliId::PathPrefix { + hunk_assignments, .. + } => DetailsAndDiffWidget::from_path_prefix( + hunk_assignments, + &*self.syntax_set.get()?, + &*self.dark_theme.get()?, + )?, + })) + } + + pub(super) fn render(&self, area: Rect, frame: &mut Frame) { + let layout = Layout::horizontal([Constraint::Length(1), Constraint::Min(1)]).split(area); + + let block = Block::new() + .borders(Borders::LEFT) + .border_style(Style::default().dim()); + frame.render_widget(block, layout[0]); + + if let Some(diff) = &self.widget { + diff.render(self.scroll_top, layout[1], frame); + } + } +} + +#[derive(Debug)] +enum DetailsAndDiffWidget { + FromCommit { + header_items: Vec>, + message: String, + diff_line_items: Vec>, + }, + FromDiffLines { + diff_line_items: Vec>, + }, +} + +impl DetailsAndDiffWidget { + fn from_commit( + ctx: &mut Context, + commit_id: gix::ObjectId, + syntax_set: &SyntaxSet, + theme: &Theme, + ) -> anyhow::Result { + let commit_details = + but_api::diff::commit_details(ctx, commit_id, but_api::diff::ComputeLineStats::No)?; + + let header_items = Vec::from([ + ListItem::new(Line::from_iter([ + Span::raw(format!("{:<11}", "Commit ID:")), + Span::raw(commit_id.to_hex().to_string()).blue(), + ])), + ListItem::new(Line::from_iter( + once(Span::raw(format!("{:<11}", "Author:"))) + .chain(render_signature(&commit_details.commit.author)), + )), + ListItem::new(Line::from_iter( + once(Span::raw(format!("{:<11}", "Committer:"))) + .chain(render_signature(&commit_details.commit.committer)), + )), + ]); + + let message = commit_details.commit.message.to_string(); + + let tree_changes = commit_details + .diff_with_first_parent + .iter() + .map(|change| TreeChange::from(change.clone())) + .collect::>(); + + let mut diff_line_items = Vec::new(); + render_tree_changes(ctx, &tree_changes, syntax_set, theme, &mut diff_line_items); + + Ok(DetailsAndDiffWidget::FromCommit { + header_items, + message, + diff_line_items, + }) + } + + fn from_committed_file( + ctx: &mut Context, + commit_id: gix::ObjectId, + path: &BStr, + syntax_set: &SyntaxSet, + theme: &Theme, + ) -> anyhow::Result { + let commit_details = + but_api::diff::commit_details(ctx, commit_id, but_api::diff::ComputeLineStats::No)?; + + let tree_changes = commit_details + .diff_with_first_parent + .iter() + .filter(|change| change.path == path) + .map(|change| TreeChange::from(change.clone())) + .collect::>(); + + let mut diff_line_items = Vec::new(); + render_tree_changes(ctx, &tree_changes, syntax_set, theme, &mut diff_line_items); + + Ok(DetailsAndDiffWidget::FromDiffLines { diff_line_items }) + } + + fn from_branch( + ctx: &mut Context, + name: String, + syntax_set: &SyntaxSet, + theme: &Theme, + ) -> anyhow::Result { + let tree_changes = but_api::branch::branch_diff(ctx, name)?; + + let mut diff_line_items = Vec::new(); + render_tree_changes( + ctx, + &tree_changes.changes, + syntax_set, + theme, + &mut diff_line_items, + ); + + Ok(DetailsAndDiffWidget::FromDiffLines { diff_line_items }) + } + + fn from_uncommitted( + uncommitted: &UncommittedCliId, + syntax_set: &SyntaxSet, + theme: &Theme, + ) -> anyhow::Result { + let mut diff_line_items = Vec::new(); + + // the path is the same for all hunks so only show that once + let first_hunk = uncommitted.hunk_assignments.first(); + render_hunk_path_header( + first_hunk.path_bytes.as_ref(), + Some(ShortIdOrTreeStatus::ShortId(&uncommitted.id)), + &mut diff_line_items, + ); + + let mut hunk_assignments_iter = uncommitted.hunk_assignments.iter().peekable(); + while let Some(hunk_assignment) = hunk_assignments_iter.next() { + render_hunk_assignment(hunk_assignment, syntax_set, theme, &mut diff_line_items); + + if hunk_assignments_iter.peek().is_some() { + diff_line_items.push(ListItem::new("")); + } + } + + Ok(DetailsAndDiffWidget::FromDiffLines { diff_line_items }) + } + + fn from_unassigned( + ctx: &mut Context, + syntax_set: &SyntaxSet, + theme: &Theme, + ) -> anyhow::Result { + let context_lines = ctx.settings.context_lines; + let (_guard, repo, ws, mut db) = ctx.workspace_and_db_mut()?; + let changes = but_core::diff::ui::worktree_changes(&repo)?.changes; + let (assignments, _assignments_error) = but_hunk_assignment::assignments_with_fallback( + db.hunk_assignments_mut()?, + &repo, + &ws, + Some(changes), + context_lines, + )?; + let hunk_assignments = assignments + .iter() + .filter(|assignment| assignment.stack_id.is_none()); + + let mut diff_line_items = Vec::new(); + group_and_render_hunk_assignments( + hunk_assignments, + &mut diff_line_items, + syntax_set, + theme, + ); + + Ok(DetailsAndDiffWidget::FromDiffLines { diff_line_items }) + } + + fn from_stack( + ctx: &mut Context, + stack: StackId, + syntax_set: &SyntaxSet, + theme: &Theme, + ) -> anyhow::Result { + let context_lines = ctx.settings.context_lines; + let (_guard, repo, ws, mut db) = ctx.workspace_and_db_mut()?; + let changes = but_core::diff::ui::worktree_changes(&repo)?.changes; + let (assignments, _assignments_error) = but_hunk_assignment::assignments_with_fallback( + db.hunk_assignments_mut()?, + &repo, + &ws, + Some(changes), + context_lines, + )?; + let hunk_assignments = assignments + .iter() + .filter(|assignment| assignment.stack_id.is_some_and(|s| s == stack)); + + let mut diff_line_items = Vec::new(); + group_and_render_hunk_assignments( + hunk_assignments, + &mut diff_line_items, + syntax_set, + theme, + ); + + Ok(DetailsAndDiffWidget::FromDiffLines { diff_line_items }) + } + + fn from_path_prefix<'a>( + hunk_assignments: impl IntoIterator, + syntax_set: &SyntaxSet, + theme: &Theme, + ) -> anyhow::Result { + let mut diff_line_items = Vec::new(); + + let mut hunk_assignments_iter = hunk_assignments.into_iter().peekable(); + while let Some((id, hunk_assignment)) = hunk_assignments_iter.next() { + render_hunk_path_header( + hunk_assignment.path_bytes.as_ref(), + Some(ShortIdOrTreeStatus::ShortId(id)), + &mut diff_line_items, + ); + + render_hunk_assignment(hunk_assignment, syntax_set, theme, &mut diff_line_items); + + if hunk_assignments_iter.peek().is_some() { + diff_line_items.push(ListItem::new("")); + } + } + + Ok(DetailsAndDiffWidget::FromDiffLines { diff_line_items }) + } + + fn total_rows(&self, width: u16) -> usize { + self.items_for_width(width).count() + } + + fn render(&self, scroll_top: usize, area: Rect, buf: &mut Frame) { + let items = self.items_for_width(area.width).skip(scroll_top); + List::new(items).render(area, buf.buffer_mut()); + } + + fn items_for_width(&self, width: u16) -> impl Iterator> { + let width = usize::from(width).max(1); + + match self { + DetailsAndDiffWidget::FromCommit { + header_items, + message, + diff_line_items, + } => { + let iter = header_items + .clone() + .into_iter() + .chain([ListItem::new("")]) + .chain( + textwrap::wrap(message, textwrap::Options::new(width)) + .into_iter() + .map(|line| ListItem::new(line.into_owned())), + ) + .chain([ListItem::new("")]) + .chain(diff_line_items.clone()); + itertools::Either::Left(iter) + } + DetailsAndDiffWidget::FromDiffLines { diff_line_items } => { + itertools::Either::Right(diff_line_items.clone().into_iter()) + } + } + } +} + +fn group_and_render_hunk_assignments<'a, I>( + hunk_assignments: I, + out: &mut Vec>, + syntax_set: &SyntaxSet, + theme: &Theme, +) where + I: IntoIterator, +{ + // group hunks for the same file + let mut path_to_hunk_assignments = BTreeMap::<_, Vec<_>>::new(); + for hunk_assignment in hunk_assignments { + path_to_hunk_assignments + .entry(&hunk_assignment.path_bytes) + .or_default() + .push(hunk_assignment); + } + + let mut path_to_hunk_assignments_iter = path_to_hunk_assignments.into_iter().peekable(); + while let Some((path, hunk_assignments)) = path_to_hunk_assignments_iter.next() { + render_hunk_path_header(path.as_ref(), None, out); + + let mut hunk_assignments_iter = hunk_assignments.into_iter().peekable(); + while let Some(hunk_assignment) = hunk_assignments_iter.next() { + render_hunk_assignment(hunk_assignment, syntax_set, theme, out); + + if hunk_assignments_iter.peek().is_some() { + out.push(ListItem::new("")); + } + } + + if path_to_hunk_assignments_iter.peek().is_some() { + out.push(ListItem::new("")); + } + } +} + +fn render_hunk_assignment( + hunk_assignment: &HunkAssignment, + syntax_set: &SyntaxSet, + theme: &Theme, + out: &mut Vec>, +) { + if let Some(hunk_header) = hunk_assignment.hunk_header { + if let Some(diff) = hunk_assignment.diff.clone() { + let hunks = Vec::from([DiffHunk { + old_start: hunk_header.old_start, + old_lines: hunk_header.old_lines, + new_start: hunk_header.new_start, + new_lines: hunk_header.new_lines, + diff, + }]); + + let is_result_of_binary_to_text_conversion = false; + + render_unified_patch( + hunk_assignment.path_bytes.as_ref(), + syntax_set, + theme, + hunks, + is_result_of_binary_to_text_conversion, + out, + ); + } else { + out.push(ListItem::new("No diff available")); + } + } else { + out.push(ListItem::new( + "File is too large or binary - no diff available", + )); + } +} + +fn render_tree_changes( + ctx: &mut Context, + tree_changes: &[TreeChange], + syntax_set: &SyntaxSet, + theme: &Theme, + out: &mut Vec>, +) { + for tree_change in tree_changes { + render_hunk_path_header( + tree_change.path.as_ref(), + Some(ShortIdOrTreeStatus::TreeStatus(&tree_change.status)), + out, + ); + + if let Some(patch) = but_api::diff::tree_change_diffs(ctx, tree_change.clone()) + .ok() + .flatten() + { + match patch { + UnifiedPatch::Patch { + hunks, + is_result_of_binary_to_text_conversion, + lines_added: _, + lines_removed: _, + } => { + render_unified_patch( + tree_change.path.as_ref(), + syntax_set, + theme, + hunks, + is_result_of_binary_to_text_conversion, + out, + ); + } + UnifiedPatch::Binary => { + out.push(ListItem::new("Binary file - no diff available")); + } + UnifiedPatch::TooLarge { size_in_bytes } => { + out.push(ListItem::new(format!( + "File too large ({size_in_bytes} bytes) - no diff available" + ))); + } + } + + out.push(ListItem::new("")); + } + } +} + +enum ShortIdOrTreeStatus<'a> { + ShortId(&'a str), + TreeStatus(&'a TreeStatus), +} + +fn render_hunk_path_header( + path: &BStr, + status: Option>, + out: &mut Vec>, +) { + let status = status.map(|id_or_status| match id_or_status { + ShortIdOrTreeStatus::ShortId(id) => Span::raw(id.to_owned()).blue(), + ShortIdOrTreeStatus::TreeStatus(status) => change_status(status), + }); + let path = path.to_string(); + let path_line = Line::from_iter( + [Span::raw(" ")] + .into_iter() + .chain( + status + .into_iter() + .flat_map(|status| [status, Span::raw(": ")]), + ) + .chain([Span::raw(path)]), + ); + out.extend(bordered_line_top_right_bottom(path_line).map(ListItem::new)); + out.push(ListItem::from("")); +} + +fn change_status(status: &TreeStatus) -> Span<'static> { + match status { + TreeStatus::Addition { .. } => Span::raw("added").green(), + TreeStatus::Deletion { .. } => Span::raw("deleted").red(), + TreeStatus::Modification { .. } => Span::raw("modified").magenta(), + TreeStatus::Rename { .. } => Span::raw("renamed").blue(), + } +} + +fn bordered_line_top_right_bottom(mut text: Line<'static>) -> impl Iterator> { + let width_including_padding = text.width() + 1; + + text.spans.extend([Span::raw(" "), Span::raw("│").dim()]); + + [ + Line::from_iter(repeat_n("─", width_including_padding).chain(once("╮"))).dim(), + text, + Line::from_iter(repeat_n("─", width_including_padding).chain(once("╯"))).dim(), + ] + .into_iter() +} + +fn render_signature(sig: &Signature) -> impl IntoIterator> { + [ + Span::raw(sig.name.to_string()).yellow(), + Span::raw(" <"), + Span::raw(sig.email.to_string()).yellow(), + Span::raw(">"), + Span::raw(" ("), + Span::raw(sig.time.format_or_unix(gix::date::time::format::DEFAULT)).green(), + Span::raw(")"), + ] + .into_iter() +} + +fn render_unified_patch( + path: &BStr, + syntax_set: &SyntaxSet, + theme: &Theme, + hunks: Vec, + is_result_of_binary_to_text_conversion: bool, + out: &mut Vec>, +) { + let syntax = { + let path = path.to_path_lossy(); + path.extension() + .and_then(|ext| syntax_set.find_syntax_by_extension(ext.to_str()?)) + .or_else(|| { + path.file_name() + .and_then(|file_name| syntax_set.find_syntax_by_extension(file_name.to_str()?)) + }) + .unwrap_or_else(|| syntax_set.find_syntax_plain_text()) + }; + + let mut highlight_lines = HighlightLines::new(syntax, theme); + + let mut hunk_iter = hunks.into_iter().peekable(); + while let Some(hunk) = hunk_iter.next() { + let DiffHunk { + old_start, + new_start, + diff, + old_lines: _, + new_lines: _, + } = hunk; + + if is_result_of_binary_to_text_conversion { + out.push(ListItem::new( + "(diff generated from binary-to-text conversion)", + )); + } + + if let Some(headers) = diff.lines().next() { + out.extend([ + ListItem::new(Span::raw(headers.to_str_lossy().to_string()).dim()), + ListItem::new(Line::from_iter(repeat_n("─", headers.to_str_lossy().width())).dim()), + ]); + } + + let (old_width, new_width) = { + let mut old_line = old_start; + let mut new_line = new_start; + for line in diff.lines().skip(1) { + if line.starts_with(b"+") { + new_line += 1; + } else if line.starts_with(b"-") { + old_line += 1; + } else { + old_line += 1; + new_line += 1; + } + } + (num_digits(old_line), num_digits(new_line)) + }; + + let mut old_line_num = old_start; + let mut new_line_num = new_start; + + for line in diff.lines().skip(1) { + let item = if let Some(rest) = line.strip_prefix(b"+") { + let code = rest.to_str_lossy().to_string(); + let item = ListItem::new(Line::from_iter( + [ + Span::raw(" ".repeat(old_width as _)), + Span::raw(" ┊ ").dim(), + Span::raw(" ".repeat((new_width - num_digits(new_line_num)) as _)), + Span::raw(new_line_num.to_string()).fg(*PLUS_EMPH_BG), + Span::raw(" │ ").dim(), + Span::raw("+").bg(*PLUS_BG), + ] + .into_iter() + .chain(syntax_highlight( + &code, + Some(*PLUS_BG), + &mut highlight_lines, + syntax_set, + )), + )); + new_line_num += 1; + item + } else if let Some(rest) = line.strip_prefix(b"-") { + let code = rest.to_str_lossy().to_string(); + + let item = ListItem::new(Line::from_iter( + [ + Span::raw(" ".repeat((old_width - num_digits(old_line_num)) as _)), + Span::raw(old_line_num.to_string()).fg(*MINUS_EMPH_BG), + Span::raw(" ┊ ").dim(), + Span::raw(" ".repeat(new_width as _)), + Span::raw(" │ ").dim(), + Span::raw("-").bg(*MINUS_BG), + ] + .into_iter() + .chain(syntax_highlight( + &code, + Some(*MINUS_BG), + &mut highlight_lines, + syntax_set, + )), + )); + old_line_num += 1; + item + } else { + let line = line.strip_prefix(b" ").unwrap_or(line); + + let code = line.to_str_lossy().to_string(); + + let item = ListItem::new(Line::from_iter( + [ + Span::raw(" ".repeat((old_width - num_digits(old_line_num)) as _)), + Span::raw(old_line_num.to_string()).dark_gray(), + Span::raw(" ┊ ").dim(), + Span::raw(" ".repeat((new_width - num_digits(new_line_num)) as _)), + Span::raw(new_line_num.to_string()).dark_gray(), + Span::raw(" │ ").dim(), + ] + .into_iter() + .chain(syntax_highlight( + &code, + None, + &mut highlight_lines, + syntax_set, + )), + )); + old_line_num += 1; + new_line_num += 1; + item + }; + out.push(item); + } + + if hunk_iter.peek().is_some() { + out.push(ListItem::new("")); + } + } +} + +fn num_digits(n: u32) -> u32 { + if n == 0 { 1 } else { n.ilog10() + 1 } +} + +fn syntax_highlight( + code: &str, + bg: Option, + highlight_lines: &mut HighlightLines<'_>, + syntax_set: &SyntaxSet, +) -> impl Iterator> { + let Ok(ranges) = highlight_lines.highlight_line(code, syntax_set) else { + return itertools::Either::Left(std::iter::empty()); + }; + + let spans = ranges.into_iter().map(move |(style, text)| { + let color = Color::Rgb(style.foreground.r, style.foreground.g, style.foreground.b); + let span = Span::raw(text.to_owned()).fg(color); + if let Some(background) = bg { + span.bg(background) + } else { + span + } + }); + + itertools::Either::Right(spans) +} diff --git a/crates/but/src/command/legacy/status/tui/key_bind.rs b/crates/but/src/command/legacy/status/tui/key_bind.rs index 0e7de43fae3..f0b6cea2c2b 100644 --- a/crates/but/src/command/legacy/status/tui/key_bind.rs +++ b/crates/but/src/command/legacy/status/tui/key_bind.rs @@ -8,7 +8,7 @@ use crate::command::legacy::status::tui::{ CommandMessage, ConfirmMessage, Message, Mode, ModeDiscriminant, RewordMessage, RubMessage, }; -use super::{BranchMessage, CommitMessage, FilesMessage, MoveMessage}; +use super::{BranchMessage, CommitMessage, DetailsMessage, FilesMessage, MoveMessage}; pub(super) fn default_key_binds() -> KeyBinds { let mut key_binds = KeyBinds::new(); @@ -163,6 +163,53 @@ fn register_global_key_binds(key_binds: &mut KeyBinds) { message: Message::EnterNormalMode, hide_from_hotbar: true, }); + + key_binds.register(StaticKeyBind { + short_description: "scroll details down", + chord_display: "ctrl+n", + key_matcher: press().control().code(KeyCode::Char('n')), + modes: all_except_text_input_modes.clone(), + message: Message::Details(DetailsMessage::ScrollDown(1)), + hide_from_hotbar: true, + }); + + key_binds.register(StaticKeyBind { + short_description: "scroll details up", + chord_display: "ctrl+p", + key_matcher: press().control().code(KeyCode::Char('p')), + modes: all_except_text_input_modes.clone(), + message: Message::Details(DetailsMessage::ScrollUp(1)), + hide_from_hotbar: true, + }); + + let jump_distance = 30; + + key_binds.register(StaticKeyBind { + short_description: "jump details down", + chord_display: "ctrl+d", + key_matcher: press().control().code(KeyCode::Char('d')), + modes: all_except_text_input_modes.clone(), + message: Message::Details(DetailsMessage::ScrollDown(jump_distance)), + hide_from_hotbar: true, + }); + + key_binds.register(StaticKeyBind { + short_description: "jump details up", + chord_display: "ctrl+u", + key_matcher: press().control().code(KeyCode::Char('u')), + modes: all_except_text_input_modes.clone(), + message: Message::Details(DetailsMessage::ScrollUp(jump_distance)), + hide_from_hotbar: true, + }); + + key_binds.register(StaticKeyBind { + short_description: "diff", + chord_display: "d", + key_matcher: press().code(KeyCode::Char('d')), + modes: all_except_text_input_modes.clone(), + message: Message::Details(DetailsMessage::ToggleVisibility), + hide_from_hotbar: false, + }); } fn register_quit_key_binds(key_binds: &mut KeyBinds, modes: Vec) { diff --git a/crates/but/src/command/legacy/status/tui/mod.rs b/crates/but/src/command/legacy/status/tui/mod.rs index 779e0938a02..d188a1fd448 100644 --- a/crates/but/src/command/legacy/status/tui/mod.rs +++ b/crates/but/src/command/legacy/status/tui/mod.rs @@ -1,4 +1,11 @@ -use std::{borrow::Cow, ffi::OsString, process::Command, sync::Arc, time::Duration}; +use std::{ + borrow::Cow, + ffi::OsString, + ops::{Deref, DerefMut}, + process::Command, + sync::Arc, + time::Duration, +}; use anyhow::Context as _; use but_core::tree::create_tree::RejectionReason; @@ -27,6 +34,7 @@ use crate::{ tui::{ confirm::{Confirm, ConfirmMessage}, cursor::{Cursor, is_selectable_in_mode}, + details::{Details, DetailsMessage}, graph_extension::{ExtensionDirection, extend_connector_spans}, highlight::{Highlights, with_highlight}, key_bind::{KeyBinds, confirm_key_binds, default_key_binds}, @@ -46,6 +54,7 @@ use super::{ mod confirm; mod cursor; +mod details; mod graph_extension; mod highlight; mod key_bind; @@ -195,6 +204,18 @@ where app.should_render = true; } + if app.details.is_dirty() { + let selection = app + .cursor + .selected_line(&app.status_lines) + .and_then(|line| line.data.cli_id()) + .map(|id| &**id); + if let Err(err) = app.details.update(ctx, selection) { + messages.push(Message::ShowError(Arc::new(err))); + } + app.should_render = true; + } + Ok(()) } @@ -229,6 +250,7 @@ struct App { renders: u64, highlight: Highlights, confirm: Option, + details: Details, } impl App { @@ -250,6 +272,7 @@ impl App { renders: 0, highlight: Default::default(), confirm: None, + details: Default::default(), } } @@ -261,9 +284,20 @@ impl App { } } + fn status_content_area(&self, terminal_area: Rect) -> Rect { + Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).split(terminal_area)[0] + } + + fn details_viewport(&self, terminal_area: Rect) -> Rect { + let content_area = self.status_content_area(terminal_area); + self.status_layout(content_area) + .details_area + .unwrap_or(content_area) + } + /// Returns the number of terminal rows available for rendering the status list. fn status_viewport_height(&self, terminal_area: Rect) -> usize { - usize::from(terminal_area.height.saturating_sub(1)).max(1) + usize::from(self.status_content_area(terminal_area).height).max(1) } /// Returns the rendered height in terminal rows for the given status line. @@ -367,8 +401,12 @@ impl App { anyhow::Error: From<::Error>, { self.should_render = true; - let visible_height = - self.status_viewport_height(terminal_guard.terminal_mut().size()?.into()); + let terminal_area: Rect = terminal_guard.terminal_mut().size()?.into(); + let visible_height = self.status_viewport_height(terminal_area); + + if self.details.needs_update_after_message(&msg) { + self.details.mark_dirty(); + } match msg { Message::Quit => { @@ -490,6 +528,11 @@ impl App { Message::RunAfterConfirmation(f) => { (f.0)(self, ctx, messages)?; } + Message::Details(details_message) => { + let details_viewport = self.details_viewport(terminal_area); + self.details + .try_handle_message(details_message, details_viewport)?; + } } self.ensure_cursor_visible(visible_height); @@ -1578,16 +1621,40 @@ impl App { self.render_hotbar(content_layout[1], frame); } - fn render_status(&self, area: Rect, frame: &mut Frame) { - let (content_area, debug_area) = if self.debug_enabled { + fn status_layout(&self, area: Rect) -> StatusLayout { + let (main_content_area, debug_area) = if self.debug_enabled { let layout = - Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]) + Layout::horizontal([Constraint::Percentage(70), Constraint::Percentage(30)]) .split(area); (layout[0], Some(layout[1])) } else { (area, None) }; + let (content_area, details_area) = match self.details.visibility() { + details::DetailsVisibility::Hidden => (main_content_area, None), + details::DetailsVisibility::VisibleVertical => { + let layout = + Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(main_content_area); + (layout[0], Some(layout[1])) + } + }; + + StatusLayout { + content_area, + details_area, + debug_area, + } + } + + fn render_status(&self, area: Rect, frame: &mut Frame) { + let StatusLayout { + content_area, + details_area, + debug_area, + } = self.status_layout(area); + let visible_height = content_area.height as usize; let items = self .status_lines @@ -1602,6 +1669,10 @@ impl App { frame.render_widget(list, content_area); + if let Some(details_area) = details_area { + self.details.render(details_area, frame); + } + self.render_inline_reword(content_area, frame); self.render_toasts(content_area, frame); @@ -2239,6 +2310,7 @@ enum Message { Files(FilesMessage), Move(MoveMessage), Branch(BranchMessage), + Details(DetailsMessage), // Utilities CopySelection, @@ -2779,6 +2851,26 @@ impl std::fmt::Debug for DebugType { } } +impl From for DebugType { + fn from(value: T) -> Self { + Self(value) + } +} + +impl Deref for DebugType { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for DebugType { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + #[expect(dead_code)] fn run_after_confirmation_msg(f: F) -> Message where @@ -2788,3 +2880,9 @@ where f(app, ctx, messages) }))) } + +struct StatusLayout { + content_area: Rect, + details_area: Option, + debug_area: Option, +} diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/basic_cursor_movement_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/basic_cursor_movement_001.txt index aa8fa46266b..c2a92ff4ec9 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/basic_cursor_movement_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/basic_cursor_movement_001.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/branch_mode_down_moves_from_branch_to_merge_base_target_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/branch_mode_down_moves_from_branch_to_merge_base_target_final.txt index 3e625a997ab..3e6b1d22467 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/branch_mode_down_moves_from_branch_to_merge_base_target_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/branch_mode_down_moves_from_branch_to_merge_base_target_final.txt @@ -17,4 +17,4 @@ " " " " " " -" branch ↓/j down • ↑/k up • q quit • n new • m move • esc back " +" branch ↓/j down • ↑/k up • q quit • d diff • n new • m move • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/branch_mode_from_commit_jumps_to_nearest_preceding_branch_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/branch_mode_from_commit_jumps_to_nearest_preceding_branch_final.txt index f73377b4db7..580361cb2ea 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/branch_mode_from_commit_jumps_to_nearest_preceding_branch_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/branch_mode_from_commit_jumps_to_nearest_preceding_branch_final.txt @@ -17,4 +17,4 @@ " " " " " " -" branch ↓/j down • ↑/k up • q quit • n new • m move • esc back " +" branch ↓/j down • ↑/k up • q quit • d diff • n new • m move • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/branch_mode_from_unassigned_jumps_to_first_branch_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/branch_mode_from_unassigned_jumps_to_first_branch_final.txt index 39946cf607d..65f0cdd0472 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/branch_mode_from_unassigned_jumps_to_first_branch_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/branch_mode_from_unassigned_jumps_to_first_branch_final.txt @@ -17,4 +17,4 @@ " " " " " " -" branch ↓/j down • ↑/k up • q quit • n new • m move • esc back " +" branch ↓/j down • ↑/k up • q quit • d diff • n new • m move • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_003.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_003.txt index aa8fa46266b..c2a92ff4ec9 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_003.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_003.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_confirm_on_source_is_noop_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_confirm_on_source_is_noop_final.txt index 8fe2d74e06d..256729a261a 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_confirm_on_source_is_noop_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_confirm_on_source_is_noop_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_list_rub_can_escape_scope_and_esc_reenters_file_list_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_list_rub_can_escape_scope_and_esc_reenters_file_list_final.txt index c423da7c3e7..9e00544250f 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_list_rub_can_escape_scope_and_esc_reenters_file_list_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_list_rub_can_escape_scope_and_esc_reenters_file_list_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_list_scopes_cursor_to_files_in_selected_commit_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_list_scopes_cursor_to_files_in_selected_commit_final.txt index c423da7c3e7..9e00544250f 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_list_scopes_cursor_to_files_in_selected_commit_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_list_scopes_cursor_to_files_in_selected_commit_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_toggle_off_from_commit_row_preserves_commit_selection_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_toggle_off_from_commit_row_preserves_commit_selection_final.txt index da362399ffd..cb20f05462a 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_toggle_off_from_commit_row_preserves_commit_selection_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_toggle_off_from_commit_row_preserves_commit_selection_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_toggle_on_commit_without_files_is_noop_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_toggle_on_commit_without_files_is_noop_final.txt index fc19fa2df3b..4833805a90b 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_toggle_on_commit_without_files_is_noop_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_file_toggle_on_commit_without_files_is_noop_final.txt @@ -5,4 +5,4 @@ "├╯ " "┊ " "┴ 0dc3733 [origin/main] 2000-01-02 add M " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_from_unstaged_changes_creates_commit_visible_in_tui_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_from_unstaged_changes_creates_commit_visible_in_tui_final.txt index 94bcfb3045b..6e7adcf6ac5 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_from_unstaged_changes_creates_commit_visible_in_tui_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_from_unstaged_changes_creates_commit_visible_in_tui_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_can_toggle_commit_target_insert_side_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_can_toggle_commit_target_insert_side_final.txt index ad4cd002c85..f1eb4437560 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_can_toggle_commit_target_insert_side_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_can_toggle_commit_target_insert_side_final.txt @@ -17,4 +17,4 @@ " " " " " " -" commit ↓/j down • ↑/k up • q quit • enter commit • a above • b below • esc back " +" commit ↓/j down • ↑/k up • q quit • d diff • enter commit • a above • b below • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_enter_and_escape_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_enter_and_escape_final.txt index 8fe2d74e06d..256729a261a 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_enter_and_escape_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_enter_and_escape_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_from_staged_changes_stays_within_current_stack_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_from_staged_changes_stays_within_current_stack_final.txt index ca38a3bdcf9..4b79ee0abc3 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_from_staged_changes_stays_within_current_stack_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_from_staged_changes_stays_within_current_stack_final.txt @@ -17,4 +17,4 @@ " " " " " " -" commit ↓/j down • ↑/k up • q quit • enter commit • a above • b below • esc back " +" commit ↓/j down • ↑/k up • q quit • d diff • enter commit • a above • b below • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_not_entered_from_non_commitable_rows_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_not_entered_from_non_commitable_rows_final.txt index aa8fa46266b..c2a92ff4ec9 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_not_entered_from_non_commitable_rows_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_not_entered_from_non_commitable_rows_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_shows_commit_above_on_commit_rows_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_shows_commit_above_on_commit_rows_final.txt index ad4cd002c85..f1eb4437560 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_shows_commit_above_on_commit_rows_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_mode_shows_commit_above_on_commit_rows_final.txt @@ -17,4 +17,4 @@ " " " " " " -" commit ↓/j down • ↑/k up • q quit • enter commit • a above • b below • esc back " +" commit ↓/j down • ↑/k up • q quit • d diff • enter commit • a above • b below • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_to_commit_above_creates_commit_visible_in_tui_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_to_commit_above_creates_commit_visible_in_tui_final.txt index 94bcfb3045b..6e7adcf6ac5 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_to_commit_above_creates_commit_visible_in_tui_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_to_commit_above_creates_commit_visible_in_tui_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_to_commit_below_creates_commit_visible_in_tui_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_to_commit_below_creates_commit_visible_in_tui_final.txt index d671c3a95f8..f7c65365ba9 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_to_commit_below_creates_commit_visible_in_tui_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/commit_to_commit_below_creates_commit_visible_in_tui_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/creating_empty_commits_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/creating_empty_commits_001.txt index aa8fa46266b..c2a92ff4ec9 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/creating_empty_commits_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/creating_empty_commits_001.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/creating_empty_commits_002.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/creating_empty_commits_002.txt index 84771922fc6..75bca3e130d 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/creating_empty_commits_002.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/creating_empty_commits_002.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/creating_empty_commits_003.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/creating_empty_commits_003.txt index c1730536524..80a46d11208 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/creating_empty_commits_003.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/creating_empty_commits_003.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_down_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_down_001.txt index 23fef531ae2..8dd98398bc0 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_down_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_down_001.txt @@ -5,4 +5,4 @@ "┊● 9477ae7 add A " "├╯ " "┊ " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_down_002.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_down_002.txt index 136df622287..a825088b4ea 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_down_002.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_down_002.txt @@ -5,4 +5,4 @@ "├╯ " "┊ " "┴ 0dc3733 [origin/main] 2000-01-02 add M " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_up_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_up_001.txt index 136df622287..a825088b4ea 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_up_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_up_001.txt @@ -5,4 +5,4 @@ "├╯ " "┊ " "┴ 0dc3733 [origin/main] 2000-01-02 add M " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_up_002.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_up_002.txt index 23fef531ae2..8dd98398bc0 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_up_002.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/cursor_movement_scrolls_viewport_up_002.txt @@ -5,4 +5,4 @@ "┊● 9477ae7 add A " "├╯ " "┊ " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/entering_branch_mode_closes_global_file_list_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/entering_branch_mode_closes_global_file_list_final.txt index 9af2894e124..0d45837cb66 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/entering_branch_mode_closes_global_file_list_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/entering_branch_mode_closes_global_file_list_final.txt @@ -17,4 +17,4 @@ " " " " " " -" branch ↓/j down • ↑/k up • q quit • n new • m move • esc back " +" branch ↓/j down • ↑/k up • q quit • d diff • n new • m move • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_in_normal_mode_closes_commit_file_list_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_in_normal_mode_closes_commit_file_list_final.txt index da362399ffd..cb20f05462a 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_in_normal_mode_closes_commit_file_list_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_in_normal_mode_closes_commit_file_list_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_in_normal_mode_closes_global_file_list_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_in_normal_mode_closes_global_file_list_final.txt index da362399ffd..cb20f05462a 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_in_normal_mode_closes_global_file_list_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_in_normal_mode_closes_global_file_list_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_leaves_branch_mode_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_leaves_branch_mode_final.txt index aa8fa46266b..c2a92ff4ec9 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_leaves_branch_mode_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_leaves_branch_mode_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_leaves_move_mode_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_leaves_move_mode_final.txt index aa8fa46266b..c2a92ff4ec9 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_leaves_move_mode_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/esc_leaves_move_mode_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/focus_reload_in_branch_mode_preserves_branch_selection_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/focus_reload_in_branch_mode_preserves_branch_selection_final.txt index 39946cf607d..65f0cdd0472 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/focus_reload_in_branch_mode_preserves_branch_selection_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/focus_reload_in_branch_mode_preserves_branch_selection_final.txt @@ -17,4 +17,4 @@ " " " " " " -" branch ↓/j down • ↑/k up • q quit • n new • m move • esc back " +" branch ↓/j down • ↑/k up • q quit • d diff • n new • m move • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/focus_reload_in_branch_mode_preserves_merge_base_selection_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/focus_reload_in_branch_mode_preserves_merge_base_selection_final.txt index 3e625a997ab..3e6b1d22467 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/focus_reload_in_branch_mode_preserves_merge_base_selection_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/focus_reload_in_branch_mode_preserves_merge_base_selection_final.txt @@ -17,4 +17,4 @@ " " " " " " -" branch ↓/j down • ↑/k up • q quit • n new • m move • esc back " +" branch ↓/j down • ↑/k up • q quit • d diff • n new • m move • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/global_file_list_does_not_restrict_cursor_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/global_file_list_does_not_restrict_cursor_final.txt index 83a8b43d6eb..6702926190e 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/global_file_list_does_not_restrict_cursor_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/global_file_list_does_not_restrict_cursor_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/inline_reword_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/inline_reword_001.txt index aa8fa46266b..c2a92ff4ec9 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/inline_reword_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/inline_reword_001.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/inline_reword_002.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/inline_reword_002.txt index 84771922fc6..75bca3e130d 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/inline_reword_002.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/inline_reword_002.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/inline_reword_005.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/inline_reword_005.txt index 01cce0e262e..63e77f82561 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/inline_reword_005.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/inline_reword_005.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_b_enters_and_leaves_branch_mode_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_b_enters_and_leaves_branch_mode_001.txt index 39946cf607d..65f0cdd0472 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_b_enters_and_leaves_branch_mode_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_b_enters_and_leaves_branch_mode_001.txt @@ -17,4 +17,4 @@ " " " " " " -" branch ↓/j down • ↑/k up • q quit • n new • m move • esc back " +" branch ↓/j down • ↑/k up • q quit • d diff • n new • m move • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_c_enters_and_leaves_commit_mode_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_c_enters_and_leaves_commit_mode_001.txt index e4433c2409e..c7bb9534015 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_c_enters_and_leaves_commit_mode_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_c_enters_and_leaves_commit_mode_001.txt @@ -17,4 +17,4 @@ " " " " " " -" commit ↓/j down • ↑/k up • q quit • enter commit • a above • b below • esc back " +" commit ↓/j down • ↑/k up • q quit • d diff • enter commit • a above • b below • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_m_enters_and_leaves_move_mode_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_m_enters_and_leaves_move_mode_001.txt index 1e3fb0d27fe..971661325e2 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_m_enters_and_leaves_move_mode_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_m_enters_and_leaves_move_mode_001.txt @@ -17,4 +17,4 @@ " " " " " " -" move ↓/j down • ↑/k up • q quit • enter move • a above • b below • esc back " +" move ↓/j down • ↑/k up • q quit • d diff • enter move • a above • b below • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_r_enters_and_leaves_rub_mode_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_r_enters_and_leaves_rub_mode_001.txt index a2325a4b080..03978b7b175 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_r_enters_and_leaves_rub_mode_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/mode_toggle_key_r_enters_and_leaves_rub_mode_001.txt @@ -17,4 +17,4 @@ " " " " " " -" rub ↓/j down • ↑/k up • q quit • enter confirm • esc back " +" rub ↓/j down • ↑/k up • q quit • d diff • enter confirm • esc back " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/move_branch_onto_other_branch_reorders_stacks_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/move_branch_onto_other_branch_reorders_stacks_final.txt index 0dad0cb7aa5..f7e891c985b 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/move_branch_onto_other_branch_reorders_stacks_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/move_branch_onto_other_branch_reorders_stacks_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/move_branch_to_merge_base_tears_off_branch_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/move_branch_to_merge_base_tears_off_branch_final.txt index f6593997c80..20132796ad9 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/move_branch_to_merge_base_tears_off_branch_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/move_branch_to_merge_base_tears_off_branch_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/move_commit_above_other_commit_reorders_tui_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/move_commit_above_other_commit_reorders_tui_final.txt index 010e767551c..5d6e4ec8dbe 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/move_commit_above_other_commit_reorders_tui_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/move_commit_above_other_commit_reorders_tui_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/move_commit_below_other_commit_reorders_tui_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/move_commit_below_other_commit_reorders_tui_final.txt index dafd5ec8d8e..fac0db647c8 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/move_commit_below_other_commit_reorders_tui_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/move_commit_below_other_commit_reorders_tui_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/new_branch_from_merge_base_in_branch_mode_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/new_branch_from_merge_base_in_branch_mode_final.txt index 828f149a56a..07bf0cdc177 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/new_branch_from_merge_base_in_branch_mode_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/new_branch_from_merge_base_in_branch_mode_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/reload_preserves_visible_selection_when_scrolled_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/reload_preserves_visible_selection_when_scrolled_001.txt index 136df622287..a825088b4ea 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/reload_preserves_visible_selection_when_scrolled_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/reload_preserves_visible_selection_when_scrolled_001.txt @@ -5,4 +5,4 @@ "├╯ " "┊ " "┴ 0dc3733 [origin/main] 2000-01-02 add M " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/rub_api_unassigned_to_commit.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/rub_api_unassigned_to_commit.txt index 50b99f9d6b5..ce06d3e3fa0 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/rub_api_unassigned_to_commit.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/rub_api_unassigned_to_commit.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/rubbing_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/rubbing_001.txt index aa8fa46266b..c2a92ff4ec9 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/rubbing_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/rubbing_001.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/rubbing_002.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/rubbing_002.txt index 8fe2d74e06d..256729a261a 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/rubbing_002.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/rubbing_002.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/rubbing_003.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/rubbing_003.txt index fa5fdc73d2e..1aad17e1655 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/rubbing_003.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/rubbing_003.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/section_jumps_scroll_viewport_when_target_is_offscreen_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/section_jumps_scroll_viewport_when_target_is_offscreen_001.txt index 23fef531ae2..8dd98398bc0 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/section_jumps_scroll_viewport_when_target_is_offscreen_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/section_jumps_scroll_viewport_when_target_is_offscreen_001.txt @@ -5,4 +5,4 @@ "┊● 9477ae7 add A " "├╯ " "┊ " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/section_jumps_scroll_viewport_when_target_is_offscreen_002.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/section_jumps_scroll_viewport_when_target_is_offscreen_002.txt index fe6c104d753..dfc33d65f3c 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/section_jumps_scroll_viewport_when_target_is_offscreen_002.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/section_jumps_scroll_viewport_when_target_is_offscreen_002.txt @@ -5,4 +5,4 @@ "┊● d3e2ba3 add B " "├╯ " "┊ " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/section_jumps_scroll_viewport_when_target_is_offscreen_003.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/section_jumps_scroll_viewport_when_target_is_offscreen_003.txt index 23fef531ae2..8dd98398bc0 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/section_jumps_scroll_viewport_when_target_is_offscreen_003.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/section_jumps_scroll_viewport_when_target_is_offscreen_003.txt @@ -5,4 +5,4 @@ "┊● 9477ae7 add A " "├╯ " "┊ " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/shows_full_error_cause_chain_with_multiple_contexts_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/shows_full_error_cause_chain_with_multiple_contexts_001.txt index 57887bcc3fb..d888e64204f 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/shows_full_error_cause_chain_with_multiple_contexts_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/shows_full_error_cause_chain_with_multiple_contexts_001.txt @@ -17,4 +17,4 @@ " │ 1: context-level-1 │ " " │ 2: root-cause-END-MARKER │ " " └──────────────────────────────┘ " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/shows_full_error_when_message_wraps_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/shows_full_error_when_message_wraps_001.txt index 82b8c81ce09..bbc3fcfb781 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/shows_full_error_when_message_wraps_001.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/shows_full_error_when_message_wraps_001.txt @@ -17,4 +17,4 @@ "│ error-with-end-marker: this is a deliberately long error message that should wrap over multiple │ " "│ lines without clipping and it must include END-MARKER │ " "└─────────────────────────────────────────────────────────────────────────────────────────────────┘ " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword" diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/staged_source_commit_cannot_be_forced_to_other_stack_target_final.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/staged_source_commit_cannot_be_forced_to_other_stack_target_final.txt index fb350dcfb9a..2032822561a 100644 --- a/crates/but/src/command/legacy/status/tui/tests/snapshots/staged_source_commit_cannot_be_forced_to_other_stack_target_final.txt +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/staged_source_commit_cannot_be_forced_to_other_stack_target_final.txt @@ -17,4 +17,4 @@ " " " " " " -" normal ↓/j down • ↑/k up • q quit • r rub • c commit • m move • b branch • enter reword inline •" +" normal ↓/j down • ↑/k up • q quit • d diff • r rub • c commit • m move • b branch • enter reword"