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