Repository: sayanarijit/xplr (language: rust)

Issue:
xplr.util.is_file has a typo
Code for `xplr.util.is_file` contains a typo.

https://github.com/sayanarijit/xplr/blob/ad8afa9d38bef21ba72abcee03759db2725cc932/src/lua/util.rs#L163

The line should read 

```rust
lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_file()))?;
```


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5f86caca..cb863a5e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -16,18 +16,18 @@ dependencies = [
 
 [[package]]
 name = "aho-corasick"
-version = "1.1.2"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "allocator-api2"
-version = "0.2.16"
+version = "0.2.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
+checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
 
 [[package]]
 name = "android-tzdata"
@@ -52,14 +52,12 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "ansi-to-tui"
-version = "4.0.1"
+version = "3.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8438af3d7e7dccdb98eff55e5351587d9bec2294daff505fc9a061bd14d22db0"
+checksum = "0b0e348dcd256ba06d44d5deabc88a7c0e80ee7303158253ca069bcd9e9b7f57"
 dependencies = [
  "nom",
  "ratatui",
- "simdutf8",
- "smallvec",
  "thiserror",
 ]
 
@@ -71,9 +69,9 @@ checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
 
 [[package]]
 name = "anyhow"
-version = "1.0.81"
+version = "1.0.82"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
+checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
 
 [[package]]
 name = "arrayvec"
@@ -98,9 +96,9 @@ dependencies = [
 
 [[package]]
 name = "autocfg"
-version = "1.1.0"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
 
 [[package]]
 name = "beef"
@@ -116,9 +114,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.4.2"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
 dependencies = [
  "serde",
 ]
@@ -136,9 +134,9 @@ dependencies = [
 
 [[package]]
 name = "bumpalo"
-version = "3.15.4"
+version = "3.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
 
 [[package]]
 name = "cassowary"
@@ -163,9 +161,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.0.90"
+version = "1.0.96"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
+checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd"
 
 [[package]]
 name = "cfg-if"
@@ -175,16 +173,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "chrono"
-version = "0.4.35"
+version = "0.4.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
  "js-sys",
  "num-traits",
  "wasm-bindgen",
- "windows-targets 0.52.4",
+ "windows-targets 0.52.5",
 ]
 
 [[package]]
@@ -216,9 +214,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.3"
+version = "4.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
+checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
 dependencies = [
  "clap_builder",
 ]
@@ -357,7 +355,7 @@ version = "0.27.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "crossterm_winapi",
  "libc",
  "mio",
@@ -503,9 +501,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
 
 [[package]]
 name = "either"
-version = "1.10.0"
+version = "1.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
+checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
 
 [[package]]
 name = "equivalent"
@@ -559,9 +557,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.12"
+version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
+checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
 dependencies = [
  "cfg-if",
  "libc",
@@ -570,9 +568,9 @@ dependencies = [
 
 [[package]]
 name = "half"
-version = "2.4.0"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e"
+checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
 dependencies = [
  "cfg-if",
  "crunchy",
@@ -580,9 +578,9 @@ dependencies = [
 
 [[package]]
 name = "hashbrown"
-version = "0.14.3"
+version = "0.14.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
 dependencies = [
  "ahash",
  "allocator-api2",
@@ -649,9 +647,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
 
 [[package]]
 name = "indexmap"
-version = "2.2.5"
+version = "2.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
+checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
 dependencies = [
  "equivalent",
  "hashbrown",
@@ -660,9 +658,9 @@ dependencies = [
 
 [[package]]
 name = "indoc"
-version = "2.0.4"
+version = "2.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
+checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
 
 [[package]]
 name = "is-terminal"
@@ -695,9 +693,9 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.10"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
 [[package]]
 name = "jf"
@@ -726,9 +724,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.153"
+version = "0.2.154"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
 
 [[package]]
 name = "libm"
@@ -738,13 +736,12 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
 
 [[package]]
 name = "libredox"
-version = "0.0.1"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "libc",
- "redox_syscall",
 ]
 
 [[package]]
@@ -755,9 +752,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
 
 [[package]]
 name = "lock_api"
-version = "0.4.11"
+version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
 dependencies = [
  "autocfg",
  "scopeguard",
@@ -798,9 +795,9 @@ dependencies = [
 
 [[package]]
 name = "luajit-src"
-version = "210.5.7+d06beb0"
+version = "210.5.8+5790d25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d251fdacdabbf87704cf48ac1f8b1eb23d6e10855c3ee08e5beb25b4be2e9e4"
+checksum = "441f18d9ad792e871fc2f7f2cb8902c386f6f56fdbddef3b835b61475e375346"
 dependencies = [
  "cc",
  "which",
@@ -808,9 +805,9 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "2.7.1"
+version = "2.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
 
 [[package]]
 name = "memoffset"
@@ -857,9 +854,9 @@ dependencies = [
 
 [[package]]
 name = "mlua"
-version = "0.9.6"
+version = "0.9.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "868d02cb5eb97761bbf6bd6922c1c7a88b8ea252bbf43bd8350a0bf8497a1fc0"
+checksum = "6d9bed6bce296397a9d6a86f995dd10a547a4e6949825d45225906bdcbfe7367"
 dependencies = [
  "bstr",
  "erased-serde",
@@ -873,9 +870,9 @@ dependencies = [
 
 [[package]]
 name = "mlua-sys"
-version = "0.5.1"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2847b42764435201d8cbee1f517edb79c4cca4181877b90047587c89e1b7bce4"
+checksum = "d16a9ba1dd2c6ac971b204262d434c24d65067038598f0638b64e5dca28d52b8"
 dependencies = [
  "cc",
  "cfg-if",
@@ -981,9 +978,9 @@ dependencies = [
 
 [[package]]
 name = "parking_lot"
-version = "0.12.1"
+version = "0.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
 dependencies = [
  "lock_api",
  "parking_lot_core",
@@ -991,15 +988,15 @@ dependencies = [
 
 [[package]]
 name = "parking_lot_core"
-version = "0.9.9"
+version = "0.9.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
 dependencies = [
  "cfg-if",
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-targets 0.48.5",
+ "windows-targets 0.52.5",
 ]
 
 [[package]]
@@ -1101,18 +1098,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.79"
+version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
+checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.35"
+version = "1.0.36"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
 dependencies = [
  "proc-macro2",
 ]
@@ -1123,7 +1120,7 @@ version = "0.26.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bcb12f8fbf6c62614b0d56eb352af54f6a22410c3b079eb53ee93c7b97dd31d8"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "cassowary",
  "compact_str",
  "crossterm",
@@ -1140,9 +1137,9 @@ dependencies = [
 
 [[package]]
 name = "rayon"
-version = "1.9.0"
+version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
 dependencies = [
  "either",
  "rayon-core",
@@ -1160,18 +1157,18 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.4.1"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
 dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.5.0",
 ]
 
 [[package]]
 name = "redox_users"
-version = "0.4.4"
+version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
+checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
 dependencies = [
  "getrandom",
  "libredox",
@@ -1180,9 +1177,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.10.3"
+version = "1.10.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
+checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -1203,9 +1200,9 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
 
 [[package]]
 name = "rustc-hash"
@@ -1215,11 +1212,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
 [[package]]
 name = "rustix"
-version = "0.38.31"
+version = "0.38.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "errno",
  "libc",
  "linux-raw-sys",
@@ -1228,9 +1225,9 @@ dependencies = [
 
 [[package]]
 name = "rustversion"
-version = "1.0.14"
+version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47"
 
 [[package]]
 name = "ryu"
@@ -1255,9 +1252,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "serde"
-version = "1.0.197"
+version = "1.0.199"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
+checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
 dependencies = [
  "serde_derive",
 ]
@@ -1274,20 +1271,20 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.197"
+version = "1.0.199"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
+checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.53",
+ "syn 2.0.60",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.114"
+version = "1.0.116"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
+checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
 dependencies = [
  "itoa",
  "ryu",
@@ -1296,9 +1293,9 @@ dependencies = [
 
 [[package]]
 name = "serde_yaml"
-version = "0.9.33"
+version = "0.9.34+deprecated"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
 dependencies = [
  "indexmap",
  "itoa",
@@ -1330,19 +1327,13 @@ dependencies = [
 
 [[package]]
 name = "signal-hook-registry"
-version = "1.4.1"
+version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
 dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "simdutf8"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
-
 [[package]]
 name = "skim"
 version = "0.10.4"
@@ -1370,9 +1361,9 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.13.1"
+version = "1.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
 
 [[package]]
 name = "smawk"
@@ -1431,7 +1422,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.53",
+ "syn 2.0.60",
 ]
 
 [[package]]
@@ -1447,9 +1438,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.53"
+version = "2.0.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
+checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1486,22 +1477,22 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.58"
+version = "1.0.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
+checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.58"
+version = "1.0.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
+checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.53",
+ "syn 2.0.60",
 ]
 
 [[package]]
@@ -1516,9 +1507,9 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.34"
+version = "0.3.36"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
+checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
 dependencies = [
  "deranged",
  "itoa",
@@ -1539,9 +1530,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
 
 [[package]]
 name = "time-macros"
-version = "0.2.17"
+version = "0.2.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
+checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
 dependencies = [
  "num-conv",
  "time-core",
@@ -1620,9 +1611,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.11"
+version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
 
 [[package]]
 name = "unicode_categories"
@@ -1715,7 +1706,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.53",
+ "syn 2.0.60",
  "wasm-bindgen-shared",
 ]
 
@@ -1737,7 +1728,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.53",
+ "syn 2.0.60",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -1760,15 +1751,14 @@ dependencies = [
 
 [[package]]
 name = "which"
-version = "6.0.0"
+version = "6.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c"
+checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7"
 dependencies = [
  "either",
  "home",
- "once_cell",
  "rustix",
- "windows-sys 0.52.0",
+ "winsafe",
 ]
 
 [[package]]
@@ -1789,11 +1779,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
 [[package]]
 name = "winapi-util"
-version = "0.1.6"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
 dependencies = [
- "winapi",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -1808,7 +1798,7 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
 dependencies = [
- "windows-targets 0.52.4",
+ "windows-targets 0.52.5",
 ]
 
 [[package]]
@@ -1826,7 +1816,7 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets 0.52.4",
+ "windows-targets 0.52.5",
 ]
 
 [[package]]
@@ -1846,17 +1836,18 @@ dependencies = [
 
 [[package]]
 name = "windows-targets"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
 dependencies = [
- "windows_aarch64_gnullvm 0.52.4",
- "windows_aarch64_msvc 0.52.4",
- "windows_i686_gnu 0.52.4",
- "windows_i686_msvc 0.52.4",
- "windows_x86_64_gnu 0.52.4",
- "windows_x86_64_gnullvm 0.52.4",
- "windows_x86_64_msvc 0.52.4",
+ "windows_aarch64_gnullvm 0.52.5",
+ "windows_aarch64_msvc 0.52.5",
+ "windows_i686_gnu 0.52.5",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.5",
+ "windows_x86_64_gnu 0.52.5",
+ "windows_x86_64_gnullvm 0.52.5",
+ "windows_x86_64_msvc 0.52.5",
 ]
 
 [[package]]
@@ -1867,9 +1858,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
 
 [[package]]
 name = "windows_aarch64_msvc"
@@ -1879,9 +1870,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
 
 [[package]]
 name = "windows_i686_gnu"
@@ -1891,9 +1882,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
 
 [[package]]
 name = "windows_i686_msvc"
@@ -1903,9 +1900,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
 
 [[package]]
 name = "windows_x86_64_gnu"
@@ -1915,9 +1912,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
@@ -1927,9 +1924,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
 
 [[package]]
 name = "windows_x86_64_msvc"
@@ -1939,9 +1936,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.52.4"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
+
+[[package]]
+name = "winsafe"
+version = "0.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
+checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
 
 [[package]]
 name = "xdg"
@@ -1951,7 +1954,7 @@ checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
 
 [[package]]
 name = "xplr"
-version = "0.21.7"
+version = "0.21.8"
 dependencies = [
  "ansi-to-tui",
  "anyhow",
@@ -2002,5 +2005,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.53",
+ "syn 2.0.60",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 6c50de08..7aa17395 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,7 +8,7 @@ path = './benches/criterion.rs'
 
 [package]
 name = 'xplr'
-version = '0.21.7'
+version = '0.21.8'
 authors = ['Arijit Basu <hi@arijitbasu.in>']
 edition = '2021'
 description = 'A hackable, minimal, fast TUI file explorer'
@@ -28,7 +28,7 @@ natord = "1.0.9"
 anyhow = "1.0.81"
 serde_yaml = "0.9.33"
 crossterm = { version = "0.27.0", features = [], default-features = false }
-ansi-to-tui = "4.0.1"
+ansi-to-tui = "3.1.0"
 regex = "1.10.3"
 gethostname = "0.4.3"
 serde_json = "1.0.114"
@@ -57,7 +57,7 @@ version = "2.0.4"
 default-features = false
 
 [dependencies.tui]
-version = "0.26.1"
+version = "=0.26.1"  # https://github.com/ratatui-org/ratatui/issues/1032
 default-features = false
 features = ['crossterm', 'serde']
 package = 'ratatui'
@@ -73,7 +73,7 @@ features = ['serde']
 
 [dependencies.mlua]
 version = "0.9.6"
-features = ['luajit', 'vendored', 'serialize', 'send']
+features = ['luajit', 'serialize', 'send']
 
 [dependencies.tui-input]
 version = "0.8.0"
@@ -90,5 +90,5 @@ panic = 'abort'
 strip = true
 
 [features]
-
-
+default = ["vendored-lua"]
+vendored-lua = ["mlua/vendored"]
diff --git a/README.md b/README.md
index 2d42ca63..9758bc6e 100644
--- a/README.md
+++ b/README.md
@@ -12,18 +12,6 @@ A hackable, minimal, fast TUI file explorer
 <img src="https://img.shields.io/crates/v/xplr.svg" />
 </a>
 
-<a href="https://github.com/sayanarijit/xplr/commits">
-<img src="https://img.shields.io/github/commit-activity/m/sayanarijit/xplr" />
-</a>
-
-<a href="https://matrix.to/#/#xplr-pub:matrix.org">
-<img alt="Matrix" src="https://img.shields.io/matrix/xplr-pub:matrix.org?color=0DB787&label=matrix&logo=Matrix">
-</a>
-  
-<a href="https://discord.gg/JmasSPCcz3">
-<img src="https://img.shields.io/discord/834369918312382485?color=5865F2&label=discord&logo=Discord" />
-</a>
-
 </p>
 
 <p align="center">
@@ -38,7 +26,6 @@ https://user-images.githubusercontent.com/11632726/166747867-8a4573f2-cb2f-43a6-
   [<a href="https://xplr.dev/en/awesome-hacks">Hacks</a>]
   [<a href="https://xplr.dev/en/awesome-plugins">Plugins</a>]
   [<a href="https://xplr.dev/en/awesome-integrations">Integrations</a>]
-  [<a href="https://xplr.dev/en/community">Community</a>]
 </h3>
 
 xplr is a terminal UI based file explorer that aims to increase our terminal
@@ -60,7 +47,7 @@ integration][15], enabling you to achieve insane terminal productivity.
 
 - [[Article] What is a TUI file explorer & why would you need one? ~ xplr.stck.me](https://xplr.stck.me/post/25252/What-is-a-TUI-file-explorer-why-would-you-need-one)
 
-- [[Article] FOSSPicks - Linux Magazine](https://www.linux-magazine.com/Issues/2022/258/FOSSPicks/(offset)/6)
+- [[Article] FOSSPicks - Linux Magazine](<https://www.linux-magazine.com/Issues/2022/258/FOSSPicks/(offset)/6>)
 
 ## Packaging
 
diff --git a/RELEASE.md b/RELEASE.md
index 8f30cda9..75bdd3e4 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -3,8 +3,8 @@
 See [install.md](./docs/en/src/install.md#build-from-source)
 
 Note: xplr ships with vendored luajit. If the platform can't compile this,
-you need to grep out the feature "vendored" from the "mlua" dependency
-specified in [Cargo.toml](./Cargo.toml), and static link luajit yourself.
+you need to compile using `--no-default-features` argument to avoid using
+vendored luajit, so that you can static link luajit yourself.
 
 # Release
 
diff --git a/benches/criterion.rs b/benches/criterion.rs
index 04d31a48..cff705b1 100644
--- a/benches/criterion.rs
+++ b/benches/criterion.rs
@@ -98,6 +98,7 @@ fn draw_benchmark(c: &mut Criterion) {
     });
 
     let lua = mlua::Lua::new();
+    let mut ui = ui::UI::new(&lua);
     let mut app =
         app::App::create("xplr".into(), None, PWD.into(), &lua, None, [].into())
             .expect("failed to create app");
@@ -121,7 +122,7 @@ fn draw_benchmark(c: &mut Criterion) {
 
     c.bench_function("draw on terminal", |b| {
         b.iter(|| {
-            terminal.draw(|f| ui::draw(f, &app, &lua)).unwrap();
+            terminal.draw(|f| ui.draw(f, &app)).unwrap();
         })
     });
 
diff --git a/docs/en/src/SUMMARY.md b/docs/en/src/SUMMARY.md
index 210136cf..2087022c 100644
--- a/docs/en/src/SUMMARY.md
+++ b/docs/en/src/SUMMARY.md
@@ -39,8 +39,6 @@
   - [Awesome Integrations][20]
 - [Alternatives][22]
 - [Upgrade Guide][23]
-- [Community][24]
-- [Contribute][25]
 
 [1]: introduction.md
 [2]: quickstart.md
@@ -64,8 +62,6 @@
 [20]: awesome-integrations.md
 [22]: alternatives.md
 [23]: upgrade-guide.md
-[24]: community.md
-[25]: contribute.md
 [26]: column-renderer.md
 [27]: key-bindings.md
 [28]: configure-key-bindings.md
diff --git a/docs/en/src/alternatives.md b/docs/en/src/alternatives.md
index 273e9d81..0fe9c8c0 100644
--- a/docs/en/src/alternatives.md
+++ b/docs/en/src/alternatives.md
@@ -15,6 +15,7 @@ These are the alternative TUI/CLI file managers/explorers you might want to chec
 - [clifm][11]
 - [clifm][12] (non curses)
 - [felix][14]
+- [yazi][15]
 
 [add more][13]
 
@@ -30,5 +31,6 @@ These are the alternative TUI/CLI file managers/explorers you might want to chec
 [10]: https://git.2f30.org/noice/
 [11]: https://github.com/pasqu4le/clifm
 [12]: https://github.com/leo-arch/clifm
-[13]: community.md
+[13]: https://github.com/sayanarijit/xplr/edit/dev/docs/en/src/alternatives.md
 [14]: https://github.com/kyoheiu/felix
+[15]: https://github.com/sxyazi/yazi
diff --git a/docs/en/src/awesome-hacks.md b/docs/en/src/awesome-hacks.md
index fa4be462..8cfb030e 100644
--- a/docs/en/src/awesome-hacks.md
+++ b/docs/en/src/awesome-hacks.md
@@ -6,7 +6,7 @@ too small or too niche for a full fledge [plugin][2].
 
 Do you have something cool to share?
 
-[Edit this file][3] or [share them here][4] or [let us know][5].
+[Edit this file][3] or [share them here][4].
 
 You can try these hacks by writing them to a file, say `hack.lua` and passing
 it to xplr with `--extra-config` or `-C`.
@@ -526,7 +526,6 @@ xplr.config.modes.builtin.default.key_bindings.on_key.T = {
 [2]: plugin.md
 [3]: https://github.com/sayanarijit/xplr/edit/main/docs/en/src/awesome-hacks.md
 [4]: https://github.com/sayanarijit/xplr/discussions/categories/show-and-tell
-[5]: community.md
 [6]: https://gifyu.com/image/rGSR
 [7]: https://s4.gifyu.com/images/xplr-bookmark.gif
 [8]: https://github.com/sayanarijit
diff --git a/docs/en/src/community.md b/docs/en/src/community.md
deleted file mode 100644
index a280b7aa..00000000
--- a/docs/en/src/community.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Community
-
-Building an active community of awesome people and learning stuff together is
-one of my reasons to publish this tool and maintain it. Hence, please feel free
-to reach out via your preferred way.
-
-- Real-time chat lovers can join our [**matrix room**][3] or [**discord channel**][1].
-- Forum discussion veterans can [**start a new GitHub discussion**][2].
-
-[1]: https://discord.gg/JmasSPCcz3
-[2]: https://github.com/sayanarijit/xplr/discussions
-[3]: https://matrix.to/#/#xplr-pub:matrix.org
diff --git a/docs/en/src/contribute.md b/docs/en/src/contribute.md
deleted file mode 100644
index a01ddbab..00000000
--- a/docs/en/src/contribute.md
+++ /dev/null
@@ -1,32 +0,0 @@
-If you like xplr, and want to contribute, that would be really awesome.
-
-You can contribute to this project in the following ways
-
-- Contribute your time and expertise (read [CONTRIBUTING.md][1] for instructions).
-
-  - **Developers:** You can help me improve my code, fix things, implement features etc.
-  - **Repository maintainers:** You can save the users from the pain of managing xplr in their system manually.
-  - **Code Reviewers:** Teach me your ways of code.
-  - **Designers:** You can make the logo even more awesome, donate stickers and blog post worthy pictures.
-  - **Bloggers, YouTubers & broadcasters:** You can help spread the word.
-
-- Contribute by donating or sponsoring me via any of the following ways.
-  - [GitHub Sponsors][5]
-  - [Open Collective][2]
-  - [ko-fi][3]
-  - [liberapay][6]
-  - [PayPal][7]
-
-For further queries or concern related to `xplr`, [just ask us][4].
-
-### Backers
-
-<a href="https://opencollective.com/xplr#backer"><img src="https://opencollective.com/xplr/tiers/backer.svg?width=890" /></a>
-
-[1]: https://github.com/sayanarijit/xplr/blob/main/CONTRIBUTING.md
-[2]: https://opencollective.com/xplr
-[3]: https://ko-fi.com/sayanarijit
-[4]: community.md
-[5]: https://github.com/sponsors/sayanarijit?o=esb
-[6]: https://liberapay.com/sayanarijit
-[7]: https://paypal.me/sayanarijit
diff --git a/docs/en/src/general-config.md b/docs/en/src/general-config.md
index 75fa861b..b559f3ee 100644
--- a/docs/en/src/general-config.md
+++ b/docs/en/src/general-config.md
@@ -42,6 +42,19 @@ Set it to `true` if you want to hide all remaps in the help menu.
 
 Type: boolean
 
+#### xplr.config.general.paginated_scrolling
+
+Set it to `true` if you want paginated scrolling.
+
+Type: boolean
+
+#### xplr.config.general.scroll_padding
+
+Set the padding value to the scroll area.
+Only applicable when `xplr.config.general.paginated_scrolling = false`.
+
+Type: boolean
+
 #### xplr.config.general.enforce_bounded_index_navigation
 
 Set it to `true` if you want the cursor to stay in the same position when
diff --git a/docs/en/src/install.md b/docs/en/src/install.md
index 114e7eea..518247ee 100644
--- a/docs/en/src/install.md
+++ b/docs/en/src/install.md
@@ -238,23 +238,6 @@ cargo build --locked --release --bin xplr
 sudo cp target/release/xplr /usr/local/bin/
 ```
 
-## Android
-
-### [Termux][24]
-
-```bash
-pkg install rust make binutils
-cargo install --locked xplr
-
-# Run
-~/.cargo/bin/xplr
-```
-
-> Please note that xplr isn't heavily tested on Termux, hence things might need
-> a little tweaking and fixing for a smooth user experience.
-
-![termux demo][23]
-
 [1]: #direct-download
 [2]: #from-cratesio
 [3]: #build-from-source
@@ -278,7 +261,6 @@ cargo install --locked xplr
 [21]: https://www.gnu.org/software/make/
 [22]: https://git-scm.com/
 [23]: https://github.com/sayanarijit/xplr/assets/11632726/3b61e8c8-76f0-48e8-8734-50e9e7e495b7
-[24]: https://termux.dev/
 [25]: https://gifyu.com/image/tF2D
 [26]: https://github.com/sayanarijit/xplr/releases/latest/download/xplr-linux-musl.tar.gz
 [27]: https://pkgs.alpinelinux.org/packages?name=xplr
diff --git a/docs/en/src/introduction.md b/docs/en/src/introduction.md
index d48d8cec..5e684894 100644
--- a/docs/en/src/introduction.md
+++ b/docs/en/src/introduction.md
@@ -84,8 +84,6 @@ Some of the coolest features xplr provide beside the basic stuff:
     (`:` `q` `s`).
   - Quit with failure (`ctrl-c`).
 
-**Q.** What features should be added here? [let us know][20].
-
 [1]: layouts.md
 [2]: configure-key-bindings.md
 [3]: awesome-plugins.md
@@ -105,6 +103,5 @@ Some of the coolest features xplr provide beside the basic stuff:
 [17]: node_types.md
 [18]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua
 [19]: messages.md#startfifo
-[20]: community.md
 [21]: messages.md#virtual-root
 [22]: configuration.md#hooks
diff --git a/docs/en/src/layout.md b/docs/en/src/layout.md
index ec603a84..a053193f 100644
--- a/docs/en/src/layout.md
+++ b/docs/en/src/layout.md
@@ -495,6 +495,7 @@ It contains the following information:
 
 - [layout_size][37]
 - [screen_size][37]
+- [scrolltop][57]
 - [app][38]
 
 ### Size
@@ -508,6 +509,12 @@ It contains the following information:
 
 Every field is of integer type.
 
+### scrolltop
+
+Type: integer
+
+The start index of the visible nodes in the table.
+
 ### app
 
 This is a lightweight version of the [Lua Context][39]. In this context, the
@@ -587,3 +594,4 @@ Hence, only the following fields are available.
 [54]: borders.md#border-type
 [55]: #customlayout
 [56]: sum-type.md
+[57]: #scrolltop
diff --git a/docs/en/src/sum-type.md b/docs/en/src/sum-type.md
index 26e93dd5..f7df1a55 100644
--- a/docs/en/src/sum-type.md
+++ b/docs/en/src/sum-type.md
@@ -90,13 +90,7 @@ And then we'd go on documenting whatever `Layout Config` is.
 So, there you go. This is exactly what sum types are - glorified enums that can
 have nested types in each branch.
 
----
-
-If you're still confused about something, or if you found an error in this
-explanation, feel free to [discuss together][5].
-
 [1]: https://en.wikipedia.org/wiki/Tagged_union
 [2]: layout.md
 [3]: message.md
 [4]: style.md#color
-[5]: community.md
diff --git a/docs/en/src/upgrade-guide.md b/docs/en/src/upgrade-guide.md
index 54bb05cc..511b9655 100644
--- a/docs/en/src/upgrade-guide.md
+++ b/docs/en/src/upgrade-guide.md
@@ -45,7 +45,7 @@ compatibility.
 
 ### Instructions
 
-#### [v0.20.2][48] -> [v0.21.7][49]
+#### [v0.20.2][48] -> [v0.21.8][49]
 
 - Some plugins might stop rendering colors. Wait for them to update.
 - Rename `xplr.config.general.sort_and_filter_ui.search_identifier` to
@@ -127,6 +127,13 @@ compatibility.
   - You can use `c` and `m` keys in default mode to quickly copy
     and move focused or selected files, without having to change directory.
   - Use `xplr.util.debug()` to debug lua values.
+- Since v0.21.8:
+  - Scroll behavior will default to vim-like continuous scrolling. You can set
+    `xplr.config.general.paginated_scrolling = true` to revert back to the
+    paginated scrolling.
+  - Set `xplr.config.general.scroll_padding` to customize the scroll padding.
+  - The calculated `scrolltop` value will be passed as part of the
+    `Content Rendeder Argument` in `Dynamic` layout renderer functions.
 
 Thanks to @noahmayr for contributing to a major part of this release.
 
@@ -214,8 +221,6 @@ Thanks to @noahmayr for contributing to a major part of this release.
   - ScrollUpHalf ---- {
   - ScrollDownHalf -- }
 
-<sub>Like this project so far? **[Please consider contributing][5]**.</sub>
-
 #### [v0.17.6][45] -> [v0.18.0][46]
 
 - Key binding `f` `r` and `f` `R` will now filter using regex.
@@ -481,7 +486,6 @@ Else do the following:
 [2]: https://github.com/sayanarijit/xplr/releases/tag/v0.13.7
 [3]: https://github.com/sayanarijit/xplr/releases/tag/v0.14.7
 [4]: https://github.com/sayanarijit/xplr/pull/229#issue-662426960
-[5]: contribute.md
 [6]: https://github.com/sayanarijit/xplr/releases/tag/v0.12.1
 [7]: https://docs.rs/xplr/latest/xplr/app/enum.ExternalMsg.html#variant.CallLua
 [8]: https://docs.rs/xplr/latest/xplr/app/enum.ExternalMsg.html#variant.CallLuaSilently
@@ -525,5 +529,5 @@ Else do the following:
 [46]: https://github.com/sayanarijit/xplr/releases/tag/v0.18.0
 [47]: https://github.com/sayanarijit/xplr/releases/tag/v0.19.4
 [48]: https://github.com/sayanarijit/xplr/releases/tag/v0.20.2
-[49]: https://github.com/sayanarijit/xplr/releases/tag/v0.21.7
+[49]: https://github.com/sayanarijit/xplr/releases/tag/v0.21.8
 [50]: https://github.com/lotabout/skim#search-syntax
diff --git a/docs/en/src/writing-plugins.md b/docs/en/src/writing-plugins.md
index 21786e35..e56e0382 100644
--- a/docs/en/src/writing-plugins.md
+++ b/docs/en/src/writing-plugins.md
@@ -70,8 +70,6 @@ Visit [Awesome Plugins][5] for xplr plugin examples.
 
 - [Tip: A list of hacks yet to make it as Lua plugins][15]
 - [Tip: Some UI and themeing tips][12]
-- [Tip: A list of handy utility functions][13]
-- [Tip: Share tips and tricks working with Lua][14]
 - [Tutorial: Adding a New Mode][6]
 - [Example: Using Environment Variables and Pipes][7]
 - [Example: Using Lua Function Calls][8]
@@ -92,7 +90,5 @@ Visit [Awesome Plugins][5] for xplr plugin examples.
 [10]: column-renderer.md#example-customizing-table-renderer
 [11]: layout.md#example-render-a-custom-dynamic-table
 [12]: https://github.com/sayanarijit/xplr/discussions/274
-[13]: https://github.com/sayanarijit/xplr/discussions/273
-[14]: https://github.com/sayanarijit/xplr/discussions/250
-[15]: https://github.com/sayanarijit/xplr/wiki/Hacks
+[15]: awesome-hacks.md
 [16]: https://github.com/sayanarijit/xplr/discussions/529#discussioncomment-4073734
diff --git a/docs/en/src/xplr.util.md b/docs/en/src/xplr.util.md
index fed14d0d..7ef601c9 100644
--- a/docs/en/src/xplr.util.md
+++ b/docs/en/src/xplr.util.md
@@ -397,7 +397,7 @@ xplr.util.to_yaml({ foo = "bar" })
 Get a [Style][3] object for the given path based on the LS_COLORS
 environment variable.
 
-Type: function( path:string ) -> [Style][3]|nil
+Type: function( path:string ) -> [Style][3]
 
 Example:
 
diff --git a/docs/script/deploy-cf.sh b/docs/script/deploy-cf.sh
new file mode 100644
index 00000000..1acd71a6
--- /dev/null
+++ b/docs/script/deploy-cf.sh
@@ -0,0 +1,11 @@
+v="0.4.37"
+
+curl -L https://github.com/rust-lang/mdBook/releases/download/v$v/mdbook-v$v-x86_64-unknown-linux-gnu.tar.gz -o mdbook.tgz \
+  && tar xzvf mdbook.tgz \
+  && ./mdbook build docs/en \
+  && mkdir dist \
+  && mv -v docs/en/book/html dist/en \
+  && mv -v assets dist \
+  && mv -v docs/landing/index.html docs/landing/css docs/landing/js dist \
+  && rm -v mdbook \
+  && rm -v mdbook.tgz
diff --git a/src/app.rs b/src/app.rs
index 49f3b333..e4700df7 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -335,12 +335,12 @@ impl App {
             &config
                 .general
                 .initial_mode
-                .to_owned()
+                .clone()
                 .unwrap_or_else(|| "default".into()),
         ) {
             Some(m) => m.clone().sanitized(
                 config.general.read_only,
-                config.general.global_key_bindings.to_owned(),
+                config.general.global_key_bindings.clone(),
             ),
             None => {
                 bail!("'default' mode is missing")
@@ -351,7 +351,7 @@ impl App {
             &config
                 .general
                 .initial_layout
-                .to_owned()
+                .clone()
                 .unwrap_or_else(|| "default".into()),
         ) {
             Some(l) => l.clone(),
@@ -387,7 +387,7 @@ impl App {
         }
 
         if let Some(sorters) = &config.general.initial_sorting {
-            explorer_config.sorters = sorters.clone();
+            explorer_config.sorters.clone_from(sorters);
         };
 
         let hostname = gethostname().to_string_lossy().to_string();
@@ -820,7 +820,6 @@ impl App {
 
     fn focus_previous(mut self) -> Result<Self> {
         let bounded = self.config.general.enforce_bounded_index_navigation;
-
         if let Some(dir) = self.directory_buffer_mut() {
             dir.focus = if dir.focus == 0 {
                 if bounded {
@@ -905,7 +904,6 @@ impl App {
 
     fn focus_next(mut self) -> Result<Self> {
         let bounded = self.config.general.enforce_bounded_index_navigation;
-
         if let Some(dir) = self.directory_buffer_mut() {
             dir.focus = if (dir.focus + 1) == dir.total {
                 if bounded {
@@ -917,6 +915,7 @@ impl App {
                 dir.focus + 1
             }
         };
+
         Ok(self)
     }
 
@@ -995,7 +994,7 @@ impl App {
     fn follow_symlink(self) -> Result<Self> {
         if let Some(pth) = self
             .focused_node()
-            .and_then(|n| n.symlink.to_owned().map(|s| s.absolute_path))
+            .and_then(|n| n.symlink.clone().map(|s| s.absolute_path))
         {
             self.focus_path(&pth, true)
         } else {
@@ -1382,7 +1381,7 @@ impl App {
             self = self.push_mode();
             self.mode = mode.sanitized(
                 self.config.general.read_only,
-                self.config.general.global_key_bindings.to_owned(),
+                self.config.general.global_key_bindings.clone(),
             );
 
             // Hooks
@@ -1407,7 +1406,7 @@ impl App {
             self = self.push_mode();
             self.mode = mode.sanitized(
                 self.config.general.read_only,
-                self.config.general.global_key_bindings.to_owned(),
+                self.config.general.global_key_bindings.clone(),
             );
 
             // Hooks
@@ -1434,7 +1433,7 @@ impl App {
 
     fn switch_layout_builtin(mut self, layout: &str) -> Result<Self> {
         if let Some(l) = self.config.layouts.builtin.get(layout) {
-            self.layout = l.to_owned();
+            self.layout = l.clone();
 
             // Hooks
             if !self.hooks.on_layout_switch.is_empty() {
@@ -1450,7 +1449,7 @@ impl App {
 
     fn switch_layout_custom(mut self, layout: &str) -> Result<Self> {
         if let Some(l) = self.config.layouts.get_custom(layout) {
-            self.layout = l.to_owned();
+            self.layout = l.clone();
 
             // Hooks
             if !self.hooks.on_layout_switch.is_empty() {
@@ -1575,7 +1574,7 @@ impl App {
 
     pub fn select(mut self) -> Result<Self> {
         let count = self.selection.len();
-        if let Some(n) = self.focused_node().map(|n| n.to_owned()) {
+        if let Some(n) = self.focused_node().cloned() {
             self.selection.insert(n);
         }
 
@@ -1630,7 +1629,7 @@ impl App {
 
     pub fn un_select(mut self) -> Result<Self> {
         let count = self.selection.len();
-        if let Some(n) = self.focused_node().map(|n| n.to_owned()) {
+        if let Some(n) = self.focused_node().cloned() {
             self.selection
                 .retain(|s| s.absolute_path != n.absolute_path);
         }
@@ -1804,7 +1803,7 @@ impl App {
             .config
             .general
             .initial_sorting
-            .to_owned()
+            .clone()
             .unwrap_or_default();
         Ok(self)
     }
diff --git a/src/compat.rs b/src/compat.rs
index bafd50cb..469777cd 100644
--- a/src/compat.rs
+++ b/src/compat.rs
@@ -7,7 +7,7 @@ use crate::ui::block;
 use crate::ui::string_to_text;
 use crate::ui::Constraint;
 use crate::ui::ContentRendererArg;
-use mlua::Lua;
+use crate::ui::UI;
 use serde::{Deserialize, Serialize};
 use tui::layout::Constraint as TuiConstraint;
 use tui::layout::Rect as TuiRect;
@@ -60,12 +60,11 @@ pub struct CustomContent {
 
 /// A cursed function from crate::ui.
 pub fn draw_custom_content(
+    ui: &mut UI,
     f: &mut Frame,
-    screen_size: TuiRect,
     layout_size: TuiRect,
     app: &app::App,
     content: CustomContent,
-    lua: &Lua,
 ) {
     let config = app.config.general.panel_ui.default.clone();
     let title = content.title;
@@ -85,12 +84,13 @@ pub fn draw_custom_content(
             let ctx = ContentRendererArg {
                 app: app.to_lua_ctx_light(),
                 layout_size: layout_size.into(),
-                screen_size: screen_size.into(),
+                screen_size: ui.screen_size.into(),
+                scrolltop: ui.scrolltop as u16,
             };
 
-            let render = lua::serialize(lua, &ctx)
+            let render = lua::serialize(ui.lua, &ctx)
                 .map(|arg| {
-                    lua::call(lua, &render, arg).unwrap_or_else(|e| format!("{e:?}"))
+                    lua::call(ui.lua, &render, arg).unwrap_or_else(|e| format!("{e:?}"))
                 })
                 .unwrap_or_else(|e| e.to_string());
 
@@ -121,12 +121,13 @@ pub fn draw_custom_content(
             let ctx = ContentRendererArg {
                 app: app.to_lua_ctx_light(),
                 layout_size: layout_size.into(),
-                screen_size: screen_size.into(),
+                screen_size: ui.screen_size.into(),
+                scrolltop: ui.scrolltop as u16,
             };
 
-            let items = lua::serialize(lua, &ctx)
+            let items = lua::serialize(ui.lua, &ctx)
                 .map(|arg| {
-                    lua::call(lua, &render, arg)
+                    lua::call(ui.lua, &render, arg)
                         .unwrap_or_else(|e| vec![format!("{e:?}")])
                 })
                 .unwrap_or_else(|e| vec![e.to_string()])
@@ -161,7 +162,7 @@ pub fn draw_custom_content(
 
             let widths = widths
                 .into_iter()
-                .map(|w| w.to_tui(screen_size, layout_size))
+                .map(|w| w.to_tui(ui.screen_size, layout_size))
                 .collect::<Vec<TuiConstraint>>();
 
             let content = Table::new(rows, widths)
@@ -182,12 +183,13 @@ pub fn draw_custom_content(
             let ctx = ContentRendererArg {
                 app: app.to_lua_ctx_light(),
                 layout_size: layout_size.into(),
-                screen_size: screen_size.into(),
+                screen_size: ui.screen_size.into(),
+                scrolltop: ui.scrolltop as u16,
             };
 
-            let rows = lua::serialize(lua, &ctx)
+            let rows = lua::serialize(ui.lua, &ctx)
                 .map(|arg| {
-                    lua::call(lua, &render, arg)
+                    lua::call(ui.lua, &render, arg)
                         .unwrap_or_else(|e| vec![vec![format!("{e:?}")]])
                 })
                 .unwrap_or_else(|e| vec![vec![e.to_string()]])
@@ -204,7 +206,7 @@ pub fn draw_custom_content(
 
             let widths = widths
                 .into_iter()
-                .map(|w| w.to_tui(screen_size, layout_size))
+                .map(|w| w.to_tui(ui.screen_size, layout_size))
                 .collect::<Vec<TuiConstraint>>();
 
             let mut content = Table::new(rows, &widths).block(block(
diff --git a/src/config.rs b/src/config.rs
index db08f86b..5f34a322 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -55,7 +55,7 @@ pub struct NodeTypeConfig {
 impl NodeTypeConfig {
     pub fn extend(mut self, other: &Self) -> Self {
         self.style = self.style.extend(&other.style);
-        self.meta.extend(other.meta.to_owned());
+        self.meta.extend(other.meta.clone());
         self
     }
 }
@@ -85,11 +85,11 @@ pub struct NodeTypesConfig {
 impl NodeTypesConfig {
     pub fn get(&self, node: &Node) -> NodeTypeConfig {
         let mut node_type = if node.is_symlink {
-            self.symlink.to_owned()
+            self.symlink.clone()
         } else if node.is_dir {
-            self.directory.to_owned()
+            self.directory.clone()
         } else {
-            self.file.to_owned()
+            self.file.clone()
         };
 
         let mut me = node.mime_essence.splitn(2, '/');
@@ -104,7 +104,7 @@ impl NodeTypesConfig {
             node_type = node_type.extend(conf);
         }
 
-        if let Some(conf) = self.extension.get(&node.extension) {
+        if let (Some(conf), false) = (self.extension.get(&node.extension), node.is_dir) {
             node_type = node_type.extend(conf);
         }
 
@@ -141,7 +141,7 @@ pub struct UiElement {
 
 impl UiElement {
     pub fn extend(mut self, other: &Self) -> Self {
-        self.format = other.format.to_owned().or(self.format);
+        self.format = other.format.clone().or(self.format);
         self.style = self.style.extend(&other.style);
         self
     }
@@ -353,6 +353,12 @@ pub struct GeneralConfig {
 
     #[serde(default)]
     pub global_key_bindings: KeyBindings,
+
+    #[serde(default)]
+    pub paginated_scrolling: bool,
+
+    #[serde(default)]
+    pub scroll_padding: usize,
 }
 
 #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
@@ -638,8 +644,8 @@ impl PanelUiConfig {
     pub fn extend(mut self, other: &Self) -> Self {
         self.title = self.title.extend(&other.title);
         self.style = self.style.extend(&other.style);
-        self.borders = other.borders.to_owned().or(self.borders);
-        self.border_type = other.border_type.to_owned().or(self.border_type);
+        self.borders = other.borders.clone().or(self.borders);
+        self.border_type = other.border_type.or(self.border_type);
         self.border_style = self.border_style.extend(&other.border_style);
         self
     }
diff --git a/src/dirs.rs b/src/dirs.rs
index 4bbad82f..60a7ecaa 100644
--- a/src/dirs.rs
+++ b/src/dirs.rs
@@ -22,5 +22,5 @@ pub fn runtime_dir() -> PathBuf {
     else {
         return env::temp_dir();
     };
-    dir.to_owned()
+    dir.clone()
 }
diff --git a/src/init.lua b/src/init.lua
index a36ddb7b..e5b93c91 100644
--- a/src/init.lua
+++ b/src/init.lua
@@ -91,6 +91,17 @@ xplr.config.general.enable_recover_mode = false
 -- Type: boolean
 xplr.config.general.hide_remaps_in_help_menu = false
 
+-- Set it to `true` if you want paginated scrolling.
+--
+-- Type: boolean
+xplr.config.general.paginated_scrolling = false
+
+-- Set the padding value to the scroll area.
+-- Only applicable when `xplr.config.general.paginated_scrolling = false`.
+--
+-- Type: boolean
+xplr.config.general.scroll_padding = 5
+
 -- Set it to `true` if you want the cursor to stay in the same position when
 -- the focus is on the first path and you navigate to the previous path
 -- (by pressing `up`/`k`), or when the focus is on the last path and you
@@ -478,7 +489,7 @@ xplr.config.general.sort_and_filter_ui.search_identifiers = {
 --
 -- Type: nullable string
 xplr.config.general.sort_and_filter_ui.search_direction_identifiers.ordered.format =
-"↓"
+  "↓"
 
 -- The shape of unordered indicator for search ordering identifiers in Sort & filter panel.
 --
@@ -676,7 +687,7 @@ xplr.config.general.panel_ui.sort_and_filter.border_style = {}
 -- Type: nullable list of [Node Sorter](https://xplr.dev/en/sorting#node-sorter-applicable)
 xplr.config.general.initial_sorting = {
   { sorter = "ByCanonicalIsDir", reverse = true },
-  { sorter = "ByIRelativePath",  reverse = false },
+  { sorter = "ByIRelativePath", reverse = false },
 }
 
 -- The name of one of the modes to use when xplr loads.
@@ -1325,23 +1336,23 @@ xplr.config.modes.builtin.default = {
 }
 
 xplr.config.modes.builtin.default.key_bindings.on_key["v"] =
-    xplr.config.modes.builtin.default.key_bindings.on_key["space"]
+  xplr.config.modes.builtin.default.key_bindings.on_key["space"]
 xplr.config.modes.builtin.default.key_bindings.on_key["V"] =
-    xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-a"]
+  xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-a"]
 xplr.config.modes.builtin.default.key_bindings.on_key["/"] =
-    xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-f"]
+  xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-f"]
 xplr.config.modes.builtin.default.key_bindings.on_key["h"] =
-    xplr.config.modes.builtin.default.key_bindings.on_key["left"]
+  xplr.config.modes.builtin.default.key_bindings.on_key["left"]
 xplr.config.modes.builtin.default.key_bindings.on_key["j"] =
-    xplr.config.modes.builtin.default.key_bindings.on_key["down"]
+  xplr.config.modes.builtin.default.key_bindings.on_key["down"]
 xplr.config.modes.builtin.default.key_bindings.on_key["k"] =
-    xplr.config.modes.builtin.default.key_bindings.on_key["up"]
+  xplr.config.modes.builtin.default.key_bindings.on_key["up"]
 xplr.config.modes.builtin.default.key_bindings.on_key["l"] =
-    xplr.config.modes.builtin.default.key_bindings.on_key["right"]
+  xplr.config.modes.builtin.default.key_bindings.on_key["right"]
 xplr.config.modes.builtin.default.key_bindings.on_key["tab"] =
-    xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-i"] -- compatibility workaround
+  xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-i"] -- compatibility workaround
 xplr.config.modes.builtin.default.key_bindings.on_key["?"] =
-    xplr.config.general.global_key_bindings.on_key["f1"]
+  xplr.config.general.global_key_bindings.on_key["f1"]
 
 -- The builtin debug error mode.
 --
@@ -1993,9 +2004,9 @@ xplr.config.modes.builtin.number = {
 }
 
 xplr.config.modes.builtin.number.key_bindings.on_key["j"] =
-    xplr.config.modes.builtin.number.key_bindings.on_key["down"]
+  xplr.config.modes.builtin.number.key_bindings.on_key["down"]
 xplr.config.modes.builtin.number.key_bindings.on_key["k"] =
-    xplr.config.modes.builtin.number.key_bindings.on_key["up"]
+  xplr.config.modes.builtin.number.key_bindings.on_key["up"]
 
 -- The builtin go to mode.
 --
@@ -2479,9 +2490,9 @@ xplr.config.modes.builtin.search = {
 }
 
 xplr.config.modes.builtin.search.key_bindings.on_key["ctrl-n"] =
-    xplr.config.modes.builtin.search.key_bindings.on_key["down"]
+  xplr.config.modes.builtin.search.key_bindings.on_key["down"]
 xplr.config.modes.builtin.search.key_bindings.on_key["ctrl-p"] =
-    xplr.config.modes.builtin.search.key_bindings.on_key["up"]
+  xplr.config.modes.builtin.search.key_bindings.on_key["up"]
 
 -- The builtin filter mode.
 --
@@ -3113,8 +3124,8 @@ xplr.fn.builtin.fmt_general_selection_item = function(n)
   if n.is_dir then
     shortened = shortened .. "/"
   end
-  local ls_style = xplr.util.lscolor(n.absolute_path)
   local meta_style = xplr.util.node_type(n).style
+  local ls_style = xplr.util.lscolor(n.absolute_path)
   local style = xplr.util.style_mix({ ls_style, meta_style })
   return xplr.util.paint(shortened:gsub("\n", nl), style)
 end
@@ -3137,8 +3148,8 @@ end
 xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m)
   local nl = xplr.util.paint("\\n", { add_modifiers = { "Italic", "Dim" } })
   local r = m.tree .. m.prefix
-  local style = xplr.util.lscolor(m.absolute_path)
-  style = xplr.util.style_mix({ style, m.style })
+  local ls_style = xplr.util.lscolor(m.absolute_path)
+  local style = xplr.util.style_mix({ ls_style, m.style })
 
   if m.meta.icon == nil then
     r = r .. ""
@@ -3161,7 +3172,7 @@ xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m)
       r = r .. "×"
     else
       local symlink_path =
-          xplr.util.shorten(m.symlink.absolute_path, { base = m.parent })
+        xplr.util.shorten(m.symlink.absolute_path, { base = m.parent })
       if m.symlink.is_dir then
         symlink_path = symlink_path .. "/"
       end
@@ -3183,14 +3194,14 @@ xplr.fn.builtin.fmt_general_table_row_cols_2 = function(m)
   local T = xplr.util.paint("T", { fg = "Red" })
 
   return xplr.util
-      .permissions_rwx(m.permissions)
-      :gsub("r", r)
-      :gsub("w", w)
-      :gsub("x", x)
-      :gsub("s", s)
-      :gsub("S", S)
-      :gsub("t", t)
-      :gsub("T", T)
+    .permissions_rwx(m.permissions)
+    :gsub("r", r)
+    :gsub("w", w)
+    :gsub("x", x)
+    :gsub("s", s)
+    :gsub("S", S)
+    :gsub("t", t)
+    :gsub("T", T)
 end
 
 -- Renders the fourth column in the table
diff --git a/src/input.rs b/src/input.rs
index dd1fba58..b9f91152 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -647,7 +647,7 @@ impl Key {
             Self::ShiftZ => Some('Z'),
 
             Self::Space => Some(' '),
-            Self::Special(c) => Some(c.to_owned()),
+            Self::Special(c) => Some(*c),
 
             _ => None,
         }
diff --git a/src/lua/mod.rs b/src/lua/mod.rs
index 49c26402..467a3bfb 100644
--- a/src/lua/mod.rs
+++ b/src/lua/mod.rs
@@ -160,24 +160,24 @@ mod tests {
         assert!(check_version(VERSION, "foo path").is_ok());
 
         // Current release if OK
-        assert!(check_version("0.21.7", "foo path").is_ok());
+        assert!(check_version("0.21.8", "foo path").is_ok());
 
         // Prev major release is ERR
         // - Not yet
 
         // Prev minor release is ERR (Change when we get to v1)
-        assert!(check_version("0.20.7", "foo path").is_err());
+        assert!(check_version("0.20.8", "foo path").is_err());
 
         // Prev bugfix release is OK
-        assert!(check_version("0.21.6", "foo path").is_ok());
+        assert!(check_version("0.21.7", "foo path").is_ok());
 
         // Next major release is ERR
-        assert!(check_version("1.20.7", "foo path").is_err());
+        assert!(check_version("1.20.8", "foo path").is_err());
 
         // Next minor release is ERR
-        assert!(check_version("0.22.7", "foo path").is_err());
+        assert!(check_version("0.22.8", "foo path").is_err());
 
         // Next bugfix release is ERR (Change when we get to v1)
-        assert!(check_version("0.21.8", "foo path").is_err());
+        assert!(check_version("0.21.9", "foo path").is_err());
     }
 }
diff --git a/src/lua/util.rs b/src/lua/util.rs
index da01f3cb..6d761e3a 100644
--- a/src/lua/util.rs
+++ b/src/lua/util.rs
@@ -160,7 +160,7 @@ pub fn is_dir<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
 /// ```
 pub fn is_file<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
     let func =
-        lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_dir()))?;
+        lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_file()))?;
     util.set("is_file", func)?;
     Ok(util)
 }
@@ -654,7 +654,7 @@ pub fn to_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
 /// Get a [Style][3] object for the given path based on the LS_COLORS
 /// environment variable.
 ///
-/// Type: function( path:string ) -> [Style][3]|nil
+/// Type: function( path:string ) -> [Style][3]
 ///
 /// Example:
 ///
@@ -664,7 +664,10 @@ pub fn to_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
 /// ```
 pub fn lscolor<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
     let func = lua.create_function(move |lua, path: String| {
-        let style = LS_COLORS.style_for_path(path).map(Style::from);
+        let style = LS_COLORS
+            .style_for_path(path)
+            .map(Style::from)
+            .unwrap_or_default();
         lua::serialize(lua, &style).map_err(LuaError::custom)
     })?;
     util.set("lscolor", func)?;
diff --git a/src/runner.rs b/src/runner.rs
index 4b80274b..4470135b 100644
--- a/src/runner.rs
+++ b/src/runner.rs
@@ -8,7 +8,8 @@ use crate::explorer;
 use crate::lua;
 use crate::pipe;
 use crate::pwd_watcher;
-use crate::ui;
+use crate::ui::NO_COLOR;
+use crate::ui::UI;
 use crate::yaml;
 use anyhow::{bail, Error, Result};
 use crossterm::event;
@@ -285,7 +286,7 @@ impl Runner {
         tx_pwd_watcher.send(app.pwd.clone())?;
 
         let mut result = Ok(None);
-        let session_path = app.session_path.to_owned();
+        let session_path = app.session_path.clone();
 
         term::enable_raw_mode()?;
 
@@ -341,6 +342,9 @@ impl Runner {
             None,
         ))?;
 
+        // UI
+        let mut ui = UI::new(&lua);
+
         'outer: for task in rx_msg_in {
             match app.handle_task(task) {
                 Ok(a) => {
@@ -411,7 +415,7 @@ impl Runner {
                             }
 
                             ScrollUpHalf => {
-                                app = app.focus_next_by_relative_index(
+                                app = app.focus_previous_by_relative_index(
                                     terminal.size()?.height as usize / 2,
                                 )?;
                             }
@@ -470,13 +474,13 @@ impl Runner {
                                 }
 
                                 if app.pwd != last_pwd {
-                                    last_pwd = app.pwd.clone();
+                                    last_pwd.clone_from(&app.pwd);
 
                                     // $PWD watcher
                                     tx_pwd_watcher.send(app.pwd.clone())?;
 
                                     // OSC 7: Change CWD
-                                    if !(*ui::NO_COLOR) {
+                                    if !(*NO_COLOR) {
                                         write!(
                                             terminal.backend_mut(),
                                             "\x1b]7;file://{}{}\x1b\\",
@@ -493,7 +497,7 @@ impl Runner {
                                 }
 
                                 // UI
-                                terminal.draw(|f| ui::draw(f, &app, &lua))?;
+                                terminal.draw(|f| ui.draw(f, &app))?;
                             }
 
                             EnableMouse => {
diff --git a/src/ui.rs b/src/ui.rs
index 0d3dda03..5ff5d991 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -60,6 +60,33 @@ pub fn string_to_text<'a>(string: String) -> Text<'a> {
     }
 }
 
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Rect {
+    x: u16,
+    y: u16,
+    height: u16,
+    width: u16,
+}
+
+impl From<TuiRect> for Rect {
+    fn from(tui: TuiRect) -> Self {
+        Self {
+            x: tui.x,
+            y: tui.y,
+            height: tui.height,
+            width: tui.width,
+        }
+    }
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ContentRendererArg {
+    pub app: app::LuaContextLight,
+    pub screen_size: Rect,
+    pub layout_size: Rect,
+    pub scrolltop: u16,
+}
+
 #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
 #[serde(deny_unknown_fields)]
 pub struct LayoutOptions {
@@ -81,7 +108,7 @@ impl LayoutOptions {
         self.margin = other.margin.or(self.margin);
         self.horizontal_margin = other.horizontal_margin.or(self.horizontal_margin);
         self.vertical_margin = other.vertical_margin.or(self.vertical_margin);
-        self.constraints = other.constraints.to_owned().or(self.constraints);
+        self.constraints = other.constraints.clone().or(self.constraints);
         self
     }
 }
@@ -154,7 +181,7 @@ impl Layout {
                 },
             ) => Self::Horizontal {
                 config: sconfig.extend(oconfig),
-                splits: osplits.to_owned(),
+                splits: osplits.clone(),
             },
 
             (
@@ -168,9 +195,9 @@ impl Layout {
                 },
             ) => Self::Vertical {
                 config: sconfig.extend(oconfig),
-                splits: osplits.to_owned(),
+                splits: osplits.clone(),
             },
-            (_, other) => other.to_owned(),
+            (_, other) => other.clone(),
         }
     }
 
@@ -192,7 +219,7 @@ impl Layout {
             },
             other => {
                 if other == *target {
-                    replacement.to_owned()
+                    replacement.clone()
                 } else {
                     other
                 }
@@ -364,14 +391,10 @@ impl Style {
     pub fn extend(mut self, other: &Self) -> Self {
         self.fg = other.fg.or(self.fg);
         self.bg = other.bg.or(self.bg);
-        self.add_modifiers = extend_optional_modifiers(
-            self.add_modifiers,
-            other.add_modifiers.to_owned(),
-        );
-        self.sub_modifiers = extend_optional_modifiers(
-            self.sub_modifiers,
-            other.sub_modifiers.to_owned(),
-        );
+        self.add_modifiers =
+            extend_optional_modifiers(self.add_modifiers, other.add_modifiers.clone());
+        self.sub_modifiers =
+            extend_optional_modifiers(self.sub_modifiers, other.sub_modifiers.clone());
         self
     }
 }
@@ -594,12 +617,12 @@ pub struct ResolvedNodeUiMetadata {
 impl From<ResolvedNode> for ResolvedNodeUiMetadata {
     fn from(node: ResolvedNode) -> Self {
         Self {
-            absolute_path: node.absolute_path.to_owned(),
-            extension: node.extension.to_owned(),
+            absolute_path: node.absolute_path.clone(),
+            extension: node.extension.clone(),
             is_dir: node.is_dir,
             is_file: node.is_file,
             is_readonly: node.is_readonly,
-            mime_essence: node.mime_essence.to_owned(),
+            mime_essence: node.mime_essence.clone(),
             size: node.size,
             human_size: node.human_size,
             created: node.created,
@@ -663,21 +686,21 @@ impl NodeUiMetadata {
         style: Style,
     ) -> Self {
         Self {
-            parent: node.parent.to_owned(),
-            relative_path: node.relative_path.to_owned(),
-            absolute_path: node.absolute_path.to_owned(),
-            extension: node.extension.to_owned(),
+            parent: node.parent.clone(),
+            relative_path: node.relative_path.clone(),
+            absolute_path: node.absolute_path.clone(),
+            extension: node.extension.clone(),
             is_symlink: node.is_symlink,
             is_broken: node.is_broken,
             is_dir: node.is_dir,
             is_file: node.is_file,
             is_readonly: node.is_readonly,
-            mime_essence: node.mime_essence.to_owned(),
+            mime_essence: node.mime_essence.clone(),
             size: node.size,
-            human_size: node.human_size.to_owned(),
-            permissions: node.permissions.to_owned(),
-            canonical: node.canonical.to_owned().map(ResolvedNode::into),
-            symlink: node.symlink.to_owned().map(ResolvedNode::into),
+            human_size: node.human_size.clone(),
+            permissions: node.permissions,
+            canonical: node.canonical.clone().map(ResolvedNode::into),
+            symlink: node.symlink.clone().map(ResolvedNode::into),
             created: node.created,
             last_modified: node.last_modified,
             uid: node.uid,
@@ -703,7 +726,7 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> {
         .borders(TuiBorders::from_bits_truncate(
             config
                 .borders
-                .to_owned()
+                .clone()
                 .unwrap_or_default()
                 .iter()
                 .map(|b| b.bits())
@@ -718,788 +741,750 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> {
         .border_style(config.border_style)
 }
 
-fn draw_table(
-    f: &mut Frame,
-    screen_size: TuiRect,
-    layout_size: TuiRect,
-    app: &app::App,
-    lua: &Lua,
-) {
-    let panel_config = &app.config.general.panel_ui;
-    let config = panel_config.default.to_owned().extend(&panel_config.table);
-    let app_config = app.config.to_owned();
-    let header_height = app_config.general.table.header.height.unwrap_or(1);
-    let height: usize =
-        (layout_size.height.max(header_height + 2) - (header_height + 2)).into();
-    let row_style = app_config.general.table.row.style.to_owned();
-
-    let rows = app
-        .directory_buffer
-        .as_ref()
-        .map(|dir| {
-            dir.nodes
-                .iter()
-                .enumerate()
-                .skip(height * (dir.focus / height.max(1)))
-                .take(height)
-                .map(|(index, node)| {
-                    let is_focused = dir.focus == index;
-
-                    let is_selected = app
-                        .selection
-                        .iter()
-                        .any(|s| s.absolute_path == node.absolute_path);
-
-                    let is_first = index == 0;
-                    let is_last = index == dir.total.max(1) - 1;
-
-                    let tree = app_config
-                        .general
-                        .table
-                        .tree
-                        .to_owned()
-                        .map(|t| {
-                            if is_last {
-                                t.2.format
-                            } else if is_first {
-                                t.0.format
-                            } else {
-                                t.1.format
-                            }
-                        })
-                        .unwrap_or_default();
+pub struct UI<'lua> {
+    pub lua: &'lua Lua,
+    pub screen_size: TuiRect,
+    pub scrolltop: usize,
+}
 
-                    let node_type = app_config.node_types.get(node);
+impl<'lua> UI<'lua> {
+    pub fn new(lua: &'lua Lua) -> Self {
+        let screen_size = Default::default();
+        let scrolltop = 0;
+        Self {
+            lua,
+            scrolltop,
+            screen_size,
+        }
+    }
+}
 
-                    let (relative_index, is_before_focus, is_after_focus) =
-                        match dir.focus.cmp(&index) {
-                            Ordering::Greater => (dir.focus - index, true, false),
-                            Ordering::Less => (index - dir.focus, false, true),
-                            Ordering::Equal => (0, false, false),
-                        };
+impl UI<'_> {
+    fn draw_table(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
+        let panel_config = &app.config.general.panel_ui;
+        let config = panel_config.default.clone().extend(&panel_config.table);
+        let app_config = app.config.clone();
+        let header_height = app_config.general.table.header.height.unwrap_or(1);
+        let height: usize = layout_size.height.saturating_sub(header_height + 2).into();
+        let row_style = app_config.general.table.row.style.clone();
+
+        let rows = app
+            .directory_buffer
+            .as_ref()
+            .map(|dir| {
+                // Scroll
+                if app.config.general.paginated_scrolling {
+                    // Paginated scrolling
+                    self.scrolltop = height * (dir.focus / height.max(1))
+                } else {
+                    // Vim-like-scrolling
+                    let padding = app.config.general.scroll_padding;
+                    if dir.focus >= (self.scrolltop + height).saturating_sub(padding) {
+                        // Scrolling down
+                        self.scrolltop = (dir.focus + padding + 1)
+                            .saturating_sub(height)
+                            .min(dir.total.saturating_sub(height));
+                    } else if dir.focus < self.scrolltop + padding {
+                        // Scrolling up
+                        self.scrolltop = dir.focus.saturating_sub(padding);
+                    }
+                };
 
-                    let (mut prefix, mut suffix, mut style) = {
-                        let ui = app_config.general.default_ui.to_owned();
-                        (ui.prefix, ui.suffix, ui.style.extend(&node_type.style))
-                    };
+                dir.nodes
+                    .iter()
+                    .enumerate()
+                    .skip(self.scrolltop)
+                    .take(height)
+                    .map(|(index, node)| {
+                        let is_focused = dir.focus == index;
+
+                        let is_selected = app
+                            .selection
+                            .iter()
+                            .any(|s| s.absolute_path == node.absolute_path);
+
+                        let is_first = index == 0;
+                        let is_last = index == dir.total.max(1) - 1;
+
+                        let tree = app_config
+                            .general
+                            .table
+                            .tree
+                            .clone()
+                            .map(|t| {
+                                if is_last {
+                                    t.2.format
+                                } else if is_first {
+                                    t.0.format
+                                } else {
+                                    t.1.format
+                                }
+                            })
+                            .unwrap_or_default();
+
+                        let node_type = app_config.node_types.get(node);
+
+                        let (relative_index, is_before_focus, is_after_focus) =
+                            match dir.focus.cmp(&index) {
+                                Ordering::Greater => (dir.focus - index, true, false),
+                                Ordering::Less => (index - dir.focus, false, true),
+                                Ordering::Equal => (0, false, false),
+                            };
+
+                        let (mut prefix, mut suffix, mut style) = {
+                            let ui = app_config.general.default_ui.clone();
+                            (ui.prefix, ui.suffix, ui.style.extend(&node_type.style))
+                        };
 
-                    if is_focused && is_selected {
-                        let ui = app_config.general.focus_selection_ui.to_owned();
-                        prefix = ui.prefix.to_owned().or(prefix);
-                        suffix = ui.suffix.to_owned().or(suffix);
-                        style = style.extend(&ui.style);
-                    } else if is_selected {
-                        let ui = app_config.general.selection_ui.to_owned();
-                        prefix = ui.prefix.to_owned().or(prefix);
-                        suffix = ui.suffix.to_owned().or(suffix);
-                        style = style.extend(&ui.style);
-                    } else if is_focused {
-                        let ui = app_config.general.focus_ui.to_owned();
-                        prefix = ui.prefix.to_owned().or(prefix);
-                        suffix = ui.suffix.to_owned().or(suffix);
-                        style = style.extend(&ui.style);
-                    };
+                        if is_focused && is_selected {
+                            let ui = app_config.general.focus_selection_ui.clone();
+                            prefix = ui.prefix.clone().or(prefix);
+                            suffix = ui.suffix.clone().or(suffix);
+                            style = style.extend(&ui.style);
+                        } else if is_selected {
+                            let ui = app_config.general.selection_ui.clone();
+                            prefix = ui.prefix.clone().or(prefix);
+                            suffix = ui.suffix.clone().or(suffix);
+                            style = style.extend(&ui.style);
+                        } else if is_focused {
+                            let ui = app_config.general.focus_ui.clone();
+                            prefix = ui.prefix.clone().or(prefix);
+                            suffix = ui.suffix.clone().or(suffix);
+                            style = style.extend(&ui.style);
+                        };
 
-                    let meta = NodeUiMetadata::new(
-                        node,
-                        index,
-                        relative_index,
-                        is_before_focus,
-                        is_after_focus,
-                        tree.unwrap_or_default(),
-                        prefix.unwrap_or_default(),
-                        suffix.unwrap_or_default(),
-                        is_selected,
-                        is_focused,
-                        dir.total,
-                        node_type.meta,
-                        style,
-                    );
-
-                    let cols = lua::serialize::<NodeUiMetadata>(lua, &meta)
-                        .map(|v| {
-                            app_config
-                                .general
-                                .table
-                                .row
-                                .cols
-                                .to_owned()
-                                .unwrap_or_default()
-                                .iter()
-                                .filter_map(|c| {
-                                    c.format.as_ref().map(|f| {
-                                        let out = lua::call(lua, f, v.clone())
-                                            .unwrap_or_else(|e| format!("{e:?}"));
-                                        (string_to_text(out), c.style.to_owned())
+                        let meta = NodeUiMetadata::new(
+                            node,
+                            index,
+                            relative_index,
+                            is_before_focus,
+                            is_after_focus,
+                            tree.unwrap_or_default(),
+                            prefix.unwrap_or_default(),
+                            suffix.unwrap_or_default(),
+                            is_selected,
+                            is_focused,
+                            dir.total,
+                            node_type.meta,
+                            style,
+                        );
+
+                        let cols = lua::serialize::<NodeUiMetadata>(self.lua, &meta)
+                            .map(|v| {
+                                app_config
+                                    .general
+                                    .table
+                                    .row
+                                    .cols
+                                    .clone()
+                                    .unwrap_or_default()
+                                    .iter()
+                                    .filter_map(|c| {
+                                        c.format.as_ref().map(|f| {
+                                            let out = lua::call(self.lua, f, v.clone())
+                                                .unwrap_or_else(|e| format!("{e:?}"));
+                                            (string_to_text(out), c.style.clone())
+                                        })
                                     })
-                                })
-                                .collect::<Vec<(Text, Style)>>()
-                        })
-                        .unwrap_or_default()
-                        .into_iter()
-                        .map(|(text, style)| Cell::from(text).style(style))
-                        .collect::<Vec<Cell>>();
+                                    .collect::<Vec<(Text, Style)>>()
+                            })
+                            .unwrap_or_default()
+                            .into_iter()
+                            .map(|(text, style)| Cell::from(text).style(style))
+                            .collect::<Vec<Cell>>();
+
+                        Row::new(cols).style(row_style.clone())
+                    })
+                    .collect::<Vec<Row>>()
+            })
+            .unwrap_or_default();
+
+        let table_constraints: Vec<TuiConstraint> = app_config
+            .general
+            .table
+            .col_widths
+            .clone()
+            .unwrap_or_default()
+            .into_iter()
+            .map(|c| c.to_tui(self.screen_size, layout_size))
+            .collect();
+
+        let pwd = if let Some(vroot) = app.vroot.as_ref() {
+            app.pwd.strip_prefix(vroot).unwrap_or(&app.pwd)
+        } else {
+            &app.pwd
+        }
+        .trim_matches('/');
 
-                    Row::new(cols).style(row_style.to_owned())
-                })
-                .collect::<Vec<Row>>()
-        })
-        .unwrap_or_default();
-
-    let table_constraints: Vec<TuiConstraint> = app_config
-        .general
-        .table
-        .col_widths
-        .to_owned()
-        .unwrap_or_default()
-        .into_iter()
-        .map(|c| c.to_tui(screen_size, layout_size))
-        .collect();
-
-    let pwd = if let Some(vroot) = app.vroot.as_ref() {
-        app.pwd.strip_prefix(vroot).unwrap_or(&app.pwd)
-    } else {
-        &app.pwd
+        let pwd = path::escape(pwd);
+
+        let vroot_indicator = if app.vroot.is_some() { "vroot:" } else { "" };
+
+        let node_count = app.directory_buffer.as_ref().map(|d| d.total).unwrap_or(0);
+        let node_count = if node_count == 0 {
+            String::new()
+        } else {
+            format!("({node_count}) ")
+        };
+
+        let table = Table::new(rows, table_constraints)
+            .style(app_config.general.table.style.clone())
+            .highlight_style(app_config.general.focus_ui.style.clone())
+            .column_spacing(app_config.general.table.col_spacing.unwrap_or_default())
+            .block(block(
+                config,
+                format!(" {vroot_indicator}/{pwd} {node_count}"),
+            ));
+
+        let table = table.clone().header(
+            Row::new(
+                app_config
+                    .general
+                    .table
+                    .header
+                    .cols
+                    .clone()
+                    .unwrap_or_default()
+                    .iter()
+                    .map(|c| {
+                        Cell::from(c.format.clone().unwrap_or_default())
+                            .style(c.style.clone())
+                    })
+                    .collect::<Vec<Cell>>(),
+            )
+            .height(header_height)
+            .style(app_config.general.table.header.style.clone()),
+        );
+
+        f.render_widget(table, layout_size);
     }
-    .trim_matches('/');
 
-    let pwd = path::escape(pwd);
+    fn draw_selection(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
+        let panel_config = &app.config.general.panel_ui;
+        let config = panel_config.default.clone().extend(&panel_config.selection);
 
-    let vroot_indicator = if app.vroot.is_some() { "vroot:" } else { "" };
+        let selection_count = app.selection.len();
 
-    let node_count = app.directory_buffer.as_ref().map(|d| d.total).unwrap_or(0);
-    let node_count = if node_count == 0 {
-        String::new()
-    } else {
-        format!("({node_count}) ")
-    };
-
-    let table = Table::new(rows, table_constraints)
-        .style(app_config.general.table.style.to_owned())
-        .highlight_style(app_config.general.focus_ui.style.to_owned())
-        .column_spacing(app_config.general.table.col_spacing.unwrap_or_default())
-        .block(block(
-            config,
-            format!(" {vroot_indicator}/{pwd} {node_count}"),
-        ));
+        let selection: Vec<ListItem> = app
+            .selection
+            .iter()
+            .rev()
+            .take((layout_size.height.max(2) - 2).into())
+            .rev()
+            .map(|n| {
+                let out = app
+                    .config
+                    .general
+                    .selection
+                    .item
+                    .format
+                    .as_ref()
+                    .map(|f| {
+                        lua::serialize::<Node>(self.lua, n)
+                            .and_then(|n| lua::call(self.lua, f, n))
+                            .unwrap_or_else(|e| format!("{e:?}"))
+                    })
+                    .unwrap_or_else(|| n.absolute_path.clone());
+                string_to_text(out)
+            })
+            .map(|i| {
+                ListItem::new(i).style(app.config.general.selection.item.style.clone())
+            })
+            .collect();
 
-    let table = table.to_owned().header(
-        Row::new(
-            app_config
-                .general
-                .table
-                .header
-                .cols
-                .to_owned()
-                .unwrap_or_default()
-                .iter()
-                .map(|c| {
-                    Cell::from(c.format.to_owned().unwrap_or_default())
-                        .style(c.style.to_owned())
-                })
-                .collect::<Vec<Cell>>(),
-        )
-        .height(header_height)
-        .style(app_config.general.table.header.style.to_owned()),
-    );
+        // Selected items
+        let selection_count = if selection_count == 0 {
+            String::new()
+        } else {
+            format!("({selection_count}) ")
+        };
 
-    f.render_widget(table, layout_size);
-}
+        let selection_list = List::new(selection)
+            .block(block(config, format!(" Selection {selection_count}")));
+
+        f.render_widget(selection_list, layout_size);
+    }
+
+    fn draw_help_menu(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
+        let panel_config = &app.config.general.panel_ui;
 
-fn draw_selection(
-    f: &mut Frame,
-    _screen_size: TuiRect,
-    layout_size: TuiRect,
-    app: &app::App,
-    lua: &Lua,
-) {
-    let panel_config = &app.config.general.panel_ui;
-    let config = panel_config
-        .default
-        .to_owned()
-        .extend(&panel_config.selection);
-
-    let selection_count = app.selection.len();
-
-    let selection: Vec<ListItem> = app
-        .selection
-        .iter()
-        .rev()
-        .take((layout_size.height.max(2) - 2).into())
-        .rev()
-        .map(|n| {
-            let out = app
-                .config
-                .general
-                .selection
-                .item
-                .format
+        let config = panel_config.default.clone().extend(&panel_config.help_menu);
+
+        let help_menu_rows = app
+            .mode
+            .help_menu()
+            .into_iter()
+            .map(|l| match l {
+                HelpMenuLine::Paragraph(p) => Row::new([Cell::from(p)].to_vec()),
+                HelpMenuLine::KeyMap(k, remaps, h) => Row::new({
+                    if app.config.general.hide_remaps_in_help_menu {
+                        [Cell::from(k), Cell::from(h)].to_vec()
+                    } else {
+                        [Cell::from(k), Cell::from(remaps.join("|")), Cell::from(h)]
+                            .to_vec()
+                    }
+                }),
+            })
+            .collect::<Vec<Row>>();
+
+        let widths = if app.config.general.hide_remaps_in_help_menu {
+            vec![TuiConstraint::Percentage(20), TuiConstraint::Percentage(80)]
+        } else {
+            vec![
+                TuiConstraint::Percentage(20),
+                TuiConstraint::Percentage(20),
+                TuiConstraint::Percentage(60),
+            ]
+        };
+        let help_menu = Table::new(help_menu_rows, widths).block(block(
+            config,
+            format!(" Help [{}{}] ", &app.mode.name, read_only_indicator(app)),
+        ));
+        f.render_widget(help_menu, layout_size);
+    }
+
+    fn draw_input_buffer(
+        &mut self,
+        f: &mut Frame,
+        layout_size: TuiRect,
+        app: &app::App,
+    ) {
+        if let Some(input) = app.input.buffer.as_ref() {
+            let panel_config = &app.config.general.panel_ui;
+            let config = panel_config
+                .default
+                .clone()
+                .extend(&panel_config.input_and_logs);
+
+            let cursor_offset_left = config
+                .borders
                 .as_ref()
-                .map(|f| {
-                    lua::serialize::<Node>(lua, n)
-                        .and_then(|n| lua::call(lua, f, n))
-                        .unwrap_or_else(|e| format!("{e:?}"))
-                })
-                .unwrap_or_else(|| n.absolute_path.clone());
-            string_to_text(out)
-        })
-        .map(|i| {
-            ListItem::new(i).style(app.config.general.selection.item.style.to_owned())
-        })
-        .collect();
-
-    // Selected items
-    let selection_count = if selection_count == 0 {
-        String::new()
-    } else {
-        format!("({selection_count}) ")
-    };
+                .map(|b| b.contains(&Border::Left))
+                .unwrap_or(false) as u16
+                + app.input.prompt.chars().count() as u16;
 
-    let selection_list = List::new(selection)
-        .block(block(config, format!(" Selection {selection_count}")));
+            let cursor_offset_right = config
+                .borders
+                .as_ref()
+                .map(|b| b.contains(&Border::Right))
+                .unwrap_or(false) as u16
+                + 1;
+
+            let offset_width = cursor_offset_left + cursor_offset_right;
+            let width = layout_size.width.max(offset_width) - offset_width;
+            let scroll = input.visual_scroll(width.into()) as u16;
+
+            let input_buf = Paragraph::new(Line::from(vec![
+                Span::styled(
+                    app.input.prompt.clone(),
+                    app.config.general.prompt.style.clone(),
+                ),
+                Span::raw(input.value()),
+            ]))
+            .scroll((0, scroll))
+            .block(block(
+                config,
+                format!(
+                    " Input [{}{}]{} ",
+                    app.mode.name,
+                    read_only_indicator(app),
+                    selection_indicator(app),
+                ),
+            ));
+
+            f.render_widget(input_buf, layout_size);
+            f.set_cursor(
+                // Put cursor past the end of the input text
+                layout_size.x
+                    + (input.visual_cursor() as u16).min(width)
+                    + cursor_offset_left,
+                // Move one line down, from the border to the input line
+                layout_size.y + 1,
+            );
+        };
+    }
 
-    f.render_widget(selection_list, layout_size);
-}
+    fn draw_sort_n_filter(
+        &mut self,
+        f: &mut Frame,
+        layout_size: TuiRect,
+        app: &app::App,
+    ) {
+        let panel_config = &app.config.general.panel_ui;
+        let config = panel_config
+            .default
+            .clone()
+            .extend(&panel_config.sort_and_filter);
+        let ui = app.config.general.sort_and_filter_ui.clone();
+        let filter_by: &IndexSet<NodeFilterApplicable> = &app.explorer_config.filters;
+        let sort_by: &IndexSet<NodeSorterApplicable> = &app.explorer_config.sorters;
+        let search = app.explorer_config.searcher.as_ref();
+
+        let defaultui = &ui.default_identifier;
+        let forwardui = defaultui
+            .clone()
+            .extend(&ui.sort_direction_identifiers.forward);
+        let reverseui = defaultui
+            .clone()
+            .extend(&ui.sort_direction_identifiers.reverse);
+
+        let orderedui = defaultui
+            .clone()
+            .extend(&ui.search_direction_identifiers.ordered);
+        let unorderedui = defaultui
+            .clone()
+            .extend(&ui.search_direction_identifiers.unordered);
+
+        let is_ordered_search = search.as_ref().map(|s| !s.unordered).unwrap_or(false);
+
+        let mut spans = filter_by
+            .iter()
+            .map(|f| {
+                ui.filter_identifiers
+                    .get(&f.filter)
+                    .map(|u| {
+                        let ui = defaultui.clone().extend(u);
+                        (
+                            Span::styled(
+                                ui.format.clone().unwrap_or_default(),
+                                ui.style.clone(),
+                            ),
+                            Span::styled(f.input.clone(), ui.style),
+                        )
+                    })
+                    .unwrap_or((Span::raw("f"), Span::raw("")))
+            })
+            .chain(search.iter().map(|s| {
+                ui.search_identifiers
+                    .get(&s.algorithm)
+                    .map(|u| {
+                        let direction = if s.unordered {
+                            &unorderedui
+                        } else {
+                            &orderedui
+                        };
+                        let ui = defaultui.clone().extend(u);
+                        let f = ui
+                            .format
+                            .as_ref()
+                            .map(|f| format!("{f}{p}", p = &s.pattern))
+                            .unwrap_or_else(|| s.pattern.clone());
+                        (
+                            Span::styled(f, ui.style),
+                            Span::styled(
+                                direction.format.clone().unwrap_or_default(),
+                                direction.style.clone(),
+                            ),
+                        )
+                    })
+                    .unwrap_or((Span::raw("/"), Span::raw(&s.pattern)))
+            }))
+            .chain(
+                sort_by
+                    .iter()
+                    .map(|s| {
+                        let direction = if s.reverse { &reverseui } else { &forwardui };
+                        ui.sorter_identifiers
+                            .get(&s.sorter)
+                            .map(|u| {
+                                let ui = defaultui.clone().extend(u);
+                                (
+                                    Span::styled(
+                                        ui.format.clone().unwrap_or_default(),
+                                        ui.style,
+                                    ),
+                                    Span::styled(
+                                        direction.format.clone().unwrap_or_default(),
+                                        direction.style.clone(),
+                                    ),
+                                )
+                            })
+                            .unwrap_or((Span::raw("s"), Span::raw("")))
+                    })
+                    .take(if !is_ordered_search { sort_by.len() } else { 0 }),
+            )
+            .zip(std::iter::repeat(Span::styled(
+                ui.separator.format.clone().unwrap_or_default(),
+                ui.separator.style.clone(),
+            )))
+            .flat_map(|((a, b), c)| vec![a, b, c])
+            .collect::<Vec<Span>>();
+
+        spans.pop();
+
+        let item_count = filter_by.len() + sort_by.len();
+        let item_count = if item_count == 0 {
+            String::new()
+        } else {
+            format!("({item_count}) ")
+        };
 
-fn draw_help_menu(
-    f: &mut Frame,
-    _screen_size: TuiRect,
-    layout_size: TuiRect,
-    app: &app::App,
-    _: &Lua,
-) {
-    let panel_config = &app.config.general.panel_ui;
-
-    let config = panel_config
-        .default
-        .to_owned()
-        .extend(&panel_config.help_menu);
-
-    let help_menu_rows = app
-        .mode
-        .help_menu()
-        .into_iter()
-        .map(|l| match l {
-            HelpMenuLine::Paragraph(p) => Row::new([Cell::from(p)].to_vec()),
-            HelpMenuLine::KeyMap(k, remaps, h) => Row::new({
-                if app.config.general.hide_remaps_in_help_menu {
-                    [Cell::from(k), Cell::from(h)].to_vec()
-                } else {
-                    [Cell::from(k), Cell::from(remaps.join("|")), Cell::from(h)].to_vec()
-                }
-            }),
-        })
-        .collect::<Vec<Row>>();
+        let p = Paragraph::new(Line::from(spans))
+            .block(block(config, format!(" Sort & filter {item_count}")));
 
-    let widths = if app.config.general.hide_remaps_in_help_menu {
-        vec![TuiConstraint::Percentage(20), TuiConstraint::Percentage(80)]
-    } else {
-        vec![
-            TuiConstraint::Percentage(20),
-            TuiConstraint::Percentage(20),
-            TuiConstraint::Percentage(60),
-        ]
-    };
-    let help_menu = Table::new(help_menu_rows, widths).block(block(
-        config,
-        format!(" Help [{}{}] ", &app.mode.name, read_only_indicator(app)),
-    ));
-    f.render_widget(help_menu, layout_size);
-}
+        f.render_widget(p, layout_size);
+    }
 
-fn draw_input_buffer(
-    f: &mut Frame,
-    _screen_size: TuiRect,
-    layout_size: TuiRect,
-    app: &app::App,
-    _: &Lua,
-) {
-    if let Some(input) = app.input.buffer.as_ref() {
+    fn draw_logs(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
         let panel_config = &app.config.general.panel_ui;
         let config = panel_config
             .default
-            .to_owned()
+            .clone()
             .extend(&panel_config.input_and_logs);
+        let logs_config = app.config.general.logs.clone();
+        let logs = if app.logs_hidden {
+            vec![]
+        } else {
+            app.logs
+                .iter()
+                .rev()
+                .take(layout_size.height as usize)
+                .map(|log| {
+                    let fd = format_description!("[hour]:[minute]:[second]");
+                    let time =
+                        log.created_at.format(fd).unwrap_or_else(|_| "when?".into());
+                    let cfg = match log.level {
+                        app::LogLevel::Info => &logs_config.info,
+                        app::LogLevel::Warning => &logs_config.warning,
+                        app::LogLevel::Success => &logs_config.success,
+                        app::LogLevel::Error => &logs_config.error,
+                    };
 
-        let cursor_offset_left = config
-            .borders
-            .as_ref()
-            .map(|b| b.contains(&Border::Left))
-            .unwrap_or(false) as u16
-            + app.input.prompt.chars().count() as u16;
+                    let prefix =
+                        format!("{time}|{0}", cfg.format.clone().unwrap_or_default());
 
-        let cursor_offset_right = config
-            .borders
-            .as_ref()
-            .map(|b| b.contains(&Border::Right))
-            .unwrap_or(false) as u16
-            + 1;
-
-        let offset_width = cursor_offset_left + cursor_offset_right;
-        let width = layout_size.width.max(offset_width) - offset_width;
-        let scroll = input.visual_scroll(width.into()) as u16;
-
-        let input_buf = Paragraph::new(Line::from(vec![
-            Span::styled(
-                app.input.prompt.to_owned(),
-                app.config.general.prompt.style.to_owned(),
-            ),
-            Span::raw(input.value()),
-        ]))
-        .scroll((0, scroll))
-        .block(block(
+                    let padding = " ".repeat(prefix.chars().count());
+
+                    let txt = log
+                        .message
+                        .lines()
+                        .enumerate()
+                        .map(|(i, line)| {
+                            if i == 0 {
+                                format!("{prefix}) {line}")
+                            } else {
+                                format!("{padding}  {line}")
+                            }
+                        })
+                        .take(layout_size.height as usize)
+                        .collect::<Vec<_>>()
+                        .join("\n");
+
+                    ListItem::new(txt).style(cfg.style.clone())
+                })
+                .collect::<Vec<ListItem>>()
+        };
+
+        let logs_count = app.logs.len();
+        let logs_count = if logs_count == 0 {
+            String::new()
+        } else {
+            format!(" ({logs_count})")
+        };
+
+        let logs_list = List::new(logs).block(block(
             config,
             format!(
-                " Input [{}{}]{} ",
+                " Logs{} [{}{}]{} ",
+                logs_count,
                 app.mode.name,
                 read_only_indicator(app),
-                selection_indicator(app),
+                selection_indicator(app)
             ),
         ));
 
-        f.render_widget(input_buf, layout_size);
-        f.set_cursor(
-            // Put cursor past the end of the input text
-            layout_size.x
-                + (input.visual_cursor() as u16).min(width)
-                + cursor_offset_left,
-            // Move one line down, from the border to the input line
-            layout_size.y + 1,
-        );
-    };
-}
-
-fn draw_sort_n_filter(
-    f: &mut Frame,
-    _screen_size: TuiRect,
-    layout_size: TuiRect,
-    app: &app::App,
-    _: &Lua,
-) {
-    let panel_config = &app.config.general.panel_ui;
-    let config = panel_config
-        .default
-        .to_owned()
-        .extend(&panel_config.sort_and_filter);
-    let ui = app.config.general.sort_and_filter_ui.to_owned();
-    let filter_by: &IndexSet<NodeFilterApplicable> = &app.explorer_config.filters;
-    let sort_by: &IndexSet<NodeSorterApplicable> = &app.explorer_config.sorters;
-    let search = app.explorer_config.searcher.as_ref();
-
-    let defaultui = &ui.default_identifier;
-    let forwardui = defaultui
-        .to_owned()
-        .extend(&ui.sort_direction_identifiers.forward);
-    let reverseui = defaultui
-        .to_owned()
-        .extend(&ui.sort_direction_identifiers.reverse);
-
-    let orderedui = defaultui
-        .to_owned()
-        .extend(&ui.search_direction_identifiers.ordered);
-    let unorderedui = defaultui
-        .to_owned()
-        .extend(&ui.search_direction_identifiers.unordered);
-
-    let is_ordered_search = search.as_ref().map(|s| !s.unordered).unwrap_or(false);
-
-    let mut spans = filter_by
-        .iter()
-        .map(|f| {
-            ui.filter_identifiers
-                .get(&f.filter)
-                .map(|u| {
-                    let ui = defaultui.to_owned().extend(u);
-                    (
-                        Span::styled(
-                            ui.format.to_owned().unwrap_or_default(),
-                            ui.style.to_owned(),
-                        ),
-                        Span::styled(f.input.to_owned(), ui.style),
-                    )
-                })
-                .unwrap_or((Span::raw("f"), Span::raw("")))
-        })
-        .chain(search.iter().map(|s| {
-            ui.search_identifiers
-                .get(&s.algorithm)
-                .map(|u| {
-                    let direction = if s.unordered {
-                        &unorderedui
-                    } else {
-                        &orderedui
-                    };
-                    let ui = defaultui.to_owned().extend(u);
-                    let f = ui
-                        .format
-                        .as_ref()
-                        .map(|f| format!("{f}{p}", p = &s.pattern))
-                        .unwrap_or_else(|| s.pattern.clone());
-                    (
-                        Span::styled(f, ui.style),
-                        Span::styled(
-                            direction.format.to_owned().unwrap_or_default(),
-                            direction.style.to_owned(),
-                        ),
-                    )
-                })
-                .unwrap_or((Span::raw("/"), Span::raw(&s.pattern)))
-        }))
-        .chain(
-            sort_by
-                .iter()
-                .map(|s| {
-                    let direction = if s.reverse { &reverseui } else { &forwardui };
-                    ui.sorter_identifiers
-                        .get(&s.sorter)
-                        .map(|u| {
-                            let ui = defaultui.to_owned().extend(u);
-                            (
-                                Span::styled(
-                                    ui.format.to_owned().unwrap_or_default(),
-                                    ui.style,
-                                ),
-                                Span::styled(
-                                    direction.format.to_owned().unwrap_or_default(),
-                                    direction.style.to_owned(),
-                                ),
-                            )
-                        })
-                        .unwrap_or((Span::raw("s"), Span::raw("")))
-                })
-                .take(if !is_ordered_search { sort_by.len() } else { 0 }),
-        )
-        .zip(std::iter::repeat(Span::styled(
-            ui.separator.format.to_owned().unwrap_or_default(),
-            ui.separator.style.to_owned(),
-        )))
-        .flat_map(|((a, b), c)| vec![a, b, c])
-        .collect::<Vec<Span>>();
-
-    spans.pop();
-
-    let item_count = filter_by.len() + sort_by.len();
-    let item_count = if item_count == 0 {
-        String::new()
-    } else {
-        format!("({item_count}) ")
-    };
-
-    let p = Paragraph::new(Line::from(spans))
-        .block(block(config, format!(" Sort & filter {item_count}")));
-
-    f.render_widget(p, layout_size);
-}
+        f.render_widget(logs_list, layout_size);
+    }
 
-fn draw_logs(
-    f: &mut Frame,
-    _screen_size: TuiRect,
-    layout_size: TuiRect,
-    app: &app::App,
-    _: &Lua,
-) {
-    let panel_config = &app.config.general.panel_ui;
-    let config = panel_config
-        .default
-        .to_owned()
-        .extend(&panel_config.input_and_logs);
-    let logs_config = app.config.general.logs.to_owned();
-    let logs = if app.logs_hidden {
-        vec![]
-    } else {
-        app.logs
-            .iter()
-            .rev()
-            .take(layout_size.height as usize)
-            .map(|log| {
-                let fd = format_description!("[hour]:[minute]:[second]");
-                let time = log.created_at.format(fd).unwrap_or_else(|_| "when?".into());
-                let cfg = match log.level {
-                    app::LogLevel::Info => &logs_config.info,
-                    app::LogLevel::Warning => &logs_config.warning,
-                    app::LogLevel::Success => &logs_config.success,
-                    app::LogLevel::Error => &logs_config.error,
-                };
+    fn draw_nothing(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
+        let panel_config = &app.config.general.panel_ui;
+        let config = panel_config.default.clone();
+        let nothing = Paragraph::new("").block(block(config, "".into()));
+        f.render_widget(nothing, layout_size);
+    }
 
-                let prefix =
-                    format!("{time}|{0}", cfg.format.to_owned().unwrap_or_default());
+    fn draw_dynamic(
+        &mut self,
+        f: &mut Frame,
+        layout_size: TuiRect,
+        app: &app::App,
+        func: &str,
+    ) {
+        let ctx = ContentRendererArg {
+            app: app.to_lua_ctx_light(),
+            layout_size: layout_size.into(),
+            screen_size: self.screen_size.into(),
+            scrolltop: self.scrolltop as u16,
+        };
 
-                let padding = " ".repeat(prefix.chars().count());
+        let panel: CustomPanel = lua::serialize(self.lua, &ctx)
+            .and_then(|arg| lua::call(self.lua, func, arg))
+            .unwrap_or_else(|e| CustomPanel::CustomParagraph {
+                ui: app.config.general.panel_ui.default.clone(),
+                body: format!("{e:?}"),
+            });
 
-                let txt = log
-                    .message
-                    .lines()
-                    .enumerate()
-                    .map(|(i, line)| {
-                        if i == 0 {
-                            format!("{prefix}) {line}")
-                        } else {
-                            format!("{padding}  {line}")
-                        }
-                    })
-                    .take(layout_size.height as usize)
-                    .collect::<Vec<_>>()
-                    .join("\n");
+        self.draw_static(f, layout_size, app, panel);
+    }
 
-                ListItem::new(txt).style(cfg.style.to_owned())
-            })
-            .collect::<Vec<ListItem>>()
-    };
+    fn draw_static(
+        &mut self,
+        f: &mut Frame,
+        layout_size: TuiRect,
+        app: &app::App,
+        panel: CustomPanel,
+    ) {
+        let defaultui = app.config.general.panel_ui.default.clone();
+        match panel {
+            CustomPanel::CustomParagraph { ui, body } => {
+                let config = defaultui.extend(&ui);
+                let body = string_to_text(body);
+                let content = Paragraph::new(body).block(block(config, "".into()));
+                f.render_widget(content, layout_size);
+            }
 
-    let logs_count = app.logs.len();
-    let logs_count = if logs_count == 0 {
-        String::new()
-    } else {
-        format!(" ({logs_count})")
-    };
-
-    let logs_list = List::new(logs).block(block(
-        config,
-        format!(
-            " Logs{} [{}{}]{} ",
-            logs_count,
-            app.mode.name,
-            read_only_indicator(app),
-            selection_indicator(app)
-        ),
-    ));
-
-    f.render_widget(logs_list, layout_size);
-}
+            CustomPanel::CustomList { ui, body } => {
+                let config = defaultui.extend(&ui);
 
-pub fn draw_nothing(
-    f: &mut Frame,
-    _screen_size: TuiRect,
-    layout_size: TuiRect,
-    app: &app::App,
-    _lua: &Lua,
-) {
-    let panel_config = &app.config.general.panel_ui;
-    let config = panel_config.default.to_owned();
-    let nothing = Paragraph::new("").block(block(config, "".into()));
-    f.render_widget(nothing, layout_size);
-}
+                let items = body
+                    .into_iter()
+                    .map(string_to_text)
+                    .map(ListItem::new)
+                    .collect::<Vec<ListItem>>();
 
-pub fn draw_dynamic(
-    f: &mut Frame,
-    screen_size: TuiRect,
-    layout_size: TuiRect,
-    app: &app::App,
-    func: &str,
-    lua: &Lua,
-) {
-    let ctx = ContentRendererArg {
-        app: app.to_lua_ctx_light(),
-        layout_size: layout_size.into(),
-        screen_size: screen_size.into(),
-    };
-
-    let panel: CustomPanel = lua::serialize(lua, &ctx)
-        .and_then(|arg| lua::call(lua, func, arg))
-        .unwrap_or_else(|e| CustomPanel::CustomParagraph {
-            ui: app.config.general.panel_ui.default.clone(),
-            body: format!("{e:?}"),
-        });
+                let content = List::new(items).block(block(config, "".into()));
+                f.render_widget(content, layout_size);
+            }
 
-    draw_static(f, screen_size, layout_size, app, panel, lua);
-}
+            CustomPanel::CustomTable {
+                ui,
+                widths,
+                col_spacing,
+                body,
+            } => {
+                let config = defaultui.extend(&ui);
+                let rows = body
+                    .into_iter()
+                    .map(|cols| {
+                        Row::new(
+                            cols.into_iter()
+                                .map(string_to_text)
+                                .map(Cell::from)
+                                .collect::<Vec<Cell>>(),
+                        )
+                    })
+                    .collect::<Vec<Row>>();
 
-pub fn draw_static(
-    f: &mut Frame,
-    screen_size: TuiRect,
-    layout_size: TuiRect,
-    app: &app::App,
-    panel: CustomPanel,
-    _lua: &Lua,
-) {
-    let defaultui = app.config.general.panel_ui.default.clone();
-    match panel {
-        CustomPanel::CustomParagraph { ui, body } => {
-            let config = defaultui.extend(&ui);
-            let body = string_to_text(body);
-            let content = Paragraph::new(body).block(block(config, "".into()));
-            f.render_widget(content, layout_size);
-        }
+                let widths = widths
+                    .into_iter()
+                    .map(|w| w.to_tui(self.screen_size, layout_size))
+                    .collect::<Vec<TuiConstraint>>();
 
-        CustomPanel::CustomList { ui, body } => {
-            let config = defaultui.extend(&ui);
+                let content = Table::new(rows, widths)
+                    .column_spacing(col_spacing.unwrap_or(1))
+                    .block(block(config, "".into()));
 
-            let items = body
-                .into_iter()
-                .map(string_to_text)
-                .map(ListItem::new)
-                .collect::<Vec<ListItem>>();
+                f.render_widget(content, layout_size);
+            }
 
-            let content = List::new(items).block(block(config, "".into()));
-            f.render_widget(content, layout_size);
+            CustomPanel::CustomLayout(layout) => {
+                self.draw_layout(layout, f, layout_size, app);
+            }
         }
+    }
 
-        CustomPanel::CustomTable {
-            ui,
-            widths,
-            col_spacing,
-            body,
-        } => {
-            let config = defaultui.extend(&ui);
-            let rows = body
-                .into_iter()
-                .map(|cols| {
-                    Row::new(
-                        cols.into_iter()
-                            .map(string_to_text)
-                            .map(Cell::from)
-                            .collect::<Vec<Cell>>(),
+    fn draw_layout(
+        &mut self,
+        layout: Layout,
+        f: &mut Frame,
+        layout_size: TuiRect,
+        app: &app::App,
+    ) {
+        match layout {
+            Layout::Nothing => self.draw_nothing(f, layout_size, app),
+            Layout::Table => self.draw_table(f, layout_size, app),
+            Layout::SortAndFilter => self.draw_sort_n_filter(f, layout_size, app),
+            Layout::HelpMenu => self.draw_help_menu(f, layout_size, app),
+            Layout::Selection => self.draw_selection(f, layout_size, app),
+            Layout::InputAndLogs => {
+                if app.input.buffer.is_some() {
+                    self.draw_input_buffer(f, layout_size, app);
+                } else {
+                    self.draw_logs(f, layout_size, app);
+                };
+            }
+            Layout::Static(panel) => self.draw_static(f, layout_size, app, *panel),
+            Layout::Dynamic(ref func) => self.draw_dynamic(f, layout_size, app, func),
+            Layout::CustomContent(content) => {
+                draw_custom_content(self, f, layout_size, app, *content)
+            }
+            Layout::Horizontal { config, splits } => {
+                let chunks = TuiLayout::default()
+                    .direction(Direction::Horizontal)
+                    .constraints(
+                        config
+                            .constraints
+                            .clone()
+                            .unwrap_or_default()
+                            .iter()
+                            .map(|c| c.to_tui(self.screen_size, layout_size))
+                            .collect::<Vec<TuiConstraint>>(),
                     )
-                })
-                .collect::<Vec<Row>>();
-
-            let widths = widths
-                .into_iter()
-                .map(|w| w.to_tui(screen_size, layout_size))
-                .collect::<Vec<TuiConstraint>>();
-
-            let content = Table::new(rows, widths)
-                .column_spacing(col_spacing.unwrap_or(1))
-                .block(block(config, "".into()));
-
-            f.render_widget(content, layout_size);
-        }
+                    .horizontal_margin(
+                        config
+                            .horizontal_margin
+                            .or(config.margin)
+                            .unwrap_or_default(),
+                    )
+                    .vertical_margin(
+                        config.vertical_margin.or(config.margin).unwrap_or_default(),
+                    )
+                    .split(layout_size);
 
-        CustomPanel::CustomLayout(layout) => {
-            draw_layout(layout, f, screen_size, layout_size, app, _lua);
-        }
-    }
-}
+                splits
+                    .into_iter()
+                    .zip(chunks.iter())
+                    .for_each(|(split, chunk)| self.draw_layout(split, f, *chunk, app));
+            }
 
-#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
-pub struct Rect {
-    x: u16,
-    y: u16,
-    height: u16,
-    width: u16,
-}
+            Layout::Vertical { config, splits } => {
+                let chunks = TuiLayout::default()
+                    .direction(Direction::Vertical)
+                    .constraints(
+                        config
+                            .constraints
+                            .clone()
+                            .unwrap_or_default()
+                            .iter()
+                            .map(|c| c.to_tui(self.screen_size, layout_size))
+                            .collect::<Vec<TuiConstraint>>(),
+                    )
+                    .horizontal_margin(
+                        config
+                            .horizontal_margin
+                            .or(config.margin)
+                            .unwrap_or_default(),
+                    )
+                    .vertical_margin(
+                        config.vertical_margin.or(config.margin).unwrap_or_default(),
+                    )
+                    .split(layout_size);
 
-impl From<TuiRect> for Rect {
-    fn from(tui: TuiRect) -> Self {
-        Self {
-            x: tui.x,
-            y: tui.y,
-            height: tui.height,
-            width: tui.width,
+                splits
+                    .into_iter()
+                    .zip(chunks.iter())
+                    .for_each(|(split, chunk)| self.draw_layout(split, f, *chunk, app));
+            }
         }
     }
-}
-
-#[derive(Debug, Clone, Serialize)]
-pub struct ContentRendererArg {
-    pub app: app::LuaContextLight,
-    pub screen_size: Rect,
-    pub layout_size: Rect,
-}
 
-pub fn draw_layout(
-    layout: Layout,
-    f: &mut Frame,
-    screen_size: TuiRect,
-    layout_size: TuiRect,
-    app: &app::App,
-    lua: &Lua,
-) {
-    match layout {
-        Layout::Nothing => draw_nothing(f, screen_size, layout_size, app, lua),
-        Layout::Table => draw_table(f, screen_size, layout_size, app, lua),
-        Layout::SortAndFilter => {
-            draw_sort_n_filter(f, screen_size, layout_size, app, lua)
-        }
-        Layout::HelpMenu => draw_help_menu(f, screen_size, layout_size, app, lua),
-        Layout::Selection => draw_selection(f, screen_size, layout_size, app, lua),
-        Layout::InputAndLogs => {
-            if app.input.buffer.is_some() {
-                draw_input_buffer(f, screen_size, layout_size, app, lua);
-            } else {
-                draw_logs(f, screen_size, layout_size, app, lua);
-            };
-        }
-        Layout::Static(panel) => {
-            draw_static(f, screen_size, layout_size, app, *panel, lua)
-        }
-        Layout::Dynamic(ref func) => {
-            draw_dynamic(f, screen_size, layout_size, app, func, lua)
-        }
-        Layout::CustomContent(content) => {
-            draw_custom_content(f, screen_size, layout_size, app, *content, lua)
-        }
-        Layout::Horizontal { config, splits } => {
-            let chunks = TuiLayout::default()
-                .direction(Direction::Horizontal)
-                .constraints(
-                    config
-                        .constraints
-                        .to_owned()
-                        .unwrap_or_default()
-                        .iter()
-                        .map(|c| c.to_tui(screen_size, layout_size))
-                        .collect::<Vec<TuiConstraint>>(),
-                )
-                .horizontal_margin(
-                    config
-                        .horizontal_margin
-                        .or(config.margin)
-                        .unwrap_or_default(),
-                )
-                .vertical_margin(
-                    config.vertical_margin.or(config.margin).unwrap_or_default(),
-                )
-                .split(layout_size);
-
-            splits
-                .into_iter()
-                .zip(chunks.iter())
-                .for_each(|(split, chunk)| {
-                    draw_layout(split, f, screen_size, *chunk, app, lua)
-                });
-        }
-
-        Layout::Vertical { config, splits } => {
-            let chunks = TuiLayout::default()
-                .direction(Direction::Vertical)
-                .constraints(
-                    config
-                        .constraints
-                        .to_owned()
-                        .unwrap_or_default()
-                        .iter()
-                        .map(|c| c.to_tui(screen_size, layout_size))
-                        .collect::<Vec<TuiConstraint>>(),
-                )
-                .horizontal_margin(
-                    config
-                        .horizontal_margin
-                        .or(config.margin)
-                        .unwrap_or_default(),
-                )
-                .vertical_margin(
-                    config.vertical_margin.or(config.margin).unwrap_or_default(),
-                )
-                .split(layout_size);
-
-            splits
-                .into_iter()
-                .zip(chunks.iter())
-                .for_each(|(split, chunk)| {
-                    draw_layout(split, f, screen_size, *chunk, app, lua)
-                });
-        }
+    pub fn draw(&mut self, f: &mut Frame, app: &app::App) {
+        self.screen_size = f.size();
+        let layout = app.mode.layout.as_ref().unwrap_or(&app.layout).clone();
+        self.draw_layout(layout, f, self.screen_size, app);
     }
 }
 
-pub fn draw(f: &mut Frame, app: &app::App, lua: &Lua) {
-    let screen_size = f.size();
-    let layout = app.mode.layout.as_ref().unwrap_or(&app.layout).to_owned();
-
-    draw_layout(layout, f, screen_size, screen_size, app, lua);
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -1534,7 +1519,7 @@ mod tests {
         };
 
         assert_eq!(
-            a.to_owned().extend(&b),
+            a.clone().extend(&b),
             Style {
                 fg: Some(Color::Red),
                 bg: Some(Color::Blue),
@@ -1554,7 +1539,7 @@ mod tests {
         );
 
         assert_eq!(
-            a.to_owned().extend(&c),
+            a.clone().extend(&c),
             Style {
                 fg: Some(Color::Cyan),
                 bg: Some(Color::Magenta),


Test patch: