mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 17:08:21 +00:00
add syntax highlighting (#727)
This commit is contained in:
parent
a31f185154
commit
1034dc1aaf
11 changed files with 700 additions and 32 deletions
225
Cargo.lock
generated
225
Cargo.lock
generated
|
|
@ -26,6 +26,15 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "ansi_term"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
|
@ -50,6 +59,17 @@ dependencies = [
|
||||||
"nodrop",
|
"nodrop",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async_utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"log",
|
||||||
|
"pretty_assertions",
|
||||||
|
"rayon-core",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "asyncgit"
|
name = "asyncgit"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
|
|
@ -107,6 +127,30 @@ version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-set"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
|
||||||
|
dependencies = [
|
||||||
|
"bit-vec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-vec"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
|
@ -191,6 +235,15 @@ dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|
@ -320,6 +373,16 @@ version = "1.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fancy-regex"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae91abf6555234338687bb47913978d275539235fcb77ba9863b779090b42b14"
|
||||||
|
dependencies = [
|
||||||
|
"bit-set",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetree"
|
name = "filetree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -328,6 +391,24 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crc32fast",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
@ -397,6 +478,7 @@ name = "gitui"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async_utils",
|
||||||
"asyncgit",
|
"asyncgit",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
|
@ -410,6 +492,7 @@ dependencies = [
|
||||||
"easy-cast",
|
"easy-cast",
|
||||||
"filetree",
|
"filetree",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"pprof",
|
"pprof",
|
||||||
"rayon-core",
|
"rayon-core",
|
||||||
|
|
@ -418,6 +501,7 @@ dependencies = [
|
||||||
"scopetime",
|
"scopetime",
|
||||||
"serde",
|
"serde",
|
||||||
"simplelog",
|
"simplelog",
|
||||||
|
"syntect",
|
||||||
"textwrap 0.13.4",
|
"textwrap 0.13.4",
|
||||||
"tui",
|
"tui",
|
||||||
"unicode-truncate",
|
"unicode-truncate",
|
||||||
|
|
@ -528,6 +612,12 @@ version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazycell"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.94"
|
version = "0.2.94"
|
||||||
|
|
@ -574,6 +664,21 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "line-wrap"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9"
|
||||||
|
dependencies = [
|
||||||
|
"safemem",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linked-hash-map"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
|
@ -808,6 +913,20 @@ version = "0.3.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plist"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "679104537029ed2287c216bfb942bbf723f48ee98f0aef15611634173a74ef21"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"chrono",
|
||||||
|
"indexmap",
|
||||||
|
"line-wrap",
|
||||||
|
"serde",
|
||||||
|
"xml-rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pprof"
|
name = "pprof"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
|
@ -949,6 +1068,23 @@ dependencies = [
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|
@ -984,6 +1120,27 @@ version = "0.1.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce"
|
checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "safemem"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
@ -1017,6 +1174,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.64"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serial_test"
|
name = "serial_test"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|
@ -1133,6 +1301,28 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syntect"
|
||||||
|
version = "4.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2bfac2b23b4d049dc9a89353b4e06bbc85a8f42020cccbe5409a115cf19031e5"
|
||||||
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
|
"bitflags",
|
||||||
|
"fancy-regex",
|
||||||
|
"flate2",
|
||||||
|
"fnv",
|
||||||
|
"lazy_static",
|
||||||
|
"lazycell",
|
||||||
|
"plist",
|
||||||
|
"regex-syntax",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"walkdir",
|
||||||
|
"yaml-rust",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sys-info"
|
name = "sys-info"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
@ -1310,6 +1500,17 @@ version = "0.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.10.2+wasi-snapshot-preview1"
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
|
|
@ -1342,8 +1543,32 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xml-rs"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yaml-rust"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||||
|
dependencies = [
|
||||||
|
"linked-hash-map",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ keywords = [
|
||||||
scopetime = { path = "./scopetime", version = "0.1" }
|
scopetime = { path = "./scopetime", version = "0.1" }
|
||||||
asyncgit = { path = "./asyncgit", version = "0.15" }
|
asyncgit = { path = "./asyncgit", version = "0.15" }
|
||||||
filetree = { path = "./filetree" }
|
filetree = { path = "./filetree" }
|
||||||
|
async_utils = { path = "./async_utils" }
|
||||||
crossterm = { version = "0.19", features = [ "serde" ] }
|
crossterm = { version = "0.19", features = [ "serde" ] }
|
||||||
clap = { version = "2.33", default-features = false }
|
clap = { version = "2.33", default-features = false }
|
||||||
tui = { version = "0.15", default-features = false, features = ['crossterm', 'serde'] }
|
tui = { version = "0.15", default-features = false, features = ['crossterm', 'serde'] }
|
||||||
|
|
@ -44,6 +45,8 @@ textwrap = "0.13"
|
||||||
unicode-truncate = "0.2"
|
unicode-truncate = "0.2"
|
||||||
easy-cast = "0.4"
|
easy-cast = "0.4"
|
||||||
bugreport = "0.4"
|
bugreport = "0.4"
|
||||||
|
lazy_static = "1.4"
|
||||||
|
syntect = { version = "4.5", default-features = false, features = ["metadata", "default-fancy"]}
|
||||||
|
|
||||||
[target.'cfg(all(target_family="unix",not(target_os="macos")))'.dependencies]
|
[target.'cfg(all(target_family="unix",not(target_os="macos")))'.dependencies]
|
||||||
which = "4.1"
|
which = "4.1"
|
||||||
|
|
@ -63,6 +66,8 @@ timing=["scopetime/enabled"]
|
||||||
members=[
|
members=[
|
||||||
"asyncgit",
|
"asyncgit",
|
||||||
"scopetime",
|
"scopetime",
|
||||||
|
"async_utils",
|
||||||
|
"filetree",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
||||||
4
Makefile
4
Makefile
|
|
@ -45,12 +45,12 @@ fmt:
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
touch src/main.rs
|
touch src/main.rs
|
||||||
cargo clean -p gitui -p asyncgit -p scopetime -p filetree
|
cargo clean -p gitui -p asyncgit -p scopetime -p filetree -p async_utils
|
||||||
cargo clippy --workspace --all-features
|
cargo clippy --workspace --all-features
|
||||||
|
|
||||||
clippy-nightly:
|
clippy-nightly:
|
||||||
touch src/main.rs
|
touch src/main.rs
|
||||||
cargo clean -p gitui -p asyncgit -p scopetime -p filetree
|
cargo clean -p gitui -p asyncgit -p scopetime -p filetree -p async_utils
|
||||||
cargo +nightly clippy --all-features
|
cargo +nightly clippy --all-features
|
||||||
|
|
||||||
check: fmt clippy test
|
check: fmt clippy test
|
||||||
|
|
|
||||||
21
async_utils/Cargo.toml
Normal file
21
async_utils/Cargo.toml
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "async_utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Stephan Dilly <dilly.stephan@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
description = "async job utils"
|
||||||
|
homepage = "https://github.com/extrawurst/gitui"
|
||||||
|
repository = "https://github.com/extrawurst/gitui"
|
||||||
|
readme = "README.md"
|
||||||
|
license-file = "LICENSE.md"
|
||||||
|
categories = ["asynchronous","concurrency"]
|
||||||
|
keywords = ["parallel", "thread", "concurrency", "performance"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rayon-core = "1.9"
|
||||||
|
crossbeam-channel = "0.5"
|
||||||
|
log = "0.4"
|
||||||
|
thiserror = "1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions = "0.7"
|
||||||
1
async_utils/LICENSE.md
Symbolic link
1
async_utils/LICENSE.md
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../LICENSE.md
|
||||||
200
async_utils/src/lib.rs
Normal file
200
async_utils/src/lib.rs
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
use crossbeam_channel::Sender;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
pub trait AsyncJob: Send + Sync + Clone {
|
||||||
|
fn run(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AsyncSingleJob<J: AsyncJob, T: Copy + Send + 'static> {
|
||||||
|
next: Arc<Mutex<Option<J>>>,
|
||||||
|
last: Arc<Mutex<Option<J>>>,
|
||||||
|
sender: Sender<T>,
|
||||||
|
pending: Arc<Mutex<()>>,
|
||||||
|
notification: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<J: 'static + AsyncJob, T: Copy + Send + 'static>
|
||||||
|
AsyncSingleJob<J, T>
|
||||||
|
{
|
||||||
|
///
|
||||||
|
pub fn new(sender: Sender<T>, value: T) -> Self {
|
||||||
|
Self {
|
||||||
|
next: Arc::new(Mutex::new(None)),
|
||||||
|
last: Arc::new(Mutex::new(None)),
|
||||||
|
pending: Arc::new(Mutex::new(())),
|
||||||
|
notification: value,
|
||||||
|
sender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn is_pending(&self) -> bool {
|
||||||
|
self.pending.try_lock().is_err()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// makes sure `next` is cleared and returns `true` if it actually canceled something
|
||||||
|
pub fn cancel(&mut self) -> bool {
|
||||||
|
if let Ok(mut next) = self.next.lock() {
|
||||||
|
if next.is_some() {
|
||||||
|
*next = None;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return clone of last result
|
||||||
|
pub fn get_last(&self) -> Option<J> {
|
||||||
|
if let Ok(last) = self.last.lock() {
|
||||||
|
last.clone()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn spawn(&mut self, task: J) -> bool {
|
||||||
|
self.schedule_next(task);
|
||||||
|
self.check_for_job()
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn check_for_job(&self) -> bool {
|
||||||
|
if self.is_pending() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(task) = self.take_next() {
|
||||||
|
let self_arc = self.clone();
|
||||||
|
|
||||||
|
rayon_core::spawn(move || {
|
||||||
|
self_arc.run_job(task);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: return Result
|
||||||
|
fn run_job(&self, mut task: J) {
|
||||||
|
//limit the pending scope
|
||||||
|
{
|
||||||
|
let _pending = self.pending.lock().expect("");
|
||||||
|
|
||||||
|
task.run();
|
||||||
|
|
||||||
|
if let Ok(mut last) = self.last.lock() {
|
||||||
|
*last = Some(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sender.send(self.notification).expect("send failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.check_for_job();
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
fn schedule_next(&mut self, task: J) {
|
||||||
|
if let Ok(mut next) = self.next.lock() {
|
||||||
|
*next = Some(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
fn take_next(&self) -> Option<J> {
|
||||||
|
if let Ok(mut next) = self.next.lock() {
|
||||||
|
next.take()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crossbeam_channel::unbounded;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use std::{
|
||||||
|
sync::atomic::AtomicU32, thread::sleep, time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct TestJob {
|
||||||
|
v: Arc<AtomicU32>,
|
||||||
|
value_to_add: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncJob for TestJob {
|
||||||
|
fn run(&mut self) {
|
||||||
|
sleep(Duration::from_millis(100));
|
||||||
|
|
||||||
|
self.v.fetch_add(
|
||||||
|
self.value_to_add,
|
||||||
|
std::sync::atomic::Ordering::Relaxed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notificaton = ();
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_overwrite() {
|
||||||
|
let (sender, receiver) = unbounded();
|
||||||
|
|
||||||
|
let mut job: AsyncSingleJob<TestJob, Notificaton> =
|
||||||
|
AsyncSingleJob::new(sender, ());
|
||||||
|
|
||||||
|
let task = TestJob {
|
||||||
|
v: Arc::new(AtomicU32::new(1)),
|
||||||
|
value_to_add: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(job.spawn(task.clone()));
|
||||||
|
sleep(Duration::from_millis(1));
|
||||||
|
for _ in 0..5 {
|
||||||
|
assert!(!job.spawn(task.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let _foo = receiver.recv().unwrap();
|
||||||
|
let _foo = receiver.recv().unwrap();
|
||||||
|
assert!(receiver.is_empty());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
task.v.load(std::sync::atomic::Ordering::Relaxed),
|
||||||
|
3
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cancel() {
|
||||||
|
let (sender, receiver) = unbounded();
|
||||||
|
|
||||||
|
let mut job: AsyncSingleJob<TestJob, Notificaton> =
|
||||||
|
AsyncSingleJob::new(sender, ());
|
||||||
|
|
||||||
|
let task = TestJob {
|
||||||
|
v: Arc::new(AtomicU32::new(1)),
|
||||||
|
value_to_add: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(job.spawn(task.clone()));
|
||||||
|
sleep(Duration::from_millis(1));
|
||||||
|
|
||||||
|
for _ in 0..5 {
|
||||||
|
assert!(!job.spawn(task.clone()));
|
||||||
|
}
|
||||||
|
assert!(job.cancel());
|
||||||
|
|
||||||
|
let _foo = receiver.recv().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
task.v.load(std::sync::atomic::Ordering::Relaxed),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -77,6 +77,9 @@ pub enum AsyncNotification {
|
||||||
Fetch,
|
Fetch,
|
||||||
///
|
///
|
||||||
Blame,
|
Blame,
|
||||||
|
///
|
||||||
|
//TODO: this does not belong here
|
||||||
|
SyntaxHighlighting,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// current working directory `./`
|
/// current working directory `./`
|
||||||
|
|
|
||||||
|
|
@ -338,6 +338,7 @@ impl App {
|
||||||
self.push_popup.update_git(ev)?;
|
self.push_popup.update_git(ev)?;
|
||||||
self.push_tags_popup.update_git(ev)?;
|
self.push_tags_popup.update_git(ev)?;
|
||||||
self.pull_popup.update_git(ev)?;
|
self.pull_popup.update_git(ev)?;
|
||||||
|
self.revision_files_popup.update(ev);
|
||||||
|
|
||||||
//TODO: better system for this
|
//TODO: better system for this
|
||||||
// can we simply process the queue here and everyone just uses the queue to schedule a cmd update?
|
// can we simply process the queue here and everyone just uses the queue to schedule a cmd update?
|
||||||
|
|
@ -362,6 +363,7 @@ impl App {
|
||||||
|| self.push_popup.any_work_pending()
|
|| self.push_popup.any_work_pending()
|
||||||
|| self.push_tags_popup.any_work_pending()
|
|| self.push_tags_popup.any_work_pending()
|
||||||
|| self.pull_popup.any_work_pending()
|
|| self.pull_popup.any_work_pending()
|
||||||
|
|| self.revision_files_popup.any_work_pending()
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
use std::{
|
|
||||||
cell::Cell, collections::BTreeSet, convert::From, path::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
visibility_blocking, CommandBlocking, CommandInfo, Component,
|
visibility_blocking, CommandBlocking, CommandInfo, Component,
|
||||||
DrawableComponent, EventState,
|
DrawableComponent, EventState,
|
||||||
|
|
@ -10,9 +6,10 @@ use crate::{
|
||||||
keys::SharedKeyConfig,
|
keys::SharedKeyConfig,
|
||||||
queue::{InternalEvent, Queue},
|
queue::{InternalEvent, Queue},
|
||||||
strings::{self, order},
|
strings::{self, order},
|
||||||
ui::{self, style::SharedTheme},
|
ui::{self, style::SharedTheme, AsyncSyntaxJob},
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use async_utils::AsyncSingleJob;
|
||||||
use asyncgit::{
|
use asyncgit::{
|
||||||
sync::{self, CommitId, TreeFile},
|
sync::{self, CommitId, TreeFile},
|
||||||
AsyncNotification, CWD,
|
AsyncNotification, CWD,
|
||||||
|
|
@ -20,6 +17,10 @@ use asyncgit::{
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use filetree::{FileTree, MoveSelection};
|
use filetree::{FileTree, MoveSelection};
|
||||||
|
use itertools::Either;
|
||||||
|
use std::{
|
||||||
|
cell::Cell, collections::BTreeSet, convert::From, path::Path,
|
||||||
|
};
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
|
@ -36,8 +37,11 @@ pub struct RevisionFilesComponent {
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
title: String,
|
title: String,
|
||||||
theme: SharedTheme,
|
theme: SharedTheme,
|
||||||
|
//TODO: store TreeFiles in `tree`
|
||||||
files: Vec<TreeFile>,
|
files: Vec<TreeFile>,
|
||||||
current_file: Option<(String, String)>,
|
current_file: Option<(String, Either<ui::SyntaxText, String>)>,
|
||||||
|
async_highlighting:
|
||||||
|
AsyncSingleJob<AsyncSyntaxJob, AsyncNotification>,
|
||||||
tree: FileTree,
|
tree: FileTree,
|
||||||
scroll_top: Cell<usize>,
|
scroll_top: Cell<usize>,
|
||||||
revision: Option<CommitId>,
|
revision: Option<CommitId>,
|
||||||
|
|
@ -49,7 +53,7 @@ impl RevisionFilesComponent {
|
||||||
///
|
///
|
||||||
pub fn new(
|
pub fn new(
|
||||||
queue: &Queue,
|
queue: &Queue,
|
||||||
_sender: &Sender<AsyncNotification>,
|
sender: &Sender<AsyncNotification>,
|
||||||
theme: SharedTheme,
|
theme: SharedTheme,
|
||||||
key_config: SharedKeyConfig,
|
key_config: SharedKeyConfig,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
@ -57,6 +61,10 @@ impl RevisionFilesComponent {
|
||||||
queue: queue.clone(),
|
queue: queue.clone(),
|
||||||
title: String::new(),
|
title: String::new(),
|
||||||
tree: FileTree::default(),
|
tree: FileTree::default(),
|
||||||
|
async_highlighting: AsyncSingleJob::new(
|
||||||
|
sender.clone(),
|
||||||
|
AsyncNotification::SyntaxHighlighting,
|
||||||
|
),
|
||||||
theme,
|
theme,
|
||||||
scroll_top: Cell::new(0),
|
scroll_top: Cell::new(0),
|
||||||
current_file: None,
|
current_file: None,
|
||||||
|
|
@ -89,6 +97,28 @@ impl RevisionFilesComponent {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn update(&mut self, ev: AsyncNotification) {
|
||||||
|
if ev == AsyncNotification::SyntaxHighlighting {
|
||||||
|
if let Some(job) = self.async_highlighting.get_last() {
|
||||||
|
if let Some((path, content)) =
|
||||||
|
self.current_file.as_mut()
|
||||||
|
{
|
||||||
|
if let Some(syntax) = (*job.text).clone() {
|
||||||
|
if syntax.path() == Path::new(path) {
|
||||||
|
*content = Either::Left(syntax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn any_work_pending(&self) -> bool {
|
||||||
|
self.async_highlighting.is_pending()
|
||||||
|
}
|
||||||
|
|
||||||
fn tree_item_to_span<'a>(
|
fn tree_item_to_span<'a>(
|
||||||
item: &'a filetree::FileTreeItem,
|
item: &'a filetree::FileTreeItem,
|
||||||
theme: &SharedTheme,
|
theme: &SharedTheme,
|
||||||
|
|
@ -133,6 +163,7 @@ impl RevisionFilesComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selection_changed(&mut self) {
|
fn selection_changed(&mut self) {
|
||||||
|
//TODO: retrieve TreeFile from tree datastructure
|
||||||
if let Some(file) = self.tree.selected_file().map(|file| {
|
if let Some(file) = self.tree.selected_file().map(|file| {
|
||||||
file.full_path()
|
file.full_path()
|
||||||
.strip_prefix("./")
|
.strip_prefix("./")
|
||||||
|
|
@ -154,19 +185,30 @@ impl RevisionFilesComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_file(&mut self, path: String) {
|
fn load_file(&mut self, path: String) {
|
||||||
if let Some(item) = self
|
let path_path = Path::new(&path);
|
||||||
.files
|
if let Some(item) =
|
||||||
.iter()
|
self.files.iter().find(|f| f.path.ends_with(path_path))
|
||||||
.find(|f| f.path.ends_with(Path::new(&path)))
|
|
||||||
{
|
{
|
||||||
|
//TODO: fetch file content async aswell
|
||||||
match sync::tree_file_content(CWD, item) {
|
match sync::tree_file_content(CWD, item) {
|
||||||
Ok(content) => {
|
Ok(content) => {
|
||||||
self.current_file = Some((path, content))
|
self.async_highlighting.spawn(
|
||||||
|
AsyncSyntaxJob::new(
|
||||||
|
content.clone(),
|
||||||
|
path.clone(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.current_file =
|
||||||
|
Some((path, Either::Right(content)))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.current_file = Some((
|
self.current_file = Some((
|
||||||
path,
|
path,
|
||||||
format!("error loading file: {}", e),
|
Either::Right(format!(
|
||||||
|
"error loading file: {}",
|
||||||
|
e
|
||||||
|
)),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -239,12 +281,15 @@ impl DrawableComponent for RevisionFilesComponent {
|
||||||
items,
|
items,
|
||||||
);
|
);
|
||||||
|
|
||||||
let content = Paragraph::new(Text::from(
|
let content = Paragraph::new(
|
||||||
self.current_file
|
self.current_file.as_ref().map_or_else(
|
||||||
.as_ref()
|
|| Text::from(""),
|
||||||
.map(|(_, content)| content.as_str())
|
|(_, content)| match content {
|
||||||
.unwrap_or_default(),
|
Either::Left(syn) => syn.into(),
|
||||||
))
|
Either::Right(s) => Text::from(s.as_str()),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
.wrap(Wrap { trim: false });
|
.wrap(Wrap { trim: false });
|
||||||
f.render_widget(content, chunks[1]);
|
f.render_widget(content, chunks[1]);
|
||||||
}
|
}
|
||||||
|
|
@ -290,15 +335,11 @@ impl Component for RevisionFilesComponent {
|
||||||
) -> Result<EventState> {
|
) -> Result<EventState> {
|
||||||
if self.is_visible() {
|
if self.is_visible() {
|
||||||
if let Event::Key(key) = event {
|
if let Event::Key(key) = event {
|
||||||
let consumed = if key == self.key_config.exit_popup {
|
if key == self.key_config.exit_popup {
|
||||||
self.hide();
|
self.hide();
|
||||||
true
|
|
||||||
} else if key == self.key_config.blame {
|
} else if key == self.key_config.blame {
|
||||||
if self.blame() {
|
if self.blame() {
|
||||||
self.hide();
|
self.hide();
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
} else if tree_nav(
|
} else if tree_nav(
|
||||||
&mut self.tree,
|
&mut self.tree,
|
||||||
|
|
@ -306,13 +347,10 @@ impl Component for RevisionFilesComponent {
|
||||||
key,
|
key,
|
||||||
) {
|
) {
|
||||||
self.selection_changed();
|
self.selection_changed();
|
||||||
true
|
}
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok(consumed.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Ok(EventState::Consumed);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(EventState::NotConsumed)
|
Ok(EventState::NotConsumed)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
mod scrollbar;
|
mod scrollbar;
|
||||||
mod scrolllist;
|
mod scrolllist;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
mod syntax_text;
|
||||||
|
|
||||||
pub use scrollbar::draw_scrollbar;
|
pub use scrollbar::draw_scrollbar;
|
||||||
pub use scrolllist::{draw_list, draw_list_block};
|
pub use scrolllist::{draw_list, draw_list_block};
|
||||||
|
pub use syntax_text::{AsyncSyntaxJob, SyntaxText};
|
||||||
use tui::layout::{Constraint, Direction, Layout, Rect};
|
use tui::layout::{Constraint, Direction, Layout, Rect};
|
||||||
|
|
||||||
/// return the scroll position (line) necessary to have the `selection` in view if it is not already
|
/// return the scroll position (line) necessary to have the `selection` in view if it is not already
|
||||||
|
|
|
||||||
171
src/ui/syntax_text.rs
Normal file
171
src/ui/syntax_text.rs
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
use async_utils::AsyncJob;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use scopetime::scope_time;
|
||||||
|
use std::{
|
||||||
|
ffi::OsStr,
|
||||||
|
ops::Range,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use syntect::{
|
||||||
|
highlighting::{
|
||||||
|
FontStyle, HighlightState, Highlighter,
|
||||||
|
RangedHighlightIterator, Style, ThemeSet,
|
||||||
|
},
|
||||||
|
parsing::{ParseState, ScopeStack, SyntaxSet},
|
||||||
|
};
|
||||||
|
use tui::text::{Span, Spans};
|
||||||
|
|
||||||
|
//TODO: no clone, make user consume result
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct SyntaxLine {
|
||||||
|
items: Vec<(Style, usize, Range<usize>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: no clone, make user consume result
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SyntaxText {
|
||||||
|
text: String,
|
||||||
|
lines: Vec<SyntaxLine>,
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref SYNTAX_SET: SyntaxSet =
|
||||||
|
SyntaxSet::load_defaults_nonewlines();
|
||||||
|
static ref THEME_SET: ThemeSet = ThemeSet::load_defaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyntaxText {
|
||||||
|
pub fn new(text: String, file_path: &Path) -> Self {
|
||||||
|
scope_time!("syntax_highlighting");
|
||||||
|
log::debug!("syntax: {:?}", file_path);
|
||||||
|
|
||||||
|
let mut state = {
|
||||||
|
let syntax = file_path
|
||||||
|
.extension()
|
||||||
|
.and_then(OsStr::to_str)
|
||||||
|
.map_or_else(
|
||||||
|
|| {
|
||||||
|
SYNTAX_SET.find_syntax_by_path(
|
||||||
|
file_path.to_str().unwrap_or_default(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|ext| SYNTAX_SET.find_syntax_by_extension(ext),
|
||||||
|
);
|
||||||
|
|
||||||
|
ParseState::new(syntax.unwrap_or_else(|| {
|
||||||
|
SYNTAX_SET.find_syntax_plain_text()
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
let highlighter = Highlighter::new(
|
||||||
|
&THEME_SET.themes["base16-eighties.dark"],
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut syntax_lines: Vec<SyntaxLine> = Vec::new();
|
||||||
|
|
||||||
|
let mut highlight_state =
|
||||||
|
HighlightState::new(&highlighter, ScopeStack::new());
|
||||||
|
|
||||||
|
for (number, line) in text.lines().enumerate() {
|
||||||
|
let ops = state.parse_line(line, &SYNTAX_SET);
|
||||||
|
let iter = RangedHighlightIterator::new(
|
||||||
|
&mut highlight_state,
|
||||||
|
&ops[..],
|
||||||
|
line,
|
||||||
|
&highlighter,
|
||||||
|
);
|
||||||
|
|
||||||
|
syntax_lines.push(SyntaxLine {
|
||||||
|
items: iter
|
||||||
|
.map(|(style, _, range)| (style, number, range))
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
text,
|
||||||
|
lines: syntax_lines,
|
||||||
|
path: file_path.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn path(&self) -> &Path {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a SyntaxText> for tui::text::Text<'a> {
|
||||||
|
fn from(v: &'a SyntaxText) -> Self {
|
||||||
|
let mut result_lines: Vec<Spans> =
|
||||||
|
Vec::with_capacity(v.lines.len());
|
||||||
|
|
||||||
|
for (syntax_line, line_content) in
|
||||||
|
v.lines.iter().zip(v.text.lines())
|
||||||
|
{
|
||||||
|
let mut line_span =
|
||||||
|
Spans(Vec::with_capacity(syntax_line.items.len()));
|
||||||
|
|
||||||
|
for (style, _, range) in &syntax_line.items {
|
||||||
|
let item_content = &line_content[range.clone()];
|
||||||
|
let item_style = syntact_style_to_tui(style);
|
||||||
|
|
||||||
|
line_span
|
||||||
|
.0
|
||||||
|
.push(Span::styled(item_content, item_style));
|
||||||
|
}
|
||||||
|
|
||||||
|
result_lines.push(line_span);
|
||||||
|
}
|
||||||
|
|
||||||
|
result_lines.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syntact_style_to_tui(style: &Style) -> tui::style::Style {
|
||||||
|
let mut res =
|
||||||
|
tui::style::Style::default().fg(tui::style::Color::Rgb(
|
||||||
|
style.foreground.r,
|
||||||
|
style.foreground.g,
|
||||||
|
style.foreground.b,
|
||||||
|
));
|
||||||
|
|
||||||
|
if style.font_style.contains(FontStyle::BOLD) {
|
||||||
|
res = res.add_modifier(tui::style::Modifier::BOLD);
|
||||||
|
}
|
||||||
|
if style.font_style.contains(FontStyle::ITALIC) {
|
||||||
|
res = res.add_modifier(tui::style::Modifier::ITALIC);
|
||||||
|
}
|
||||||
|
if style.font_style.contains(FontStyle::UNDERLINE) {
|
||||||
|
res = res.add_modifier(tui::style::Modifier::UNDERLINED);
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct AsyncSyntaxJob {
|
||||||
|
//TODO: can we merge input and text into a single enum to represent the state transition?
|
||||||
|
pub input: Option<(String, String)>,
|
||||||
|
pub text: Arc<Option<SyntaxText>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncSyntaxJob {
|
||||||
|
pub fn new(content: String, path: String) -> Self {
|
||||||
|
Self {
|
||||||
|
input: Some((content, path)),
|
||||||
|
text: Arc::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncJob for AsyncSyntaxJob {
|
||||||
|
fn run(&mut self) {
|
||||||
|
if let Some((text, path)) = self.input.take() {
|
||||||
|
let syntax = SyntaxText::new(text, Path::new(&path));
|
||||||
|
self.text = Arc::new(Some(syntax));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue