mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
terminal: experimental ghostty backend
This commit is contained in:
parent
560f784776
commit
38e2a4af35
40 changed files with 11384 additions and 1354 deletions
220
Cargo.lock
generated
220
Cargo.lock
generated
|
|
@ -519,7 +519,7 @@ version = "0.25.1"
|
|||
source = "git+https://github.com/zed-industries/alacritty?rev=9d9640d4#9d9640d4e56d67a09d049f9c0a300aae08d4f61e"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"home",
|
||||
"libc",
|
||||
"log",
|
||||
|
|
@ -575,7 +575,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "7c88dbbce13b232b26250e1e2e6ac18b6a891a646b8148285036ebce260ac5c3"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -2015,7 +2015,7 @@ version = "0.71.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.11.0",
|
||||
|
|
@ -2035,7 +2035,7 @@ version = "0.72.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.11.0",
|
||||
|
|
@ -2091,9 +2091,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
|
@ -2496,7 +2496,7 @@ name = "calloop"
|
|||
version = "0.14.3"
|
||||
source = "git+https://github.com/zed-industries/calloop#eb6b4fd17b9af5ecc226546bdd04185391b3e265"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"polling",
|
||||
"rustix 1.1.2",
|
||||
"slab",
|
||||
|
|
@ -3141,7 +3141,7 @@ version = "0.26.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"block",
|
||||
"cocoa-foundation 0.2.0",
|
||||
"core-foundation 0.10.0",
|
||||
|
|
@ -3171,7 +3171,7 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"block",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics-types 0.2.0",
|
||||
|
|
@ -3769,7 +3769,7 @@ version = "0.24.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics-types 0.2.0",
|
||||
"foreign-types 0.5.0",
|
||||
|
|
@ -3782,7 +3782,7 @@ version = "0.24.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics-types 0.1.3",
|
||||
"foreign-types 0.5.0",
|
||||
|
|
@ -3806,7 +3806,7 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"core-foundation 0.10.0",
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -3817,7 +3817,7 @@ version = "0.5.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4416167a69126e617f8d0a214af0e3c1dbdeffcb100ddf72dcd1a1ac9893c146"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"block",
|
||||
"cfg-if",
|
||||
"core-foundation 0.10.0",
|
||||
|
|
@ -3917,7 +3917,7 @@ version = "0.17.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c5c9868e64aa6c5410629a83450e142c80e721c727a5bc0fb18107af6c2d66b"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"fontdb",
|
||||
"harfrust",
|
||||
"linebender_resource_handle",
|
||||
|
|
@ -4745,9 +4745,8 @@ dependencies = [
|
|||
name = "debugger_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"client",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
|
|
@ -4783,6 +4782,7 @@ dependencies = [
|
|||
"sysinfo 0.37.2",
|
||||
"task",
|
||||
"tasks_ui",
|
||||
"terminal",
|
||||
"terminal_view",
|
||||
"text",
|
||||
"theme",
|
||||
|
|
@ -4794,6 +4794,7 @@ dependencies = [
|
|||
"ui_input",
|
||||
"unindent",
|
||||
"util",
|
||||
"vte",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
"zlog",
|
||||
|
|
@ -5062,7 +5063,7 @@ version = "0.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
|
|
@ -7349,7 +7350,7 @@ version = "0.20.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"libc",
|
||||
"libgit2-sys",
|
||||
"log",
|
||||
|
|
@ -7417,7 +7418,6 @@ name = "git_ui"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent_settings",
|
||||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
"askpass",
|
||||
"buffer_diff",
|
||||
|
|
@ -7472,6 +7472,7 @@ dependencies = [
|
|||
"ui_input",
|
||||
"unindent",
|
||||
"util",
|
||||
"vte",
|
||||
"watch",
|
||||
"windows 0.61.3",
|
||||
"workspace",
|
||||
|
|
@ -7498,7 +7499,7 @@ version = "0.21.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
|
|
@ -7667,7 +7668,7 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"gpu-descriptor-types",
|
||||
"hashbrown 0.15.5",
|
||||
]
|
||||
|
|
@ -7678,7 +7679,7 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -7690,7 +7691,7 @@ dependencies = [
|
|||
"async-task",
|
||||
"backtrace",
|
||||
"bindgen 0.71.1",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"block",
|
||||
"cbindgen",
|
||||
"chrono",
|
||||
|
|
@ -7778,7 +7779,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"as-raw-xcb-connection",
|
||||
"ashpd",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytemuck",
|
||||
"calloop",
|
||||
"calloop-wayland-source",
|
||||
|
|
@ -8122,7 +8123,7 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f9f40651a03bc0f7316bd75267ff5767e93017ef3cfffe76c6aa7252cc5a31c"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytemuck",
|
||||
"core_maths",
|
||||
"read-fonts 0.37.0",
|
||||
|
|
@ -8276,7 +8277,7 @@ version = "0.21.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd54745cfacb7b97dee45e8fdb91814b62bccddb481debb7de0f9ee6b7bf5b43"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"byteorder",
|
||||
"heed-traits",
|
||||
"heed-types",
|
||||
|
|
@ -8947,7 +8948,7 @@ version = "0.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -9025,6 +9026,18 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "int-enum"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e366a1634cccc76b4cfd3e7580de9b605e4d93f1edac48d786c1f867c0def495"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"proc-macro2-diagnostics",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interpolate_name"
|
||||
version = "0.2.4"
|
||||
|
|
@ -9947,6 +9960,21 @@ dependencies = [
|
|||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libghostty-vt"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8afe5cc9ae303133220e530b28b7addbbf591160bb1564b88f7ee61387fee74"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"int-enum",
|
||||
"libghostty-vt-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libghostty-vt-sys"
|
||||
version = "0.1.1"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.18.3+1.9.2"
|
||||
|
|
@ -9991,7 +10019,7 @@ version = "0.1.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"libc",
|
||||
"redox_syscall 0.5.18",
|
||||
]
|
||||
|
|
@ -10735,7 +10763,7 @@ version = "0.33.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"block",
|
||||
"core-graphics-types 0.2.0",
|
||||
"foreign-types 0.5.0",
|
||||
|
|
@ -10794,7 +10822,7 @@ version = "0.26.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e16d10087ae9e375bad7a40e8ef5504bc08e808ccc6019067ff9de42a84570f"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"debugid",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
|
|
@ -10809,7 +10837,7 @@ version = "0.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e1fc14d6ded915b8e850801465e7096f77ed60bf87e4e85878d463720d9dc4d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"byteorder",
|
||||
"cfg-if",
|
||||
"crash-context",
|
||||
|
|
@ -11010,7 +11038,7 @@ source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9
|
|||
dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set 0.9.1",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.2.1",
|
||||
"codespan-reporting",
|
||||
|
|
@ -11094,7 +11122,7 @@ version = "0.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"ndk-sys",
|
||||
|
|
@ -11140,7 +11168,7 @@ version = "0.28.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.1.1",
|
||||
"libc",
|
||||
|
|
@ -11152,7 +11180,7 @@ version = "0.29.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.2.1",
|
||||
"libc",
|
||||
|
|
@ -11164,7 +11192,7 @@ version = "0.30.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.2.1",
|
||||
"libc",
|
||||
|
|
@ -11252,7 +11280,7 @@ version = "6.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
|
|
@ -11270,7 +11298,7 @@ name = "notify"
|
|||
version = "8.2.0"
|
||||
source = "git+https://github.com/zed-industries/notify.git?rev=ce58c24cad542c28e04ced02e20325a4ec28a31d#ce58c24cad542c28e04ced02e20325a4ec28a31d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"fsevent-sys",
|
||||
"inotify 0.11.0",
|
||||
"kqueue",
|
||||
|
|
@ -11590,7 +11618,7 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2-core-audio",
|
||||
|
|
@ -11628,7 +11656,7 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
|
|
@ -11638,7 +11666,7 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2",
|
||||
"dispatch2",
|
||||
"libc",
|
||||
|
|
@ -11657,7 +11685,7 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
|
|
@ -11680,7 +11708,7 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2",
|
||||
"dispatch2",
|
||||
"objc2",
|
||||
|
|
@ -11694,7 +11722,7 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
|
|
@ -11973,7 +12001,7 @@ version = "0.10.79"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"cfg-if",
|
||||
"foreign-types 0.3.2",
|
||||
"libc",
|
||||
|
|
@ -13192,7 +13220,7 @@ version = "0.18.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
|
|
@ -13466,7 +13494,7 @@ version = "0.17.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"hex",
|
||||
"serde",
|
||||
]
|
||||
|
|
@ -13713,7 +13741,7 @@ source = "git+https://github.com/proptest-rs/proptest?rev=3dca198a8fef1b32e3a66f
|
|||
dependencies = [
|
||||
"bit-set 0.8.0",
|
||||
"bit-vec 0.8.0",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"num-traits",
|
||||
"proptest-macro",
|
||||
"rand 0.9.4",
|
||||
|
|
@ -13919,7 +13947,7 @@ version = "0.10.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76979bea66e7875e7509c4ec5300112b316af87fa7a252ca91c448b32dfe3993"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"memchr",
|
||||
"pulldown-cmark-escape",
|
||||
"unicase",
|
||||
|
|
@ -13931,7 +13959,7 @@ version = "0.13.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
|
@ -14344,7 +14372,7 @@ version = "11.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -14491,7 +14519,7 @@ version = "0.5.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -14752,7 +14780,6 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
|||
name = "repl"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
"async-dispatcher",
|
||||
"async-task",
|
||||
|
|
@ -15301,7 +15328,7 @@ version = "0.38.44"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"errno 0.3.14",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
|
|
@ -15314,7 +15341,7 @@ version = "1.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"errno 0.3.14",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
|
|
@ -15495,7 +15522,7 @@ version = "0.20.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytemuck",
|
||||
"core_maths",
|
||||
"log",
|
||||
|
|
@ -15802,7 +15829,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"any_vec",
|
||||
"anyhow",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"collections",
|
||||
"editor",
|
||||
"fs",
|
||||
|
|
@ -15860,7 +15887,7 @@ version = "2.11.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
|
|
@ -15873,7 +15900,7 @@ version = "3.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"core-foundation 0.10.0",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
|
|
@ -16758,7 +16785,7 @@ version = "0.4.0+sdk-1.4.341.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -16919,7 +16946,7 @@ dependencies = [
|
|||
"atoi",
|
||||
"base64 0.22.1",
|
||||
"bigdecimal",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"byteorder",
|
||||
"bytes 1.11.1",
|
||||
"chrono",
|
||||
|
|
@ -16966,7 +16993,7 @@ dependencies = [
|
|||
"atoi",
|
||||
"base64 0.22.1",
|
||||
"bigdecimal",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"crc",
|
||||
|
|
@ -17561,7 +17588,7 @@ version = "0.5.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"byteorder",
|
||||
"enum-as-inner",
|
||||
"libc",
|
||||
|
|
@ -17575,7 +17602,7 @@ version = "0.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"byteorder",
|
||||
"enum-as-inner",
|
||||
"libc",
|
||||
|
|
@ -17628,7 +17655,7 @@ version = "0.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys 0.6.0",
|
||||
]
|
||||
|
|
@ -17672,7 +17699,7 @@ version = "0.27.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"cap-fs-ext",
|
||||
"cap-std",
|
||||
"fd-lock",
|
||||
|
|
@ -17876,15 +17903,19 @@ dependencies = [
|
|||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
"async-channel 2.5.0",
|
||||
"base64 0.22.1",
|
||||
"collections",
|
||||
"feature_flags",
|
||||
"futures 0.3.32",
|
||||
"futures-lite 1.13.0",
|
||||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
"libc",
|
||||
"libghostty-vt",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"portable-pty",
|
||||
"rand 0.9.4",
|
||||
"regex",
|
||||
"release_channel",
|
||||
|
|
@ -17900,6 +17931,7 @@ dependencies = [
|
|||
"urlencoding",
|
||||
"util",
|
||||
"util_macros",
|
||||
"vte",
|
||||
"windows 0.61.3",
|
||||
]
|
||||
|
||||
|
|
@ -18612,7 +18644,7 @@ version = "0.4.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytes 1.11.1",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
|
|
@ -18631,7 +18663,7 @@ version = "0.6.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytes 1.11.1",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
|
|
@ -19684,7 +19716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"cursor-icon",
|
||||
"log",
|
||||
"memchr",
|
||||
|
|
@ -19965,7 +19997,7 @@ version = "0.201.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"indexmap 2.11.4",
|
||||
"semver",
|
||||
]
|
||||
|
|
@ -19976,7 +20008,7 @@ version = "0.221.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap 2.11.4",
|
||||
"semver",
|
||||
|
|
@ -19989,7 +20021,7 @@ version = "0.227.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap 2.11.4",
|
||||
"semver",
|
||||
|
|
@ -20001,7 +20033,7 @@ version = "0.236.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9b1e81f3eb254cf7404a82cee6926a4a3ccc5aad80cc3d43608a070c67aa1d7"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap 2.11.4",
|
||||
"semver",
|
||||
|
|
@ -20014,7 +20046,7 @@ version = "0.244.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap 2.11.4",
|
||||
"semver",
|
||||
|
|
@ -20040,7 +20072,7 @@ dependencies = [
|
|||
"addr2line",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"bumpalo",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
|
|
@ -20287,7 +20319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1706803e83b9bae726a0f55e7c1bbf78a7421cf2da68c940c70978e91dfc0339"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"heck 0.5.0",
|
||||
"indexmap 2.11.4",
|
||||
"wit-parser 0.236.1",
|
||||
|
|
@ -20301,7 +20333,7 @@ checksum = "1a430602ec54d0e32fbb61d2d8c7e5885eaa9dbc1664b6ed57fb57df439810a0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytes 1.11.1",
|
||||
"cap-fs-ext",
|
||||
"cap-net-ext",
|
||||
|
|
@ -20392,7 +20424,7 @@ version = "0.31.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"rustix 1.1.2",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
|
|
@ -20415,7 +20447,7 @@ version = "0.32.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
|
|
@ -20427,7 +20459,7 @@ version = "0.3.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
|
|
@ -20440,7 +20472,7 @@ version = "0.3.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
|
|
@ -20590,7 +20622,7 @@ version = "29.0.3"
|
|||
source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.2.1",
|
||||
|
|
@ -20621,7 +20653,7 @@ dependencies = [
|
|||
"arrayvec",
|
||||
"bit-set 0.9.1",
|
||||
"bit-vec 0.9.1",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytemuck",
|
||||
"cfg_aliases 0.2.1",
|
||||
"document-features",
|
||||
|
|
@ -20678,7 +20710,7 @@ dependencies = [
|
|||
"arrayvec",
|
||||
"ash",
|
||||
"bit-set 0.9.1",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
|
|
@ -20736,7 +20768,7 @@ name = "wgpu-types"
|
|||
version = "29.0.3"
|
||||
source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytemuck",
|
||||
"js-sys",
|
||||
"log",
|
||||
|
|
@ -20800,7 +20832,7 @@ checksum = "1979d3ed3ffc017538e518da6faa66b129f9229492981fc51004f28cb86db792"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"wasmtime",
|
||||
|
|
@ -21675,7 +21707,7 @@ version = "0.36.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
|
|
@ -21694,7 +21726,7 @@ version = "0.22.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "288f992ea30e6b5c531b52cdd5f3be81c148554b09ea416f058d16556ba92c27"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"wit-bindgen-rt 0.22.0",
|
||||
"wit-bindgen-rust-macro 0.22.0",
|
||||
]
|
||||
|
|
@ -21768,7 +21800,7 @@ version = "0.41.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"futures 0.3.32",
|
||||
"once_cell",
|
||||
]
|
||||
|
|
@ -21870,7 +21902,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"indexmap 2.11.4",
|
||||
"log",
|
||||
"serde",
|
||||
|
|
@ -21889,7 +21921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "635c3adc595422cbf2341a17fb73a319669cc8d33deed3a48368a841df86b676"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"indexmap 2.11.4",
|
||||
"log",
|
||||
"serde",
|
||||
|
|
@ -21908,7 +21940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"indexmap 2.11.4",
|
||||
"log",
|
||||
"serde",
|
||||
|
|
@ -22222,7 +22254,7 @@ name = "xim-parser"
|
|||
version = "0.2.1"
|
||||
source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -22653,7 +22685,7 @@ name = "zed-font-kit"
|
|||
version = "0.14.1-zed"
|
||||
source = "git+https://github.com/zed-industries/font-kit?rev=94b0f28166665e8fd2f53ff6d268a14955c82269#94b0f28166665e8fd2f53ff6d268a14955c82269"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.1",
|
||||
"byteorder",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics 0.24.0",
|
||||
|
|
|
|||
22
Cargo.toml
22
Cargo.toml
|
|
@ -1,5 +1,6 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
exclude = ["crates/libghostty-vt-sys"]
|
||||
members = [
|
||||
"crates/acp_thread",
|
||||
"crates/acp_tools",
|
||||
|
|
@ -268,14 +269,14 @@ edition = "2024"
|
|||
#
|
||||
|
||||
acp_tools = { path = "crates/acp_tools" }
|
||||
acp_thread = { path = "crates/acp_thread" }
|
||||
acp_thread = { path = "crates/acp_thread", default-features = false }
|
||||
action_log = { path = "crates/action_log" }
|
||||
activity_indicator = { path = "crates/activity_indicator" }
|
||||
agent = { path = "crates/agent" }
|
||||
agent_servers = { path = "crates/agent_servers" }
|
||||
agent_servers = { path = "crates/agent_servers", default-features = false }
|
||||
agent_settings = { path = "crates/agent_settings" }
|
||||
agent_skills = { path = "crates/agent_skills" }
|
||||
agent_ui = { path = "crates/agent_ui" }
|
||||
agent_ui = { path = "crates/agent_ui", default-features = false }
|
||||
ai_onboarding = { path = "crates/ai_onboarding" }
|
||||
anthropic = { path = "crates/anthropic" }
|
||||
askpass = { path = "crates/askpass" }
|
||||
|
|
@ -317,7 +318,7 @@ dap_adapters = { path = "crates/dap_adapters" }
|
|||
db = { path = "crates/db" }
|
||||
debug_adapter_extension = { path = "crates/debug_adapter_extension" }
|
||||
debugger_tools = { path = "crates/debugger_tools" }
|
||||
debugger_ui = { path = "crates/debugger_ui" }
|
||||
debugger_ui = { path = "crates/debugger_ui", default-features = false }
|
||||
deepseek = { path = "crates/deepseek" }
|
||||
derive_refineable = { path = "crates/refineable/derive_refineable" }
|
||||
dev_container = { path = "crates/dev_container" }
|
||||
|
|
@ -381,7 +382,7 @@ language_models_cloud = { path = "crates/language_models_cloud" }
|
|||
language_onboarding = { path = "crates/language_onboarding" }
|
||||
language_selector = { path = "crates/language_selector" }
|
||||
language_tools = { path = "crates/language_tools" }
|
||||
languages = { path = "crates/languages" }
|
||||
languages = { path = "crates/languages", default-features = false }
|
||||
line_ending_selector = { path = "crates/line_ending_selector" }
|
||||
livekit_api = { path = "crates/livekit_api" }
|
||||
livekit_client = { path = "crates/livekit_client" }
|
||||
|
|
@ -416,7 +417,7 @@ perf = { path = "tooling/perf" }
|
|||
picker = { path = "crates/picker" }
|
||||
prettier = { path = "crates/prettier" }
|
||||
settings_profile_selector = { path = "crates/settings_profile_selector" }
|
||||
project = { path = "crates/project" }
|
||||
project = { path = "crates/project", default-features = false }
|
||||
project_panel = { path = "crates/project_panel" }
|
||||
project_symbols = { path = "crates/project_symbols" }
|
||||
prompt_store = { path = "crates/prompt_store" }
|
||||
|
|
@ -427,7 +428,7 @@ release_channel = { path = "crates/release_channel" }
|
|||
remote = { path = "crates/remote" }
|
||||
remote_connection = { path = "crates/remote_connection" }
|
||||
remote_server = { path = "crates/remote_server" }
|
||||
repl = { path = "crates/repl" }
|
||||
repl = { path = "crates/repl", default-features = false }
|
||||
reqwest_client = { path = "crates/reqwest_client" }
|
||||
rodio = { git = "https://github.com/RustAudio/rodio", rev = "e50e726ddd0292f6ef9de0dda6b90af4ed1fb66a", features = ["wav", "playback", "wav_output", "recording"] }
|
||||
rope = { path = "crates/rope" }
|
||||
|
|
@ -459,8 +460,8 @@ task = { path = "crates/task" }
|
|||
tasks_ui = { path = "crates/tasks_ui" }
|
||||
telemetry = { path = "crates/telemetry" }
|
||||
telemetry_events = { path = "crates/telemetry_events" }
|
||||
terminal = { path = "crates/terminal" }
|
||||
terminal_view = { path = "crates/terminal_view" }
|
||||
terminal = { path = "crates/terminal", default-features = false }
|
||||
terminal_view = { path = "crates/terminal_view", default-features = false }
|
||||
text = { path = "crates/text" }
|
||||
theme = { path = "crates/theme" }
|
||||
theme_extension = { path = "crates/theme_extension" }
|
||||
|
|
@ -617,6 +618,7 @@ jsonwebtoken = "10.0"
|
|||
jupyter-protocol = "1.4.0"
|
||||
jupyter-websocket-client = "1.1.0"
|
||||
libc = "0.2"
|
||||
libghostty-vt = "0.1.1"
|
||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||
linkify = "0.10.0"
|
||||
libwebrtc = "0.3.26"
|
||||
|
|
@ -796,6 +798,7 @@ unindent = "0.2.0"
|
|||
url = "2.2"
|
||||
urlencoding = "2.1.2"
|
||||
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
|
||||
vte = { version = "0.15.0", features = ["ansi"] }
|
||||
walkdir = "2.5"
|
||||
wasm-encoder = "0.221"
|
||||
wasmparser = "0.221"
|
||||
|
|
@ -889,6 +892,7 @@ calloop = { git = "https://github.com/zed-industries/calloop" }
|
|||
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1" }
|
||||
libwebrtc = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1" }
|
||||
webrtc-sys = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1" }
|
||||
libghostty-vt-sys = { path = "crates/libghostty-vt-sys" }
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ path = "src/acp_thread.rs"
|
|||
doctest = false
|
||||
|
||||
[features]
|
||||
default = ["alacritty-backend"]
|
||||
alacritty-backend = ["project/alacritty-backend", "terminal/alacritty-backend"]
|
||||
libghostty-vt = ["project/libghostty-vt", "terminal/libghostty-vt"]
|
||||
test-support = ["gpui/test-support", "project/test-support", "dep:parking_lot"]
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
|
|
@ -3308,6 +3308,7 @@ mod tests {
|
|||
0,
|
||||
cx.background_executor(),
|
||||
PathStyle::local(),
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
builder.subscribe(cx)
|
||||
|
|
@ -3389,6 +3390,7 @@ mod tests {
|
|||
0,
|
||||
cx.background_executor(),
|
||||
PathStyle::local(),
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
builder.subscribe(cx)
|
||||
|
|
@ -5004,6 +5006,7 @@ mod tests {
|
|||
0,
|
||||
cx.background_executor(),
|
||||
PathStyle::local(),
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
builder.subscribe(cx)
|
||||
|
|
@ -5051,6 +5054,7 @@ mod tests {
|
|||
0,
|
||||
cx.background_executor(),
|
||||
PathStyle::local(),
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
builder.subscribe(cx)
|
||||
|
|
@ -5112,6 +5116,7 @@ mod tests {
|
|||
0,
|
||||
cx.background_executor(),
|
||||
PathStyle::local(),
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
builder.subscribe(cx)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,26 @@ publish.workspace = true
|
|||
license = "GPL-3.0-or-later"
|
||||
|
||||
[features]
|
||||
test-support = ["acp_thread/test-support", "gpui/test-support", "project/test-support", "dep:env_logger", "client/test-support", "dep:gpui_tokio", "reqwest_client/test-support"]
|
||||
default = ["alacritty-backend"]
|
||||
alacritty-backend = [
|
||||
"acp_thread/alacritty-backend",
|
||||
"project/alacritty-backend",
|
||||
"terminal/alacritty-backend",
|
||||
]
|
||||
libghostty-vt = [
|
||||
"acp_thread/libghostty-vt",
|
||||
"project/libghostty-vt",
|
||||
"terminal/libghostty-vt",
|
||||
]
|
||||
test-support = [
|
||||
"acp_thread/test-support",
|
||||
"gpui/test-support",
|
||||
"project/test-support",
|
||||
"dep:env_logger",
|
||||
"client/test-support",
|
||||
"dep:gpui_tokio",
|
||||
"reqwest_client/test-support",
|
||||
]
|
||||
e2e = []
|
||||
|
||||
[lints]
|
||||
|
|
|
|||
|
|
@ -4240,6 +4240,7 @@ fn handle_session_notification(
|
|||
0,
|
||||
cx.background_executor(),
|
||||
thread.project().read(cx).path_style(cx),
|
||||
cx,
|
||||
)?;
|
||||
let lower = cx.new(|cx| builder.subscribe(cx));
|
||||
thread.on_terminal_provider_event(
|
||||
|
|
|
|||
|
|
@ -13,6 +13,21 @@ path = "src/agent_ui.rs"
|
|||
doctest = false
|
||||
|
||||
[features]
|
||||
default = ["alacritty-backend"]
|
||||
alacritty-backend = [
|
||||
"acp_thread/alacritty-backend",
|
||||
"agent_servers/alacritty-backend",
|
||||
"project/alacritty-backend",
|
||||
"terminal/alacritty-backend",
|
||||
"terminal_view/alacritty-backend",
|
||||
]
|
||||
libghostty-vt = [
|
||||
"acp_thread/libghostty-vt",
|
||||
"agent_servers/libghostty-vt",
|
||||
"project/libghostty-vt",
|
||||
"terminal/libghostty-vt",
|
||||
"terminal_view/libghostty-vt",
|
||||
]
|
||||
test-support = [
|
||||
"acp_thread/test-support",
|
||||
"eval_utils",
|
||||
|
|
|
|||
|
|
@ -814,6 +814,10 @@ impl AgentTerminal {
|
|||
fn custom_title(&self, cx: &App) -> Option<SharedString> {
|
||||
self.view.read(cx).custom_title().map(SharedString::from)
|
||||
}
|
||||
|
||||
fn is_ghostty_backend(&self, cx: &App) -> bool {
|
||||
self.view.read(cx).terminal().read(cx).is_ghostty_backend()
|
||||
}
|
||||
}
|
||||
|
||||
enum BaseView {
|
||||
|
|
@ -4646,13 +4650,14 @@ impl AgentPanel {
|
|||
}
|
||||
}
|
||||
VisibleSurface::Terminal(_) => {
|
||||
if let Some((terminal_id, title_editor, title)) =
|
||||
if let Some((terminal_id, title_editor, title, is_ghostty_backend)) =
|
||||
self.active_terminal_id().and_then(|terminal_id| {
|
||||
self.terminals.get(&terminal_id).map(|terminal| {
|
||||
(
|
||||
terminal_id,
|
||||
terminal.title_editor.clone(),
|
||||
terminal.title(cx),
|
||||
terminal.is_ghostty_backend(cx),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -4676,7 +4681,15 @@ impl AgentPanel {
|
|||
.flex_1()
|
||||
.cursor_text()
|
||||
.overflow_x_scroll()
|
||||
.child(Label::new(title).color(Color::Muted).single_line())
|
||||
.child(
|
||||
h_flex()
|
||||
.min_w_0()
|
||||
.gap_1()
|
||||
.when(is_ghostty_backend, |this| {
|
||||
this.child(Self::render_ghostty_terminal_backend_indicator())
|
||||
})
|
||||
.child(Label::new(title).color(Color::Muted).single_line()),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.edit_terminal_title(terminal_id, window, cx);
|
||||
}))
|
||||
|
|
@ -4725,6 +4738,14 @@ impl AgentPanel {
|
|||
.into_any()
|
||||
}
|
||||
|
||||
fn render_ghostty_terminal_backend_indicator() -> impl IntoElement {
|
||||
div()
|
||||
.id("agent-panel-terminal-ghostty-backend-indicator")
|
||||
.flex_none()
|
||||
.tooltip(Tooltip::text("Ghostty terminal backend"))
|
||||
.child(Label::new("👻").size(LabelSize::Small).color(Color::Muted))
|
||||
}
|
||||
|
||||
fn handle_regenerate_thread_title(conversation_view: Entity<ConversationView>, cx: &mut App) {
|
||||
conversation_view.update(cx, |conversation_view, cx| {
|
||||
if let Some(thread) = conversation_view.as_native_thread(cx) {
|
||||
|
|
@ -5935,6 +5956,7 @@ impl AgentPanel {
|
|||
cx.entity_id().as_u64(),
|
||||
cx.background_executor(),
|
||||
path_style,
|
||||
cx,
|
||||
)?;
|
||||
let terminal = cx.new(|cx| builder.subscribe(cx));
|
||||
let terminal_view = cx.new(|cx| {
|
||||
|
|
|
|||
|
|
@ -1679,6 +1679,7 @@ mod tests {
|
|||
0,
|
||||
cx.background_executor(),
|
||||
PathStyle::local(),
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
.subscribe(cx)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,17 @@ path = "src/debugger_ui.rs"
|
|||
doctest = false
|
||||
|
||||
[features]
|
||||
default = ["alacritty-backend"]
|
||||
alacritty-backend = [
|
||||
"project/alacritty-backend",
|
||||
"terminal/alacritty-backend",
|
||||
"terminal_view/alacritty-backend",
|
||||
]
|
||||
libghostty-vt = [
|
||||
"project/libghostty-vt",
|
||||
"terminal/libghostty-vt",
|
||||
"terminal_view/libghostty-vt",
|
||||
]
|
||||
test-support = [
|
||||
"dap/test-support",
|
||||
"dap_adapters/test-support",
|
||||
|
|
@ -26,7 +37,6 @@ test-support = [
|
|||
]
|
||||
|
||||
[dependencies]
|
||||
alacritty_terminal.workspace = true
|
||||
anyhow.workspace = true
|
||||
bitflags.workspace = true
|
||||
client.workspace = true
|
||||
|
|
@ -64,6 +74,7 @@ settings.workspace = true
|
|||
sysinfo.workspace = true
|
||||
task.workspace = true
|
||||
tasks_ui.workspace = true
|
||||
terminal.workspace = true
|
||||
terminal_view.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
|
|
@ -74,6 +85,7 @@ ui.workspace = true
|
|||
ui_input.workspace = true
|
||||
unindent = { workspace = true, optional = true }
|
||||
util.workspace = true
|
||||
vte.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use super::{
|
|||
stack_frame_list::{StackFrameList, StackFrameListEvent},
|
||||
variable_list::VariableList,
|
||||
};
|
||||
use alacritty_terminal::vte::ansi;
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use dap::{CompletionItem, CompletionItemType, OutputEvent};
|
||||
|
|
@ -24,12 +23,12 @@ use project::{
|
|||
search_history::{SearchHistory, SearchHistoryCursor},
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::fmt::Write;
|
||||
use std::{ops::Range, rc::Rc, usize};
|
||||
use theme::Theme;
|
||||
use theme_settings::ThemeSettings;
|
||||
use ui::{ContextMenu, Divider, PopoverMenu, SplitButton, Tooltip, prelude::*};
|
||||
use util::ResultExt;
|
||||
use vte::ansi;
|
||||
|
||||
actions!(
|
||||
console,
|
||||
|
|
@ -181,7 +180,8 @@ impl Console {
|
|||
ansi::Processor::<ansi::StdSyncHandler>::default();
|
||||
|
||||
let trimmed_output = event.output.trim_end();
|
||||
let _ = writeln!(&mut scratch, "{trimmed_output}");
|
||||
scratch.push_str(trimmed_output);
|
||||
scratch.push('\n');
|
||||
ansi_processor.advance(&mut ansi_handler, scratch.as_bytes());
|
||||
let output = std::mem::take(&mut ansi_handler.output);
|
||||
to_insert.extend(output.chars());
|
||||
|
|
@ -229,6 +229,7 @@ impl Console {
|
|||
|
||||
for (range, color) in spans {
|
||||
let Some(color) = color else { continue };
|
||||
let color = terminal::TerminalColor::from(color);
|
||||
let start_offset = range.start;
|
||||
let range = buffer.anchor_after(MultiBufferOffset(range.start))
|
||||
..buffer.anchor_before(MultiBufferOffset(range.end));
|
||||
|
|
@ -868,85 +869,9 @@ impl ansi::Handler for ConsoleHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn color_fetcher(color: ansi::Color) -> fn(&Theme) -> Hsla {
|
||||
let color_fetcher: fn(&Theme) -> Hsla = match color {
|
||||
// Named and theme defined colors
|
||||
ansi::Color::Named(n) => match n {
|
||||
ansi::NamedColor::Black => |theme| theme.colors().terminal_ansi_black,
|
||||
ansi::NamedColor::Red => |theme| theme.colors().terminal_ansi_red,
|
||||
ansi::NamedColor::Green => |theme| theme.colors().terminal_ansi_green,
|
||||
ansi::NamedColor::Yellow => |theme| theme.colors().terminal_ansi_yellow,
|
||||
ansi::NamedColor::Blue => |theme| theme.colors().terminal_ansi_blue,
|
||||
ansi::NamedColor::Magenta => |theme| theme.colors().terminal_ansi_magenta,
|
||||
ansi::NamedColor::Cyan => |theme| theme.colors().terminal_ansi_cyan,
|
||||
ansi::NamedColor::White => |theme| theme.colors().terminal_ansi_white,
|
||||
ansi::NamedColor::BrightBlack => |theme| theme.colors().terminal_ansi_bright_black,
|
||||
ansi::NamedColor::BrightRed => |theme| theme.colors().terminal_ansi_bright_red,
|
||||
ansi::NamedColor::BrightGreen => |theme| theme.colors().terminal_ansi_bright_green,
|
||||
ansi::NamedColor::BrightYellow => |theme| theme.colors().terminal_ansi_bright_yellow,
|
||||
ansi::NamedColor::BrightBlue => |theme| theme.colors().terminal_ansi_bright_blue,
|
||||
ansi::NamedColor::BrightMagenta => |theme| theme.colors().terminal_ansi_bright_magenta,
|
||||
ansi::NamedColor::BrightCyan => |theme| theme.colors().terminal_ansi_bright_cyan,
|
||||
ansi::NamedColor::BrightWhite => |theme| theme.colors().terminal_ansi_bright_white,
|
||||
ansi::NamedColor::Foreground => |theme| theme.colors().terminal_foreground,
|
||||
ansi::NamedColor::Background => |theme| theme.colors().terminal_background,
|
||||
ansi::NamedColor::Cursor => |theme| theme.players().local().cursor,
|
||||
ansi::NamedColor::DimBlack => |theme| theme.colors().terminal_ansi_dim_black,
|
||||
ansi::NamedColor::DimRed => |theme| theme.colors().terminal_ansi_dim_red,
|
||||
ansi::NamedColor::DimGreen => |theme| theme.colors().terminal_ansi_dim_green,
|
||||
ansi::NamedColor::DimYellow => |theme| theme.colors().terminal_ansi_dim_yellow,
|
||||
ansi::NamedColor::DimBlue => |theme| theme.colors().terminal_ansi_dim_blue,
|
||||
ansi::NamedColor::DimMagenta => |theme| theme.colors().terminal_ansi_dim_magenta,
|
||||
ansi::NamedColor::DimCyan => |theme| theme.colors().terminal_ansi_dim_cyan,
|
||||
ansi::NamedColor::DimWhite => |theme| theme.colors().terminal_ansi_dim_white,
|
||||
ansi::NamedColor::BrightForeground => |theme| theme.colors().terminal_bright_foreground,
|
||||
ansi::NamedColor::DimForeground => |theme| theme.colors().terminal_dim_foreground,
|
||||
},
|
||||
// 'True' colors
|
||||
ansi::Color::Spec(_) => |theme| theme.colors().editor_background,
|
||||
// 8 bit, indexed colors
|
||||
ansi::Color::Indexed(i) => {
|
||||
match i {
|
||||
// 0-15 are the same as the named colors above
|
||||
0 => |theme| theme.colors().terminal_ansi_black,
|
||||
1 => |theme| theme.colors().terminal_ansi_red,
|
||||
2 => |theme| theme.colors().terminal_ansi_green,
|
||||
3 => |theme| theme.colors().terminal_ansi_yellow,
|
||||
4 => |theme| theme.colors().terminal_ansi_blue,
|
||||
5 => |theme| theme.colors().terminal_ansi_magenta,
|
||||
6 => |theme| theme.colors().terminal_ansi_cyan,
|
||||
7 => |theme| theme.colors().terminal_ansi_white,
|
||||
8 => |theme| theme.colors().terminal_ansi_bright_black,
|
||||
9 => |theme| theme.colors().terminal_ansi_bright_red,
|
||||
10 => |theme| theme.colors().terminal_ansi_bright_green,
|
||||
11 => |theme| theme.colors().terminal_ansi_bright_yellow,
|
||||
12 => |theme| theme.colors().terminal_ansi_bright_blue,
|
||||
13 => |theme| theme.colors().terminal_ansi_bright_magenta,
|
||||
14 => |theme| theme.colors().terminal_ansi_bright_cyan,
|
||||
15 => |theme| theme.colors().terminal_ansi_bright_white,
|
||||
// 16-231 are a 6x6x6 RGB color cube, mapped to 0-255 using steps defined by XTerm.
|
||||
// See: https://github.com/xterm-x11/xterm-snapshots/blob/master/256colres.pl
|
||||
// 16..=231 => {
|
||||
// let (r, g, b) = rgb_for_index(index as u8);
|
||||
// rgba_color(
|
||||
// if r == 0 { 0 } else { r * 40 + 55 },
|
||||
// if g == 0 { 0 } else { g * 40 + 55 },
|
||||
// if b == 0 { 0 } else { b * 40 + 55 },
|
||||
// )
|
||||
// }
|
||||
// 232-255 are a 24-step grayscale ramp from (8, 8, 8) to (238, 238, 238).
|
||||
// 232..=255 => {
|
||||
// let i = index as u8 - 232; // Align index to 0..24
|
||||
// let value = i * 10 + 8;
|
||||
// rgba_color(value, value, value)
|
||||
// }
|
||||
// For compatibility with the alacritty::Colors interface
|
||||
// See: https://github.com/alacritty/alacritty/blob/master/alacritty_terminal/src/term/color.rs
|
||||
_ => |_| gpui::black(),
|
||||
}
|
||||
}
|
||||
};
|
||||
color_fetcher
|
||||
fn color_fetcher(color: ansi::Color) -> impl Fn(&Theme) -> Hsla {
|
||||
let color = terminal::TerminalColor::from(color);
|
||||
move |theme| terminal_view::terminal_element::convert_color(&color, theme)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -47,6 +47,18 @@ impl FeatureFlag for DiffReviewFeatureFlag {
|
|||
}
|
||||
register_feature_flag!(DiffReviewFeatureFlag);
|
||||
|
||||
pub struct GhosttyTerminalFeatureFlag;
|
||||
|
||||
impl FeatureFlag for GhosttyTerminalFeatureFlag {
|
||||
const NAME: &'static str = "ghostty-terminal";
|
||||
type Value = PresenceFlag;
|
||||
|
||||
fn enabled_for_staff() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
register_feature_flag!(GhosttyTerminalFeatureFlag);
|
||||
|
||||
pub struct UpdatePlanToolFeatureFlag;
|
||||
|
||||
impl FeatureFlag for UpdatePlanToolFeatureFlag {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ test-support = ["multi_buffer/test-support", "remote_connection/test-support"]
|
|||
|
||||
[dependencies]
|
||||
agent_settings.workspace = true
|
||||
alacritty_terminal.workspace = true
|
||||
anyhow.workspace = true
|
||||
askpass.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
|
|
@ -72,6 +71,8 @@ zed_actions.workspace = true
|
|||
zeroize.workspace = true
|
||||
ztracing.workspace = true
|
||||
tracing.workspace = true
|
||||
vte.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ use crate::{
|
|||
git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector,
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use alacritty_terminal::vte::ansi;
|
||||
use anyhow::Context as _;
|
||||
use askpass::AskPassDelegate;
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
|
|
@ -81,6 +80,7 @@ use ui::{
|
|||
};
|
||||
use util::paths::PathStyle;
|
||||
use util::{ResultExt, TryFutureExt, markdown::MarkdownInlineCode, maybe, rel_path::RelPath};
|
||||
use vte::ansi;
|
||||
use workspace::SERIALIZATION_THROTTLE_TIME;
|
||||
use workspace::{
|
||||
Workspace,
|
||||
|
|
@ -8924,8 +8924,6 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_git_output_handler_strips_ansi_codes() {
|
||||
use alacritty_terminal::vte::ansi;
|
||||
|
||||
let cases = [
|
||||
("no escape codes here\n", "no escape codes here\n"),
|
||||
("\x1b[31mhello\x1b[0m", "hello"),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ license = "GPL-3.0-or-later"
|
|||
workspace = true
|
||||
|
||||
[features]
|
||||
default = ["alacritty-backend"]
|
||||
alacritty-backend = ["project/alacritty-backend", "terminal/alacritty-backend"]
|
||||
libghostty-vt = ["project/libghostty-vt", "terminal/libghostty-vt"]
|
||||
test-support = [
|
||||
"load-grammars"
|
||||
]
|
||||
|
|
|
|||
19
crates/libghostty-vt-sys/Cargo.toml
Normal file
19
crates/libghostty-vt-sys/Cargo.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "libghostty-vt-sys"
|
||||
version = "0.1.1"
|
||||
edition = "2024"
|
||||
rust-version = "1.93"
|
||||
links = "ghostty-vt"
|
||||
build = "build.rs"
|
||||
description = "Raw FFI bindings for libghostty-vt, the Ghostty terminal emulation library"
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/uzaaft/libghostty-rs"
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = []
|
||||
bindgen-tool = ["dep:bindgen"]
|
||||
|
||||
[dependencies]
|
||||
bindgen = { version = "0.72.1", optional = true }
|
||||
34
crates/libghostty-vt-sys/README.md
Normal file
34
crates/libghostty-vt-sys/README.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# libghostty-vt-sys
|
||||
|
||||
Raw FFI bindings for `libghostty-vt`, the Ghostty terminal emulation library.
|
||||
|
||||
Zed depends on the published `libghostty-vt` Rust crate, but patches its `libghostty-vt-sys` dependency to this local crate via `[patch.crates-io]`. The wrapper crate stays external; this sys crate is vendored only because Zed needs different build behavior than the published sys crate currently provides.
|
||||
|
||||
## Why this crate is vendored
|
||||
|
||||
- Zed must bundle the terminal backend. Users should not need to install Ghostty, place a Ghostty dynamic library on their system, or configure a runtime library path for Zed to start.
|
||||
- Zed needs a static `libghostty-vt.a` build so release artifacts can link the backend into Zed directly.
|
||||
- The Ghostty source revision must be pinned and reproducible for CI and release builds.
|
||||
- The build has to link Ghostty's static native dependencies (`simdutf` and `highway`) explicitly.
|
||||
- On macOS, Zed still targets older systems than Ghostty's default Zig build target. For fetched sources, this crate patches Ghostty's deployment target from macOS 13 to macOS 11 before building.
|
||||
- The generated FFI bindings are checked in so regular builds and docs.rs do not need bindgen or a Zig toolchain just to type-check the Rust API surface.
|
||||
|
||||
## Build behavior
|
||||
|
||||
By default, `build.rs` fetches `ghostty-org/ghostty` at the pinned commit in `GHOSTTY_COMMIT`, builds it with:
|
||||
|
||||
```sh
|
||||
zig build -Demit-lib-vt --prefix <out-dir>/ghostty-install
|
||||
```
|
||||
|
||||
and links:
|
||||
|
||||
- `libghostty-vt.a`
|
||||
- `libsimdutf.a`
|
||||
- `libhighway.a`
|
||||
|
||||
Set `GHOSTTY_SOURCE_DIR` to point at a local Ghostty checkout while developing or testing a newer Ghostty revision. When this override is used, the source is treated as external and the macOS deployment-target patch is not applied; point it at an already-patched checkout if you need the macOS 11 target.
|
||||
|
||||
## Removing the patch
|
||||
|
||||
This vendored sys crate should go away once the published `libghostty-vt-sys` crate supports the requirements above directly: static bundled builds, explicit static dependency linkage, a Zed-compatible macOS deployment target, pinned-source or otherwise reproducible release builds, and checked-in bindings suitable for docs and CI.
|
||||
223
crates/libghostty-vt-sys/build.rs
Normal file
223
crates/libghostty-vt-sys/build.rs
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
/// Pinned ghostty commit. Update this to pull a newer version.
|
||||
const GHOSTTY_REPO: &str = "https://github.com/ghostty-org/ghostty.git";
|
||||
const GHOSTTY_COMMIT: &str = "bebca84668947bfc92b9a30ed58712e1c34eee1d";
|
||||
|
||||
enum GhosttySource {
|
||||
Fetched(PathBuf),
|
||||
External(PathBuf),
|
||||
}
|
||||
|
||||
impl GhosttySource {
|
||||
fn path(&self) -> &Path {
|
||||
match self {
|
||||
Self::Fetched(path) | Self::External(path) => path,
|
||||
}
|
||||
}
|
||||
|
||||
fn can_patch(&self) -> bool {
|
||||
matches!(self, Self::Fetched(_))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// docs.rs has no Zig toolchain. The checked-in bindings in src/bindings.rs
|
||||
// are enough for generating documentation, so skip the entire native
|
||||
// build when running under docs.rs.
|
||||
if env::var("DOCS_RS").is_ok() {
|
||||
return;
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-env-changed=LIBGHOSTTY_VT_SYS_NO_VENDOR");
|
||||
println!("cargo:rerun-if-env-changed=GHOSTTY_SOURCE_DIR");
|
||||
println!("cargo:rerun-if-env-changed=TARGET");
|
||||
println!("cargo:rerun-if-env-changed=HOST");
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR must be set"));
|
||||
let target = env::var("TARGET").expect("TARGET must be set");
|
||||
let host = env::var("HOST").expect("HOST must be set");
|
||||
|
||||
// Locate ghostty source: env override > fetch into OUT_DIR.
|
||||
let ghostty_source = match env::var("GHOSTTY_SOURCE_DIR") {
|
||||
Ok(dir) => {
|
||||
let path = PathBuf::from(dir);
|
||||
assert!(
|
||||
path.join("build.zig").exists(),
|
||||
"GHOSTTY_SOURCE_DIR does not contain build.zig: {}",
|
||||
path.display()
|
||||
);
|
||||
GhosttySource::External(path)
|
||||
}
|
||||
Err(_) => GhosttySource::Fetched(fetch_ghostty(&out_dir)),
|
||||
};
|
||||
let ghostty_dir = ghostty_source.path();
|
||||
|
||||
if target.contains("darwin") {
|
||||
ensure_ghostty_macos_deployment_target(ghostty_dir, ghostty_source.can_patch());
|
||||
}
|
||||
|
||||
// Build libghostty-vt via zig.
|
||||
let install_prefix = out_dir.join("ghostty-install");
|
||||
|
||||
let mut build = Command::new("zig");
|
||||
build
|
||||
.arg("build")
|
||||
.arg("-Demit-lib-vt")
|
||||
.arg("--prefix")
|
||||
.arg(&install_prefix)
|
||||
.current_dir(&ghostty_dir);
|
||||
|
||||
// Only pass -Dtarget when cross-compiling. For native builds, let zig
|
||||
// auto-detect the host (matches how ghostty's own CMakeLists.txt works).
|
||||
if target != host {
|
||||
let zig_target = zig_target(&target);
|
||||
build.arg(format!("-Dtarget={zig_target}"));
|
||||
}
|
||||
|
||||
run(build, "zig build");
|
||||
|
||||
let lib_dir = install_prefix.join("lib");
|
||||
let include_dir = install_prefix.join("include");
|
||||
|
||||
let lib_name = "libghostty-vt.a";
|
||||
|
||||
assert!(
|
||||
lib_dir.join(lib_name).exists(),
|
||||
"expected static library at {}",
|
||||
lib_dir.join(lib_name).display()
|
||||
);
|
||||
assert!(
|
||||
include_dir.join("ghostty").join("vt.h").exists(),
|
||||
"expected header at {}",
|
||||
include_dir.join("ghostty").join("vt.h").display()
|
||||
);
|
||||
|
||||
let simdutf_dir = static_dependency_dir(&ghostty_dir, "libsimdutf.a");
|
||||
let highway_dir = static_dependency_dir(&ghostty_dir, "libhighway.a");
|
||||
|
||||
println!("cargo:rustc-link-search=native={}", lib_dir.display());
|
||||
println!("cargo:rustc-link-search=native={}", simdutf_dir.display());
|
||||
println!("cargo:rustc-link-search=native={}", highway_dir.display());
|
||||
println!("cargo:rustc-link-lib=static=ghostty-vt");
|
||||
println!("cargo:rustc-link-lib=static=simdutf");
|
||||
println!("cargo:rustc-link-lib=static=highway");
|
||||
if target.contains("darwin") {
|
||||
println!("cargo:rustc-link-lib=dylib=c++");
|
||||
} else if target.contains("linux") {
|
||||
println!("cargo:rustc-link-lib=dylib=stdc++");
|
||||
}
|
||||
println!("cargo:include={}", include_dir.display());
|
||||
}
|
||||
|
||||
fn ensure_ghostty_macos_deployment_target(ghostty_dir: &Path, can_patch: bool) {
|
||||
let config_path = ghostty_dir.join("src").join("build").join("Config.zig");
|
||||
let source = std::fs::read_to_string(&config_path)
|
||||
.unwrap_or_else(|error| panic!("failed to read {}: {error}", config_path.display()));
|
||||
|
||||
let macos_13 = ".macos => .{ .semver = .{\n .major = 13,\n .minor = 0,\n .patch = 0,\n } },";
|
||||
let macos_11 = ".macos => .{ .semver = .{\n .major = 11,\n .minor = 0,\n .patch = 0,\n } },";
|
||||
|
||||
if source.contains(macos_11) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert!(
|
||||
source.contains(macos_13),
|
||||
"failed to find macOS deployment target in {}",
|
||||
config_path.display()
|
||||
);
|
||||
assert!(
|
||||
can_patch,
|
||||
"GHOSTTY_SOURCE_DIR uses Ghostty's default macOS 13 deployment target; unset \
|
||||
GHOSTTY_SOURCE_DIR to use the vendored source, or point it at an already-patched source"
|
||||
);
|
||||
|
||||
let patched = source.replace(macos_13, macos_11);
|
||||
std::fs::write(&config_path, patched)
|
||||
.unwrap_or_else(|error| panic!("failed to write {}: {error}", config_path.display()));
|
||||
}
|
||||
|
||||
fn static_dependency_dir(ghostty_dir: &Path, file_name: &str) -> PathBuf {
|
||||
let cache_dir = ghostty_dir.join(".zig-cache").join("o");
|
||||
let entries = std::fs::read_dir(&cache_dir)
|
||||
.unwrap_or_else(|error| panic!("failed to read {}: {error}", cache_dir.display()));
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry.unwrap_or_else(|error| {
|
||||
panic!("failed to read entry in {}: {error}", cache_dir.display())
|
||||
});
|
||||
let path = entry.path();
|
||||
if path.join(file_name).exists() {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
panic!("failed to find {file_name} under {}", cache_dir.display());
|
||||
}
|
||||
|
||||
/// Clone ghostty at the pinned commit into OUT_DIR/ghostty-src.
|
||||
/// Reuses an existing clone if the commit matches.
|
||||
fn fetch_ghostty(out_dir: &Path) -> PathBuf {
|
||||
let src_dir = out_dir.join("ghostty-src");
|
||||
let stamp = src_dir.join(".ghostty-commit");
|
||||
|
||||
// Skip fetch if we already have the right commit.
|
||||
if stamp.exists()
|
||||
&& let Ok(existing) = std::fs::read_to_string(&stamp)
|
||||
&& existing.trim() == GHOSTTY_COMMIT
|
||||
{
|
||||
return src_dir;
|
||||
}
|
||||
|
||||
// Clean and clone fresh.
|
||||
if src_dir.exists() {
|
||||
std::fs::remove_dir_all(&src_dir)
|
||||
.unwrap_or_else(|e| panic!("failed to remove {}: {e}", src_dir.display()));
|
||||
}
|
||||
|
||||
eprintln!("Fetching ghostty {GHOSTTY_COMMIT} ...");
|
||||
|
||||
let mut clone = Command::new("git");
|
||||
clone
|
||||
.arg("clone")
|
||||
.arg("--filter=blob:none")
|
||||
.arg("--no-checkout")
|
||||
.arg(GHOSTTY_REPO)
|
||||
.arg(&src_dir);
|
||||
run(clone, "git clone ghostty");
|
||||
|
||||
let mut checkout = Command::new("git");
|
||||
checkout
|
||||
.arg("checkout")
|
||||
.arg(GHOSTTY_COMMIT)
|
||||
.current_dir(&src_dir);
|
||||
run(checkout, "git checkout ghostty commit");
|
||||
|
||||
std::fs::write(&stamp, GHOSTTY_COMMIT).unwrap_or_else(|e| panic!("failed to write stamp: {e}"));
|
||||
|
||||
src_dir
|
||||
}
|
||||
|
||||
fn run(mut command: Command, context: &str) {
|
||||
let status = command
|
||||
.status()
|
||||
.unwrap_or_else(|error| panic!("failed to execute {context}: {error}"));
|
||||
assert!(status.success(), "{context} failed with status {status}");
|
||||
}
|
||||
|
||||
fn zig_target(target: &str) -> String {
|
||||
let value = match target {
|
||||
"x86_64-unknown-linux-gnu" => "x86_64-linux-gnu",
|
||||
"x86_64-unknown-linux-musl" => "x86_64-linux-musl",
|
||||
"aarch64-unknown-linux-gnu" => "aarch64-linux-gnu",
|
||||
"aarch64-unknown-linux-musl" => "aarch64-linux-musl",
|
||||
"aarch64-apple-darwin" => "aarch64-macos-none",
|
||||
"x86_64-apple-darwin" => "x86_64-macos-none",
|
||||
other => panic!("unsupported Rust target for vendored build: {other}"),
|
||||
};
|
||||
value.to_owned()
|
||||
}
|
||||
2472
crates/libghostty-vt-sys/src/bindings.rs
Normal file
2472
crates/libghostty-vt-sys/src/bindings.rs
Normal file
File diff suppressed because it is too large
Load diff
240
crates/libghostty-vt-sys/src/lib.rs
Normal file
240
crates/libghostty-vt-sys/src/lib.rs
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(clippy::all)]
|
||||
#![allow(rustdoc::all)]
|
||||
|
||||
mod bindings;
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
pub use bindings::*;
|
||||
|
||||
/// Initialize a "sized" FFI object.
|
||||
#[macro_export]
|
||||
macro_rules! sized {
|
||||
($ty:ty) => {{
|
||||
let mut t = <$ty as ::std::default::Default>::default();
|
||||
t.size = ::std::mem::size_of::<$ty>();
|
||||
t
|
||||
}};
|
||||
}
|
||||
|
||||
impl<S> From<S> for GhosttyString
|
||||
where
|
||||
S: Deref<Target = str>,
|
||||
{
|
||||
fn from(value: S) -> Self {
|
||||
Self {
|
||||
ptr: value.as_ptr(),
|
||||
len: value.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GhosttyString {
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must uphold that the associated lifetime is valid
|
||||
/// with the given context behind the FFI string, and that it contains
|
||||
/// valid UTF-8 data.
|
||||
pub unsafe fn to_str<'a>(self) -> &'a str {
|
||||
// SAFETY: To be upheld by caller
|
||||
let slice = unsafe { std::slice::from_raw_parts(self.ptr, self.len) };
|
||||
unsafe { std::str::from_utf8_unchecked(slice) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Canonical list of exported `libghostty-vt` C functions represented by checked-in bindings.
|
||||
pub const EXPORTED_API_SYMBOLS: &[&str] = &[
|
||||
"ghostty_build_info",
|
||||
"ghostty_cell_get",
|
||||
"ghostty_color_rgb_get",
|
||||
"ghostty_focus_encode",
|
||||
"ghostty_formatter_format_alloc",
|
||||
"ghostty_formatter_format_buf",
|
||||
"ghostty_formatter_free",
|
||||
"ghostty_formatter_terminal_new",
|
||||
"ghostty_grid_ref_cell",
|
||||
"ghostty_grid_ref_graphemes",
|
||||
"ghostty_grid_ref_row",
|
||||
"ghostty_grid_ref_style",
|
||||
"ghostty_key_encoder_encode",
|
||||
"ghostty_key_encoder_free",
|
||||
"ghostty_key_encoder_new",
|
||||
"ghostty_key_encoder_setopt",
|
||||
"ghostty_key_encoder_setopt_from_terminal",
|
||||
"ghostty_key_event_free",
|
||||
"ghostty_key_event_get_action",
|
||||
"ghostty_key_event_get_composing",
|
||||
"ghostty_key_event_get_consumed_mods",
|
||||
"ghostty_key_event_get_key",
|
||||
"ghostty_key_event_get_mods",
|
||||
"ghostty_key_event_get_unshifted_codepoint",
|
||||
"ghostty_key_event_get_utf8",
|
||||
"ghostty_key_event_new",
|
||||
"ghostty_key_event_set_action",
|
||||
"ghostty_key_event_set_composing",
|
||||
"ghostty_key_event_set_consumed_mods",
|
||||
"ghostty_key_event_set_key",
|
||||
"ghostty_key_event_set_mods",
|
||||
"ghostty_key_event_set_unshifted_codepoint",
|
||||
"ghostty_key_event_set_utf8",
|
||||
"ghostty_mode_report_encode",
|
||||
"ghostty_mouse_encoder_encode",
|
||||
"ghostty_mouse_encoder_free",
|
||||
"ghostty_mouse_encoder_new",
|
||||
"ghostty_mouse_encoder_reset",
|
||||
"ghostty_mouse_encoder_setopt",
|
||||
"ghostty_mouse_encoder_setopt_from_terminal",
|
||||
"ghostty_mouse_event_clear_button",
|
||||
"ghostty_mouse_event_free",
|
||||
"ghostty_mouse_event_get_action",
|
||||
"ghostty_mouse_event_get_button",
|
||||
"ghostty_mouse_event_get_mods",
|
||||
"ghostty_mouse_event_get_position",
|
||||
"ghostty_mouse_event_new",
|
||||
"ghostty_mouse_event_set_action",
|
||||
"ghostty_mouse_event_set_button",
|
||||
"ghostty_mouse_event_set_mods",
|
||||
"ghostty_mouse_event_set_position",
|
||||
"ghostty_osc_command_data",
|
||||
"ghostty_osc_command_type",
|
||||
"ghostty_osc_end",
|
||||
"ghostty_osc_free",
|
||||
"ghostty_osc_new",
|
||||
"ghostty_osc_next",
|
||||
"ghostty_osc_reset",
|
||||
"ghostty_paste_is_safe",
|
||||
"ghostty_render_state_colors_get",
|
||||
"ghostty_render_state_free",
|
||||
"ghostty_render_state_get",
|
||||
"ghostty_render_state_new",
|
||||
"ghostty_render_state_row_cells_free",
|
||||
"ghostty_render_state_row_cells_get",
|
||||
"ghostty_render_state_row_cells_new",
|
||||
"ghostty_render_state_row_cells_next",
|
||||
"ghostty_render_state_row_cells_select",
|
||||
"ghostty_render_state_row_get",
|
||||
"ghostty_render_state_row_iterator_free",
|
||||
"ghostty_render_state_row_iterator_new",
|
||||
"ghostty_render_state_row_iterator_next",
|
||||
"ghostty_render_state_row_set",
|
||||
"ghostty_render_state_set",
|
||||
"ghostty_render_state_update",
|
||||
"ghostty_row_get",
|
||||
"ghostty_sgr_attribute_tag",
|
||||
"ghostty_sgr_attribute_value",
|
||||
"ghostty_sgr_free",
|
||||
"ghostty_sgr_new",
|
||||
"ghostty_sgr_next",
|
||||
"ghostty_sgr_reset",
|
||||
"ghostty_sgr_set_params",
|
||||
"ghostty_sgr_unknown_full",
|
||||
"ghostty_sgr_unknown_partial",
|
||||
"ghostty_size_report_encode",
|
||||
"ghostty_style_default",
|
||||
"ghostty_style_is_default",
|
||||
"ghostty_terminal_free",
|
||||
"ghostty_terminal_get",
|
||||
"ghostty_terminal_grid_ref",
|
||||
"ghostty_terminal_mode_get",
|
||||
"ghostty_terminal_mode_set",
|
||||
"ghostty_terminal_new",
|
||||
"ghostty_terminal_reset",
|
||||
"ghostty_terminal_resize",
|
||||
"ghostty_terminal_scroll_viewport",
|
||||
"ghostty_terminal_vt_write",
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use super::EXPORTED_API_SYMBOLS;
|
||||
|
||||
fn parse_binding_symbols(input: &str) -> BTreeSet<String> {
|
||||
input
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let line = line.trim();
|
||||
if !line.starts_with("pub fn ghostty_") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start = "pub fn ".len();
|
||||
let rest = &line[start..];
|
||||
let end = rest.find('(')?;
|
||||
Some(rest[..end].to_owned())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_header_symbols(input: &str) -> BTreeSet<String> {
|
||||
let mut symbols = BTreeSet::new();
|
||||
let mut statement = String::new();
|
||||
|
||||
for line in input.lines() {
|
||||
let trimmed = line.trim();
|
||||
|
||||
if trimmed.starts_with('#') || trimmed.starts_with("//") || trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip static inline functions (they are inlined, not exported symbols)
|
||||
if trimmed.starts_with("static") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !statement.is_empty() {
|
||||
statement.push(' ');
|
||||
}
|
||||
statement.push_str(trimmed);
|
||||
|
||||
if !trimmed.ends_with(';') && !trimmed.ends_with('{') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(end) = statement.find('(') {
|
||||
let before_paren = &statement[..end];
|
||||
if let Some(candidate) = before_paren.split_whitespace().last() {
|
||||
// Strip leading * for pointer-returning functions
|
||||
let candidate = candidate.trim_start_matches('*');
|
||||
if candidate.starts_with("ghostty_")
|
||||
&& candidate
|
||||
.chars()
|
||||
.all(|char| char.is_ascii_alphanumeric() || char == '_')
|
||||
{
|
||||
symbols.insert(candidate.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statement.clear();
|
||||
}
|
||||
|
||||
symbols
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exported_manifest_matches_bindings() {
|
||||
let from_bindings = parse_binding_symbols(include_str!("bindings.rs"));
|
||||
let from_manifest: BTreeSet<String> = EXPORTED_API_SYMBOLS
|
||||
.iter()
|
||||
.map(|symbol| (*symbol).to_owned())
|
||||
.collect();
|
||||
assert_eq!(from_manifest, from_bindings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exported_manifest_is_sorted_and_unique() {
|
||||
let mut prev = "";
|
||||
for symbol in EXPORTED_API_SYMBOLS {
|
||||
assert!(
|
||||
*symbol > prev,
|
||||
"EXPORTED_API_SYMBOLS is not sorted or has duplicates: {prev:?} >= {symbol:?}"
|
||||
);
|
||||
prev = symbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,9 @@ required-features = ["test-support"]
|
|||
path = "tests/integration/project_tests.rs"
|
||||
|
||||
[features]
|
||||
default = ["alacritty-backend"]
|
||||
alacritty-backend = ["terminal/alacritty-backend"]
|
||||
libghostty-vt = ["terminal/libghostty-vt"]
|
||||
test-support = [
|
||||
"buffer_diff/test-support",
|
||||
"client/test-support",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,17 @@ license = "GPL-3.0-or-later"
|
|||
workspace = true
|
||||
|
||||
[features]
|
||||
default = ["alacritty-backend"]
|
||||
alacritty-backend = [
|
||||
"project/alacritty-backend",
|
||||
"terminal/alacritty-backend",
|
||||
"terminal_view/alacritty-backend",
|
||||
]
|
||||
libghostty-vt = [
|
||||
"project/libghostty-vt",
|
||||
"terminal/libghostty-vt",
|
||||
"terminal_view/libghostty-vt",
|
||||
]
|
||||
test-support = []
|
||||
|
||||
[lib]
|
||||
|
|
@ -16,7 +27,6 @@ path = "src/repl.rs"
|
|||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
alacritty_terminal.workspace = true
|
||||
anyhow.workspace = true
|
||||
async-dispatcher.workspace = true
|
||||
async-task.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
//! # Plain Text Output
|
||||
//!
|
||||
//! This module provides functionality for rendering plain text output in a terminal-like format.
|
||||
//! It uses the Alacritty terminal emulator backend to process and display text, supporting
|
||||
//! ANSI escape sequences for formatting, colors, and other terminal features.
|
||||
//! It uses Zed's terminal emulator to process and display text, supporting ANSI escape
|
||||
//! sequences for formatting, colors, and other terminal features.
|
||||
//!
|
||||
//! The main component of this module is the `TerminalOutput` struct, which handles the parsing
|
||||
//! and rendering of text input, simulating a basic terminal environment within REPL output.
|
||||
|
|
@ -15,20 +15,14 @@
|
|||
//! - Error tracebacks
|
||||
//!
|
||||
|
||||
use alacritty_terminal::{
|
||||
event::VoidListener,
|
||||
grid::Dimensions as _,
|
||||
index::{Column, Line, Point},
|
||||
term::Config,
|
||||
vte::ansi::Processor,
|
||||
};
|
||||
use gpui::{Bounds, ClipboardItem, Entity, FontStyle, Pixels, TextStyle, WhiteSpace, canvas, size};
|
||||
use language::Buffer;
|
||||
use settings::Settings as _;
|
||||
use terminal::terminal_settings::TerminalSettings;
|
||||
use terminal::{Terminal, TerminalBuilder, terminal_settings::TerminalSettings};
|
||||
use terminal_view::terminal_element::TerminalElement;
|
||||
use theme_settings::ThemeSettings;
|
||||
use ui::{IntoElement, prelude::*};
|
||||
use util::paths::PathStyle;
|
||||
|
||||
use crate::outputs::OutputContent;
|
||||
use crate::repl_settings::ReplSettings;
|
||||
|
|
@ -43,15 +37,13 @@ use crate::repl_settings::ReplSettings;
|
|||
/// * text/plain content
|
||||
/// * error tracebacks
|
||||
///
|
||||
/// It uses the Alacritty terminal emulator backend to process and render text,
|
||||
/// It uses Zed's terminal emulator backend to process and render text,
|
||||
/// supporting ANSI escape sequences for text formatting and colors.
|
||||
///
|
||||
pub struct TerminalOutput {
|
||||
full_buffer: Option<Entity<Buffer>>,
|
||||
/// ANSI escape sequence processor for parsing input text.
|
||||
parser: Processor,
|
||||
/// Alacritty terminal instance that manages the terminal state and content.
|
||||
handler: alacritty_terminal::Term<VoidListener>,
|
||||
terminal: Option<Entity<Terminal>>,
|
||||
full_text: String,
|
||||
}
|
||||
|
||||
/// Returns the default text style for the terminal output.
|
||||
|
|
@ -141,16 +133,32 @@ impl TerminalOutput {
|
|||
/// This method initializes a new terminal emulator with default configuration
|
||||
/// and sets up the necessary components for handling terminal events and rendering.
|
||||
///
|
||||
pub fn new(window: &mut Window, cx: &mut App) -> Self {
|
||||
let term = alacritty_terminal::Term::new(
|
||||
Config::default(),
|
||||
&terminal_size(window, cx),
|
||||
VoidListener,
|
||||
);
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let terminal_bounds = terminal_size(window, cx);
|
||||
let background_executor = cx.background_executor().clone();
|
||||
let terminal = match TerminalBuilder::new_display_only(
|
||||
TerminalSettings::get_global(cx).cursor_shape,
|
||||
TerminalSettings::get_global(cx).alternate_scroll,
|
||||
TerminalSettings::get_global(cx).max_scroll_history_lines,
|
||||
0,
|
||||
&background_executor,
|
||||
PathStyle::local(),
|
||||
cx,
|
||||
) {
|
||||
Ok(builder) => Some(cx.new(|cx| {
|
||||
let mut terminal = builder.subscribe(cx);
|
||||
terminal.set_size(terminal_bounds);
|
||||
terminal
|
||||
})),
|
||||
Err(error) => {
|
||||
log::error!("failed to initialize REPL terminal output: {error}");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
parser: Processor::new(),
|
||||
handler: term,
|
||||
terminal,
|
||||
full_text: String::new(),
|
||||
full_buffer: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -167,7 +175,7 @@ impl TerminalOutput {
|
|||
/// # Returns
|
||||
///
|
||||
/// A new instance of `TerminalOutput` containing the provided text.
|
||||
pub fn from(text: &str, window: &mut Window, cx: &mut App) -> Self {
|
||||
pub fn from(text: &str, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let mut output = Self::new(window, cx);
|
||||
output.append_text(text, cx);
|
||||
output
|
||||
|
|
@ -199,15 +207,14 @@ impl TerminalOutput {
|
|||
/// # Arguments
|
||||
///
|
||||
/// * `text` - A string slice containing the text to be appended.
|
||||
pub fn append_text(&mut self, text: &str, cx: &mut App) {
|
||||
for byte in text.as_bytes() {
|
||||
if *byte == b'\n' {
|
||||
// Dirty (?) hack to move the cursor down
|
||||
self.parser.advance(&mut self.handler, &[b'\r']);
|
||||
self.parser.advance(&mut self.handler, &[b'\n']);
|
||||
} else {
|
||||
self.parser.advance(&mut self.handler, &[*byte]);
|
||||
}
|
||||
pub fn append_text(&mut self, text: &str, cx: &mut Context<Self>) {
|
||||
if let Some(terminal) = &self.terminal {
|
||||
self.full_text = terminal.update(cx, |terminal, cx| {
|
||||
terminal.write_output(text.as_bytes(), cx);
|
||||
Self::sanitize_terminal_text(terminal.get_content())
|
||||
});
|
||||
} else {
|
||||
self.full_text.push_str(text);
|
||||
}
|
||||
|
||||
// This will keep the buffer up to date, though with some terminal codes it won't be perfect
|
||||
|
|
@ -219,6 +226,10 @@ impl TerminalOutput {
|
|||
}
|
||||
|
||||
pub fn full_text(&self) -> String {
|
||||
self.full_text.clone()
|
||||
}
|
||||
|
||||
fn sanitize_terminal_text(text: String) -> String {
|
||||
fn sanitize(mut line: String) -> Option<String> {
|
||||
line.retain(|ch| ch != '\u{0}' && ch != '\r');
|
||||
if line.trim().is_empty() {
|
||||
|
|
@ -228,32 +239,10 @@ impl TerminalOutput {
|
|||
Some(trimmed.to_owned())
|
||||
}
|
||||
|
||||
let mut lines = Vec::new();
|
||||
|
||||
// Get the total number of lines, including history
|
||||
let total_lines = self.handler.grid().total_lines();
|
||||
let visible_lines = self.handler.screen_lines();
|
||||
let history_lines = total_lines - visible_lines;
|
||||
|
||||
// Capture history lines in correct order (oldest to newest)
|
||||
for line in (0..history_lines).rev() {
|
||||
let line_index = Line(-(line as i32) - 1);
|
||||
let start = Point::new(line_index, Column(0));
|
||||
let end = Point::new(line_index, Column(self.handler.columns() - 1));
|
||||
if let Some(cleaned) = sanitize(self.handler.bounds_to_string(start, end)) {
|
||||
lines.push(cleaned);
|
||||
}
|
||||
}
|
||||
|
||||
// Capture visible lines
|
||||
for line in 0..visible_lines {
|
||||
let line_index = Line(line as i32);
|
||||
let start = Point::new(line_index, Column(0));
|
||||
let end = Point::new(line_index, Column(self.handler.columns() - 1));
|
||||
if let Some(cleaned) = sanitize(self.handler.bounds_to_string(start, end)) {
|
||||
lines.push(cleaned);
|
||||
}
|
||||
}
|
||||
let lines = text
|
||||
.lines()
|
||||
.filter_map(|line| sanitize(line.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if lines.is_empty() {
|
||||
String::new()
|
||||
|
|
@ -320,26 +309,32 @@ impl Render for TerminalOutput {
|
|||
/// the layout of the terminal grid, calculates the dimensions of the output, and
|
||||
/// creates a canvas element that paints the terminal cells and background rectangles.
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let Some(terminal) = self.terminal.clone() else {
|
||||
return div().child(self.full_text.clone()).into_any_element();
|
||||
};
|
||||
|
||||
let content = terminal.update(cx, |terminal, cx| {
|
||||
terminal.sync(window, cx);
|
||||
terminal.last_content().clone()
|
||||
});
|
||||
let text_style = text_style(window, cx);
|
||||
let text_system = window.text_system();
|
||||
|
||||
let grid = self
|
||||
.handler
|
||||
.renderable_content()
|
||||
.display_iter
|
||||
.map(|ic| terminal::IndexedCell {
|
||||
point: ic.point,
|
||||
cell: ic.cell.clone(),
|
||||
});
|
||||
let minimum_contrast = TerminalSettings::get_global(cx).minimum_contrast;
|
||||
let (rects, batched_text_runs) =
|
||||
TerminalElement::layout_grid(grid, 0, &text_style, None, minimum_contrast, cx);
|
||||
let (rects, batched_text_runs) = TerminalElement::layout_grid(
|
||||
content.cells.into_iter(),
|
||||
0,
|
||||
&text_style,
|
||||
None,
|
||||
minimum_contrast,
|
||||
cx,
|
||||
);
|
||||
|
||||
// lines are 0-indexed, so we must add 1 to get the number of lines
|
||||
let text_line_height = text_style.line_height_in_pixels(window.rem_size());
|
||||
let num_lines = batched_text_runs
|
||||
.iter()
|
||||
.map(|b| b.start_point.line)
|
||||
.map(|b| b.start_point.line())
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
+ 1;
|
||||
|
|
@ -386,6 +381,7 @@ impl Render for TerminalOutput {
|
|||
)
|
||||
// We must set the height explicitly for the editor block to size itself correctly
|
||||
.h(height)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@ publish.workspace = true
|
|||
license = "GPL-3.0-or-later"
|
||||
|
||||
[features]
|
||||
default = ["alacritty-backend"]
|
||||
alacritty-backend = ["dep:alacritty_terminal"]
|
||||
test-support = [
|
||||
"collections/test-support",
|
||||
"gpui/test-support",
|
||||
"settings/test-support",
|
||||
]
|
||||
libghostty-vt = ["dep:libghostty-vt", "dep:portable-pty"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
@ -21,7 +24,7 @@ doctest = false
|
|||
|
||||
[dependencies]
|
||||
async-channel.workspace = true
|
||||
alacritty_terminal.workspace = true
|
||||
alacritty_terminal = { workspace = true, optional = true }
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
futures.workspace = true
|
||||
|
|
@ -43,8 +46,13 @@ thiserror.workspace = true
|
|||
url.workspace = true
|
||||
util.workspace = true
|
||||
urlencoding.workspace = true
|
||||
vte.workspace = true
|
||||
parking_lot.workspace = true
|
||||
percent-encoding.workspace = true
|
||||
libghostty-vt = { workspace = true, optional = true }
|
||||
base64.workspace = true
|
||||
feature_flags.workspace = true
|
||||
portable-pty = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
|
|
|
|||
2207
crates/terminal/src/ghostty_backend.rs
Normal file
2207
crates/terminal/src/ghostty_backend.rs
Normal file
File diff suppressed because it is too large
Load diff
352
crates/terminal/src/ghostty_pty.rs
Normal file
352
crates/terminal/src/ghostty_pty.rs
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
#[cfg(unix)]
|
||||
use std::ffi::CStr;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
io::{ErrorKind, Read, Write},
|
||||
sync::mpsc,
|
||||
thread::{self, JoinHandle},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use futures::channel::mpsc::UnboundedSender;
|
||||
use log::{debug, error};
|
||||
use portable_pty::{Child, MasterPty, PtySize};
|
||||
|
||||
use crate::{PtyEvent, TerminalBackendEvent, TerminalBounds};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 0x10_0000;
|
||||
const DRAIN_ON_EXIT_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
enum GhosttyPtyMsg {
|
||||
Input(Cow<'static, [u8]>),
|
||||
Resize(TerminalBounds),
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
pub(super) struct GhosttyPtyEventLoop {
|
||||
master: Box<dyn MasterPty + Send>,
|
||||
reader: Box<dyn Read + Send>,
|
||||
writer: Box<dyn Write + Send>,
|
||||
child: Box<dyn Child + Send + Sync>,
|
||||
receiver: mpsc::Receiver<GhosttyPtyMsg>,
|
||||
sender: mpsc::Sender<GhosttyPtyMsg>,
|
||||
events_tx: UnboundedSender<PtyEvent>,
|
||||
drain_on_exit: bool,
|
||||
}
|
||||
|
||||
impl GhosttyPtyEventLoop {
|
||||
pub(super) fn new(
|
||||
events_tx: UnboundedSender<PtyEvent>,
|
||||
master: Box<dyn MasterPty + Send>,
|
||||
child: Box<dyn Child + Send + Sync>,
|
||||
drain_on_exit: bool,
|
||||
) -> Result<Self> {
|
||||
let reader = master
|
||||
.try_clone_reader()
|
||||
.context("failed to clone ghostty pty reader")?;
|
||||
let writer = master
|
||||
.take_writer()
|
||||
.context("failed to take ghostty pty writer")?;
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
|
||||
Ok(Self {
|
||||
master,
|
||||
reader,
|
||||
writer,
|
||||
child,
|
||||
receiver,
|
||||
sender,
|
||||
events_tx,
|
||||
drain_on_exit,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn channel(&self) -> GhosttyPtySender {
|
||||
GhosttyPtySender {
|
||||
sender: self.sender.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn spawn(self) -> JoinHandle<()> {
|
||||
let Self {
|
||||
master,
|
||||
reader,
|
||||
writer,
|
||||
child,
|
||||
receiver,
|
||||
sender,
|
||||
events_tx,
|
||||
drain_on_exit,
|
||||
} = self;
|
||||
let (reader_done_sender, reader_done_receiver) = mpsc::channel();
|
||||
|
||||
thread::spawn({
|
||||
let events_tx = events_tx.clone();
|
||||
move || Self::read_pty(reader, events_tx, reader_done_sender)
|
||||
});
|
||||
|
||||
thread::spawn({
|
||||
move || {
|
||||
Self::wait_for_child(
|
||||
child,
|
||||
events_tx,
|
||||
drain_on_exit,
|
||||
reader_done_receiver,
|
||||
sender,
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
thread::spawn(move || Self::control_pty(master, writer, receiver))
|
||||
}
|
||||
|
||||
fn read_pty(
|
||||
mut reader: Box<dyn Read + Send>,
|
||||
events_tx: UnboundedSender<PtyEvent>,
|
||||
done_sender: mpsc::Sender<()>,
|
||||
) {
|
||||
let mut buffer = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
loop {
|
||||
match reader.read(&mut buffer) {
|
||||
Ok(0) => break,
|
||||
Ok(read) => {
|
||||
send_pty_event(
|
||||
&events_tx,
|
||||
PtyEvent::Output(buffer[..read].to_vec()),
|
||||
"output",
|
||||
);
|
||||
}
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::Interrupted => continue,
|
||||
_ => {
|
||||
error!("error reading from ghostty pty: {error}");
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if done_sender.send(()).is_err() {
|
||||
debug!("failed to send ghostty pty reader completion");
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_child(
|
||||
mut child: Box<dyn Child + Send + Sync>,
|
||||
events_tx: UnboundedSender<PtyEvent>,
|
||||
drain_on_exit: bool,
|
||||
reader_done_receiver: mpsc::Receiver<()>,
|
||||
sender: mpsc::Sender<GhosttyPtyMsg>,
|
||||
) {
|
||||
match child.wait() {
|
||||
Ok(status) => {
|
||||
if drain_on_exit
|
||||
&& let Err(error) = reader_done_receiver.recv_timeout(DRAIN_ON_EXIT_TIMEOUT)
|
||||
{
|
||||
debug!("timed out draining ghostty pty after child exit: {error}");
|
||||
}
|
||||
|
||||
send_pty_event(
|
||||
&events_tx,
|
||||
PtyEvent::Event(TerminalBackendEvent::ChildExit(
|
||||
portable_exit_status_to_raw_status(status),
|
||||
)),
|
||||
"child-exit",
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
error!("error waiting for ghostty pty child process: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
send_pty_event(
|
||||
&events_tx,
|
||||
PtyEvent::Event(TerminalBackendEvent::Wakeup),
|
||||
"wakeup",
|
||||
);
|
||||
|
||||
if let Err(error) = sender.send(GhosttyPtyMsg::Shutdown) {
|
||||
debug!("failed to stop ghostty pty control loop after child exit: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
fn control_pty(
|
||||
master: Box<dyn MasterPty + Send>,
|
||||
mut writer: Box<dyn Write + Send>,
|
||||
receiver: mpsc::Receiver<GhosttyPtyMsg>,
|
||||
) {
|
||||
while let Ok(message) = receiver.recv() {
|
||||
match message {
|
||||
GhosttyPtyMsg::Input(input) => {
|
||||
if let Err(error) = writer.write_all(&input) {
|
||||
debug!("failed to write to ghostty pty: {error}");
|
||||
if matches!(
|
||||
error.kind(),
|
||||
ErrorKind::BrokenPipe | ErrorKind::NotConnected
|
||||
) {
|
||||
break;
|
||||
}
|
||||
} else if let Err(error) = writer.flush() {
|
||||
debug!("failed to flush ghostty pty writer: {error}");
|
||||
}
|
||||
}
|
||||
GhosttyPtyMsg::Resize(bounds) => {
|
||||
if let Err(error) = master.resize(portable_pty_size(bounds)) {
|
||||
error!("failed to resize ghostty pty: {error}");
|
||||
}
|
||||
}
|
||||
GhosttyPtyMsg::Shutdown => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct GhosttyPtySender {
|
||||
sender: mpsc::Sender<GhosttyPtyMsg>,
|
||||
}
|
||||
|
||||
impl GhosttyPtySender {
|
||||
fn send(&self, message: GhosttyPtyMsg) {
|
||||
if let Err(error) = self.sender.send(message) {
|
||||
debug!("failed to send ghostty pty message: {error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct GhosttyPtyNotifier {
|
||||
sender: GhosttyPtySender,
|
||||
}
|
||||
|
||||
impl GhosttyPtyNotifier {
|
||||
pub(super) fn new(sender: GhosttyPtySender) -> Self {
|
||||
Self { sender }
|
||||
}
|
||||
|
||||
pub(super) fn notify<B>(&self, bytes: B)
|
||||
where
|
||||
B: Into<Cow<'static, [u8]>>,
|
||||
{
|
||||
let bytes = bytes.into();
|
||||
if !bytes.is_empty() {
|
||||
self.sender.send(GhosttyPtyMsg::Input(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn resize(&self, bounds: TerminalBounds) {
|
||||
self.sender.send(GhosttyPtyMsg::Resize(bounds));
|
||||
}
|
||||
|
||||
pub(super) fn shutdown(&self) {
|
||||
self.sender.send(GhosttyPtyMsg::Shutdown);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn portable_pty_size(bounds: TerminalBounds) -> PtySize {
|
||||
let rows = bounds.num_lines().max(1).min(u16::MAX as usize) as u16;
|
||||
let cols = bounds.num_columns().max(1).min(u16::MAX as usize) as u16;
|
||||
let cell_width = f32::from(bounds.cell_width()).max(1.0);
|
||||
let cell_height = f32::from(bounds.line_height()).max(1.0);
|
||||
|
||||
PtySize {
|
||||
rows,
|
||||
cols,
|
||||
pixel_width: pixels_to_u16(cell_width * f32::from(cols)),
|
||||
pixel_height: pixels_to_u16(cell_height * f32::from(rows)),
|
||||
}
|
||||
}
|
||||
|
||||
fn pixels_to_u16(pixels: f32) -> u16 {
|
||||
pixels.max(0.0).min(u16::MAX as f32) as u16
|
||||
}
|
||||
|
||||
fn portable_exit_status_to_raw_status(status: portable_pty::ExitStatus) -> i32 {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if let Some(signal) = status.signal()
|
||||
&& let Some(signal) = portable_signal_to_raw_signal(signal)
|
||||
{
|
||||
return signal;
|
||||
}
|
||||
|
||||
let code = status.exit_code().min((i32::MAX >> 8) as u32) as i32;
|
||||
code << 8
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
status.exit_code().min(i32::MAX as u32) as i32
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn portable_signal_to_raw_signal(signal: &str) -> Option<i32> {
|
||||
for signal_number in 1..128 {
|
||||
let description = unsafe { libc::strsignal(signal_number) };
|
||||
if description.is_null() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let description = unsafe { CStr::from_ptr(description) }.to_string_lossy();
|
||||
if description == signal {
|
||||
return Some(signal_number);
|
||||
}
|
||||
}
|
||||
|
||||
signal
|
||||
.rsplit(|character: char| !character.is_ascii_digit())
|
||||
.find_map(|segment| {
|
||||
let signal_number = segment.parse::<i32>().ok()?;
|
||||
(1..128).contains(&signal_number).then_some(signal_number)
|
||||
})
|
||||
}
|
||||
|
||||
fn send_pty_event(
|
||||
events_tx: &UnboundedSender<PtyEvent>,
|
||||
event: PtyEvent,
|
||||
description: &'static str,
|
||||
) {
|
||||
if let Err(error) = events_tx.unbounded_send(event) {
|
||||
debug!("failed to send ghostty pty {description} event: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_portable_exit_status_to_raw_status_preserves_unix_signal() {
|
||||
use std::os::unix::process::ExitStatusExt as _;
|
||||
|
||||
let signal_name = unsafe { libc::strsignal(libc::SIGTERM) };
|
||||
assert!(!signal_name.is_null());
|
||||
let signal_name = unsafe { CStr::from_ptr(signal_name) }
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let raw_status =
|
||||
portable_exit_status_to_raw_status(portable_pty::ExitStatus::with_signal(&signal_name));
|
||||
let exit_status = std::process::ExitStatus::from_raw(raw_status);
|
||||
|
||||
assert_eq!(exit_status.signal(), Some(libc::SIGTERM));
|
||||
assert_eq!(exit_status.code(), None);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_portable_exit_status_to_raw_status_preserves_exit_code() {
|
||||
use std::os::unix::process::ExitStatusExt as _;
|
||||
|
||||
let raw_status =
|
||||
portable_exit_status_to_raw_status(portable_pty::ExitStatus::with_exit_code(42));
|
||||
let exit_status = std::process::ExitStatus::from_raw(raw_status);
|
||||
|
||||
assert_eq!(exit_status.code(), Some(42));
|
||||
assert_eq!(exit_status.signal(), None);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
use alacritty_terminal::vte::ansi::Rgb as AlacRgb;
|
||||
use gpui::Rgba;
|
||||
use vte::ansi::Rgb as VteRgb;
|
||||
|
||||
//Convenience method to convert from a GPUI color to an alacritty Rgb
|
||||
pub fn to_alac_rgb(color: impl Into<Rgba>) -> AlacRgb {
|
||||
pub(crate) fn to_vte_rgb(color: impl Into<Rgba>) -> VteRgb {
|
||||
let color = color.into();
|
||||
let r = ((color.r * color.a) * 255.) as u8;
|
||||
let g = ((color.g * color.a) * 255.) as u8;
|
||||
let b = ((color.b * color.a) * 255.) as u8;
|
||||
AlacRgb { r, g, b }
|
||||
VteRgb { r, g, b }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
/// The mappings defined in this file where created from reading the alacritty source
|
||||
use alacritty_terminal::term::TermMode;
|
||||
use gpui::Keystroke;
|
||||
|
||||
use crate::TerminalModes;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum AlacModifiers {
|
||||
enum TerminalModifiers {
|
||||
None,
|
||||
Alt,
|
||||
Ctrl,
|
||||
|
|
@ -14,7 +15,7 @@ enum AlacModifiers {
|
|||
Other,
|
||||
}
|
||||
|
||||
impl AlacModifiers {
|
||||
impl TerminalModifiers {
|
||||
fn new(ks: &Keystroke) -> Self {
|
||||
match (
|
||||
ks.modifiers.alt,
|
||||
|
|
@ -22,147 +23,171 @@ impl AlacModifiers {
|
|||
ks.modifiers.shift,
|
||||
ks.modifiers.platform,
|
||||
) {
|
||||
(false, false, false, false) => AlacModifiers::None,
|
||||
(true, false, false, false) => AlacModifiers::Alt,
|
||||
(false, true, false, false) => AlacModifiers::Ctrl,
|
||||
(false, false, true, false) => AlacModifiers::Shift,
|
||||
(false, true, true, false) => AlacModifiers::CtrlShift,
|
||||
_ => AlacModifiers::Other,
|
||||
(false, false, false, false) => TerminalModifiers::None,
|
||||
(true, false, false, false) => TerminalModifiers::Alt,
|
||||
(false, true, false, false) => TerminalModifiers::Ctrl,
|
||||
(false, false, true, false) => TerminalModifiers::Shift,
|
||||
(false, true, true, false) => TerminalModifiers::CtrlShift,
|
||||
_ => TerminalModifiers::Other,
|
||||
}
|
||||
}
|
||||
|
||||
fn any(&self) -> bool {
|
||||
match &self {
|
||||
AlacModifiers::None => false,
|
||||
AlacModifiers::Alt => true,
|
||||
AlacModifiers::Ctrl => true,
|
||||
AlacModifiers::Shift => true,
|
||||
AlacModifiers::CtrlShift => true,
|
||||
AlacModifiers::Other => true,
|
||||
TerminalModifiers::None => false,
|
||||
TerminalModifiers::Alt => true,
|
||||
TerminalModifiers::Ctrl => true,
|
||||
TerminalModifiers::Shift => true,
|
||||
TerminalModifiers::CtrlShift => true,
|
||||
TerminalModifiers::Other => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_esc_str(
|
||||
keystroke: &Keystroke,
|
||||
mode: &TermMode,
|
||||
mode: TerminalModes,
|
||||
option_as_meta: bool,
|
||||
) -> Option<Cow<'static, str>> {
|
||||
let modifiers = AlacModifiers::new(keystroke);
|
||||
let modifiers = TerminalModifiers::new(keystroke);
|
||||
|
||||
// Manual Bindings including modifiers
|
||||
let manual_esc_str: Option<&'static str> = match (keystroke.key.as_ref(), &modifiers) {
|
||||
//Basic special keys
|
||||
("tab", AlacModifiers::None) => Some("\x09"),
|
||||
("escape", AlacModifiers::None) => Some("\x1b"),
|
||||
("enter", AlacModifiers::None) => Some("\x0d"),
|
||||
("enter", AlacModifiers::Shift) => Some("\x0a"),
|
||||
("enter", AlacModifiers::Alt) => Some("\x1b\x0d"),
|
||||
("backspace", AlacModifiers::None) => Some("\x7f"),
|
||||
("tab", TerminalModifiers::None) => Some("\x09"),
|
||||
("escape", TerminalModifiers::None) => Some("\x1b"),
|
||||
("enter", TerminalModifiers::None) => Some("\x0d"),
|
||||
("enter", TerminalModifiers::Shift) => Some("\x0a"),
|
||||
("enter", TerminalModifiers::Alt) => Some("\x1b\x0d"),
|
||||
("backspace", TerminalModifiers::None) => Some("\x7f"),
|
||||
//Interesting escape codes
|
||||
("tab", AlacModifiers::Shift) => Some("\x1b[Z"),
|
||||
("backspace", AlacModifiers::Ctrl) => Some("\x08"),
|
||||
("backspace", AlacModifiers::Alt) => Some("\x1b\x7f"),
|
||||
("backspace", AlacModifiers::Shift) => Some("\x7f"),
|
||||
("space", AlacModifiers::Ctrl) => Some("\x00"),
|
||||
("home", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => Some("\x1bOH"),
|
||||
("home", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => Some("\x1b[H"),
|
||||
("end", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => Some("\x1bOF"),
|
||||
("end", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => Some("\x1b[F"),
|
||||
("up", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => Some("\x1bOA"),
|
||||
("up", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => Some("\x1b[A"),
|
||||
("down", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => Some("\x1bOB"),
|
||||
("down", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => Some("\x1b[B"),
|
||||
("right", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => Some("\x1bOC"),
|
||||
("right", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => Some("\x1b[C"),
|
||||
("left", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => Some("\x1bOD"),
|
||||
("left", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => Some("\x1b[D"),
|
||||
("back", AlacModifiers::None) => Some("\x7f"),
|
||||
("insert", AlacModifiers::None) => Some("\x1b[2~"),
|
||||
("delete", AlacModifiers::None) => Some("\x1b[3~"),
|
||||
("pageup", AlacModifiers::None) => Some("\x1b[5~"),
|
||||
("pagedown", AlacModifiers::None) => Some("\x1b[6~"),
|
||||
("f1", AlacModifiers::None) => Some("\x1bOP"),
|
||||
("f2", AlacModifiers::None) => Some("\x1bOQ"),
|
||||
("f3", AlacModifiers::None) => Some("\x1bOR"),
|
||||
("f4", AlacModifiers::None) => Some("\x1bOS"),
|
||||
("f5", AlacModifiers::None) => Some("\x1b[15~"),
|
||||
("f6", AlacModifiers::None) => Some("\x1b[17~"),
|
||||
("f7", AlacModifiers::None) => Some("\x1b[18~"),
|
||||
("f8", AlacModifiers::None) => Some("\x1b[19~"),
|
||||
("f9", AlacModifiers::None) => Some("\x1b[20~"),
|
||||
("f10", AlacModifiers::None) => Some("\x1b[21~"),
|
||||
("f11", AlacModifiers::None) => Some("\x1b[23~"),
|
||||
("f12", AlacModifiers::None) => Some("\x1b[24~"),
|
||||
("f13", AlacModifiers::None) => Some("\x1b[25~"),
|
||||
("f14", AlacModifiers::None) => Some("\x1b[26~"),
|
||||
("f15", AlacModifiers::None) => Some("\x1b[28~"),
|
||||
("f16", AlacModifiers::None) => Some("\x1b[29~"),
|
||||
("f17", AlacModifiers::None) => Some("\x1b[31~"),
|
||||
("f18", AlacModifiers::None) => Some("\x1b[32~"),
|
||||
("f19", AlacModifiers::None) => Some("\x1b[33~"),
|
||||
("f20", AlacModifiers::None) => Some("\x1b[34~"),
|
||||
("tab", TerminalModifiers::Shift) => Some("\x1b[Z"),
|
||||
("backspace", TerminalModifiers::Ctrl) => Some("\x08"),
|
||||
("backspace", TerminalModifiers::Alt) => Some("\x1b\x7f"),
|
||||
("backspace", TerminalModifiers::Shift) => Some("\x7f"),
|
||||
("space", TerminalModifiers::Ctrl) => Some("\x00"),
|
||||
("home", TerminalModifiers::None) if mode.contains(TerminalModes::APP_CURSOR) => {
|
||||
Some("\x1bOH")
|
||||
}
|
||||
("home", TerminalModifiers::None) if !mode.contains(TerminalModes::APP_CURSOR) => {
|
||||
Some("\x1b[H")
|
||||
}
|
||||
("end", TerminalModifiers::None) if mode.contains(TerminalModes::APP_CURSOR) => {
|
||||
Some("\x1bOF")
|
||||
}
|
||||
("end", TerminalModifiers::None) if !mode.contains(TerminalModes::APP_CURSOR) => {
|
||||
Some("\x1b[F")
|
||||
}
|
||||
("up", TerminalModifiers::None) if mode.contains(TerminalModes::APP_CURSOR) => {
|
||||
Some("\x1bOA")
|
||||
}
|
||||
("up", TerminalModifiers::None) if !mode.contains(TerminalModes::APP_CURSOR) => {
|
||||
Some("\x1b[A")
|
||||
}
|
||||
("down", TerminalModifiers::None) if mode.contains(TerminalModes::APP_CURSOR) => {
|
||||
Some("\x1bOB")
|
||||
}
|
||||
("down", TerminalModifiers::None) if !mode.contains(TerminalModes::APP_CURSOR) => {
|
||||
Some("\x1b[B")
|
||||
}
|
||||
("right", TerminalModifiers::None) if mode.contains(TerminalModes::APP_CURSOR) => {
|
||||
Some("\x1bOC")
|
||||
}
|
||||
("right", TerminalModifiers::None) if !mode.contains(TerminalModes::APP_CURSOR) => {
|
||||
Some("\x1b[C")
|
||||
}
|
||||
("left", TerminalModifiers::None) if mode.contains(TerminalModes::APP_CURSOR) => {
|
||||
Some("\x1bOD")
|
||||
}
|
||||
("left", TerminalModifiers::None) if !mode.contains(TerminalModes::APP_CURSOR) => {
|
||||
Some("\x1b[D")
|
||||
}
|
||||
("back", TerminalModifiers::None) => Some("\x7f"),
|
||||
("insert", TerminalModifiers::None) => Some("\x1b[2~"),
|
||||
("delete", TerminalModifiers::None) => Some("\x1b[3~"),
|
||||
("pageup", TerminalModifiers::None) => Some("\x1b[5~"),
|
||||
("pagedown", TerminalModifiers::None) => Some("\x1b[6~"),
|
||||
("f1", TerminalModifiers::None) => Some("\x1bOP"),
|
||||
("f2", TerminalModifiers::None) => Some("\x1bOQ"),
|
||||
("f3", TerminalModifiers::None) => Some("\x1bOR"),
|
||||
("f4", TerminalModifiers::None) => Some("\x1bOS"),
|
||||
("f5", TerminalModifiers::None) => Some("\x1b[15~"),
|
||||
("f6", TerminalModifiers::None) => Some("\x1b[17~"),
|
||||
("f7", TerminalModifiers::None) => Some("\x1b[18~"),
|
||||
("f8", TerminalModifiers::None) => Some("\x1b[19~"),
|
||||
("f9", TerminalModifiers::None) => Some("\x1b[20~"),
|
||||
("f10", TerminalModifiers::None) => Some("\x1b[21~"),
|
||||
("f11", TerminalModifiers::None) => Some("\x1b[23~"),
|
||||
("f12", TerminalModifiers::None) => Some("\x1b[24~"),
|
||||
("f13", TerminalModifiers::None) => Some("\x1b[25~"),
|
||||
("f14", TerminalModifiers::None) => Some("\x1b[26~"),
|
||||
("f15", TerminalModifiers::None) => Some("\x1b[28~"),
|
||||
("f16", TerminalModifiers::None) => Some("\x1b[29~"),
|
||||
("f17", TerminalModifiers::None) => Some("\x1b[31~"),
|
||||
("f18", TerminalModifiers::None) => Some("\x1b[32~"),
|
||||
("f19", TerminalModifiers::None) => Some("\x1b[33~"),
|
||||
("f20", TerminalModifiers::None) => Some("\x1b[34~"),
|
||||
// NumpadEnter, Action::Esc("\n".into());
|
||||
//Mappings for caret notation keys
|
||||
("a", AlacModifiers::Ctrl) => Some("\x01"), //1
|
||||
("A", AlacModifiers::CtrlShift) => Some("\x01"), //1
|
||||
("b", AlacModifiers::Ctrl) => Some("\x02"), //2
|
||||
("B", AlacModifiers::CtrlShift) => Some("\x02"), //2
|
||||
("c", AlacModifiers::Ctrl) => Some("\x03"), //3
|
||||
("C", AlacModifiers::CtrlShift) => Some("\x03"), //3
|
||||
("d", AlacModifiers::Ctrl) => Some("\x04"), //4
|
||||
("D", AlacModifiers::CtrlShift) => Some("\x04"), //4
|
||||
("e", AlacModifiers::Ctrl) => Some("\x05"), //5
|
||||
("E", AlacModifiers::CtrlShift) => Some("\x05"), //5
|
||||
("f", AlacModifiers::Ctrl) => Some("\x06"), //6
|
||||
("F", AlacModifiers::CtrlShift) => Some("\x06"), //6
|
||||
("g", AlacModifiers::Ctrl) => Some("\x07"), //7
|
||||
("G", AlacModifiers::CtrlShift) => Some("\x07"), //7
|
||||
("h", AlacModifiers::Ctrl) => Some("\x08"), //8
|
||||
("H", AlacModifiers::CtrlShift) => Some("\x08"), //8
|
||||
("i", AlacModifiers::Ctrl) => Some("\x09"), //9
|
||||
("I", AlacModifiers::CtrlShift) => Some("\x09"), //9
|
||||
("j", AlacModifiers::Ctrl) => Some("\x0a"), //10
|
||||
("J", AlacModifiers::CtrlShift) => Some("\x0a"), //10
|
||||
("k", AlacModifiers::Ctrl) => Some("\x0b"), //11
|
||||
("K", AlacModifiers::CtrlShift) => Some("\x0b"), //11
|
||||
("l", AlacModifiers::Ctrl) => Some("\x0c"), //12
|
||||
("L", AlacModifiers::CtrlShift) => Some("\x0c"), //12
|
||||
("m", AlacModifiers::Ctrl) => Some("\x0d"), //13
|
||||
("M", AlacModifiers::CtrlShift) => Some("\x0d"), //13
|
||||
("n", AlacModifiers::Ctrl) => Some("\x0e"), //14
|
||||
("N", AlacModifiers::CtrlShift) => Some("\x0e"), //14
|
||||
("o", AlacModifiers::Ctrl) => Some("\x0f"), //15
|
||||
("O", AlacModifiers::CtrlShift) => Some("\x0f"), //15
|
||||
("p", AlacModifiers::Ctrl) => Some("\x10"), //16
|
||||
("P", AlacModifiers::CtrlShift) => Some("\x10"), //16
|
||||
("q", AlacModifiers::Ctrl) => Some("\x11"), //17
|
||||
("Q", AlacModifiers::CtrlShift) => Some("\x11"), //17
|
||||
("r", AlacModifiers::Ctrl) => Some("\x12"), //18
|
||||
("R", AlacModifiers::CtrlShift) => Some("\x12"), //18
|
||||
("s", AlacModifiers::Ctrl) => Some("\x13"), //19
|
||||
("S", AlacModifiers::CtrlShift) => Some("\x13"), //19
|
||||
("t", AlacModifiers::Ctrl) => Some("\x14"), //20
|
||||
("T", AlacModifiers::CtrlShift) => Some("\x14"), //20
|
||||
("u", AlacModifiers::Ctrl) => Some("\x15"), //21
|
||||
("U", AlacModifiers::CtrlShift) => Some("\x15"), //21
|
||||
("v", AlacModifiers::Ctrl) => Some("\x16"), //22
|
||||
("V", AlacModifiers::CtrlShift) => Some("\x16"), //22
|
||||
("w", AlacModifiers::Ctrl) => Some("\x17"), //23
|
||||
("W", AlacModifiers::CtrlShift) => Some("\x17"), //23
|
||||
("x", AlacModifiers::Ctrl) => Some("\x18"), //24
|
||||
("X", AlacModifiers::CtrlShift) => Some("\x18"), //24
|
||||
("y", AlacModifiers::Ctrl) => Some("\x19"), //25
|
||||
("Y", AlacModifiers::CtrlShift) => Some("\x19"), //25
|
||||
("z", AlacModifiers::Ctrl) => Some("\x1a"), //26
|
||||
("Z", AlacModifiers::CtrlShift) => Some("\x1a"), //26
|
||||
("@", AlacModifiers::Ctrl) => Some("\x00"), //0
|
||||
("[", AlacModifiers::Ctrl) => Some("\x1b"), //27
|
||||
("\\", AlacModifiers::Ctrl) => Some("\x1c"), //28
|
||||
("]", AlacModifiers::Ctrl) => Some("\x1d"), //29
|
||||
("^", AlacModifiers::Ctrl) => Some("\x1e"), //30
|
||||
("_", AlacModifiers::Ctrl) => Some("\x1f"), //31
|
||||
("?", AlacModifiers::Ctrl) => Some("\x7f"), //127
|
||||
("a", TerminalModifiers::Ctrl) => Some("\x01"), //1
|
||||
("A", TerminalModifiers::CtrlShift) => Some("\x01"), //1
|
||||
("b", TerminalModifiers::Ctrl) => Some("\x02"), //2
|
||||
("B", TerminalModifiers::CtrlShift) => Some("\x02"), //2
|
||||
("c", TerminalModifiers::Ctrl) => Some("\x03"), //3
|
||||
("C", TerminalModifiers::CtrlShift) => Some("\x03"), //3
|
||||
("d", TerminalModifiers::Ctrl) => Some("\x04"), //4
|
||||
("D", TerminalModifiers::CtrlShift) => Some("\x04"), //4
|
||||
("e", TerminalModifiers::Ctrl) => Some("\x05"), //5
|
||||
("E", TerminalModifiers::CtrlShift) => Some("\x05"), //5
|
||||
("f", TerminalModifiers::Ctrl) => Some("\x06"), //6
|
||||
("F", TerminalModifiers::CtrlShift) => Some("\x06"), //6
|
||||
("g", TerminalModifiers::Ctrl) => Some("\x07"), //7
|
||||
("G", TerminalModifiers::CtrlShift) => Some("\x07"), //7
|
||||
("h", TerminalModifiers::Ctrl) => Some("\x08"), //8
|
||||
("H", TerminalModifiers::CtrlShift) => Some("\x08"), //8
|
||||
("i", TerminalModifiers::Ctrl) => Some("\x09"), //9
|
||||
("I", TerminalModifiers::CtrlShift) => Some("\x09"), //9
|
||||
("j", TerminalModifiers::Ctrl) => Some("\x0a"), //10
|
||||
("J", TerminalModifiers::CtrlShift) => Some("\x0a"), //10
|
||||
("k", TerminalModifiers::Ctrl) => Some("\x0b"), //11
|
||||
("K", TerminalModifiers::CtrlShift) => Some("\x0b"), //11
|
||||
("l", TerminalModifiers::Ctrl) => Some("\x0c"), //12
|
||||
("L", TerminalModifiers::CtrlShift) => Some("\x0c"), //12
|
||||
("m", TerminalModifiers::Ctrl) => Some("\x0d"), //13
|
||||
("M", TerminalModifiers::CtrlShift) => Some("\x0d"), //13
|
||||
("n", TerminalModifiers::Ctrl) => Some("\x0e"), //14
|
||||
("N", TerminalModifiers::CtrlShift) => Some("\x0e"), //14
|
||||
("o", TerminalModifiers::Ctrl) => Some("\x0f"), //15
|
||||
("O", TerminalModifiers::CtrlShift) => Some("\x0f"), //15
|
||||
("p", TerminalModifiers::Ctrl) => Some("\x10"), //16
|
||||
("P", TerminalModifiers::CtrlShift) => Some("\x10"), //16
|
||||
("q", TerminalModifiers::Ctrl) => Some("\x11"), //17
|
||||
("Q", TerminalModifiers::CtrlShift) => Some("\x11"), //17
|
||||
("r", TerminalModifiers::Ctrl) => Some("\x12"), //18
|
||||
("R", TerminalModifiers::CtrlShift) => Some("\x12"), //18
|
||||
("s", TerminalModifiers::Ctrl) => Some("\x13"), //19
|
||||
("S", TerminalModifiers::CtrlShift) => Some("\x13"), //19
|
||||
("t", TerminalModifiers::Ctrl) => Some("\x14"), //20
|
||||
("T", TerminalModifiers::CtrlShift) => Some("\x14"), //20
|
||||
("u", TerminalModifiers::Ctrl) => Some("\x15"), //21
|
||||
("U", TerminalModifiers::CtrlShift) => Some("\x15"), //21
|
||||
("v", TerminalModifiers::Ctrl) => Some("\x16"), //22
|
||||
("V", TerminalModifiers::CtrlShift) => Some("\x16"), //22
|
||||
("w", TerminalModifiers::Ctrl) => Some("\x17"), //23
|
||||
("W", TerminalModifiers::CtrlShift) => Some("\x17"), //23
|
||||
("x", TerminalModifiers::Ctrl) => Some("\x18"), //24
|
||||
("X", TerminalModifiers::CtrlShift) => Some("\x18"), //24
|
||||
("y", TerminalModifiers::Ctrl) => Some("\x19"), //25
|
||||
("Y", TerminalModifiers::CtrlShift) => Some("\x19"), //25
|
||||
("z", TerminalModifiers::Ctrl) => Some("\x1a"), //26
|
||||
("Z", TerminalModifiers::CtrlShift) => Some("\x1a"), //26
|
||||
("@", TerminalModifiers::Ctrl) => Some("\x00"), //0
|
||||
("[", TerminalModifiers::Ctrl) => Some("\x1b"), //27
|
||||
("\\", TerminalModifiers::Ctrl) => Some("\x1c"), //28
|
||||
("]", TerminalModifiers::Ctrl) => Some("\x1d"), //29
|
||||
("^", TerminalModifiers::Ctrl) => Some("\x1e"), //30
|
||||
("_", TerminalModifiers::Ctrl) => Some("\x1f"), //31
|
||||
("?", TerminalModifiers::Ctrl) => Some("\x7f"), //127
|
||||
_ => None,
|
||||
};
|
||||
if let Some(esc_str) = manual_esc_str {
|
||||
|
|
@ -210,7 +235,8 @@ pub fn to_esc_str(
|
|||
}
|
||||
|
||||
if !cfg!(target_os = "macos") || option_as_meta {
|
||||
let is_alt_lowercase_ascii = modifiers == AlacModifiers::Alt && keystroke.key.is_ascii();
|
||||
let is_alt_lowercase_ascii =
|
||||
modifiers == TerminalModifiers::Alt && keystroke.key.is_ascii();
|
||||
let is_alt_uppercase_ascii =
|
||||
keystroke.modifiers.alt && keystroke.modifiers.shift && keystroke.key.is_ascii();
|
||||
if is_alt_lowercase_ascii || is_alt_uppercase_ascii {
|
||||
|
|
@ -270,47 +296,44 @@ mod test {
|
|||
key: "🖖🏻".to_string(), //2 char string
|
||||
key_char: None,
|
||||
};
|
||||
assert_eq!(to_esc_str(&ks, &TermMode::NONE, false), None);
|
||||
assert_eq!(to_esc_str(&ks, TerminalModes::NONE, false), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_application_mode() {
|
||||
let app_cursor = TermMode::APP_CURSOR;
|
||||
let none = TermMode::NONE;
|
||||
let app_cursor = TerminalModes::APP_CURSOR;
|
||||
let none = TerminalModes::NONE;
|
||||
|
||||
let up = Keystroke::parse("up").unwrap();
|
||||
let down = Keystroke::parse("down").unwrap();
|
||||
let left = Keystroke::parse("left").unwrap();
|
||||
let right = Keystroke::parse("right").unwrap();
|
||||
|
||||
assert_eq!(to_esc_str(&up, &none, false), Some("\x1b[A".into()));
|
||||
assert_eq!(to_esc_str(&down, &none, false), Some("\x1b[B".into()));
|
||||
assert_eq!(to_esc_str(&right, &none, false), Some("\x1b[C".into()));
|
||||
assert_eq!(to_esc_str(&left, &none, false), Some("\x1b[D".into()));
|
||||
assert_eq!(to_esc_str(&up, none, false), Some("\x1b[A".into()));
|
||||
assert_eq!(to_esc_str(&down, none, false), Some("\x1b[B".into()));
|
||||
assert_eq!(to_esc_str(&right, none, false), Some("\x1b[C".into()));
|
||||
assert_eq!(to_esc_str(&left, none, false), Some("\x1b[D".into()));
|
||||
|
||||
assert_eq!(to_esc_str(&up, &app_cursor, false), Some("\x1bOA".into()));
|
||||
assert_eq!(to_esc_str(&down, &app_cursor, false), Some("\x1bOB".into()));
|
||||
assert_eq!(
|
||||
to_esc_str(&right, &app_cursor, false),
|
||||
Some("\x1bOC".into())
|
||||
);
|
||||
assert_eq!(to_esc_str(&left, &app_cursor, false), Some("\x1bOD".into()));
|
||||
assert_eq!(to_esc_str(&up, app_cursor, false), Some("\x1bOA".into()));
|
||||
assert_eq!(to_esc_str(&down, app_cursor, false), Some("\x1bOB".into()));
|
||||
assert_eq!(to_esc_str(&right, app_cursor, false), Some("\x1bOC".into()));
|
||||
assert_eq!(to_esc_str(&left, app_cursor, false), Some("\x1bOD".into()));
|
||||
|
||||
let home = Keystroke::parse("home").unwrap();
|
||||
let end = Keystroke::parse("end").unwrap();
|
||||
assert_eq!(to_esc_str(&home, &none, false), Some("\x1b[H".into()));
|
||||
assert_eq!(to_esc_str(&end, &none, false), Some("\x1b[F".into()));
|
||||
assert_eq!(to_esc_str(&home, &app_cursor, false), Some("\x1bOH".into()));
|
||||
assert_eq!(to_esc_str(&end, &app_cursor, false), Some("\x1bOF".into()));
|
||||
assert_eq!(to_esc_str(&home, none, false), Some("\x1b[H".into()));
|
||||
assert_eq!(to_esc_str(&end, none, false), Some("\x1b[F".into()));
|
||||
assert_eq!(to_esc_str(&home, app_cursor, false), Some("\x1bOH".into()));
|
||||
assert_eq!(to_esc_str(&end, app_cursor, false), Some("\x1bOF".into()));
|
||||
|
||||
let shift_home = Keystroke::parse("shift-home").unwrap();
|
||||
let shift_end = Keystroke::parse("shift-end").unwrap();
|
||||
assert_eq!(
|
||||
to_esc_str(&shift_home, &none, false),
|
||||
to_esc_str(&shift_home, none, false),
|
||||
Some("\x1b[1;2H".into())
|
||||
);
|
||||
assert_eq!(
|
||||
to_esc_str(&shift_end, &none, false),
|
||||
to_esc_str(&shift_end, none, false),
|
||||
Some("\x1b[1;2F".into())
|
||||
);
|
||||
}
|
||||
|
|
@ -319,18 +342,18 @@ mod test {
|
|||
fn test_ctrl_codes() {
|
||||
let letters_lower = 'a'..='z';
|
||||
let letters_upper = 'A'..='Z';
|
||||
let mode = TermMode::ANY;
|
||||
let mode = TerminalModes::APP_CURSOR;
|
||||
|
||||
for (lower, upper) in letters_lower.zip(letters_upper) {
|
||||
assert_eq!(
|
||||
to_esc_str(
|
||||
&Keystroke::parse(&format!("ctrl-shift-{}", lower)).unwrap(),
|
||||
&mode,
|
||||
mode,
|
||||
false
|
||||
),
|
||||
to_esc_str(
|
||||
&Keystroke::parse(&format!("ctrl-{}", upper)).unwrap(),
|
||||
&mode,
|
||||
mode,
|
||||
false
|
||||
),
|
||||
"On letter: {}/{}",
|
||||
|
|
@ -347,7 +370,7 @@ mod test {
|
|||
assert_eq!(
|
||||
to_esc_str(
|
||||
&Keystroke::parse(&format!("alt-{}", character)).unwrap(),
|
||||
&TermMode::NONE,
|
||||
TerminalModes::NONE,
|
||||
true
|
||||
)
|
||||
.unwrap(),
|
||||
|
|
@ -365,7 +388,7 @@ mod test {
|
|||
assert_ne!(
|
||||
to_esc_str(
|
||||
&Keystroke::parse(&format!("alt-{}", key)).unwrap(),
|
||||
&TermMode::NONE,
|
||||
TerminalModes::NONE,
|
||||
true
|
||||
)
|
||||
.unwrap(),
|
||||
|
|
@ -378,16 +401,13 @@ mod test {
|
|||
fn test_shift_enter_newline() {
|
||||
let shift_enter = Keystroke::parse("shift-enter").unwrap();
|
||||
let regular_enter = Keystroke::parse("enter").unwrap();
|
||||
let mode = TermMode::NONE;
|
||||
let mode = TerminalModes::NONE;
|
||||
|
||||
// Shift-enter should send line feed (newline)
|
||||
assert_eq!(to_esc_str(&shift_enter, &mode, false), Some("\x0a".into()));
|
||||
assert_eq!(to_esc_str(&shift_enter, mode, false), Some("\x0a".into()));
|
||||
|
||||
// Regular enter should still send carriage return
|
||||
assert_eq!(
|
||||
to_esc_str(®ular_enter, &mode, false),
|
||||
Some("\x0d".into())
|
||||
);
|
||||
assert_eq!(to_esc_str(®ular_enter, mode, false), Some("\x0d".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#[cfg(feature = "alacritty-backend")]
|
||||
pub mod colors;
|
||||
pub mod keys;
|
||||
pub mod mouse;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
use std::cmp::{self, min};
|
||||
use std::iter::repeat;
|
||||
|
||||
use alacritty_terminal::grid::Dimensions;
|
||||
/// Most of the code, and specifically the constants, in this are copied from Alacritty,
|
||||
/// with modifications for our circumstances
|
||||
use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point as AlacPoint, Side};
|
||||
use alacritty_terminal::term::TermMode;
|
||||
use gpui::{Modifiers, MouseButton, Pixels, Point, ScrollWheelEvent, px};
|
||||
|
||||
use crate::TerminalBounds;
|
||||
use crate::{TerminalBounds, TerminalModes, TerminalPoint, TerminalSelectionSide};
|
||||
|
||||
enum MouseFormat {
|
||||
Sgr,
|
||||
|
|
@ -16,10 +13,10 @@ enum MouseFormat {
|
|||
}
|
||||
|
||||
impl MouseFormat {
|
||||
fn from_mode(mode: TermMode) -> Self {
|
||||
if mode.contains(TermMode::SGR_MOUSE) {
|
||||
fn from_mode(mode: TerminalModes) -> Self {
|
||||
if mode.contains(TerminalModes::SGR_MOUSE) {
|
||||
MouseFormat::Sgr
|
||||
} else if mode.contains(TermMode::UTF8_MOUSE) {
|
||||
} else if mode.contains(TerminalModes::UTF8_MOUSE) {
|
||||
MouseFormat::Normal(true)
|
||||
} else {
|
||||
MouseFormat::Normal(false)
|
||||
|
|
@ -28,7 +25,7 @@ impl MouseFormat {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum AlacMouseButton {
|
||||
enum MouseButtonCode {
|
||||
LeftButton = 0,
|
||||
MiddleButton = 1,
|
||||
RightButton = 2,
|
||||
|
|
@ -41,23 +38,23 @@ enum AlacMouseButton {
|
|||
Other = 99,
|
||||
}
|
||||
|
||||
impl AlacMouseButton {
|
||||
impl MouseButtonCode {
|
||||
fn from_move_button(e: Option<MouseButton>) -> Self {
|
||||
match e {
|
||||
Some(gpui::MouseButton::Left) => AlacMouseButton::LeftMove,
|
||||
Some(gpui::MouseButton::Middle) => AlacMouseButton::MiddleMove,
|
||||
Some(gpui::MouseButton::Right) => AlacMouseButton::RightMove,
|
||||
Some(gpui::MouseButton::Navigate(_)) => AlacMouseButton::Other,
|
||||
None => AlacMouseButton::NoneMove,
|
||||
Some(gpui::MouseButton::Left) => MouseButtonCode::LeftMove,
|
||||
Some(gpui::MouseButton::Middle) => MouseButtonCode::MiddleMove,
|
||||
Some(gpui::MouseButton::Right) => MouseButtonCode::RightMove,
|
||||
Some(gpui::MouseButton::Navigate(_)) => MouseButtonCode::Other,
|
||||
None => MouseButtonCode::NoneMove,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_button(e: MouseButton) -> Self {
|
||||
match e {
|
||||
gpui::MouseButton::Left => AlacMouseButton::LeftButton,
|
||||
gpui::MouseButton::Right => AlacMouseButton::MiddleButton,
|
||||
gpui::MouseButton::Middle => AlacMouseButton::RightButton,
|
||||
gpui::MouseButton::Navigate(_) => AlacMouseButton::Other,
|
||||
gpui::MouseButton::Left => MouseButtonCode::LeftButton,
|
||||
gpui::MouseButton::Right => MouseButtonCode::MiddleButton,
|
||||
gpui::MouseButton::Middle => MouseButtonCode::RightButton,
|
||||
gpui::MouseButton::Navigate(_) => MouseButtonCode::Other,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -68,27 +65,27 @@ impl AlacMouseButton {
|
|||
};
|
||||
|
||||
if is_positive {
|
||||
AlacMouseButton::ScrollUp
|
||||
MouseButtonCode::ScrollUp
|
||||
} else {
|
||||
AlacMouseButton::ScrollDown
|
||||
MouseButtonCode::ScrollDown
|
||||
}
|
||||
}
|
||||
|
||||
fn is_other(&self) -> bool {
|
||||
matches!(self, AlacMouseButton::Other)
|
||||
matches!(self, MouseButtonCode::Other)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_report(
|
||||
point: AlacPoint,
|
||||
pub(crate) fn scroll_report(
|
||||
point: TerminalPoint,
|
||||
scroll_lines: i32,
|
||||
e: &ScrollWheelEvent,
|
||||
mode: TermMode,
|
||||
mode: TerminalModes,
|
||||
) -> Option<impl Iterator<Item = Vec<u8>>> {
|
||||
if mode.intersects(TermMode::MOUSE_MODE) {
|
||||
if mode.intersects(TerminalModes::MOUSE_MODE) {
|
||||
mouse_report(
|
||||
point,
|
||||
AlacMouseButton::from_scroll(e),
|
||||
MouseButtonCode::from_scroll(e),
|
||||
true,
|
||||
e.modifiers,
|
||||
MouseFormat::from_mode(mode),
|
||||
|
|
@ -106,7 +103,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn scroll_report_repeats_for_negative_scroll_lines() {
|
||||
let grid_point = AlacPoint::new(GridLine(0), GridCol(0));
|
||||
let grid_point = TerminalPoint::new(0, 0);
|
||||
|
||||
let scroll_event = ScrollWheelEvent {
|
||||
delta: ScrollDelta::Lines(point(0., -1.)),
|
||||
|
|
@ -114,7 +111,7 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
let mode = TermMode::MOUSE_MODE;
|
||||
let mode = TerminalModes::MOUSE_MODE;
|
||||
let reports: Vec<Vec<u8>> = scroll_report(grid_point, -3, &scroll_event, mode)
|
||||
.expect("mouse mode should produce a scroll report")
|
||||
.collect();
|
||||
|
|
@ -124,7 +121,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn scroll_report_repeats_for_positive_scroll_lines() {
|
||||
let grid_point = AlacPoint::new(GridLine(0), GridCol(0));
|
||||
let grid_point = TerminalPoint::new(0, 0);
|
||||
|
||||
let scroll_event = ScrollWheelEvent {
|
||||
delta: ScrollDelta::Lines(point(0., 1.)),
|
||||
|
|
@ -132,7 +129,7 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
let mode = TermMode::MOUSE_MODE;
|
||||
let mode = TerminalModes::MOUSE_MODE;
|
||||
let reports: Vec<Vec<u8>> = scroll_report(grid_point, 3, &scroll_event, mode)
|
||||
.expect("mouse mode should produce a scroll report")
|
||||
.collect();
|
||||
|
|
@ -141,7 +138,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn alt_scroll(scroll_lines: i32) -> Vec<u8> {
|
||||
pub(crate) fn alt_scroll(scroll_lines: i32) -> Vec<u8> {
|
||||
let cmd = if scroll_lines > 0 { b'A' } else { b'B' };
|
||||
|
||||
let mut content = Vec::with_capacity(scroll_lines.unsigned_abs() as usize * 3);
|
||||
|
|
@ -153,15 +150,15 @@ pub fn alt_scroll(scroll_lines: i32) -> Vec<u8> {
|
|||
content
|
||||
}
|
||||
|
||||
pub fn mouse_button_report(
|
||||
point: AlacPoint,
|
||||
pub(crate) fn mouse_button_report(
|
||||
point: TerminalPoint,
|
||||
button: gpui::MouseButton,
|
||||
modifiers: Modifiers,
|
||||
pressed: bool,
|
||||
mode: TermMode,
|
||||
mode: TerminalModes,
|
||||
) -> Option<Vec<u8>> {
|
||||
let button = AlacMouseButton::from_button(button);
|
||||
if !button.is_other() && mode.intersects(TermMode::MOUSE_MODE) {
|
||||
let button = MouseButtonCode::from_button(button);
|
||||
if !button.is_other() && mode.intersects(TerminalModes::MOUSE_MODE) {
|
||||
mouse_report(
|
||||
point,
|
||||
button,
|
||||
|
|
@ -174,17 +171,19 @@ pub fn mouse_button_report(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn mouse_moved_report(
|
||||
point: AlacPoint,
|
||||
pub(crate) fn mouse_moved_report(
|
||||
point: TerminalPoint,
|
||||
button: Option<MouseButton>,
|
||||
modifiers: Modifiers,
|
||||
mode: TermMode,
|
||||
mode: TerminalModes,
|
||||
) -> Option<Vec<u8>> {
|
||||
let button = AlacMouseButton::from_move_button(button);
|
||||
let button = MouseButtonCode::from_move_button(button);
|
||||
|
||||
if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) {
|
||||
if !button.is_other()
|
||||
&& mode.intersects(TerminalModes::MOUSE_MOTION | TerminalModes::MOUSE_DRAG)
|
||||
{
|
||||
//Only drags are reported in drag mode, so block NoneMove.
|
||||
if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, AlacMouseButton::NoneMove) {
|
||||
if mode.contains(TerminalModes::MOUSE_DRAG) && matches!(button, MouseButtonCode::NoneMove) {
|
||||
None
|
||||
} else {
|
||||
mouse_report(point, button, true, modifiers, MouseFormat::from_mode(mode))
|
||||
|
|
@ -194,51 +193,54 @@ pub fn mouse_moved_report(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn grid_point(
|
||||
pub(crate) fn grid_point(
|
||||
pos: Point<Pixels>,
|
||||
cur_size: TerminalBounds,
|
||||
display_offset: usize,
|
||||
) -> AlacPoint {
|
||||
) -> TerminalPoint {
|
||||
grid_point_and_side(pos, cur_size, display_offset).0
|
||||
}
|
||||
|
||||
pub fn grid_point_and_side(
|
||||
pub(crate) fn grid_point_and_side(
|
||||
pos: Point<Pixels>,
|
||||
cur_size: TerminalBounds,
|
||||
display_offset: usize,
|
||||
) -> (AlacPoint, Side) {
|
||||
let mut col = GridCol((pos.x / cur_size.cell_width) as usize);
|
||||
) -> (TerminalPoint, TerminalSelectionSide) {
|
||||
let mut column = (pos.x / cur_size.cell_width) as usize;
|
||||
let cell_x = cmp::max(px(0.), pos.x) % cur_size.cell_width;
|
||||
let half_cell_width = cur_size.cell_width / 2.0;
|
||||
let mut side = if cell_x > half_cell_width {
|
||||
Side::Right
|
||||
TerminalSelectionSide::Right
|
||||
} else {
|
||||
Side::Left
|
||||
TerminalSelectionSide::Left
|
||||
};
|
||||
|
||||
if col > cur_size.last_column() {
|
||||
col = cur_size.last_column();
|
||||
side = Side::Right;
|
||||
let last_column = cur_size.num_columns().saturating_sub(1);
|
||||
if column > last_column {
|
||||
column = last_column;
|
||||
side = TerminalSelectionSide::Right;
|
||||
}
|
||||
let col = min(col, cur_size.last_column());
|
||||
let column = min(column, last_column);
|
||||
let mut line = (pos.y / cur_size.line_height) as i32;
|
||||
if line > cur_size.bottommost_line() {
|
||||
line = cur_size.bottommost_line().0;
|
||||
side = Side::Right;
|
||||
let bottommost_line = i32::try_from(cur_size.num_lines().saturating_sub(1)).unwrap_or(i32::MAX);
|
||||
if line > bottommost_line {
|
||||
line = bottommost_line;
|
||||
side = TerminalSelectionSide::Right;
|
||||
} else if line < 0 {
|
||||
side = Side::Left;
|
||||
side = TerminalSelectionSide::Left;
|
||||
}
|
||||
|
||||
let display_offset = i32::try_from(display_offset).unwrap_or(i32::MAX);
|
||||
(
|
||||
AlacPoint::new(GridLine(line - display_offset as i32), col),
|
||||
TerminalPoint::new(line.saturating_sub(display_offset), column),
|
||||
side,
|
||||
)
|
||||
}
|
||||
|
||||
///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode
|
||||
fn mouse_report(
|
||||
point: AlacPoint,
|
||||
button: AlacMouseButton,
|
||||
point: TerminalPoint,
|
||||
button: MouseButtonCode,
|
||||
pressed: bool,
|
||||
modifiers: Modifiers,
|
||||
format: MouseFormat,
|
||||
|
|
@ -272,11 +274,10 @@ fn mouse_report(
|
|||
}
|
||||
}
|
||||
|
||||
fn normal_mouse_report(point: AlacPoint, button: u8, utf8: bool) -> Option<Vec<u8>> {
|
||||
let AlacPoint { line, column } = point;
|
||||
fn normal_mouse_report(point: TerminalPoint, button: u8, utf8: bool) -> Option<Vec<u8>> {
|
||||
let max_point = if utf8 { 2015 } else { 223 };
|
||||
|
||||
if line >= max_point || column >= max_point {
|
||||
if point.line >= max_point || point.column >= max_point as usize {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
|
@ -289,22 +290,22 @@ fn normal_mouse_report(point: AlacPoint, button: u8, utf8: bool) -> Option<Vec<u
|
|||
vec![first as u8, second as u8]
|
||||
};
|
||||
|
||||
if utf8 && column >= 95 {
|
||||
msg.append(&mut mouse_pos_encode(column.0));
|
||||
if utf8 && point.column >= 95 {
|
||||
msg.append(&mut mouse_pos_encode(point.column));
|
||||
} else {
|
||||
msg.push(32 + 1 + column.0 as u8);
|
||||
msg.push(32 + 1 + point.column as u8);
|
||||
}
|
||||
|
||||
if utf8 && line >= 95 {
|
||||
msg.append(&mut mouse_pos_encode(line.0 as usize));
|
||||
if utf8 && point.line >= 95 {
|
||||
msg.append(&mut mouse_pos_encode(point.line as usize));
|
||||
} else {
|
||||
msg.push(32 + 1 + line.0 as u8);
|
||||
msg.push(32 + 1 + point.line as u8);
|
||||
}
|
||||
|
||||
Some(msg)
|
||||
}
|
||||
|
||||
fn sgr_mouse_report(point: AlacPoint, button: u8, pressed: bool) -> String {
|
||||
fn sgr_mouse_report(point: TerminalPoint, button: u8, pressed: bool) -> String {
|
||||
let c = if pressed { 'M' } else { 'm' };
|
||||
|
||||
let msg = format!(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
#[cfg(feature = "alacritty-backend")]
|
||||
use alacritty_terminal::tty::Pty;
|
||||
use gpui::{Context, Task};
|
||||
use parking_lot::{MappedRwLockReadGuard, Mutex, RwLock, RwLockReadGuard};
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
use portable_pty::{Child as PortableChild, MasterPty as PortableMasterPty};
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::num::NonZeroU32;
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, feature = "alacritty-backend"))]
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
|
|
@ -24,15 +27,28 @@ impl ProcessIdGetter {
|
|||
pub fn fallback_pid(&self) -> Pid {
|
||||
Pid::from_u32(self.fallback_pid)
|
||||
}
|
||||
|
||||
fn new_from_parts(handle: i32, fallback_pid: u32) -> ProcessIdGetter {
|
||||
ProcessIdGetter {
|
||||
handle,
|
||||
fallback_pid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl ProcessIdGetter {
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
fn new(pty: &Pty) -> ProcessIdGetter {
|
||||
ProcessIdGetter {
|
||||
handle: pty.file().as_raw_fd(),
|
||||
fallback_pid: pty.child().id(),
|
||||
}
|
||||
ProcessIdGetter::new_from_parts(pty.file().as_raw_fd(), pty.child().id())
|
||||
}
|
||||
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
fn new_portable(master: &dyn PortableMasterPty, child: &dyn PortableChild) -> ProcessIdGetter {
|
||||
ProcessIdGetter::new_from_parts(
|
||||
master.as_raw_fd().unwrap_or(-1),
|
||||
child.process_id().unwrap_or(0),
|
||||
)
|
||||
}
|
||||
|
||||
fn pid(&self) -> Option<Pid> {
|
||||
|
|
@ -54,6 +70,7 @@ impl ProcessIdGetter {
|
|||
|
||||
#[cfg(windows)]
|
||||
impl ProcessIdGetter {
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
fn new(pty: &Pty) -> ProcessIdGetter {
|
||||
let child = pty.child_watcher();
|
||||
let handle = child.raw_handle();
|
||||
|
|
@ -61,10 +78,18 @@ impl ProcessIdGetter {
|
|||
NonZeroU32::new_unchecked(GetProcessId(HANDLE(handle as _)))
|
||||
});
|
||||
|
||||
ProcessIdGetter {
|
||||
handle: handle as i32,
|
||||
fallback_pid: u32::from(fallback_pid),
|
||||
}
|
||||
ProcessIdGetter::new_from_parts(handle as i32, u32::from(fallback_pid))
|
||||
}
|
||||
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
fn new_portable(_master: &dyn PortableMasterPty, child: &dyn PortableChild) -> ProcessIdGetter {
|
||||
ProcessIdGetter::new_from_parts(
|
||||
child
|
||||
.as_raw_handle()
|
||||
.map(|handle| handle as i32)
|
||||
.unwrap_or_default(),
|
||||
child.process_id().unwrap_or(0),
|
||||
)
|
||||
}
|
||||
|
||||
fn pid(&self) -> Option<Pid> {
|
||||
|
|
@ -100,7 +125,20 @@ pub struct PtyProcessInfo {
|
|||
}
|
||||
|
||||
impl PtyProcessInfo {
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
pub fn new(pty: &Pty) -> PtyProcessInfo {
|
||||
Self::from_pid_getter(ProcessIdGetter::new(pty))
|
||||
}
|
||||
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
pub fn new_portable(
|
||||
master: &dyn PortableMasterPty,
|
||||
child: &dyn PortableChild,
|
||||
) -> PtyProcessInfo {
|
||||
Self::from_pid_getter(ProcessIdGetter::new_portable(master, child))
|
||||
}
|
||||
|
||||
fn from_pid_getter(pid_getter: ProcessIdGetter) -> PtyProcessInfo {
|
||||
let process_refresh_kind = ProcessRefreshKind::nothing()
|
||||
.with_cmd(UpdateKind::Always)
|
||||
.with_cwd(UpdateKind::Always)
|
||||
|
|
@ -111,7 +149,7 @@ impl PtyProcessInfo {
|
|||
PtyProcessInfo {
|
||||
system: RwLock::new(system),
|
||||
refresh_kind: process_refresh_kind,
|
||||
pid_getter: ProcessIdGetter::new(pty),
|
||||
pid_getter,
|
||||
current: RwLock::new(None),
|
||||
task: Mutex::new(None),
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,4 @@
|
|||
#[cfg(feature = "alacritty-backend")]
|
||||
use alacritty_terminal::{
|
||||
Term,
|
||||
event::EventListener,
|
||||
|
|
@ -10,68 +11,132 @@ use alacritty_terminal::{
|
|||
};
|
||||
use log::{info, warn};
|
||||
use regex::Regex;
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
use std::ops::Index;
|
||||
use std::{
|
||||
ops::{Index, Range},
|
||||
ops::Range,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use url::Url;
|
||||
use util::paths::{PathStyle, UrlExt};
|
||||
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
use crate::TerminalContent;
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
use crate::TerminalPoint;
|
||||
use crate::TerminalRange;
|
||||
|
||||
const URL_REGEX: &str = r#"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file://|git://|ssh:|ftp://)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>"\s{-}\^⟨⟩`']+"#;
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
const WIDE_CHAR_SPACERS: Flags =
|
||||
Flags::from_bits(Flags::LEADING_WIDE_CHAR_SPACER.bits() | Flags::WIDE_CHAR_SPACER.bits())
|
||||
.unwrap();
|
||||
|
||||
pub(super) struct RegexSearches {
|
||||
url_regex: RegexSearch,
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
url_regex: Option<RegexSearch>,
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
content_url_regex: Option<Regex>,
|
||||
path_hyperlink_regexes: Vec<Regex>,
|
||||
path_hyperlink_timeout: Duration,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(super) struct HyperlinkMatch {
|
||||
pub(super) text: String,
|
||||
pub(super) is_url: bool,
|
||||
pub(super) range: TerminalRange,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
impl From<(String, bool, Match)> for HyperlinkMatch {
|
||||
fn from((text, is_url, range): (String, bool, Match)) -> Self {
|
||||
Self {
|
||||
text,
|
||||
is_url,
|
||||
range: TerminalRange::from_alacritty(range),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RegexSearches {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
url_regex: RegexSearch::new(URL_REGEX).unwrap(),
|
||||
path_hyperlink_regexes: Vec::default(),
|
||||
path_hyperlink_timeout: Duration::default(),
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
{
|
||||
Self::new(Vec::<String>::new(), 0)
|
||||
}
|
||||
#[cfg(all(not(feature = "alacritty-backend"), feature = "libghostty-vt"))]
|
||||
{
|
||||
Self::new_ghostty(Vec::<String>::new(), 0)
|
||||
}
|
||||
#[cfg(not(any(feature = "alacritty-backend", feature = "libghostty-vt")))]
|
||||
{
|
||||
Self {
|
||||
path_hyperlink_regexes: Vec::new(),
|
||||
path_hyperlink_timeout: Duration::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl RegexSearches {
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
pub(super) fn new(
|
||||
path_hyperlink_regexes: impl IntoIterator<Item: AsRef<str>>,
|
||||
path_hyperlink_timeout_ms: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
url_regex: RegexSearch::new(URL_REGEX).unwrap(),
|
||||
path_hyperlink_regexes: path_hyperlink_regexes
|
||||
.into_iter()
|
||||
.filter_map(|regex| {
|
||||
Regex::new(regex.as_ref())
|
||||
.inspect_err(|error| {
|
||||
warn!(
|
||||
concat!(
|
||||
"Ignoring path hyperlink regex specified in ",
|
||||
"`terminal.path_hyperlink_regexes`:\n\n\t{}\n\nError: {}",
|
||||
),
|
||||
regex.as_ref(),
|
||||
error
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect(),
|
||||
url_regex: RegexSearch::new(URL_REGEX).ok(),
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
content_url_regex: None,
|
||||
path_hyperlink_regexes: Self::path_hyperlink_regexes(path_hyperlink_regexes),
|
||||
path_hyperlink_timeout: Duration::from_millis(path_hyperlink_timeout_ms),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
pub(super) fn new_ghostty(
|
||||
path_hyperlink_regexes: impl IntoIterator<Item: AsRef<str>>,
|
||||
path_hyperlink_timeout_ms: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
url_regex: None,
|
||||
content_url_regex: Regex::new(URL_REGEX).ok(),
|
||||
path_hyperlink_regexes: Self::path_hyperlink_regexes(path_hyperlink_regexes),
|
||||
path_hyperlink_timeout: Duration::from_millis(path_hyperlink_timeout_ms),
|
||||
}
|
||||
}
|
||||
|
||||
fn path_hyperlink_regexes(
|
||||
path_hyperlink_regexes: impl IntoIterator<Item: AsRef<str>>,
|
||||
) -> Vec<Regex> {
|
||||
path_hyperlink_regexes
|
||||
.into_iter()
|
||||
.filter_map(|regex| {
|
||||
Regex::new(regex.as_ref())
|
||||
.inspect_err(|error| {
|
||||
warn!(
|
||||
concat!(
|
||||
"Ignoring path hyperlink regex specified in ",
|
||||
"`terminal.path_hyperlink_regexes`:\n\n\t{}\n\nError: {}",
|
||||
),
|
||||
regex.as_ref(),
|
||||
error
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
pub(super) fn find_from_grid_point<T: EventListener>(
|
||||
term: &Term<T>,
|
||||
point: AlacPoint,
|
||||
regex_searches: &mut RegexSearches,
|
||||
path_style: PathStyle,
|
||||
) -> Option<(String, bool, Match)> {
|
||||
) -> Option<HyperlinkMatch> {
|
||||
let grid = term.grid();
|
||||
let link = grid.index(point).hyperlink();
|
||||
let found_word = if let Some(ref url) = link {
|
||||
|
|
@ -101,18 +166,16 @@ pub(super) fn find_from_grid_point<T: EventListener>(
|
|||
Some((url, true, url_match))
|
||||
} else {
|
||||
let (line_start, line_end) = (term.line_search_left(point), term.line_search_right(point));
|
||||
if let Some((url, url_match)) = RegexIter::new(
|
||||
line_start,
|
||||
line_end,
|
||||
AlacDirection::Right,
|
||||
term,
|
||||
&mut regex_searches.url_regex,
|
||||
)
|
||||
.find(|rm| rm.contains(&point))
|
||||
.map(|url_match| {
|
||||
let url = term.bounds_to_string(*url_match.start(), *url_match.end());
|
||||
sanitize_url_punctuation(url, url_match, term)
|
||||
}) {
|
||||
let url_match = regex_searches.url_regex.as_mut().and_then(|url_regex| {
|
||||
RegexIter::new(line_start, line_end, AlacDirection::Right, term, url_regex)
|
||||
.find(|rm| rm.contains(&point))
|
||||
.map(|url_match| {
|
||||
let url = term.bounds_to_string(*url_match.start(), *url_match.end());
|
||||
sanitize_url_punctuation(url, url_match, term)
|
||||
})
|
||||
});
|
||||
|
||||
if let Some((url, url_match)) = url_match {
|
||||
Some((url, true, url_match))
|
||||
} else {
|
||||
path_match(
|
||||
|
|
@ -127,35 +190,341 @@ pub(super) fn find_from_grid_point<T: EventListener>(
|
|||
}
|
||||
};
|
||||
|
||||
found_word.map(|(maybe_url_or_path, is_url, word_match)| {
|
||||
if is_url {
|
||||
// Treat "file://" IRIs like file paths to ensure
|
||||
// that line numbers at the end of the path are
|
||||
// handled correctly.
|
||||
// Use Url::to_file_path() to properly handle Windows drive letters
|
||||
// (e.g., file:///C:/path -> C:\path)
|
||||
if maybe_url_or_path.starts_with("file://") {
|
||||
if let Ok(url) = Url::parse(&maybe_url_or_path) {
|
||||
if let Ok(path) = url.to_file_path_ext(path_style) {
|
||||
return (path.to_string_lossy().into_owned(), false, word_match);
|
||||
} else if let Some(path) = try_osc8_url_to_path(url)
|
||||
&& path_style.is_posix()
|
||||
{
|
||||
return (path, false, word_match);
|
||||
}
|
||||
found_word.map(|found_word| normalize_found_word(found_word, path_style))
|
||||
}
|
||||
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
pub(super) fn find_from_content_point(
|
||||
content: &TerminalContent,
|
||||
point: TerminalPoint,
|
||||
regex_searches: &mut RegexSearches,
|
||||
path_style: PathStyle,
|
||||
) -> Option<HyperlinkMatch> {
|
||||
if let Some((text, is_url, range)) = content_cell_hyperlink(content, point) {
|
||||
return Some(normalize_hyperlink_match(text, is_url, range, path_style));
|
||||
}
|
||||
|
||||
let (line, points, hovered_point_byte_offset) = content_line_text(content, point)?;
|
||||
|
||||
let found_word = regex_searches
|
||||
.content_url_regex
|
||||
.as_ref()
|
||||
.and_then(|content_url_regex| {
|
||||
content_url_regex
|
||||
.find_iter(&line)
|
||||
.find(|url_match| url_match.range().contains(&hovered_point_byte_offset))
|
||||
.and_then(|url_match| {
|
||||
sanitize_content_url_punctuation(
|
||||
url_match.as_str().to_string(),
|
||||
url_match.range(),
|
||||
&points,
|
||||
)
|
||||
})
|
||||
.map(|(url, url_match)| (url, true, url_match))
|
||||
})
|
||||
.or_else(|| {
|
||||
content_path_match(
|
||||
&line,
|
||||
&points,
|
||||
hovered_point_byte_offset,
|
||||
point,
|
||||
&mut regex_searches.path_hyperlink_regexes,
|
||||
regex_searches.path_hyperlink_timeout,
|
||||
)
|
||||
.map(|(path, path_match)| (path, false, path_match))
|
||||
});
|
||||
|
||||
found_word
|
||||
.map(|(text, is_url, range)| normalize_hyperlink_match(text, is_url, range, path_style))
|
||||
}
|
||||
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
fn content_cell_hyperlink(
|
||||
content: &TerminalContent,
|
||||
point: TerminalPoint,
|
||||
) -> Option<(String, bool, TerminalRange)> {
|
||||
let index = content.cells.iter().position(|cell| cell.point == point)?;
|
||||
let link = content.cells[index].hyperlink()?;
|
||||
let mut start_index = index;
|
||||
while start_index > 0 && content.cells[start_index - 1].hyperlink() == Some(link) {
|
||||
start_index -= 1;
|
||||
}
|
||||
|
||||
let mut end_index = index;
|
||||
while content
|
||||
.cells
|
||||
.get(end_index + 1)
|
||||
.and_then(|cell| cell.hyperlink())
|
||||
== Some(link)
|
||||
{
|
||||
end_index += 1;
|
||||
}
|
||||
|
||||
Some((
|
||||
link.uri().to_string(),
|
||||
true,
|
||||
TerminalRange::new(
|
||||
content.cells[start_index].point,
|
||||
content.cells[end_index].point,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
fn normalize_found_word(
|
||||
found_word: (String, bool, Match),
|
||||
path_style: PathStyle,
|
||||
) -> HyperlinkMatch {
|
||||
let (maybe_url_or_path, is_url, word_match) = found_word;
|
||||
normalize_hyperlink_match(
|
||||
maybe_url_or_path,
|
||||
is_url,
|
||||
TerminalRange::from_alacritty(word_match),
|
||||
path_style,
|
||||
)
|
||||
}
|
||||
|
||||
fn normalize_hyperlink_match(
|
||||
maybe_url_or_path: String,
|
||||
is_url: bool,
|
||||
range: TerminalRange,
|
||||
path_style: PathStyle,
|
||||
) -> HyperlinkMatch {
|
||||
if is_url {
|
||||
// Treat "file://" IRIs like file paths to ensure
|
||||
// that line numbers at the end of the path are
|
||||
// handled correctly.
|
||||
// Use Url::to_file_path() to properly handle Windows drive letters
|
||||
// (e.g., file:///C:/path -> C:\path)
|
||||
if maybe_url_or_path.starts_with("file://") {
|
||||
if let Ok(url) = Url::parse(&maybe_url_or_path) {
|
||||
if let Ok(path) = url.to_file_path_ext(path_style) {
|
||||
return HyperlinkMatch {
|
||||
text: path.to_string_lossy().into_owned(),
|
||||
is_url: false,
|
||||
range,
|
||||
};
|
||||
} else if let Some(path) = try_osc8_url_to_path(url)
|
||||
&& path_style.is_posix()
|
||||
{
|
||||
return HyperlinkMatch {
|
||||
text: path,
|
||||
is_url: false,
|
||||
range,
|
||||
};
|
||||
}
|
||||
// Fallback: strip file:// prefix if URL parsing fails
|
||||
let path = maybe_url_or_path
|
||||
.strip_prefix("file://")
|
||||
.unwrap_or(&maybe_url_or_path);
|
||||
(path.to_string(), false, word_match)
|
||||
} else {
|
||||
(maybe_url_or_path, true, word_match)
|
||||
}
|
||||
// Fallback: strip file:// prefix if URL parsing fails
|
||||
let path = maybe_url_or_path
|
||||
.strip_prefix("file://")
|
||||
.unwrap_or(&maybe_url_or_path);
|
||||
HyperlinkMatch {
|
||||
text: path.to_string(),
|
||||
is_url: false,
|
||||
range,
|
||||
}
|
||||
} else {
|
||||
(maybe_url_or_path, false, word_match)
|
||||
HyperlinkMatch {
|
||||
text: maybe_url_or_path,
|
||||
is_url: true,
|
||||
range,
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
HyperlinkMatch {
|
||||
text: maybe_url_or_path,
|
||||
is_url: false,
|
||||
range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
fn content_line_text(
|
||||
content: &TerminalContent,
|
||||
point: TerminalPoint,
|
||||
) -> Option<(String, Vec<(usize, TerminalPoint)>, usize)> {
|
||||
let mut line = String::new();
|
||||
let mut points = Vec::new();
|
||||
let mut hovered_point_byte_offset = None;
|
||||
|
||||
for cell in content
|
||||
.cells
|
||||
.iter()
|
||||
.filter(|cell| cell.point.line == point.line)
|
||||
{
|
||||
if cell.is_wide_char_spacer_or_leading() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let byte_offset = line.len();
|
||||
if cell.point == point {
|
||||
hovered_point_byte_offset = Some(byte_offset);
|
||||
}
|
||||
points.push((byte_offset, cell.point));
|
||||
match cell.c {
|
||||
' ' | '\t' => line.push(' '),
|
||||
character => line.push(character),
|
||||
}
|
||||
|
||||
if let Some(characters) = cell.zerowidth() {
|
||||
for character in characters {
|
||||
points.push((line.len(), cell.point));
|
||||
line.push(*character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let trimmed_len = line.trim_ascii_end().len();
|
||||
line.truncate(trimmed_len);
|
||||
let hovered_point_byte_offset = hovered_point_byte_offset?;
|
||||
(line.len() > hovered_point_byte_offset).then_some((line, points, hovered_point_byte_offset))
|
||||
}
|
||||
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
fn sanitize_content_url_punctuation(
|
||||
url: String,
|
||||
url_match_range: Range<usize>,
|
||||
points: &[(usize, TerminalPoint)],
|
||||
) -> Option<(String, TerminalRange)> {
|
||||
let mut sanitized_url = url;
|
||||
let mut bytes_trimmed = 0;
|
||||
|
||||
let (open_parens, mut close_parens) =
|
||||
sanitized_url
|
||||
.chars()
|
||||
.fold((0, 0), |(opens, closes), character| match character {
|
||||
'(' => (opens + 1, closes),
|
||||
')' => (opens, closes + 1),
|
||||
_ => (opens, closes),
|
||||
});
|
||||
|
||||
while let Some(last_char) = sanitized_url.chars().last() {
|
||||
let should_remove = match last_char {
|
||||
'.' | ',' | ':' | ';' => true,
|
||||
'(' => true,
|
||||
')' if close_parens > open_parens => {
|
||||
close_parens -= 1;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if should_remove {
|
||||
sanitized_url.pop();
|
||||
bytes_trimmed += last_char.len_utf8();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let sanitized_range = url_match_range.start..url_match_range.end.checked_sub(bytes_trimmed)?;
|
||||
let sanitized_match = content_match_from_byte_range(points, sanitized_range)?;
|
||||
Some((sanitized_url, sanitized_match))
|
||||
}
|
||||
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
fn content_path_match(
|
||||
line: &str,
|
||||
points: &[(usize, TerminalPoint)],
|
||||
hovered_point_byte_offset: usize,
|
||||
hovered: TerminalPoint,
|
||||
path_hyperlink_regexes: &mut Vec<Regex>,
|
||||
path_hyperlink_timeout: Duration,
|
||||
) -> Option<(String, TerminalRange)> {
|
||||
if path_hyperlink_regexes.is_empty() || path_hyperlink_timeout.as_millis() == 0 {
|
||||
return None;
|
||||
}
|
||||
let search_start_time = Instant::now();
|
||||
|
||||
let timed_out = || {
|
||||
let elapsed_time = Instant::now().saturating_duration_since(search_start_time);
|
||||
(elapsed_time > path_hyperlink_timeout)
|
||||
.then_some((elapsed_time.as_millis(), path_hyperlink_timeout.as_millis()))
|
||||
};
|
||||
|
||||
for regex in path_hyperlink_regexes {
|
||||
let mut path_found = false;
|
||||
|
||||
for captures in regex.captures_iter(line) {
|
||||
path_found = true;
|
||||
let Some(full_match) = captures.get(0) else {
|
||||
continue;
|
||||
};
|
||||
let match_range = full_match.range();
|
||||
let (mut path_range, line_column) = if let Some(path) = captures.name("path") {
|
||||
let parse = |name: &str| -> Option<u32> {
|
||||
captures
|
||||
.name(name)
|
||||
.and_then(|capture| capture.as_str().parse().ok())
|
||||
};
|
||||
|
||||
(
|
||||
path.range(),
|
||||
parse("line").map(|line| (line, parse("column"))),
|
||||
)
|
||||
} else {
|
||||
(match_range.clone(), None)
|
||||
};
|
||||
let mut link_range = captures
|
||||
.name("link")
|
||||
.map_or_else(|| match_range.clone(), |link| link.range());
|
||||
|
||||
if let Some(trim) = first_unbalanced_open_paren(&line[path_range.clone()]) {
|
||||
path_range.start += trim;
|
||||
link_range.start = link_range.start.max(path_range.start);
|
||||
}
|
||||
|
||||
if !link_range.contains(&hovered_point_byte_offset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let link_match = content_match_from_byte_range(points, link_range.clone())?;
|
||||
if !link_match.contains(hovered) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut path = line[path_range].to_string();
|
||||
if let Some((line, column)) = line_column {
|
||||
path += &format!(":{line}");
|
||||
if let Some(column) = column {
|
||||
path += &format!(":{column}");
|
||||
}
|
||||
}
|
||||
|
||||
return Some((path, link_match));
|
||||
}
|
||||
|
||||
if path_found {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some((timed_out_ms, timeout_ms)) = timed_out() {
|
||||
warn!("Timed out processing path hyperlink regexes after {timed_out_ms}ms");
|
||||
info!("{timeout_ms}ms time out specified in `terminal.path_hyperlink_timeout_ms`");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "libghostty-vt")]
|
||||
fn content_match_from_byte_range(
|
||||
points: &[(usize, TerminalPoint)],
|
||||
range: Range<usize>,
|
||||
) -> Option<TerminalRange> {
|
||||
if range.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start_index = points.partition_point(|(byte_offset, _)| *byte_offset < range.start);
|
||||
let end_index = points
|
||||
.partition_point(|(byte_offset, _)| *byte_offset < range.end)
|
||||
.checked_sub(1)?;
|
||||
let start = points.get(start_index)?.1;
|
||||
let end = points.get(end_index)?.1;
|
||||
Some(TerminalRange::new(start, end))
|
||||
}
|
||||
|
||||
// OSC 8 mandates that file:// URIs must be encoded as file://{host}{path}
|
||||
|
|
@ -174,6 +543,7 @@ fn try_osc8_url_to_path(url: url::Url) -> Option<String> {
|
|||
bytes.try_into().ok()
|
||||
}
|
||||
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
fn sanitize_url_punctuation<T: EventListener>(
|
||||
url: String,
|
||||
url_match: Match,
|
||||
|
|
@ -252,6 +622,7 @@ fn first_unbalanced_open_paren(s: &str) -> Option<usize> {
|
|||
first_unmatched.filter(|_| balance > 0)
|
||||
}
|
||||
|
||||
#[cfg(feature = "alacritty-backend")]
|
||||
fn path_match<T>(
|
||||
term: &Term<T>,
|
||||
line_start: AlacPoint,
|
||||
|
|
@ -421,7 +792,7 @@ fn path_match<T>(
|
|||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(all(test, feature = "alacritty-backend"))]
|
||||
mod tests {
|
||||
use crate::terminal_settings::TerminalSettings;
|
||||
|
||||
|
|
@ -1080,8 +1451,7 @@ mod tests {
|
|||
event::VoidListener,
|
||||
grid::Scroll,
|
||||
index::{Column, Point as AlacPoint},
|
||||
term::test::mock_term,
|
||||
term::{Term, search::Match},
|
||||
term::{Term, test::mock_term},
|
||||
};
|
||||
use settings::{self, Settings, SettingsContent};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
|
@ -1114,7 +1484,7 @@ mod tests {
|
|||
TEST_TERM_AND_POINT.with(|(term, point)| {
|
||||
assert_eq!(
|
||||
find_from_grid_point_bench(term, *point)
|
||||
.map(|(path, ..)| path)
|
||||
.map(|hyperlink| hyperlink.text)
|
||||
.unwrap_or_default(),
|
||||
"/Hyperlinks/Bench/Source/zed-hyperlinks/crates/terminal",
|
||||
"Hyperlink should have been found"
|
||||
|
|
@ -1132,7 +1502,7 @@ mod tests {
|
|||
TEST_TERM_AND_POINT.with(|(term, point)| {
|
||||
assert_eq!(
|
||||
find_from_grid_point_bench(term, *point)
|
||||
.map(|(path, ..)| path)
|
||||
.map(|hyperlink| hyperlink.text)
|
||||
.unwrap_or_default(),
|
||||
"/Hyperlinks/Bench/Source/zed-hyperlinks/crates/terminal/terminal.rs:1000:42",
|
||||
"Hyperlink should have been found"
|
||||
|
|
@ -1150,7 +1520,7 @@ mod tests {
|
|||
TEST_TERM_AND_POINT.with(|(term, point)| {
|
||||
assert_eq!(
|
||||
find_from_grid_point_bench(term, *point)
|
||||
.map(|(path, ..)| path)
|
||||
.map(|hyperlink| hyperlink.text)
|
||||
.unwrap_or_default(),
|
||||
"rust-toolchain.toml",
|
||||
"Hyperlink should have been found"
|
||||
|
|
@ -1213,7 +1583,7 @@ mod tests {
|
|||
TEST_TERM_AND_POINT.with(|(term, point)| {
|
||||
assert_eq!(
|
||||
find_from_grid_point_bench(term, *point)
|
||||
.map(|(path, ..)| path)
|
||||
.map(|hyperlink| hyperlink.text)
|
||||
.unwrap_or_default(),
|
||||
"392",
|
||||
"Hyperlink should have been found"
|
||||
|
|
@ -1247,7 +1617,7 @@ mod tests {
|
|||
TEST_TERM_AND_POINT.with(|(term, point)| {
|
||||
assert_eq!(
|
||||
find_from_grid_point_bench(term, *point)
|
||||
.map(|(path, ..)| path)
|
||||
.map(|hyperlink| hyperlink.text)
|
||||
.unwrap_or_default(),
|
||||
LINE.trim_end_matches(['.', '\r', '\n']),
|
||||
"Hyperlink should have been found"
|
||||
|
|
@ -1258,7 +1628,7 @@ mod tests {
|
|||
pub fn find_from_grid_point_bench(
|
||||
term: &Term<VoidListener>,
|
||||
point: AlacPoint,
|
||||
) -> Option<(String, bool, Match)> {
|
||||
) -> Option<HyperlinkMatch> {
|
||||
const PATH_HYPERLINK_TIMEOUT_MS: u64 = 1000;
|
||||
|
||||
thread_local! {
|
||||
|
|
@ -1891,14 +2261,16 @@ mod tests {
|
|||
let check_hyperlink_match =
|
||||
CheckHyperlinkMatch::new(&term, &expected_hyperlink, source_location);
|
||||
match hyperlink_found {
|
||||
Some((hyperlink_word, false, hyperlink_match)) => {
|
||||
Some(hyperlink) if !hyperlink.is_url => {
|
||||
let hyperlink_match = hyperlink.range.to_alacritty();
|
||||
check_hyperlink_match.check_path_with_position_and_match(
|
||||
PathWithPosition::parse_str(&hyperlink_word),
|
||||
PathWithPosition::parse_str(&hyperlink.text),
|
||||
&hyperlink_match,
|
||||
);
|
||||
}
|
||||
Some((hyperlink_word, true, hyperlink_match)) => {
|
||||
check_hyperlink_match.check_iri_and_match(hyperlink_word, &hyperlink_match);
|
||||
Some(hyperlink) => {
|
||||
let hyperlink_match = hyperlink.range.to_alacritty();
|
||||
check_hyperlink_match.check_iri_and_match(hyperlink.text, &hyperlink_match);
|
||||
}
|
||||
None => {
|
||||
if expected_hyperlink.hyperlink_match.start()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
use alacritty_terminal::vte::ansi::{
|
||||
CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use gpui::{FontFallbacks, FontFeatures, FontWeight, Pixels, px};
|
||||
use schemars::JsonSchema;
|
||||
|
|
@ -163,23 +160,3 @@ impl From<settings::CursorShapeContent> for CursorShape {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CursorShape> for AlacCursorShape {
|
||||
fn from(value: CursorShape) -> Self {
|
||||
match value {
|
||||
CursorShape::Block => AlacCursorShape::Block,
|
||||
CursorShape::Underline => AlacCursorShape::Underline,
|
||||
CursorShape::Bar => AlacCursorShape::Beam,
|
||||
CursorShape::Hollow => AlacCursorShape::HollowBlock,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CursorShape> for AlacCursorStyle {
|
||||
fn from(value: CursorShape) -> Self {
|
||||
AlacCursorStyle {
|
||||
shape: value.into(),
|
||||
blinking: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ license = "GPL-3.0-or-later"
|
|||
workspace = true
|
||||
|
||||
[features]
|
||||
default = ["alacritty-backend"]
|
||||
alacritty-backend = ["project/alacritty-backend", "terminal/alacritty-backend"]
|
||||
libghostty-vt = ["project/libghostty-vt", "terminal/libghostty-vt"]
|
||||
test-support = ["editor/test-support", "gpui/test-support"]
|
||||
|
||||
[lib]
|
||||
|
|
|
|||
|
|
@ -1,14 +1,30 @@
|
|||
Design notes:
|
||||
# Terminal View
|
||||
|
||||
## Design Notes
|
||||
|
||||
This crate is split into two conceptual halves:
|
||||
- The terminal.rs file and the src/mappings/ folder, these contain the code for interacting with Alacritty and maintaining the pty event loop. Some behavior in this file is constrained by terminal protocols and standards. The Zed init function is also placed here.
|
||||
- The terminal.rs file and the src/mappings/ folder, these contain the code for interacting with terminal emulator backends and maintaining the pty event loop. Some behavior in this file is constrained by terminal protocols and standards. The Zed init function is also placed here.
|
||||
- Everything else. These other files integrate the `Terminal` struct created in terminal.rs into the rest of GPUI. The main entry point for GPUI is the terminal_view.rs file and the modal.rs file.
|
||||
|
||||
ttys are created externally, and so can fail in unexpected ways. However, GPUI currently does not have an API for models than can fail to instantiate. `TerminalBuilder` solves this by using Rust's type system to split tty instantiation into a 2 step process: first attempt to create the file handles with `TerminalBuilder::new()`, check the result, then call `TerminalBuilder::subscribe(cx)` from within a model context.
|
||||
|
||||
The TerminalView struct abstracts over failed and successful terminals, passing focus through to the associated view and allowing clients to build a terminal without worrying about errors.
|
||||
|
||||
#Input
|
||||
## Ghostty Backend
|
||||
|
||||
The optional `libghostty-vt` backend is more than a packaging swap. It lets Zed delegate VT parsing, terminal state, render snapshots, and mode-aware input encoding to Ghostty's terminal core while preserving Zed's existing `TerminalContent` and `TerminalView` integration surface.
|
||||
|
||||
What Zed gains from the backend:
|
||||
|
||||
- A terminal core with an embeddable API for render rows, cells, modes, cursor state, scrollback, title and bell callbacks, device attributes, size reports, and color-scheme responses.
|
||||
- Mode-aware key, focus, and mouse encoding based on Ghostty's current terminal state, reducing the amount of escape-sequence behavior Zed has to own locally.
|
||||
- More structured handling for the OSC sequences Zed integrates with today. Ghostty owns the terminal stream and Zed observes or adapts OSC 7 working-directory reports, OSC 8 hyperlinks, OSC 52 clipboard operations, and OSC 4/10/11/12 palette set/query/reset behavior so they continue to flow through Zed services and settings.
|
||||
- Render data that maps cleanly into existing frontend behavior: cursor shape and blink state, SGR styles, wide cells, hyperlinks, selection and copy, find, scroll-to-match, and vi-mode cursor/selection all pass through the same `TerminalContent` adapter.
|
||||
- A narrower path for future terminal conformance improvements. Advancing the pinned Ghostty source and checked-in bindings can bring in upstream terminal-protocol fixes without Zed reimplementing each behavior in its own backend.
|
||||
|
||||
The backend does not remove Zed-specific integration. PTY lifecycle, app-level clipboard policy, working-directory behavior, hyperlink activation, selection UX, and TerminalView/Agent Panel UI still live in Zed. It also does not mean every OSC path is automatically handled by Ghostty; some sequences are intentionally observed in Zed because they need to call Zed services or preserve existing user-facing behavior.
|
||||
|
||||
## Input
|
||||
|
||||
There are currently many distinct paths for getting keystrokes to the terminal:
|
||||
|
||||
|
|
|
|||
|
|
@ -13,16 +13,8 @@ use language::CursorShape;
|
|||
use settings::Settings;
|
||||
use std::time::Instant;
|
||||
use terminal::{
|
||||
IndexedCell, Terminal, TerminalBounds, TerminalContent,
|
||||
alacritty_terminal::{
|
||||
grid::Dimensions,
|
||||
index::Point as AlacPoint,
|
||||
term::{TermMode, cell::Flags},
|
||||
vte::ansi::{
|
||||
Color::{self as AnsiColor, Named},
|
||||
CursorShape as AlacCursorShape, NamedColor,
|
||||
},
|
||||
},
|
||||
IndexedCell, Terminal, TerminalBounds, TerminalColor, TerminalContent, TerminalCursorShape,
|
||||
TerminalModes, TerminalNamedColor, TerminalPoint, TerminalRange,
|
||||
terminal_settings::TerminalSettings,
|
||||
};
|
||||
use theme::{ActiveTheme, Theme};
|
||||
|
|
@ -33,7 +25,7 @@ use util::ResultExt;
|
|||
use workspace::Workspace;
|
||||
|
||||
use std::mem;
|
||||
use std::{fmt::Debug, ops::RangeInclusive, rc::Rc};
|
||||
use std::{fmt::Debug, rc::Rc};
|
||||
|
||||
use crate::{BlockContext, BlockProperties, ContentMode, TerminalMode, TerminalView};
|
||||
|
||||
|
|
@ -42,12 +34,12 @@ pub struct LayoutState {
|
|||
hitbox: Hitbox,
|
||||
batched_text_runs: Vec<BatchedTextRun>,
|
||||
rects: Vec<LayoutRect>,
|
||||
relative_highlighted_ranges: Vec<(RangeInclusive<AlacPoint>, Hsla)>,
|
||||
relative_highlighted_ranges: Vec<(TerminalRange, Hsla)>,
|
||||
cursor: Option<CursorLayout>,
|
||||
ime_cursor_bounds: Option<Bounds<Pixels>>,
|
||||
background_color: Hsla,
|
||||
dimensions: TerminalBounds,
|
||||
mode: TermMode,
|
||||
mode: TerminalModes,
|
||||
display_offset: usize,
|
||||
hyperlink_tooltip: Option<AnyElement>,
|
||||
block_below_cursor_element: Option<AnyElement>,
|
||||
|
|
@ -55,7 +47,7 @@ pub struct LayoutState {
|
|||
content_mode: ContentMode,
|
||||
}
|
||||
|
||||
/// Helper struct for converting data between Alacritty's cursor points, and displayed cursor points.
|
||||
/// Helper struct for converting terminal cursor points to displayed cursor points.
|
||||
#[derive(Copy, Clone)]
|
||||
struct DisplayCursor {
|
||||
line: i32,
|
||||
|
|
@ -63,10 +55,10 @@ struct DisplayCursor {
|
|||
}
|
||||
|
||||
impl DisplayCursor {
|
||||
fn from(cursor_point: AlacPoint, display_offset: usize) -> Self {
|
||||
fn from(cursor_point: TerminalPoint, display_offset: usize) -> Self {
|
||||
Self {
|
||||
line: cursor_point.line.0 + display_offset as i32,
|
||||
col: cursor_point.column.0,
|
||||
line: cursor_point.line + display_offset as i32,
|
||||
col: cursor_point.column,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,10 +71,30 @@ impl DisplayCursor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct LayoutPoint {
|
||||
line: i32,
|
||||
column: i32,
|
||||
}
|
||||
|
||||
impl LayoutPoint {
|
||||
fn new(line: i32, column: i32) -> Self {
|
||||
Self { line, column }
|
||||
}
|
||||
|
||||
pub fn line(&self) -> i32 {
|
||||
self.line
|
||||
}
|
||||
|
||||
pub fn column(&self) -> i32 {
|
||||
self.column
|
||||
}
|
||||
}
|
||||
|
||||
/// A batched text run that combines multiple adjacent cells with the same style
|
||||
#[derive(Debug)]
|
||||
pub struct BatchedTextRun {
|
||||
pub start_point: AlacPoint<i32, i32>,
|
||||
pub start_point: LayoutPoint,
|
||||
pub text: String,
|
||||
pub cell_count: usize,
|
||||
pub style: TextRun,
|
||||
|
|
@ -91,7 +103,7 @@ pub struct BatchedTextRun {
|
|||
|
||||
impl BatchedTextRun {
|
||||
fn new_from_char(
|
||||
start_point: AlacPoint<i32, i32>,
|
||||
start_point: LayoutPoint,
|
||||
c: char,
|
||||
style: TextRun,
|
||||
font_size: AbsoluteLength,
|
||||
|
|
@ -145,7 +157,7 @@ impl BatchedTextRun {
|
|||
origin.y + self.start_point.line as f32 * dimensions.line_height,
|
||||
);
|
||||
|
||||
let _ = window
|
||||
window
|
||||
.text_system()
|
||||
.shape_line(
|
||||
self.text.clone().into(),
|
||||
|
|
@ -160,19 +172,20 @@ impl BatchedTextRun {
|
|||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
)
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LayoutRect {
|
||||
point: AlacPoint<i32, i32>,
|
||||
point: LayoutPoint,
|
||||
num_of_cells: usize,
|
||||
color: Hsla,
|
||||
}
|
||||
|
||||
impl LayoutRect {
|
||||
fn new(point: AlacPoint<i32, i32>, num_of_cells: usize, color: Hsla) -> LayoutRect {
|
||||
fn new(point: LayoutPoint, num_of_cells: usize, color: Hsla) -> LayoutRect {
|
||||
LayoutRect {
|
||||
point,
|
||||
num_of_cells,
|
||||
|
|
@ -182,10 +195,10 @@ impl LayoutRect {
|
|||
|
||||
pub fn paint(&self, origin: Point<Pixels>, dimensions: &TerminalBounds, window: &mut Window) {
|
||||
let position = {
|
||||
let alac_point = self.point;
|
||||
let layout_point = self.point;
|
||||
point(
|
||||
(origin.x + alac_point.column as f32 * dimensions.cell_width).floor(),
|
||||
origin.y + alac_point.line as f32 * dimensions.line_height,
|
||||
(origin.x + layout_point.column as f32 * dimensions.cell_width).floor(),
|
||||
origin.y + layout_point.line as f32 * dimensions.line_height,
|
||||
)
|
||||
};
|
||||
let size = point(
|
||||
|
|
@ -326,13 +339,11 @@ impl TerminalElement {
|
|||
.track_focus(&focus)
|
||||
}
|
||||
|
||||
//Vec<Range<AlacPoint>> -> Clip out the parts of the ranges
|
||||
|
||||
pub fn layout_grid(
|
||||
grid: impl Iterator<Item = IndexedCell>,
|
||||
start_line_offset: i32,
|
||||
text_style: &TextStyle,
|
||||
hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>,
|
||||
hyperlink: Option<(HighlightStyle, &TerminalRange)>,
|
||||
minimum_contrast: f32,
|
||||
cx: &App,
|
||||
) -> (Vec<LayoutRect>, Vec<BatchedTextRun>) {
|
||||
|
|
@ -354,7 +365,7 @@ impl TerminalElement {
|
|||
// First pass: collect all cells and their backgrounds
|
||||
let linegroups = grid.into_iter().chunk_by(|i| i.point.line);
|
||||
for (line_index, (_, line)) in linegroups.into_iter().enumerate() {
|
||||
let alac_line = start_line_offset + line_index as i32;
|
||||
let display_line = start_line_offset + line_index as i32;
|
||||
|
||||
// Flush any existing batch at line boundaries
|
||||
if let Some(batch) = current_batch.take() {
|
||||
|
|
@ -366,29 +377,29 @@ impl TerminalElement {
|
|||
for cell in line {
|
||||
let mut fg = cell.fg;
|
||||
let mut bg = cell.bg;
|
||||
if cell.flags.contains(Flags::INVERSE) {
|
||||
if cell.is_inverse() {
|
||||
mem::swap(&mut fg, &mut bg);
|
||||
}
|
||||
|
||||
// Collect background regions (skip default background)
|
||||
if !matches!(bg, Named(NamedColor::Background)) {
|
||||
if !bg.is_default_background() {
|
||||
let color = convert_color(&bg, theme);
|
||||
let col = cell.point.column.0 as i32;
|
||||
let col = cell.point.column as i32;
|
||||
|
||||
// Try to extend the last region if it's on the same line with the same color
|
||||
if let Some(last_region) = background_regions.last_mut()
|
||||
&& last_region.color == color
|
||||
&& last_region.start_line == alac_line
|
||||
&& last_region.end_line == alac_line
|
||||
&& last_region.start_line == display_line
|
||||
&& last_region.end_line == display_line
|
||||
&& last_region.end_col + 1 == col
|
||||
{
|
||||
last_region.end_col = col;
|
||||
} else {
|
||||
background_regions.push(BackgroundRegion::new(alac_line, col, color));
|
||||
background_regions.push(BackgroundRegion::new(display_line, col, color));
|
||||
}
|
||||
}
|
||||
// Skip wide character spacers - they're just placeholders for the second cell of wide characters
|
||||
if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
|
||||
if cell.is_wide_char_spacer() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -415,7 +426,7 @@ impl TerminalElement {
|
|||
minimum_contrast,
|
||||
);
|
||||
|
||||
let cell_point = AlacPoint::new(alac_line, cell.point.column.0 as i32);
|
||||
let cell_point = LayoutPoint::new(display_line, cell.point.column as i32);
|
||||
let zero_width_chars = cell.zerowidth();
|
||||
|
||||
// Try to batch with existing run
|
||||
|
|
@ -477,7 +488,7 @@ impl TerminalElement {
|
|||
for region in merged_regions {
|
||||
for line in region.start_line..=region.end_line {
|
||||
rects.push(LayoutRect::new(
|
||||
AlacPoint::new(line, region.start_col),
|
||||
LayoutPoint::new(line, region.start_col),
|
||||
(region.end_col - region.start_col + 1) as usize,
|
||||
region.color,
|
||||
));
|
||||
|
|
@ -502,7 +513,7 @@ impl TerminalElement {
|
|||
|
||||
/// Computes the cursor position based on the cursor point and terminal dimensions.
|
||||
fn cursor_position(cursor_point: DisplayCursor, size: TerminalBounds) -> Option<Point<Pixels>> {
|
||||
if cursor_point.line() < size.total_lines() as i32 {
|
||||
if cursor_point.line() < size.num_lines() as i32 {
|
||||
// When on pixel boundaries round the origin down
|
||||
Some(point(
|
||||
(cursor_point.col() as f32 * size.cell_width()).floor(),
|
||||
|
|
@ -544,25 +555,20 @@ impl TerminalElement {
|
|||
/// 6x6x6 cube at 16..=231 and the 24-step grayscale ramp at 232..=255).
|
||||
/// Indices 0..=15 still go through contrast adjustment since those map to
|
||||
/// theme-defined ANSI colors that can clash with the theme background.
|
||||
fn is_app_chosen_exact_color(fg: &terminal::alacritty_terminal::vte::ansi::Color) -> bool {
|
||||
matches!(
|
||||
fg,
|
||||
terminal::alacritty_terminal::vte::ansi::Color::Spec(_)
|
||||
| terminal::alacritty_terminal::vte::ansi::Color::Indexed(16..=255)
|
||||
)
|
||||
fn is_app_chosen_exact_color(fg: &TerminalColor) -> bool {
|
||||
fg.is_app_chosen_exact()
|
||||
}
|
||||
|
||||
/// Converts the Alacritty cell styles to GPUI text styles and background color.
|
||||
/// Converts terminal cell styles to GPUI text styles and background color.
|
||||
fn cell_style(
|
||||
indexed: &IndexedCell,
|
||||
fg: terminal::alacritty_terminal::vte::ansi::Color,
|
||||
bg: terminal::alacritty_terminal::vte::ansi::Color,
|
||||
fg: TerminalColor,
|
||||
bg: TerminalColor,
|
||||
colors: &Theme,
|
||||
text_style: &TextStyle,
|
||||
hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>,
|
||||
hyperlink: Option<(HighlightStyle, &TerminalRange)>,
|
||||
minimum_contrast: f32,
|
||||
) -> TextRun {
|
||||
let flags = indexed.cell.flags;
|
||||
let skip_contrast = Self::is_app_chosen_exact_color(&fg);
|
||||
let mut fg = convert_color(&fg, colors);
|
||||
let bg = convert_color(&bg, colors);
|
||||
|
|
@ -573,32 +579,31 @@ impl TerminalElement {
|
|||
|
||||
// Ghostty uses (175/255) as the multiplier (~0.69), Alacritty uses 0.66, Kitty
|
||||
// uses 0.75. We're using 0.7 because it's pretty well in the middle of that.
|
||||
if flags.intersects(Flags::DIM) {
|
||||
if indexed.cell.is_dim() {
|
||||
fg.a *= 0.7;
|
||||
}
|
||||
|
||||
let underline = (flags.intersects(Flags::ALL_UNDERLINES)
|
||||
|| indexed.cell.hyperlink().is_some())
|
||||
.then(|| UnderlineStyle {
|
||||
color: Some(fg),
|
||||
thickness: Pixels::from(1.0),
|
||||
wavy: flags.contains(Flags::UNDERCURL),
|
||||
});
|
||||
|
||||
let strikethrough = flags
|
||||
.intersects(Flags::STRIKEOUT)
|
||||
.then(|| StrikethroughStyle {
|
||||
color: Some(fg),
|
||||
thickness: Pixels::from(1.0),
|
||||
let underline =
|
||||
(indexed.cell.has_underline() || indexed.cell.hyperlink().is_some()).then(|| {
|
||||
UnderlineStyle {
|
||||
color: Some(fg),
|
||||
thickness: Pixels::from(1.0),
|
||||
wavy: indexed.cell.has_undercurl(),
|
||||
}
|
||||
});
|
||||
|
||||
let weight = if flags.intersects(Flags::BOLD) {
|
||||
let strikethrough = indexed.cell.has_strikeout().then(|| StrikethroughStyle {
|
||||
color: Some(fg),
|
||||
thickness: Pixels::from(1.0),
|
||||
});
|
||||
|
||||
let weight = if indexed.cell.is_bold() {
|
||||
FontWeight::BOLD
|
||||
} else {
|
||||
text_style.font_weight
|
||||
};
|
||||
|
||||
let style = if flags.intersects(Flags::ITALIC) {
|
||||
let style = if indexed.cell.is_italic() {
|
||||
FontStyle::Italic
|
||||
} else {
|
||||
FontStyle::Normal
|
||||
|
|
@ -618,7 +623,7 @@ impl TerminalElement {
|
|||
};
|
||||
|
||||
if let Some((style, range)) = hyperlink
|
||||
&& range.contains(&indexed.point)
|
||||
&& range.contains(indexed.point)
|
||||
{
|
||||
if let Some(underline) = style.underline {
|
||||
result.underline = Some(underline);
|
||||
|
|
@ -654,7 +659,7 @@ impl TerminalElement {
|
|||
|
||||
fn register_mouse_listeners(
|
||||
&mut self,
|
||||
mode: TermMode,
|
||||
mode: TerminalModes,
|
||||
hitbox: &Hitbox,
|
||||
content_mode: &ContentMode,
|
||||
window: &mut Window,
|
||||
|
|
@ -760,7 +765,7 @@ impl TerminalElement {
|
|||
|
||||
// Mouse mode handlers:
|
||||
// All mouse modes need the extra click handlers
|
||||
if mode.intersects(TermMode::MOUSE_MODE) {
|
||||
if mode.intersects(TerminalModes::MOUSE_MODE) {
|
||||
self.interactivity.on_mouse_down(
|
||||
MouseButton::Right,
|
||||
TerminalElement::generic_button_handler(
|
||||
|
|
@ -1096,7 +1101,7 @@ impl Element for TerminalElement {
|
|||
}
|
||||
if let Some(selection) = selection {
|
||||
relative_highlighted_ranges
|
||||
.push((selection.start..=selection.end, player_color.selection));
|
||||
.push((selection.point_range(), player_color.selection));
|
||||
}
|
||||
|
||||
// then have that representation be converted to the appropriate highlight data structure
|
||||
|
|
@ -1204,20 +1209,22 @@ impl Element for TerminalElement {
|
|||
size: size(cursor_width.ceil(), dimensions.line_height),
|
||||
});
|
||||
|
||||
let cursor = if let AlacCursorShape::Hidden = cursor.shape {
|
||||
let cursor = if let TerminalCursorShape::Hidden = cursor.shape {
|
||||
None
|
||||
} else {
|
||||
let focused = self.focused;
|
||||
ime_cursor_bounds.map(move |bounds| {
|
||||
let (shape, text) = match cursor.shape {
|
||||
AlacCursorShape::Block if !focused => (CursorShape::Hollow, None),
|
||||
AlacCursorShape::Block => (CursorShape::Block, Some(cursor_text)),
|
||||
AlacCursorShape::Underline if !focused => (CursorShape::Hollow, None),
|
||||
AlacCursorShape::Underline => (CursorShape::Underline, None),
|
||||
AlacCursorShape::Beam if !focused => (CursorShape::Hollow, None),
|
||||
AlacCursorShape::Beam => (CursorShape::Bar, None),
|
||||
AlacCursorShape::HollowBlock => (CursorShape::Hollow, None),
|
||||
AlacCursorShape::Hidden => unreachable!(),
|
||||
TerminalCursorShape::Block if !focused => (CursorShape::Hollow, None),
|
||||
TerminalCursorShape::Block => (CursorShape::Block, Some(cursor_text)),
|
||||
TerminalCursorShape::Underline if !focused => {
|
||||
(CursorShape::Hollow, None)
|
||||
}
|
||||
TerminalCursorShape::Underline => (CursorShape::Underline, None),
|
||||
TerminalCursorShape::Bar if !focused => (CursorShape::Hollow, None),
|
||||
TerminalCursorShape::Bar => (CursorShape::Bar, None),
|
||||
TerminalCursorShape::HollowBlock => (CursorShape::Hollow, None),
|
||||
TerminalCursorShape::Hidden => unreachable!(),
|
||||
};
|
||||
|
||||
CursorLayout::new(
|
||||
|
|
@ -1234,7 +1241,7 @@ impl Element for TerminalElement {
|
|||
let block_below_cursor_element = if let Some(block) = &self.block_below_cursor {
|
||||
let terminal = self.terminal.read(cx);
|
||||
if terminal.last_content.display_offset == 0 {
|
||||
let target_line = terminal.last_content.cursor.point.line.0 + 1;
|
||||
let target_line = terminal.last_content.cursor.point.line + 1;
|
||||
let render = &block.render;
|
||||
let mut block_cx = BlockContext {
|
||||
window,
|
||||
|
|
@ -1490,7 +1497,7 @@ impl InputHandler for TerminalInputHandler {
|
|||
.read(cx)
|
||||
.last_content
|
||||
.mode
|
||||
.contains(TermMode::ALT_SCREEN)
|
||||
.contains(TerminalModes::ALT_SCREEN)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
|
|
@ -1594,7 +1601,7 @@ pub fn is_blank(cell: &IndexedCell) -> bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
if cell.bg != AnsiColor::Named(NamedColor::Background) {
|
||||
if !cell.bg.is_default_background() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1602,10 +1609,7 @@ pub fn is_blank(cell: &IndexedCell) -> bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
if cell
|
||||
.flags
|
||||
.intersects(Flags::ALL_UNDERLINES | Flags::INVERSE | Flags::STRIKEOUT)
|
||||
{
|
||||
if cell.has_visible_style_modifier() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1613,7 +1617,7 @@ pub fn is_blank(cell: &IndexedCell) -> bool {
|
|||
}
|
||||
|
||||
fn to_highlighted_range_lines(
|
||||
range: &RangeInclusive<AlacPoint>,
|
||||
range: &TerminalRange,
|
||||
layout: &LayoutState,
|
||||
origin: Point<Pixels>,
|
||||
) -> Option<(Pixels, Vec<HighlightedRangeLine>)> {
|
||||
|
|
@ -1635,24 +1639,20 @@ fn to_highlighted_range_lines(
|
|||
// of the grid data we should be looking at. But for the rendering step, we don't
|
||||
// want negatives. We want things relative to the 'viewport' (the area of the grid
|
||||
// which is currently shown according to the display offset)
|
||||
let unclamped_start = AlacPoint::new(
|
||||
range.start().line + layout.display_offset,
|
||||
range.start().column,
|
||||
);
|
||||
let unclamped_end =
|
||||
AlacPoint::new(range.end().line + layout.display_offset, range.end().column);
|
||||
let display_offset = i32::try_from(layout.display_offset).unwrap_or(i32::MAX);
|
||||
let unclamped_start_line = range.start().line.saturating_add(display_offset);
|
||||
let unclamped_start_column = range.start().column;
|
||||
let unclamped_end_line = range.end().line.saturating_add(display_offset);
|
||||
let unclamped_end_column = range.end().column;
|
||||
|
||||
// Step 2. Clamp range to viewport, and return None if it doesn't overlap
|
||||
if unclamped_end.line.0 < 0 || unclamped_start.line.0 > layout.dimensions.num_lines() as i32 {
|
||||
if unclamped_end_line < 0 || unclamped_start_line > layout.dimensions.num_lines() as i32 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let clamped_start_line = unclamped_start.line.0.max(0) as usize;
|
||||
let clamped_start_line = unclamped_start_line.max(0) as usize;
|
||||
|
||||
let clamped_end_line = unclamped_end
|
||||
.line
|
||||
.0
|
||||
.min(layout.dimensions.num_lines() as i32) as usize;
|
||||
let clamped_end_line = unclamped_end_line.min(layout.dimensions.num_lines() as i32) as usize;
|
||||
|
||||
// Convert the start of the range to pixels
|
||||
let start_y = origin.y + clamped_start_line as f32 * layout.dimensions.line_height;
|
||||
|
|
@ -1662,14 +1662,13 @@ fn to_highlighted_range_lines(
|
|||
let mut highlighted_range_lines = Vec::new();
|
||||
for line in clamped_start_line..=clamped_end_line {
|
||||
let mut line_start = 0;
|
||||
let mut line_end = layout.dimensions.columns();
|
||||
let mut line_end = layout.dimensions.num_columns();
|
||||
|
||||
if line == clamped_start_line && unclamped_start.line.0 >= 0 {
|
||||
line_start = unclamped_start.column.0;
|
||||
if line == clamped_start_line && unclamped_start_line >= 0 {
|
||||
line_start = unclamped_start_column;
|
||||
}
|
||||
if line == clamped_end_line && unclamped_end.line.0 <= layout.dimensions.num_lines() as i32
|
||||
{
|
||||
line_end = unclamped_end.column.0 + 1; // +1 for inclusive
|
||||
if line == clamped_end_line && unclamped_end_line <= layout.dimensions.num_lines() as i32 {
|
||||
line_end = unclamped_end_column + 1; // +1 for inclusive
|
||||
}
|
||||
|
||||
highlighted_range_lines.push(HighlightedRangeLine {
|
||||
|
|
@ -1682,49 +1681,45 @@ fn to_highlighted_range_lines(
|
|||
}
|
||||
|
||||
/// Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent.
|
||||
pub fn convert_color(fg: &terminal::alacritty_terminal::vte::ansi::Color, theme: &Theme) -> Hsla {
|
||||
pub fn convert_color(fg: &TerminalColor, theme: &Theme) -> Hsla {
|
||||
let colors = theme.colors();
|
||||
match fg {
|
||||
// Named and theme defined colors
|
||||
terminal::alacritty_terminal::vte::ansi::Color::Named(n) => match n {
|
||||
NamedColor::Black => colors.terminal_ansi_black,
|
||||
NamedColor::Red => colors.terminal_ansi_red,
|
||||
NamedColor::Green => colors.terminal_ansi_green,
|
||||
NamedColor::Yellow => colors.terminal_ansi_yellow,
|
||||
NamedColor::Blue => colors.terminal_ansi_blue,
|
||||
NamedColor::Magenta => colors.terminal_ansi_magenta,
|
||||
NamedColor::Cyan => colors.terminal_ansi_cyan,
|
||||
NamedColor::White => colors.terminal_ansi_white,
|
||||
NamedColor::BrightBlack => colors.terminal_ansi_bright_black,
|
||||
NamedColor::BrightRed => colors.terminal_ansi_bright_red,
|
||||
NamedColor::BrightGreen => colors.terminal_ansi_bright_green,
|
||||
NamedColor::BrightYellow => colors.terminal_ansi_bright_yellow,
|
||||
NamedColor::BrightBlue => colors.terminal_ansi_bright_blue,
|
||||
NamedColor::BrightMagenta => colors.terminal_ansi_bright_magenta,
|
||||
NamedColor::BrightCyan => colors.terminal_ansi_bright_cyan,
|
||||
NamedColor::BrightWhite => colors.terminal_ansi_bright_white,
|
||||
NamedColor::Foreground => colors.terminal_foreground,
|
||||
NamedColor::Background => colors.terminal_ansi_background,
|
||||
NamedColor::Cursor => theme.players().local().cursor,
|
||||
NamedColor::DimBlack => colors.terminal_ansi_dim_black,
|
||||
NamedColor::DimRed => colors.terminal_ansi_dim_red,
|
||||
NamedColor::DimGreen => colors.terminal_ansi_dim_green,
|
||||
NamedColor::DimYellow => colors.terminal_ansi_dim_yellow,
|
||||
NamedColor::DimBlue => colors.terminal_ansi_dim_blue,
|
||||
NamedColor::DimMagenta => colors.terminal_ansi_dim_magenta,
|
||||
NamedColor::DimCyan => colors.terminal_ansi_dim_cyan,
|
||||
NamedColor::DimWhite => colors.terminal_ansi_dim_white,
|
||||
NamedColor::BrightForeground => colors.terminal_bright_foreground,
|
||||
NamedColor::DimForeground => colors.terminal_dim_foreground,
|
||||
TerminalColor::Named(color) => match color {
|
||||
TerminalNamedColor::Black => colors.terminal_ansi_black,
|
||||
TerminalNamedColor::Red => colors.terminal_ansi_red,
|
||||
TerminalNamedColor::Green => colors.terminal_ansi_green,
|
||||
TerminalNamedColor::Yellow => colors.terminal_ansi_yellow,
|
||||
TerminalNamedColor::Blue => colors.terminal_ansi_blue,
|
||||
TerminalNamedColor::Magenta => colors.terminal_ansi_magenta,
|
||||
TerminalNamedColor::Cyan => colors.terminal_ansi_cyan,
|
||||
TerminalNamedColor::White => colors.terminal_ansi_white,
|
||||
TerminalNamedColor::BrightBlack => colors.terminal_ansi_bright_black,
|
||||
TerminalNamedColor::BrightRed => colors.terminal_ansi_bright_red,
|
||||
TerminalNamedColor::BrightGreen => colors.terminal_ansi_bright_green,
|
||||
TerminalNamedColor::BrightYellow => colors.terminal_ansi_bright_yellow,
|
||||
TerminalNamedColor::BrightBlue => colors.terminal_ansi_bright_blue,
|
||||
TerminalNamedColor::BrightMagenta => colors.terminal_ansi_bright_magenta,
|
||||
TerminalNamedColor::BrightCyan => colors.terminal_ansi_bright_cyan,
|
||||
TerminalNamedColor::BrightWhite => colors.terminal_ansi_bright_white,
|
||||
TerminalNamedColor::Foreground => colors.terminal_foreground,
|
||||
TerminalNamedColor::Background => colors.terminal_ansi_background,
|
||||
TerminalNamedColor::Cursor => theme.players().local().cursor,
|
||||
TerminalNamedColor::DimBlack => colors.terminal_ansi_dim_black,
|
||||
TerminalNamedColor::DimRed => colors.terminal_ansi_dim_red,
|
||||
TerminalNamedColor::DimGreen => colors.terminal_ansi_dim_green,
|
||||
TerminalNamedColor::DimYellow => colors.terminal_ansi_dim_yellow,
|
||||
TerminalNamedColor::DimBlue => colors.terminal_ansi_dim_blue,
|
||||
TerminalNamedColor::DimMagenta => colors.terminal_ansi_dim_magenta,
|
||||
TerminalNamedColor::DimCyan => colors.terminal_ansi_dim_cyan,
|
||||
TerminalNamedColor::DimWhite => colors.terminal_ansi_dim_white,
|
||||
TerminalNamedColor::BrightForeground => colors.terminal_bright_foreground,
|
||||
TerminalNamedColor::DimForeground => colors.terminal_dim_foreground,
|
||||
},
|
||||
// 'True' colors
|
||||
terminal::alacritty_terminal::vte::ansi::Color::Spec(rgb) => {
|
||||
terminal::rgba_color(rgb.r, rgb.g, rgb.b)
|
||||
}
|
||||
TerminalColor::Spec(rgb) => terminal::rgba_color(rgb.r, rgb.g, rgb.b),
|
||||
// 8 bit, indexed colors
|
||||
terminal::alacritty_terminal::vte::ansi::Color::Indexed(i) => {
|
||||
terminal::get_color_at_index(*i as usize, theme)
|
||||
}
|
||||
TerminalColor::Indexed(i) => terminal::get_color_at_index(*i as usize, theme),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1833,51 +1828,51 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_is_app_chosen_exact_color() {
|
||||
use terminal::alacritty_terminal::vte::ansi::{Color, NamedColor, Rgb};
|
||||
use terminal::{TerminalColor, TerminalNamedColor, TerminalRgb};
|
||||
|
||||
// Indices 0..=15 are theme-overridable ANSI colors; contrast adjustment must still apply.
|
||||
assert!(!TerminalElement::is_app_chosen_exact_color(
|
||||
&Color::Indexed(0)
|
||||
&TerminalColor::Indexed(0)
|
||||
));
|
||||
assert!(!TerminalElement::is_app_chosen_exact_color(
|
||||
&Color::Indexed(15)
|
||||
&TerminalColor::Indexed(15)
|
||||
));
|
||||
|
||||
// Boundary: index 16 is the first entry of the 6x6x6 cube — application-chosen.
|
||||
assert!(TerminalElement::is_app_chosen_exact_color(&Color::Indexed(
|
||||
16
|
||||
)));
|
||||
assert!(TerminalElement::is_app_chosen_exact_color(
|
||||
&TerminalColor::Indexed(16)
|
||||
));
|
||||
// Interior of the cube.
|
||||
assert!(TerminalElement::is_app_chosen_exact_color(&Color::Indexed(
|
||||
17
|
||||
)));
|
||||
assert!(TerminalElement::is_app_chosen_exact_color(&Color::Indexed(
|
||||
231
|
||||
)));
|
||||
assert!(TerminalElement::is_app_chosen_exact_color(
|
||||
&TerminalColor::Indexed(17)
|
||||
));
|
||||
assert!(TerminalElement::is_app_chosen_exact_color(
|
||||
&TerminalColor::Indexed(231)
|
||||
));
|
||||
// Grayscale ramp boundaries.
|
||||
assert!(TerminalElement::is_app_chosen_exact_color(&Color::Indexed(
|
||||
232
|
||||
)));
|
||||
assert!(TerminalElement::is_app_chosen_exact_color(&Color::Indexed(
|
||||
255
|
||||
)));
|
||||
assert!(TerminalElement::is_app_chosen_exact_color(
|
||||
&TerminalColor::Indexed(232)
|
||||
));
|
||||
assert!(TerminalElement::is_app_chosen_exact_color(
|
||||
&TerminalColor::Indexed(255)
|
||||
));
|
||||
|
||||
// 24-bit true color is always application-chosen.
|
||||
assert!(TerminalElement::is_app_chosen_exact_color(&Color::Spec(
|
||||
Rgb {
|
||||
assert!(TerminalElement::is_app_chosen_exact_color(
|
||||
&TerminalColor::Spec(TerminalRgb {
|
||||
r: 10,
|
||||
g: 20,
|
||||
b: 30
|
||||
}
|
||||
)));
|
||||
})
|
||||
));
|
||||
|
||||
// Named colors are theme-defined and must go through contrast adjustment.
|
||||
assert!(!TerminalElement::is_app_chosen_exact_color(&Color::Named(
|
||||
NamedColor::Red
|
||||
)));
|
||||
assert!(!TerminalElement::is_app_chosen_exact_color(&Color::Named(
|
||||
NamedColor::Foreground
|
||||
)));
|
||||
assert!(!TerminalElement::is_app_chosen_exact_color(
|
||||
&TerminalColor::Named(TerminalNamedColor::Red)
|
||||
));
|
||||
assert!(!TerminalElement::is_app_chosen_exact_color(
|
||||
&TerminalColor::Named(TerminalNamedColor::Foreground)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -2054,7 +2049,7 @@ mod tests {
|
|||
};
|
||||
|
||||
let font_size = AbsoluteLength::Pixels(px(12.0));
|
||||
let batch = BatchedTextRun::new_from_char(AlacPoint::new(0, 0), 'a', style1, font_size);
|
||||
let batch = BatchedTextRun::new_from_char(LayoutPoint::new(0, 0), 'a', style1, font_size);
|
||||
|
||||
// Should be able to append same style
|
||||
assert!(batch.can_append(&style2));
|
||||
|
|
@ -2073,7 +2068,8 @@ mod tests {
|
|||
};
|
||||
|
||||
let font_size = AbsoluteLength::Pixels(px(12.0));
|
||||
let mut batch = BatchedTextRun::new_from_char(AlacPoint::new(0, 0), 'a', style, font_size);
|
||||
let mut batch =
|
||||
BatchedTextRun::new_from_char(LayoutPoint::new(0, 0), 'a', style, font_size);
|
||||
|
||||
assert_eq!(batch.text, "a");
|
||||
assert_eq!(batch.cell_count, 1);
|
||||
|
|
@ -2102,7 +2098,8 @@ mod tests {
|
|||
};
|
||||
|
||||
let font_size = AbsoluteLength::Pixels(px(12.0));
|
||||
let mut batch = BatchedTextRun::new_from_char(AlacPoint::new(0, 0), 'x', style, font_size);
|
||||
let mut batch =
|
||||
BatchedTextRun::new_from_char(LayoutPoint::new(0, 0), 'x', style, font_size);
|
||||
|
||||
assert_eq!(batch.text, "x");
|
||||
assert_eq!(batch.cell_count, 1);
|
||||
|
|
@ -2132,7 +2129,8 @@ mod tests {
|
|||
};
|
||||
|
||||
let font_size = AbsoluteLength::Pixels(px(12.0));
|
||||
let mut batch = BatchedTextRun::new_from_char(AlacPoint::new(0, 0), 'x', style, font_size);
|
||||
let mut batch =
|
||||
BatchedTextRun::new_from_char(LayoutPoint::new(0, 0), 'x', style, font_size);
|
||||
|
||||
let combining = '\u{0301}';
|
||||
batch.append_zero_width_chars(&[combining]);
|
||||
|
|
@ -2244,17 +2242,15 @@ mod tests {
|
|||
// This works for both Scrollable and Inline modes because we filter
|
||||
// by enumerated line group index, not by cell.point.line values.
|
||||
use itertools::Itertools;
|
||||
use terminal::IndexedCell;
|
||||
use terminal::alacritty_terminal::index::{Column, Line, Point as AlacPoint};
|
||||
use terminal::alacritty_terminal::term::cell::Cell;
|
||||
use terminal::{IndexedCell, TerminalCell, TerminalPoint};
|
||||
|
||||
// Create mock cells for lines 0-23 (typical terminal with 24 visible lines)
|
||||
let mut cells = Vec::new();
|
||||
for line in 0..24i32 {
|
||||
for col in 0..3i32 {
|
||||
cells.push(IndexedCell {
|
||||
point: AlacPoint::new(Line(line), Column(col as usize)),
|
||||
cell: Cell::default(),
|
||||
point: TerminalPoint::new(line, col as usize),
|
||||
cell: TerminalCell::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -2280,14 +2276,14 @@ mod tests {
|
|||
// First filtered cell should be line 5
|
||||
assert_eq!(
|
||||
filtered.first().unwrap().point.line,
|
||||
Line(5),
|
||||
5,
|
||||
"First cell should be on line 5"
|
||||
);
|
||||
|
||||
// Last filtered cell should be line 15
|
||||
assert_eq!(
|
||||
filtered.last().unwrap().point.line,
|
||||
Line(15),
|
||||
15,
|
||||
"Last cell should be on line 15"
|
||||
);
|
||||
}
|
||||
|
|
@ -2298,9 +2294,7 @@ mod tests {
|
|||
// for scrollback history. The screen-position filtering approach works because
|
||||
// we filter by enumerated line group index, not by cell.point.line values.
|
||||
use itertools::Itertools;
|
||||
use terminal::IndexedCell;
|
||||
use terminal::alacritty_terminal::index::{Column, Line, Point as AlacPoint};
|
||||
use terminal::alacritty_terminal::term::cell::Cell;
|
||||
use terminal::{IndexedCell, TerminalCell, TerminalPoint};
|
||||
|
||||
// Simulate cells from a scrolled terminal with scrollback
|
||||
// These have negative line numbers representing scrollback history
|
||||
|
|
@ -2308,8 +2302,8 @@ mod tests {
|
|||
for line in -588i32..=-578i32 {
|
||||
for col in 0..80i32 {
|
||||
scrollback_cells.push(IndexedCell {
|
||||
point: AlacPoint::new(Line(line), Column(col as usize)),
|
||||
cell: Cell::default(),
|
||||
point: TerminalPoint::new(line, col as usize),
|
||||
cell: TerminalCell::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -2334,14 +2328,14 @@ mod tests {
|
|||
// First filtered cell should be line -585 (skipped 3 lines from -588)
|
||||
assert_eq!(
|
||||
filtered.first().unwrap().point.line,
|
||||
Line(-585),
|
||||
-585,
|
||||
"First cell should be on line -585"
|
||||
);
|
||||
|
||||
// Last filtered cell should be line -581 (5 lines: -585, -584, -583, -582, -581)
|
||||
assert_eq!(
|
||||
filtered.last().unwrap().point.line,
|
||||
Line(-581),
|
||||
-581,
|
||||
"Last cell should be on line -581"
|
||||
);
|
||||
}
|
||||
|
|
@ -2350,15 +2344,13 @@ mod tests {
|
|||
fn test_screen_position_filtering_skip_all() {
|
||||
// Test what happens when we skip more rows than exist
|
||||
use itertools::Itertools;
|
||||
use terminal::IndexedCell;
|
||||
use terminal::alacritty_terminal::index::{Column, Line, Point as AlacPoint};
|
||||
use terminal::alacritty_terminal::term::cell::Cell;
|
||||
use terminal::{IndexedCell, TerminalCell, TerminalPoint};
|
||||
|
||||
let mut cells = Vec::new();
|
||||
for line in 0..10i32 {
|
||||
cells.push(IndexedCell {
|
||||
point: AlacPoint::new(Line(line), Column(0)),
|
||||
cell: Cell::default(),
|
||||
point: TerminalPoint::new(line, 0),
|
||||
cell: TerminalCell::default(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -2422,16 +2414,15 @@ mod tests {
|
|||
// not by cell.point.line values. This makes the filtering agnostic to the
|
||||
// actual line numbers in the cells.
|
||||
use itertools::Itertools;
|
||||
use terminal::IndexedCell;
|
||||
use terminal::alacritty_terminal::index::{Column, Line, Point as AlacPoint};
|
||||
use terminal::alacritty_terminal::term::cell::Cell;
|
||||
use terminal::TerminalPoint;
|
||||
use terminal::{IndexedCell, TerminalCell};
|
||||
|
||||
// Test with positive line numbers (Inline mode style)
|
||||
let positive_cells: Vec<_> = (0..10i32)
|
||||
.flat_map(|line| {
|
||||
(0..3i32).map(move |col| IndexedCell {
|
||||
point: AlacPoint::new(Line(line), Column(col as usize)),
|
||||
cell: Cell::default(),
|
||||
point: TerminalPoint::new(line, col as usize),
|
||||
cell: TerminalCell::default(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -2440,8 +2431,8 @@ mod tests {
|
|||
let negative_cells: Vec<_> = (-10i32..0i32)
|
||||
.flat_map(|line| {
|
||||
(0..3i32).map(move |col| IndexedCell {
|
||||
point: AlacPoint::new(Line(line), Column(col as usize)),
|
||||
cell: Cell::default(),
|
||||
point: TerminalPoint::new(line, col as usize),
|
||||
cell: TerminalCell::default(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -2474,11 +2465,11 @@ mod tests {
|
|||
assert_eq!(negative_filtered.len(), 12);
|
||||
|
||||
// Positive: lines 3, 4, 5, 6
|
||||
assert_eq!(positive_filtered.first().unwrap().point.line, Line(3));
|
||||
assert_eq!(positive_filtered.last().unwrap().point.line, Line(6));
|
||||
assert_eq!(positive_filtered.first().unwrap().point.line, 3);
|
||||
assert_eq!(positive_filtered.last().unwrap().point.line, 6);
|
||||
|
||||
// Negative: lines -7, -6, -5, -4
|
||||
assert_eq!(negative_filtered.first().unwrap().point.line, Line(-7));
|
||||
assert_eq!(negative_filtered.last().unwrap().point.line, Line(-4));
|
||||
assert_eq!(negative_filtered.first().unwrap().point.line, -7);
|
||||
assert_eq!(negative_filtered.last().unwrap().point.line, -4);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -535,8 +535,7 @@ mod tests {
|
|||
use serde_json::json;
|
||||
use std::path::{Path, PathBuf};
|
||||
use terminal::{
|
||||
HoveredWord, TerminalBuilder,
|
||||
alacritty_terminal::index::Point as AlacPoint,
|
||||
HoveredWord, TerminalBuilder, TerminalPoint, TerminalRange,
|
||||
terminal_settings::{AlternateScroll, CursorShape},
|
||||
};
|
||||
use util::path;
|
||||
|
|
@ -581,6 +580,7 @@ mod tests {
|
|||
0,
|
||||
cx.background_executor(),
|
||||
PathStyle::local(),
|
||||
cx,
|
||||
)
|
||||
.expect("Failed to create display-only terminal")
|
||||
.subscribe(cx)
|
||||
|
|
@ -653,7 +653,7 @@ mod tests {
|
|||
let (hover_target, open_target) = test_path_like(
|
||||
HoveredWord {
|
||||
word: maybe_path.to_string(),
|
||||
word_match: AlacPoint::default()..=AlacPoint::default(),
|
||||
word_match: TerminalRange::new(TerminalPoint::new(0, 0), TerminalPoint::new(0, 0)),
|
||||
id: 0,
|
||||
},
|
||||
PathLikeTarget {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use settings::{
|
|||
use std::{
|
||||
any::Any,
|
||||
cmp,
|
||||
ops::{Range, RangeInclusive},
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
|
|
@ -36,11 +36,8 @@ use task::TaskId;
|
|||
use terminal::{
|
||||
Clear, Copy, Event, HoveredWord, MaybeNavigationTarget, Paste, PasteText, ScrollLineDown,
|
||||
ScrollLineUp, ScrollPageDown, ScrollPageUp, ScrollToBottom, ScrollToTop, ShowCharacterPalette,
|
||||
TaskState, TaskStatus, Terminal, TerminalBounds, ToggleViMode,
|
||||
alacritty_terminal::{
|
||||
index::Point as AlacPoint,
|
||||
term::{TermMode, point_to_viewport, search::RegexSearch},
|
||||
},
|
||||
TaskState, TaskStatus, Terminal, TerminalBounds, TerminalModes, TerminalPoint, TerminalRange,
|
||||
TerminalSearch, ToggleViMode,
|
||||
terminal_settings::{CursorShape, TerminalSettings},
|
||||
};
|
||||
use terminal_element::TerminalElement;
|
||||
|
|
@ -70,6 +67,16 @@ struct ImeState {
|
|||
marked_text: String,
|
||||
}
|
||||
|
||||
fn viewport_line_for_point(point: TerminalPoint, display_offset: usize) -> Option<usize> {
|
||||
let display_offset = i32::try_from(display_offset).unwrap_or(i32::MAX);
|
||||
let line = point.line.saturating_add(display_offset);
|
||||
if line < 0 {
|
||||
None
|
||||
} else {
|
||||
usize::try_from(line).ok()
|
||||
}
|
||||
}
|
||||
|
||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
|
||||
/// Event to transmit the scroll from the element to the view
|
||||
|
|
@ -596,7 +603,7 @@ impl TerminalView {
|
|||
.read(cx)
|
||||
.last_content
|
||||
.mode
|
||||
.contains(TermMode::ALT_SCREEN)
|
||||
.contains(TerminalModes::ALT_SCREEN)
|
||||
{
|
||||
self.terminal.update(cx, |term, cx| {
|
||||
term.try_keystroke(
|
||||
|
|
@ -639,13 +646,13 @@ impl TerminalView {
|
|||
|
||||
let line_height = terminal.last_content().terminal_bounds.line_height;
|
||||
let viewport_lines = terminal.viewport_lines();
|
||||
let cursor = point_to_viewport(
|
||||
terminal.last_content.display_offset,
|
||||
let cursor_line = viewport_line_for_point(
|
||||
terminal.last_content.cursor.point,
|
||||
terminal.last_content.display_offset,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
let max_scroll_top_in_lines =
|
||||
(block.height as usize).saturating_sub(viewport_lines.saturating_sub(cursor.line + 1));
|
||||
(block.height as usize).saturating_sub(viewport_lines.saturating_sub(cursor_line + 1));
|
||||
|
||||
max_scroll_top_in_lines as f32 * line_height
|
||||
}
|
||||
|
|
@ -770,7 +777,7 @@ impl TerminalView {
|
|||
.read(cx)
|
||||
.last_content
|
||||
.mode
|
||||
.contains(TermMode::ALT_SCREEN)
|
||||
.contains(TerminalModes::ALT_SCREEN)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -894,55 +901,55 @@ impl TerminalView {
|
|||
let mode = self.terminal.read(cx).last_content.mode;
|
||||
dispatch_context.set(
|
||||
"screen",
|
||||
if mode.contains(TermMode::ALT_SCREEN) {
|
||||
if mode.contains(TerminalModes::ALT_SCREEN) {
|
||||
"alt"
|
||||
} else {
|
||||
"normal"
|
||||
},
|
||||
);
|
||||
|
||||
if mode.contains(TermMode::APP_CURSOR) {
|
||||
if mode.contains(TerminalModes::APP_CURSOR) {
|
||||
dispatch_context.add("DECCKM");
|
||||
}
|
||||
if mode.contains(TermMode::APP_KEYPAD) {
|
||||
if mode.contains(TerminalModes::APP_KEYPAD) {
|
||||
dispatch_context.add("DECPAM");
|
||||
} else {
|
||||
dispatch_context.add("DECPNM");
|
||||
}
|
||||
if mode.contains(TermMode::SHOW_CURSOR) {
|
||||
if mode.contains(TerminalModes::SHOW_CURSOR) {
|
||||
dispatch_context.add("DECTCEM");
|
||||
}
|
||||
if mode.contains(TermMode::LINE_WRAP) {
|
||||
if mode.contains(TerminalModes::LINE_WRAP) {
|
||||
dispatch_context.add("DECAWM");
|
||||
}
|
||||
if mode.contains(TermMode::ORIGIN) {
|
||||
if mode.contains(TerminalModes::ORIGIN) {
|
||||
dispatch_context.add("DECOM");
|
||||
}
|
||||
if mode.contains(TermMode::INSERT) {
|
||||
if mode.contains(TerminalModes::INSERT) {
|
||||
dispatch_context.add("IRM");
|
||||
}
|
||||
//LNM is apparently the name for this. https://vt100.net/docs/vt510-rm/LNM.html
|
||||
if mode.contains(TermMode::LINE_FEED_NEW_LINE) {
|
||||
if mode.contains(TerminalModes::LINE_FEED_NEW_LINE) {
|
||||
dispatch_context.add("LNM");
|
||||
}
|
||||
if mode.contains(TermMode::FOCUS_IN_OUT) {
|
||||
if mode.contains(TerminalModes::FOCUS_IN_OUT) {
|
||||
dispatch_context.add("report_focus");
|
||||
}
|
||||
if mode.contains(TermMode::ALTERNATE_SCROLL) {
|
||||
if mode.contains(TerminalModes::ALTERNATE_SCROLL) {
|
||||
dispatch_context.add("alternate_scroll");
|
||||
}
|
||||
if mode.contains(TermMode::BRACKETED_PASTE) {
|
||||
if mode.contains(TerminalModes::BRACKETED_PASTE) {
|
||||
dispatch_context.add("bracketed_paste");
|
||||
}
|
||||
if mode.intersects(TermMode::MOUSE_MODE) {
|
||||
if mode.intersects(TerminalModes::MOUSE_MODE) {
|
||||
dispatch_context.add("any_mouse_reporting");
|
||||
}
|
||||
{
|
||||
let mouse_reporting = if mode.contains(TermMode::MOUSE_REPORT_CLICK) {
|
||||
let mouse_reporting = if mode.contains(TerminalModes::MOUSE_REPORT_CLICK) {
|
||||
"click"
|
||||
} else if mode.contains(TermMode::MOUSE_DRAG) {
|
||||
} else if mode.contains(TerminalModes::MOUSE_DRAG) {
|
||||
"drag"
|
||||
} else if mode.contains(TermMode::MOUSE_MOTION) {
|
||||
} else if mode.contains(TerminalModes::MOUSE_MOTION) {
|
||||
"motion"
|
||||
} else {
|
||||
"off"
|
||||
|
|
@ -950,9 +957,9 @@ impl TerminalView {
|
|||
dispatch_context.set("mouse_reporting", mouse_reporting);
|
||||
}
|
||||
{
|
||||
let format = if mode.contains(TermMode::SGR_MOUSE) {
|
||||
let format = if mode.contains(TerminalModes::SGR_MOUSE) {
|
||||
"sgr"
|
||||
} else if mode.contains(TermMode::UTF8_MOUSE) {
|
||||
} else if mode.contains(TerminalModes::UTF8_MOUSE) {
|
||||
"utf8"
|
||||
} else {
|
||||
"normal"
|
||||
|
|
@ -1131,15 +1138,15 @@ fn subscribe_for_terminal_events(
|
|||
vec![terminal_subscription, terminal_events_subscription]
|
||||
}
|
||||
|
||||
fn regex_search_for_query(query: &SearchQuery) -> Option<RegexSearch> {
|
||||
fn regex_search_for_query(query: &SearchQuery) -> Option<TerminalSearch> {
|
||||
let str = query.as_str();
|
||||
if query.is_regex() {
|
||||
if str == "." {
|
||||
return None;
|
||||
}
|
||||
RegexSearch::new(str).ok()
|
||||
TerminalSearch::new(str)
|
||||
} else {
|
||||
RegexSearch::new(®ex::escape(str)).ok()
|
||||
TerminalSearch::new(®ex::escape(str))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1334,6 +1341,7 @@ impl Item for TerminalView {
|
|||
Some(TabTooltipContent::Custom(Box::new(Tooltip::element({
|
||||
let terminal = self.terminal().read(cx);
|
||||
let title = terminal.title(false);
|
||||
let backend_name = terminal.backend_name();
|
||||
let pid = terminal.pid_getter()?.fallback_pid();
|
||||
|
||||
move |_, _| {
|
||||
|
|
@ -1346,6 +1354,11 @@ impl Item for TerminalView {
|
|||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Label::new(format!("Backend: {backend_name}"))
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}))))
|
||||
|
|
@ -1416,6 +1429,15 @@ impl Item for TerminalView {
|
|||
)
|
||||
}),
|
||||
)
|
||||
.when(terminal.is_ghostty_backend(), |this| {
|
||||
this.child(
|
||||
div()
|
||||
.id("terminal-ghostty-backend-indicator")
|
||||
.flex_none()
|
||||
.tooltip(Tooltip::text("Ghostty terminal backend"))
|
||||
.child(Label::new("👻").size(LabelSize::Small).color(Color::Muted)),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.relative()
|
||||
|
|
@ -1848,7 +1870,7 @@ impl SerializableItem for TerminalView {
|
|||
}
|
||||
|
||||
impl SearchableItem for TerminalView {
|
||||
type Match = RangeInclusive<AlacPoint>;
|
||||
type Match = TerminalRange;
|
||||
|
||||
fn supported_options(&self) -> SearchOptions {
|
||||
SearchOptions {
|
||||
|
|
@ -1962,8 +1984,8 @@ impl SearchableItem for TerminalView {
|
|||
.enumerate()
|
||||
.rev()
|
||||
.find(|(_, search_match)| {
|
||||
search_match.contains(&selection_head)
|
||||
|| search_match.start() < &selection_head
|
||||
search_match.contains(selection_head)
|
||||
|| search_match.start() < selection_head
|
||||
})
|
||||
.map(|(ix, _)| ix)
|
||||
.unwrap_or(0),
|
||||
|
|
@ -1976,8 +1998,8 @@ impl SearchableItem for TerminalView {
|
|||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, search_match)| {
|
||||
search_match.contains(&selection_head)
|
||||
|| search_match.start() > &selection_head
|
||||
search_match.contains(selection_head)
|
||||
|| search_match.start() > selection_head
|
||||
})
|
||||
.map(|(ix, _)| ix)
|
||||
.unwrap_or(matches.len().saturating_sub(1)),
|
||||
|
|
@ -2489,6 +2511,7 @@ mod tests {
|
|||
0,
|
||||
cx.background_executor(),
|
||||
PathStyle::local(),
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
.subscribe(cx)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,28 @@ default-run = "zed"
|
|||
workspace = true
|
||||
|
||||
[features]
|
||||
default = ["alacritty-backend"]
|
||||
alacritty-backend = [
|
||||
"acp_thread/alacritty-backend",
|
||||
"agent_servers/alacritty-backend",
|
||||
"agent_ui/alacritty-backend",
|
||||
"debugger_ui/alacritty-backend",
|
||||
"languages/alacritty-backend",
|
||||
"project/alacritty-backend",
|
||||
"repl/alacritty-backend",
|
||||
"terminal_view/alacritty-backend",
|
||||
]
|
||||
tracy = ["ztracing/tracy"]
|
||||
libghostty-vt = [
|
||||
"acp_thread/libghostty-vt",
|
||||
"agent_servers/libghostty-vt",
|
||||
"agent_ui/libghostty-vt",
|
||||
"debugger_ui/libghostty-vt",
|
||||
"languages/libghostty-vt",
|
||||
"project/libghostty-vt",
|
||||
"repl/libghostty-vt",
|
||||
"terminal_view/libghostty-vt",
|
||||
]
|
||||
# LEAK_BACKTRACE=1 cargo run --features zed/track-project-leak --profile release-fast
|
||||
track-project-leak = ["gpui/leak-detection"]
|
||||
test-support = [
|
||||
|
|
|
|||
Loading…
Reference in a new issue