├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ └── feature_request.md
└── workflows
│ └── rust_build.yml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── PKGBUILD
├── README.md
├── build_shell_completions_and_man_pages.rs
└── src
├── connection.rs
├── pager
├── cli.rs
├── context.rs
├── main.rs
└── neovim.rs
└── picker
├── cli.rs
├── context.rs
└── main.rs
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Please add logs
4 | title: ''
5 | labels: ''
6 | assignees: I60R
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/workflows/rust_build.yml:
--------------------------------------------------------------------------------
1 | name: Rust Build
2 |
3 | on:
4 |
5 | push:
6 | branches: [ master ]
7 | pull_request:
8 | branches: [ master ]
9 | workflow_dispatch:
10 |
11 | env:
12 |
13 | CARGO_TERM_COLOR: always
14 |
15 | jobs:
16 |
17 | build:
18 |
19 | strategy:
20 | matrix:
21 | os: [ubuntu, macos, windows]
22 |
23 | runs-on: ${{ matrix.os }}-latest
24 |
25 | steps:
26 | - name: Checkout source code
27 | uses: actions/checkout@v3
28 |
29 | #- name: Install clippy from rust toolchain
30 | # uses: actions-rs/toolchain@v1
31 | # with:
32 | # toolchain: stable
33 | # default: true
34 | # profile: minimal # minimal component installation (ie, no documentation)
35 | # components: clippy
36 | #
37 | #- name: Run clippy
38 | # uses: actions-rs/cargo@v1
39 | # with:
40 | # command: clippy
41 | # args: --locked --all-targets --all-features
42 |
43 | - if: matrix.os == 'windows'
44 | name: Build on windows
45 | run: |
46 | cargo build --verbose --release
47 | mkdir binaries
48 | move target\release\page.exe binaries
49 | move target\release\nv.exe binaries
50 |
51 | - if: matrix.os != 'windows'
52 | name: Build on ${{ matrix.os }}
53 | run: |
54 | cargo build --verbose --release
55 | mkdir binaries
56 | mv target/release/page binaries
57 | mv target/release/nv binaries
58 |
59 | #- name: Run tests
60 | # run: cargo test --verbose
61 |
62 | - name: Upload binaries
63 | uses: actions/upload-artifact@v3
64 | with:
65 | name: binaries-${{ matrix.os }}
66 | path: binaries
67 | if-no-files-found: error
68 | retention-days: 7
69 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### https://raw.github.com/github/gitignore/b3ae3810f8b0f97f24cd61c2d3dd1b5089b91801/Global/JetBrains.gitignore
2 |
3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
5 |
6 | # User-specific stuff:
7 | .idea/**/workspace.xml
8 | .idea/**/tasks.xml
9 | .idea/dictionaries
10 |
11 | # Sensitive or high-churn files:
12 | .idea/**/dataSources/
13 | .idea/**/dataSources.ids
14 | .idea/**/dataSources.xml
15 | .idea/**/dataSources.local.xml
16 | .idea/**/sqlDataSources.xml
17 | .idea/**/dynamic.xml
18 | .idea/**/uiDesigner.xml
19 |
20 | # Gradle:
21 | .idea/**/gradle.xml
22 | .idea/**/libraries
23 |
24 | # CMake
25 | cmake-build-debug/
26 |
27 | # Mongo Explorer plugin:
28 | .idea/**/mongoSettings.xml
29 |
30 | ## File-based project format:
31 | *.iws
32 |
33 | ## Plugin-specific files:
34 |
35 | # IntelliJ
36 | out/
37 |
38 | # mpeltonen/sbt-idea plugin
39 | .idea_modules/
40 |
41 | # JIRA plugin
42 | atlassian-ide-plugin.xml
43 |
44 | # Cursive Clojure plugin
45 | .idea/replstate.xml
46 |
47 | # Crashlytics plugin (for Android Studio and IntelliJ)
48 | com_crashlytics_export_strings.xml
49 | crashlytics.properties
50 | crashlytics-build.properties
51 | fabric.properties
52 |
53 |
54 | ### https://raw.github.com/github/gitignore/b3ae3810f8b0f97f24cd61c2d3dd1b5089b91801/Global/Vim.gitignore
55 |
56 | # Swap
57 | [._]*.s[a-v][a-z]
58 | [._]*.sw[a-p]
59 | [._]s[a-v][a-z]
60 | [._]sw[a-p]
61 |
62 | # Session
63 | Session.vim
64 |
65 | # Temporary
66 | .netrwhist
67 | *~
68 | # Auto-generated tag files
69 | tags
70 |
71 |
72 | ### https://raw.github.com/github/gitignore/b3ae3810f8b0f97f24cd61c2d3dd1b5089b91801/Rust.gitignore
73 |
74 | # Generated by Cargo
75 | # will have compiled files and executables
76 | /target/
77 |
78 | # These are backup files generated by rustfmt
79 | **/*.rs.bk
80 |
81 |
82 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "async-trait"
7 | version = "0.1.60"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3"
10 | dependencies = [
11 | "proc-macro2",
12 | "quote",
13 | "syn",
14 | ]
15 |
16 | [[package]]
17 | name = "atty"
18 | version = "0.2.14"
19 | source = "registry+https://github.com/rust-lang/crates.io-index"
20 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
21 | dependencies = [
22 | "hermit-abi 0.1.19",
23 | "libc",
24 | "winapi",
25 | ]
26 |
27 | [[package]]
28 | name = "autocfg"
29 | version = "1.1.0"
30 | source = "registry+https://github.com/rust-lang/crates.io-index"
31 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
32 |
33 | [[package]]
34 | name = "bitflags"
35 | version = "1.3.2"
36 | source = "registry+https://github.com/rust-lang/crates.io-index"
37 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
38 |
39 | [[package]]
40 | name = "byteorder"
41 | version = "1.4.3"
42 | source = "registry+https://github.com/rust-lang/crates.io-index"
43 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
44 |
45 | [[package]]
46 | name = "bytes"
47 | version = "0.4.12"
48 | source = "registry+https://github.com/rust-lang/crates.io-index"
49 | checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
50 | dependencies = [
51 | "byteorder",
52 | "iovec",
53 | ]
54 |
55 | [[package]]
56 | name = "bytes"
57 | version = "1.3.0"
58 | source = "registry+https://github.com/rust-lang/crates.io-index"
59 | checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
60 |
61 | [[package]]
62 | name = "cc"
63 | version = "1.0.78"
64 | source = "registry+https://github.com/rust-lang/crates.io-index"
65 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
66 |
67 | [[package]]
68 | name = "cfg-if"
69 | version = "1.0.0"
70 | source = "registry+https://github.com/rust-lang/crates.io-index"
71 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
72 |
73 | [[package]]
74 | name = "clap"
75 | version = "4.0.32"
76 | source = "registry+https://github.com/rust-lang/crates.io-index"
77 | checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39"
78 | dependencies = [
79 | "bitflags",
80 | "clap_derive",
81 | "clap_lex",
82 | "is-terminal",
83 | "once_cell",
84 | "strsim",
85 | "termcolor",
86 | "terminal_size",
87 | ]
88 |
89 | [[package]]
90 | name = "clap_complete"
91 | version = "4.0.7"
92 | source = "registry+https://github.com/rust-lang/crates.io-index"
93 | checksum = "10861370d2ba66b0f5989f83ebf35db6421713fd92351790e7fdd6c36774c56b"
94 | dependencies = [
95 | "clap",
96 | ]
97 |
98 | [[package]]
99 | name = "clap_derive"
100 | version = "4.0.21"
101 | source = "registry+https://github.com/rust-lang/crates.io-index"
102 | checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
103 | dependencies = [
104 | "heck",
105 | "proc-macro-error",
106 | "proc-macro2",
107 | "quote",
108 | "syn",
109 | ]
110 |
111 | [[package]]
112 | name = "clap_lex"
113 | version = "0.3.0"
114 | source = "registry+https://github.com/rust-lang/crates.io-index"
115 | checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
116 | dependencies = [
117 | "os_str_bytes",
118 | ]
119 |
120 | [[package]]
121 | name = "clap_mangen"
122 | version = "0.2.6"
123 | source = "registry+https://github.com/rust-lang/crates.io-index"
124 | checksum = "904eb24d05ad587557e0f484ddce5c737c30cf81372badb16d13e41c4b8340b1"
125 | dependencies = [
126 | "clap",
127 | "roff",
128 | ]
129 |
130 | [[package]]
131 | name = "errno"
132 | version = "0.2.8"
133 | source = "registry+https://github.com/rust-lang/crates.io-index"
134 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
135 | dependencies = [
136 | "errno-dragonfly",
137 | "libc",
138 | "winapi",
139 | ]
140 |
141 | [[package]]
142 | name = "errno-dragonfly"
143 | version = "0.1.2"
144 | source = "registry+https://github.com/rust-lang/crates.io-index"
145 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
146 | dependencies = [
147 | "cc",
148 | "libc",
149 | ]
150 |
151 | [[package]]
152 | name = "fern"
153 | version = "0.6.1"
154 | source = "registry+https://github.com/rust-lang/crates.io-index"
155 | checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a"
156 | dependencies = [
157 | "log",
158 | ]
159 |
160 | [[package]]
161 | name = "futures"
162 | version = "0.1.31"
163 | source = "registry+https://github.com/rust-lang/crates.io-index"
164 | checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
165 |
166 | [[package]]
167 | name = "futures"
168 | version = "0.3.25"
169 | source = "registry+https://github.com/rust-lang/crates.io-index"
170 | checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
171 | dependencies = [
172 | "futures-channel",
173 | "futures-core",
174 | "futures-executor",
175 | "futures-io",
176 | "futures-sink",
177 | "futures-task",
178 | "futures-util",
179 | ]
180 |
181 | [[package]]
182 | name = "futures-channel"
183 | version = "0.3.25"
184 | source = "registry+https://github.com/rust-lang/crates.io-index"
185 | checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
186 | dependencies = [
187 | "futures-core",
188 | "futures-sink",
189 | ]
190 |
191 | [[package]]
192 | name = "futures-core"
193 | version = "0.3.25"
194 | source = "registry+https://github.com/rust-lang/crates.io-index"
195 | checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
196 |
197 | [[package]]
198 | name = "futures-executor"
199 | version = "0.3.25"
200 | source = "registry+https://github.com/rust-lang/crates.io-index"
201 | checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
202 | dependencies = [
203 | "futures-core",
204 | "futures-task",
205 | "futures-util",
206 | ]
207 |
208 | [[package]]
209 | name = "futures-io"
210 | version = "0.3.25"
211 | source = "registry+https://github.com/rust-lang/crates.io-index"
212 | checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
213 |
214 | [[package]]
215 | name = "futures-macro"
216 | version = "0.3.25"
217 | source = "registry+https://github.com/rust-lang/crates.io-index"
218 | checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
219 | dependencies = [
220 | "proc-macro2",
221 | "quote",
222 | "syn",
223 | ]
224 |
225 | [[package]]
226 | name = "futures-sink"
227 | version = "0.3.25"
228 | source = "registry+https://github.com/rust-lang/crates.io-index"
229 | checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
230 |
231 | [[package]]
232 | name = "futures-task"
233 | version = "0.3.25"
234 | source = "registry+https://github.com/rust-lang/crates.io-index"
235 | checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
236 |
237 | [[package]]
238 | name = "futures-util"
239 | version = "0.3.25"
240 | source = "registry+https://github.com/rust-lang/crates.io-index"
241 | checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
242 | dependencies = [
243 | "futures 0.1.31",
244 | "futures-channel",
245 | "futures-core",
246 | "futures-io",
247 | "futures-macro",
248 | "futures-sink",
249 | "futures-task",
250 | "memchr",
251 | "pin-project-lite",
252 | "pin-utils",
253 | "slab",
254 | "tokio-io",
255 | ]
256 |
257 | [[package]]
258 | name = "getrandom"
259 | version = "0.1.16"
260 | source = "registry+https://github.com/rust-lang/crates.io-index"
261 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
262 | dependencies = [
263 | "cfg-if",
264 | "libc",
265 | "wasi 0.9.0+wasi-snapshot-preview1",
266 | ]
267 |
268 | [[package]]
269 | name = "heck"
270 | version = "0.4.0"
271 | source = "registry+https://github.com/rust-lang/crates.io-index"
272 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
273 |
274 | [[package]]
275 | name = "hermit-abi"
276 | version = "0.1.19"
277 | source = "registry+https://github.com/rust-lang/crates.io-index"
278 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
279 | dependencies = [
280 | "libc",
281 | ]
282 |
283 | [[package]]
284 | name = "hermit-abi"
285 | version = "0.2.6"
286 | source = "registry+https://github.com/rust-lang/crates.io-index"
287 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
288 | dependencies = [
289 | "libc",
290 | ]
291 |
292 | [[package]]
293 | name = "indoc"
294 | version = "1.0.8"
295 | source = "registry+https://github.com/rust-lang/crates.io-index"
296 | checksum = "da2d6f23ffea9d7e76c53eee25dfb67bcd8fde7f1198b0855350698c9f07c780"
297 |
298 | [[package]]
299 | name = "io-lifetimes"
300 | version = "1.0.3"
301 | source = "registry+https://github.com/rust-lang/crates.io-index"
302 | checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
303 | dependencies = [
304 | "libc",
305 | "windows-sys",
306 | ]
307 |
308 | [[package]]
309 | name = "iovec"
310 | version = "0.1.4"
311 | source = "registry+https://github.com/rust-lang/crates.io-index"
312 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
313 | dependencies = [
314 | "libc",
315 | ]
316 |
317 | [[package]]
318 | name = "is-terminal"
319 | version = "0.4.2"
320 | source = "registry+https://github.com/rust-lang/crates.io-index"
321 | checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
322 | dependencies = [
323 | "hermit-abi 0.2.6",
324 | "io-lifetimes",
325 | "rustix",
326 | "windows-sys",
327 | ]
328 |
329 | [[package]]
330 | name = "libc"
331 | version = "0.2.139"
332 | source = "registry+https://github.com/rust-lang/crates.io-index"
333 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
334 |
335 | [[package]]
336 | name = "linux-raw-sys"
337 | version = "0.1.4"
338 | source = "registry+https://github.com/rust-lang/crates.io-index"
339 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
340 |
341 | [[package]]
342 | name = "lock_api"
343 | version = "0.4.9"
344 | source = "registry+https://github.com/rust-lang/crates.io-index"
345 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
346 | dependencies = [
347 | "autocfg",
348 | "scopeguard",
349 | ]
350 |
351 | [[package]]
352 | name = "log"
353 | version = "0.4.17"
354 | source = "registry+https://github.com/rust-lang/crates.io-index"
355 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
356 | dependencies = [
357 | "cfg-if",
358 | ]
359 |
360 | [[package]]
361 | name = "memchr"
362 | version = "2.5.0"
363 | source = "registry+https://github.com/rust-lang/crates.io-index"
364 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
365 |
366 | [[package]]
367 | name = "mio"
368 | version = "0.8.5"
369 | source = "registry+https://github.com/rust-lang/crates.io-index"
370 | checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
371 | dependencies = [
372 | "libc",
373 | "log",
374 | "wasi 0.11.0+wasi-snapshot-preview1",
375 | "windows-sys",
376 | ]
377 |
378 | [[package]]
379 | name = "num-traits"
380 | version = "0.2.15"
381 | source = "registry+https://github.com/rust-lang/crates.io-index"
382 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
383 | dependencies = [
384 | "autocfg",
385 | ]
386 |
387 | [[package]]
388 | name = "num_cpus"
389 | version = "1.15.0"
390 | source = "registry+https://github.com/rust-lang/crates.io-index"
391 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
392 | dependencies = [
393 | "hermit-abi 0.2.6",
394 | "libc",
395 | ]
396 |
397 | [[package]]
398 | name = "nvim-rs"
399 | version = "0.5.0"
400 | source = "registry+https://github.com/rust-lang/crates.io-index"
401 | checksum = "1e98dcbd3b0ece3cf2b76ebc1e33e6511777ea7322884f4b7150cbc253afa37e"
402 | dependencies = [
403 | "async-trait",
404 | "futures 0.3.25",
405 | "log",
406 | "parity-tokio-ipc",
407 | "rmp",
408 | "rmpv",
409 | "tokio",
410 | "tokio-util",
411 | ]
412 |
413 | [[package]]
414 | name = "once_cell"
415 | version = "1.17.0"
416 | source = "registry+https://github.com/rust-lang/crates.io-index"
417 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
418 |
419 | [[package]]
420 | name = "os_str_bytes"
421 | version = "6.4.1"
422 | source = "registry+https://github.com/rust-lang/crates.io-index"
423 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
424 |
425 | [[package]]
426 | name = "page"
427 | version = "4.6.3"
428 | dependencies = [
429 | "async-trait",
430 | "atty",
431 | "clap",
432 | "clap_complete",
433 | "clap_mangen",
434 | "fern",
435 | "futures 0.3.25",
436 | "indoc",
437 | "log",
438 | "nvim-rs",
439 | "once_cell",
440 | "parity-tokio-ipc",
441 | "shell-words",
442 | "term_size",
443 | "tokio",
444 | "tokio-util",
445 | "walkdir",
446 | ]
447 |
448 | [[package]]
449 | name = "parity-tokio-ipc"
450 | version = "0.9.0"
451 | source = "registry+https://github.com/rust-lang/crates.io-index"
452 | checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6"
453 | dependencies = [
454 | "futures 0.3.25",
455 | "libc",
456 | "log",
457 | "rand",
458 | "tokio",
459 | "winapi",
460 | ]
461 |
462 | [[package]]
463 | name = "parking_lot"
464 | version = "0.12.1"
465 | source = "registry+https://github.com/rust-lang/crates.io-index"
466 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
467 | dependencies = [
468 | "lock_api",
469 | "parking_lot_core",
470 | ]
471 |
472 | [[package]]
473 | name = "parking_lot_core"
474 | version = "0.9.5"
475 | source = "registry+https://github.com/rust-lang/crates.io-index"
476 | checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"
477 | dependencies = [
478 | "cfg-if",
479 | "libc",
480 | "redox_syscall",
481 | "smallvec",
482 | "windows-sys",
483 | ]
484 |
485 | [[package]]
486 | name = "paste"
487 | version = "1.0.11"
488 | source = "registry+https://github.com/rust-lang/crates.io-index"
489 | checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
490 |
491 | [[package]]
492 | name = "pin-project-lite"
493 | version = "0.2.9"
494 | source = "registry+https://github.com/rust-lang/crates.io-index"
495 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
496 |
497 | [[package]]
498 | name = "pin-utils"
499 | version = "0.1.0"
500 | source = "registry+https://github.com/rust-lang/crates.io-index"
501 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
502 |
503 | [[package]]
504 | name = "ppv-lite86"
505 | version = "0.2.17"
506 | source = "registry+https://github.com/rust-lang/crates.io-index"
507 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
508 |
509 | [[package]]
510 | name = "proc-macro-error"
511 | version = "1.0.4"
512 | source = "registry+https://github.com/rust-lang/crates.io-index"
513 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
514 | dependencies = [
515 | "proc-macro-error-attr",
516 | "proc-macro2",
517 | "quote",
518 | "syn",
519 | "version_check",
520 | ]
521 |
522 | [[package]]
523 | name = "proc-macro-error-attr"
524 | version = "1.0.4"
525 | source = "registry+https://github.com/rust-lang/crates.io-index"
526 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
527 | dependencies = [
528 | "proc-macro2",
529 | "quote",
530 | "version_check",
531 | ]
532 |
533 | [[package]]
534 | name = "proc-macro2"
535 | version = "1.0.49"
536 | source = "registry+https://github.com/rust-lang/crates.io-index"
537 | checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
538 | dependencies = [
539 | "unicode-ident",
540 | ]
541 |
542 | [[package]]
543 | name = "quote"
544 | version = "1.0.23"
545 | source = "registry+https://github.com/rust-lang/crates.io-index"
546 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
547 | dependencies = [
548 | "proc-macro2",
549 | ]
550 |
551 | [[package]]
552 | name = "rand"
553 | version = "0.7.3"
554 | source = "registry+https://github.com/rust-lang/crates.io-index"
555 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
556 | dependencies = [
557 | "getrandom",
558 | "libc",
559 | "rand_chacha",
560 | "rand_core",
561 | "rand_hc",
562 | ]
563 |
564 | [[package]]
565 | name = "rand_chacha"
566 | version = "0.2.2"
567 | source = "registry+https://github.com/rust-lang/crates.io-index"
568 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
569 | dependencies = [
570 | "ppv-lite86",
571 | "rand_core",
572 | ]
573 |
574 | [[package]]
575 | name = "rand_core"
576 | version = "0.5.1"
577 | source = "registry+https://github.com/rust-lang/crates.io-index"
578 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
579 | dependencies = [
580 | "getrandom",
581 | ]
582 |
583 | [[package]]
584 | name = "rand_hc"
585 | version = "0.2.0"
586 | source = "registry+https://github.com/rust-lang/crates.io-index"
587 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
588 | dependencies = [
589 | "rand_core",
590 | ]
591 |
592 | [[package]]
593 | name = "redox_syscall"
594 | version = "0.2.16"
595 | source = "registry+https://github.com/rust-lang/crates.io-index"
596 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
597 | dependencies = [
598 | "bitflags",
599 | ]
600 |
601 | [[package]]
602 | name = "rmp"
603 | version = "0.8.11"
604 | source = "registry+https://github.com/rust-lang/crates.io-index"
605 | checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f"
606 | dependencies = [
607 | "byteorder",
608 | "num-traits",
609 | "paste",
610 | ]
611 |
612 | [[package]]
613 | name = "rmpv"
614 | version = "1.0.0"
615 | source = "registry+https://github.com/rust-lang/crates.io-index"
616 | checksum = "de8813b3a2f95c5138fe5925bfb8784175d88d6bff059ba8ce090aa891319754"
617 | dependencies = [
618 | "num-traits",
619 | "rmp",
620 | ]
621 |
622 | [[package]]
623 | name = "roff"
624 | version = "0.2.1"
625 | source = "registry+https://github.com/rust-lang/crates.io-index"
626 | checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
627 |
628 | [[package]]
629 | name = "rustix"
630 | version = "0.36.6"
631 | source = "registry+https://github.com/rust-lang/crates.io-index"
632 | checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549"
633 | dependencies = [
634 | "bitflags",
635 | "errno",
636 | "io-lifetimes",
637 | "libc",
638 | "linux-raw-sys",
639 | "windows-sys",
640 | ]
641 |
642 | [[package]]
643 | name = "same-file"
644 | version = "1.0.6"
645 | source = "registry+https://github.com/rust-lang/crates.io-index"
646 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
647 | dependencies = [
648 | "winapi-util",
649 | ]
650 |
651 | [[package]]
652 | name = "scopeguard"
653 | version = "1.1.0"
654 | source = "registry+https://github.com/rust-lang/crates.io-index"
655 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
656 |
657 | [[package]]
658 | name = "shell-words"
659 | version = "1.1.0"
660 | source = "registry+https://github.com/rust-lang/crates.io-index"
661 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
662 |
663 | [[package]]
664 | name = "signal-hook-registry"
665 | version = "1.4.0"
666 | source = "registry+https://github.com/rust-lang/crates.io-index"
667 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
668 | dependencies = [
669 | "libc",
670 | ]
671 |
672 | [[package]]
673 | name = "slab"
674 | version = "0.4.7"
675 | source = "registry+https://github.com/rust-lang/crates.io-index"
676 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
677 | dependencies = [
678 | "autocfg",
679 | ]
680 |
681 | [[package]]
682 | name = "smallvec"
683 | version = "1.10.0"
684 | source = "registry+https://github.com/rust-lang/crates.io-index"
685 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
686 |
687 | [[package]]
688 | name = "socket2"
689 | version = "0.4.7"
690 | source = "registry+https://github.com/rust-lang/crates.io-index"
691 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
692 | dependencies = [
693 | "libc",
694 | "winapi",
695 | ]
696 |
697 | [[package]]
698 | name = "strsim"
699 | version = "0.10.0"
700 | source = "registry+https://github.com/rust-lang/crates.io-index"
701 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
702 |
703 | [[package]]
704 | name = "syn"
705 | version = "1.0.107"
706 | source = "registry+https://github.com/rust-lang/crates.io-index"
707 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
708 | dependencies = [
709 | "proc-macro2",
710 | "quote",
711 | "unicode-ident",
712 | ]
713 |
714 | [[package]]
715 | name = "term_size"
716 | version = "0.3.2"
717 | source = "registry+https://github.com/rust-lang/crates.io-index"
718 | checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
719 | dependencies = [
720 | "libc",
721 | "winapi",
722 | ]
723 |
724 | [[package]]
725 | name = "termcolor"
726 | version = "1.1.3"
727 | source = "registry+https://github.com/rust-lang/crates.io-index"
728 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
729 | dependencies = [
730 | "winapi-util",
731 | ]
732 |
733 | [[package]]
734 | name = "terminal_size"
735 | version = "0.2.3"
736 | source = "registry+https://github.com/rust-lang/crates.io-index"
737 | checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907"
738 | dependencies = [
739 | "rustix",
740 | "windows-sys",
741 | ]
742 |
743 | [[package]]
744 | name = "tokio"
745 | version = "1.23.0"
746 | source = "registry+https://github.com/rust-lang/crates.io-index"
747 | checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
748 | dependencies = [
749 | "autocfg",
750 | "bytes 1.3.0",
751 | "libc",
752 | "memchr",
753 | "mio",
754 | "num_cpus",
755 | "parking_lot",
756 | "pin-project-lite",
757 | "signal-hook-registry",
758 | "socket2",
759 | "tokio-macros",
760 | "windows-sys",
761 | ]
762 |
763 | [[package]]
764 | name = "tokio-io"
765 | version = "0.1.13"
766 | source = "registry+https://github.com/rust-lang/crates.io-index"
767 | checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
768 | dependencies = [
769 | "bytes 0.4.12",
770 | "futures 0.1.31",
771 | "log",
772 | ]
773 |
774 | [[package]]
775 | name = "tokio-macros"
776 | version = "1.8.2"
777 | source = "registry+https://github.com/rust-lang/crates.io-index"
778 | checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
779 | dependencies = [
780 | "proc-macro2",
781 | "quote",
782 | "syn",
783 | ]
784 |
785 | [[package]]
786 | name = "tokio-util"
787 | version = "0.7.4"
788 | source = "registry+https://github.com/rust-lang/crates.io-index"
789 | checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
790 | dependencies = [
791 | "bytes 1.3.0",
792 | "futures-core",
793 | "futures-io",
794 | "futures-sink",
795 | "pin-project-lite",
796 | "tokio",
797 | ]
798 |
799 | [[package]]
800 | name = "unicode-ident"
801 | version = "1.0.6"
802 | source = "registry+https://github.com/rust-lang/crates.io-index"
803 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
804 |
805 | [[package]]
806 | name = "version_check"
807 | version = "0.9.4"
808 | source = "registry+https://github.com/rust-lang/crates.io-index"
809 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
810 |
811 | [[package]]
812 | name = "walkdir"
813 | version = "2.3.2"
814 | source = "registry+https://github.com/rust-lang/crates.io-index"
815 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
816 | dependencies = [
817 | "same-file",
818 | "winapi",
819 | "winapi-util",
820 | ]
821 |
822 | [[package]]
823 | name = "wasi"
824 | version = "0.9.0+wasi-snapshot-preview1"
825 | source = "registry+https://github.com/rust-lang/crates.io-index"
826 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
827 |
828 | [[package]]
829 | name = "wasi"
830 | version = "0.11.0+wasi-snapshot-preview1"
831 | source = "registry+https://github.com/rust-lang/crates.io-index"
832 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
833 |
834 | [[package]]
835 | name = "winapi"
836 | version = "0.3.9"
837 | source = "registry+https://github.com/rust-lang/crates.io-index"
838 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
839 | dependencies = [
840 | "winapi-i686-pc-windows-gnu",
841 | "winapi-x86_64-pc-windows-gnu",
842 | ]
843 |
844 | [[package]]
845 | name = "winapi-i686-pc-windows-gnu"
846 | version = "0.4.0"
847 | source = "registry+https://github.com/rust-lang/crates.io-index"
848 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
849 |
850 | [[package]]
851 | name = "winapi-util"
852 | version = "0.1.5"
853 | source = "registry+https://github.com/rust-lang/crates.io-index"
854 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
855 | dependencies = [
856 | "winapi",
857 | ]
858 |
859 | [[package]]
860 | name = "winapi-x86_64-pc-windows-gnu"
861 | version = "0.4.0"
862 | source = "registry+https://github.com/rust-lang/crates.io-index"
863 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
864 |
865 | [[package]]
866 | name = "windows-sys"
867 | version = "0.42.0"
868 | source = "registry+https://github.com/rust-lang/crates.io-index"
869 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
870 | dependencies = [
871 | "windows_aarch64_gnullvm",
872 | "windows_aarch64_msvc",
873 | "windows_i686_gnu",
874 | "windows_i686_msvc",
875 | "windows_x86_64_gnu",
876 | "windows_x86_64_gnullvm",
877 | "windows_x86_64_msvc",
878 | ]
879 |
880 | [[package]]
881 | name = "windows_aarch64_gnullvm"
882 | version = "0.42.0"
883 | source = "registry+https://github.com/rust-lang/crates.io-index"
884 | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
885 |
886 | [[package]]
887 | name = "windows_aarch64_msvc"
888 | version = "0.42.0"
889 | source = "registry+https://github.com/rust-lang/crates.io-index"
890 | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
891 |
892 | [[package]]
893 | name = "windows_i686_gnu"
894 | version = "0.42.0"
895 | source = "registry+https://github.com/rust-lang/crates.io-index"
896 | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
897 |
898 | [[package]]
899 | name = "windows_i686_msvc"
900 | version = "0.42.0"
901 | source = "registry+https://github.com/rust-lang/crates.io-index"
902 | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
903 |
904 | [[package]]
905 | name = "windows_x86_64_gnu"
906 | version = "0.42.0"
907 | source = "registry+https://github.com/rust-lang/crates.io-index"
908 | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
909 |
910 | [[package]]
911 | name = "windows_x86_64_gnullvm"
912 | version = "0.42.0"
913 | source = "registry+https://github.com/rust-lang/crates.io-index"
914 | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
915 |
916 | [[package]]
917 | name = "windows_x86_64_msvc"
918 | version = "0.42.0"
919 | source = "registry+https://github.com/rust-lang/crates.io-index"
920 | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
921 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "page"
3 | version = "4.6.3"
4 | authors = ["160R <160R@protonmail.com>"]
5 | description = "Pager powered by neovim and inspired by neovim-remote"
6 | repository = "https://github.com/I60R/page"
7 | license = "MIT"
8 | readme = "README.md"
9 | edition = "2021"
10 | rust-version = "1.65.0"
11 | build = "build_shell_completions_and_man_pages.rs"
12 |
13 |
14 | [dependencies]
15 | term_size = { version = "0.3.2", optional = true }
16 | walkdir = { version = "2.3.2", optional = true }
17 |
18 | once_cell = "1.17.0"
19 | futures = "0.3.25"
20 | async-trait = "0.1.60"
21 | tokio = { version = "1.23.0", features = ["full"] }
22 | tokio-util = { version = "0.7.4", features = ["compat"] }
23 | parity-tokio-ipc = "0.9.0"
24 | nvim-rs = { version = "0.5.0", features = ["use_tokio"] }
25 | atty = "0.2.14"
26 | shell-words = "1.1.0"
27 | log = "0.4.17"
28 | fern = "0.6.1"
29 | indoc = "1.0.8"
30 | clap = { version = "4.0.32", features = ["wrap_help", "derive", "env"] }
31 |
32 |
33 | [build-dependencies]
34 | once_cell = "1.17.0"
35 | clap = { version = "4.0.32", features = ["derive", "env"] }
36 | clap_complete = "4.0.7"
37 | clap_mangen = "0.2.6"
38 |
39 |
40 | [profile.release]
41 | lto = true
42 |
43 |
44 | [features]
45 | default = ["pager", "picker"]
46 |
47 | pager = ["dep:term_size"]
48 | picker = ["dep:walkdir"]
49 |
50 |
51 | [lib]
52 | name = "connection"
53 | path = "src/connection.rs"
54 |
55 |
56 | [[bin]]
57 | name = "page"
58 | path = "src/pager/main.rs"
59 | required-features = ["pager"]
60 |
61 | [[bin]]
62 | name = "nv"
63 | path = "src/picker/main.rs"
64 | required-features = ["picker"]
65 |
66 |
67 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017 160R
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | * this software and associated documentation files (the "Software"), to deal in
6 | * the Software without restriction, including without limitation the rights to
7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 | * the Software, and to permit persons to whom the Software is furnished to do so,
9 | * subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 |
23 |
--------------------------------------------------------------------------------
/PKGBUILD:
--------------------------------------------------------------------------------
1 | # Maintainer: 160R@protonmail.com
2 | _pkgname=page
3 | pkgname=${_pkgname}-git
4 | pkgrel=2
5 | pkgver=v4.6.3
6 | pkgdesc='Pager powered by neovim and inspired by neovim-remote'
7 | arch=('i686' 'x86_64')
8 | url="https://github.com/I60R/page"
9 | license=('MIT')
10 | depends=('neovim' 'gcc-libs' 'file' 'bat')
11 | makedepends=('rust' 'cargo' 'git')
12 | provides=('page')
13 | conflicts=('page')
14 | source=("git+https://github.com/I60R/page.git#branch=main")
15 | md5sums=('SKIP')
16 |
17 |
18 | pkgver() {
19 | checkout_project_root
20 | git describe --tags --abbrev=0
21 | }
22 |
23 | package() {
24 | checkout_project_root
25 |
26 | cargo build --release
27 |
28 | # Install binaries
29 | install -D -m755 "target/release/page" "$pkgdir/usr/bin/page"
30 | install -D -m755 "target/release/nv" "$pkgdir/usr/bin/nv"
31 |
32 | # Find last build directory where completions was generated
33 | out_dir=$(find "target" -name "assets" -type d -printf "%T+\t%p\n" | sort | awk 'NR==1{print $2}')
34 |
35 | # Install shell completions
36 | install -D -m644 "$out_dir/_page" "$pkgdir/usr/share/zsh/site-functions/_page"
37 | install -D -m644 "$out_dir/page.bash" "$pkgdir/usr/share/bash-completion/completions/page.bash"
38 | install -D -m644 "$out_dir/page.fish" "$pkgdir/usr/share/fish/completions/page.fish"
39 |
40 | install -D -m644 "$out_dir/_nv" "$pkgdir/usr/share/zsh/site-functions/_nv"
41 | install -D -m644 "$out_dir/nv.bash" "$pkgdir/usr/share/bash-completion/completions/nv.bash"
42 | install -D -m644 "$out_dir/nv.fish" "$pkgdir/usr/share/fish/completions/nv.fish"
43 |
44 | # Install man pages
45 | install -D -m644 "$out_dir/page.1" "$pkgdir/usr/share/man/man1/page.1"
46 | install -D -m644 "$out_dir/nv.1" "$pkgdir/usr/share/man/man1/nv.1"
47 |
48 | # Install MIT license
49 | install -D -m644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
50 | }
51 |
52 | # Ensures that current directory is root of repository
53 | checkout_project_root() {
54 | cd "$srcdir"
55 | cd "$_pkgname" > /dev/null 2>&1 || cd ..
56 | }
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Page
2 |
3 | [](https://github.com/I60R/page/actions/workflows/rust_build.yml)
4 | [](https://github.com/I60R/page)
5 |
6 | Allows you to redirect text into [neovim](https://github.com/neovim/neovim).
7 | You can set it as `$PAGER` to view logs, diffs, various command outputs.
8 |
9 | ANSI escape sequences will be interpreted by :term buffer, which makes `page` noticeably faster than [vimpager](https://github.com/rkitover/vimpager) and [nvimpager](https://github.com/lucc/nvimpager).
10 | And text will be displayed instantly as it arrives - no need to wait until EOF.
11 |
12 | Also, text from neovim :term buffer will be redirected directly into a new buffer in the same neovim instance - no nested neovim will be spawned.
13 | That's by utilizing `$NVIM` variable like [neovim-remote](https://github.com/mhinz/neovim-remote) does.
14 |
15 | **Bonus**: another binary named `nv` is included, which reimplements `neovim-remote` but with interface similar to `page`. There's no intention to have all `nvim --remote` features — it should be only a simple file picker that prevents spawning nested neovim instance. Also, in contrast with `neovim-remote` there are some safeguards e.g. it won't open non-text files unless explicit flag is provided for that so `nv *` opens only text files in current directory. I recommend to read `--help` output and experiment with options a bit.
16 |
17 | Ultimately, `page` and `nv` reuses all of neovim's text editing+navigating+searching facilities and will either facilitate all of plugins+mappings+options set in your neovim config.
18 |
19 | ## Usage
20 |
21 | * *under regular terminal*
22 |
23 | 
24 |
25 | * *under neovim's terminal*
26 |
27 | 
28 |
29 | ---
30 |
31 | ## CLI
32 |
33 | expand page --help
34 |
35 | ```xml
36 | Usage: page [OPTIONS] [FILE]...
37 |
38 | Arguments:
39 | [FILE]... Open provided file in separate buffer [without other flags revokes implied by default -o or -p
40 | option]
41 |
42 | Options:
43 | -o Create and use output buffer (to redirect text from page's stdin) [implied by
44 | default unless -x and/or provided without other flags]
45 | -O [] Prefetch from page's stdin: if all input fits then print it to
46 | stdout and exit without neovim usage (to emulate `less --quit-if-one-screen`)
47 | [empty: term height - 3 (space for prompt); negative: term height -
48 | ; 0: disabled and default; ignored with -o, -p, -x and when page
49 | isn't piped]
50 | -p Print path of pty device associated with output buffer (to redirect text from
51 | commands respecting output buffer size and preserving colors) [implied if page
52 | isn't piped unless -x and/or provided without other flags]
53 | -P Set $PWD as working directory at output buffer (to navigate paths with `gf`)
54 | -q [] Read no more than from page's stdin: next lines should be
55 | fetched by invoking :Page command or 'r'/'R' keypress on neovim side
56 | [empty: term height - 2 (space for tab and buffer lines); negative: term
57 | height - ; 0: disabled and default; is optional and
58 | defaults to ; doesn't take effect on buffers]
59 | -f Cursor follows content of output buffer as it appears instead of keeping top
60 | position (like `tail -f`)
61 | -F Cursor follows content of output and buffers as it appears instead of
62 | keeping top position
63 | -t Set filetype on output buffer (to enable syntax highlighting) [pager: default;
64 | not works with text echoed by -O]
65 | -b Return back to current buffer
66 | -B Return back to current buffer and enter into INSERT/TERMINAL mode
67 | -n Set title for output buffer (to display it in statusline) [env:
68 | PAGE_BUFFER_NAME=]
69 | -w Do not remap i, I, a, A, u, d, x, q (and r, R with -q) keys [wouldn't unmap on
70 | connected instance output buffer]
71 | -z [] Pagerize output when it exceeds lines (to view `journalctl`)
72 | [default: disabled; empty: 100_000]
73 | ~ ~ ~
74 |
75 | ~ ~ ~
76 | -a TCP/IP socked address or path to named pipe listened by running host neovim
77 | process [env: NVIM=/run/user/1000/nvim.9389.0]
78 | -A Arguments that will be passed to child neovim process spawned when
79 | is missing [env: NVIM_PAGE_ARGS=]
80 | -c Config that will be used by child neovim process spawned when is
81 | missing [file:$XDG_CONFIG_HOME/page/init.vim]
82 | -C Enable PageConnect PageDisconnect autocommands
83 | -e Run command on output buffer after it was created
84 | --e Run lua expr on output buffer after it was created
85 | -E Run command on output buffer after it was created or connected as instance
86 | --E Run lua expr on output buffer after it was created or connected as instance
87 | ~ ~ ~
88 | -i Create output buffer with tag or use existed with replacing its
89 | content by text from page's stdin
90 | -I Create output buffer with tag or use existed with appending
91 | to its content text from page's stdin
92 | -x Close output buffer with tag if it exists [without other
93 | flags revokes implied by defalt -o or -p option]
94 | ~ ~ ~
95 | -W Flush redirection protection that prevents from producing junk and possible
96 | overwriting of existed files by invoking commands like `ls > $(NVIM= page -E
97 | q)` where the RHS of > operator evaluates not into /path/to/pty as expected
98 | but into a bunch of whitespace-separated strings/escape sequences from neovim
99 | UI; bad things happens when some shells interpret this as many valid targets
100 | for text redirection. The protection is only printing of a path to the existed
101 | dummy directory always first before printing of a neovim UI might occur; this
102 | makes the first target for text redirection from page's output invalid and
103 | disrupts the whole redirection early before other harmful writes might occur.
104 | [env:PAGE_REDIRECTION_PROTECT; (0 to disable)]
105 | ~ ~ ~
106 | -l... Split left with ratio: window_width * 3 / ( + 1)
107 | -r... Split right with ratio: window_width * 3 / ( + 1)
108 | -u... Split above with ratio: window_height * 3 / ( + 1)
109 | -d... Split below with ratio: window_height * 3 / ( + 1)
110 | -L Split left and resize to columns
111 | -R Split right and resize to columns
112 | -U Split above and resize to rows
113 | -D Split below and resize to rows
114 | ^
115 | -+ With any of -r -l -u -d -R -L -U -D open floating window instead of split [to
116 | not overwrite data in the current terminal]
117 | ~ ~ ~
118 | -h, --help Print help information
119 | ```
120 |
121 |
122 |
123 | expand nv --help
124 |
125 | ```xml
126 | Usage: nv [OPTIONS] [FILE]...
127 |
128 | Arguments:
129 | [FILE]... Open provided files as editable [if none provided nv opens last modified file in currend
130 | directory]
131 |
132 | Options:
133 | -o Open non-text files including directories, binaries, images etc
134 | -O [] Ignoring [FILE] open all text files in the current directory and recursively
135 | open all text files in its subdirectories [0: disabled and default; empty:
136 | defaults to 1 and implied if no provided; :
137 | also opens in subdirectories at this level of depth]
138 | -v Open in `page` instead (just postfix shortcut)
139 | ~ ~ ~
140 | -f Open each [FILE] at last line
141 | -p Open and search for a specified
142 | -P Open and search backwars for a specified
143 | -b Return back to current buffer
144 | -B Return back to current buffer and enter into INSERT/TERMINAL mode
145 | -k Keep `nv` process until buffer is closed (for editing git commit message)
146 | -K Keep `nv` process until first write occur, then close buffer and neovim if
147 | it was spawned by `nv`
148 | ~ ~ ~
149 | -a TCP/IP socket address or path to named pipe listened by running host neovim
150 | process [env: NVIM=/run/user/1000/nvim.604327.0]
151 | -A Arguments that will be passed to child neovim process spawned when
152 | is missing [env: NVIM_PAGE_PICKER_ARGS=]
153 | -c Config that will be used by child neovim process spawned when is
154 | missing [file: $XDG_CONFIG_HOME/page/init.vim]
155 | -t Override filetype on each [FILE] buffer (to enable custom syntax highlighting
156 | [text: default]
157 | ~ ~ ~
158 | -e Run command on each [FILE] buffer after it was created
159 | --e Run lua expr on each [FILE] buffer after it was created
160 | -x Just run command with ignoring all other options
161 | --x Just run lua expr with ignoring all other options
162 | ~ ~ ~
163 | -l... Split left with ratio: window_width * 3 / ( + 1)
164 | -r... Split right with ratio: window_width * 3 / ( + 1)
165 | -u... Split above with ratio: window_height * 3 / ( + 1)
166 | -d... Split below with ratio: window_height * 3 / ( + 1)
167 | -L Split left and resize to columns
168 | -R Split right and resize to columns
169 | -U Split above and resize to rows
170 | -D Split below and resize to rows
171 | ^
172 | -+ With any of -r -l -u -d -R -L -U -D open floating window instead of split
173 | [to not overwrite data in the current terminal]
174 | ~ ~ ~
175 | -h, --help Print help information
176 | ```
177 |
178 |
179 |
180 | **Note**: `page` and `nv` may be unergonomic to type so I suggest users to create alias like `p` and `v`
181 |
182 | ## `nvim/init.lua` customizations
183 |
184 | ```lua
185 | -- Opacity of popup window spawned with -+ option
186 | vim.g.page_popup_winblend = 25
187 | ```
188 |
189 | ## `nvim/init.lua` customizations (pager only)
190 |
191 | Statusline appearance:
192 |
193 | ```lua
194 | -- String that will append to buffer name
195 | vim.g.page_icon_pipe = '|' -- When piped
196 | vim.g.page_icon_redirect = '>' -- When exposes pty device
197 | vim.g.page_icon_instance = '$' -- When `-i, -I` flags provided
198 | ```
199 |
200 | Autocommand hooks:
201 |
202 | ```lua
203 | -- Will run once when output buffer is created
204 | vim.api.create_autocmd('User', {
205 | pattern = 'PageOpen',
206 | callback = lua_function,
207 | })
208 |
209 | -- Will run once when file buffer is created
210 | vim.api.create_autocmd('User', {
211 | pattern = 'PageOpenFile',
212 | callback = lua_function,
213 | })
214 | ```
215 |
216 | Only with `-C` option provided:
217 |
218 | ```lua
219 | -- will run always when output buffer is created
220 | -- and also when `page` connects to instance `-i, -I` buffers:
221 | vim.api.create_autocmd('User', {
222 | pattern = 'PageConnect',
223 | callback = lua_function,
224 | })
225 |
226 | -- Will run when page process exits
227 | vim.api.create_autocmd('User', {
228 | pattern = 'PageDisconnect',
229 | callback = lua_function,
230 | })
231 | ```
232 |
233 | ## Shell hacks
234 |
235 | To use as `$PAGER` without [scrollback overflow](https://github.com/I60R/page/issues/7):
236 |
237 | ```zsh
238 | export PAGER="page -q 90000"
239 |
240 | # Alternatively
241 |
242 | export PAGER="page -z 90000" # will pagerize output
243 |
244 | # And you can combine both
245 |
246 | export PAGER="page -q 90000 -z 90000"
247 | ```
248 |
249 | To configure:
250 |
251 | ```zsh
252 | export PAGER="page -WfC -q 90000 -z 90000" # some sensible flags
253 | alias page="$PAGER"
254 |
255 | # Usage
256 | ls | page -q 100 # you can specify the same flag multiple times:
257 | # last provided will override previous
258 | ```
259 |
260 | To use as `$MANPAGER`:
261 |
262 | ```zsh
263 | export MANPAGER="page -t man"
264 |
265 | # Alternatively, to pick a bit better `man` highlighting:
266 |
267 | man () {
268 | PROGRAM="${@[-1]}"
269 | SECTION="${@[-2]}"
270 | page -W "man://$PROGRAM${SECTION:+($SECTION)}"
271 | }
272 | ```
273 |
274 | To set `nv` as popup `git` commit message editor:
275 |
276 | ```zsh
277 | # Will spawn popup editor and exit on first write
278 | git config --global core.editor "nv -K -+-R 80 -B"
279 | ```
280 |
281 | To cd into directory passed to `nv`
282 |
283 | ```zsh
284 | nv() {
285 | #stdin_is_term #one_argument #it's_dir
286 | if [ -t 1 ] && [ 1 -eq $# ] && [ -d $1 ]; then
287 | cd $1
288 | else
289 | nv $*
290 | fi
291 | }
292 |
293 | compdef _nv nv # if you have completions installed
294 | ```
295 |
296 | To automatically `lcd` into terminal's directory:
297 |
298 | ```zsh
299 | chpwd () {
300 | [ ! -z "$NVIM" ] && nv -x "lcd $PWD"
301 | }
302 | ```
303 |
304 | To circumvent neovim config picking:
305 |
306 | ```zsh
307 | page -c NONE
308 |
309 | # Alternatively, to override neovim config create this file:
310 |
311 | touch $XDG_CONFIG_HOME/page/init.lua # init.vim is also supported
312 | ```
313 |
314 | To set output buffer name as first two words from invoked command (zsh only):
315 |
316 | ```zsh
317 | preexec () {
318 | if [ -z "$NVIM" ]; then
319 | export PAGE_BUFFER_NAME="page"
320 | else
321 | WORDS=(${1// *|*})
322 | export PAGE_BUFFER_NAME="${WORDS[@]:0:2}"
323 | fi
324 | }
325 | ```
326 |
327 | ## Buffer defaults (pager)
328 |
329 |
330 | expand
331 |
332 | These commands are run on each `page` buffer creation:
333 |
334 | ```lua
335 | vim.b.page_alternate_bufnr = {$initial_buf_nr}
336 | if vim.wo.scrolloff > 999 or vim.wo.scrolloff < 0 then
337 | vim.g.page_scrolloff_backup = 0
338 | else
339 | vim.g.page_scrolloff_backup = vim.wo.scrolloff
340 | end
341 | vim.bo.scrollback, vim.wo.scrolloff, vim.wo.signcolumn, vim.wo.number =
342 | 100000, 999, 'no', false
343 | {$filetype}
344 | {$edit}
345 | vim.api.nvim_create_autocmd('BufEnter', {
346 | buffer = 0,
347 | callback = function() vim.wo.scrolloff = 999 end
348 | })
349 | vim.api.nvim_create_autocmd('BufLeave', {
350 | buffer = 0,
351 | callback = function() vim.wo.scrolloff = vim.g.page_scrolloff_backup end
352 | })
353 | {$notify_closed}
354 | {$pre}
355 | vim.cmd 'silent doautocmd User PageOpen | redraw'
356 | {$lua_provided_by_user}
357 | {$cmd_provided_by_user}
358 | {$after}
359 | ```
360 |
361 | Where:
362 |
363 | ```lua
364 | --{$initial_buf_nr}
365 | -- Is always set on all buffers created by page
366 |
367 | 'number of parent :term buffer or -1 when page isn't spawned from :term'
368 | ```
369 |
370 | ```lua
371 | --{$filetype}
372 | -- Is set only on output buffers.
373 | -- On files buffers filetypes are detected automatically.
374 |
375 | vim.bo.filetype='value of -t argument or "pager"'
376 | ```
377 |
378 | ```lua
379 | --{$edit}
380 | -- Is appended when no -w option provided
381 |
382 | vim.bo.modifiable = false
383 | _G.page_echo_notification = function(message)
384 | vim.defer_fn(function()
385 | local msg = "-- [PAGE] " .. message .. " --"
386 | vim.api.nvim_echo({{ msg, 'Comment' }, }, false, {})
387 | vim.cmd 'au CursorMoved ++once echo'
388 | end, 64)
389 | end
390 | _G.page_bound = function(top, message, move)
391 | local row, col, search
392 | if top then
393 | row, col, search = 1, 1, { '\\S', 'c' }
394 | else
395 | row, col, search = 9999999999, 9999999999, { '\\S', 'bc' }
396 | end
397 | vim.api.nvim_call_function('cursor', { row, col })
398 | vim.api.nvim_call_function('search', search)
399 | if move ~= nil then move() end
400 | _G.page_echo_notification(message)
401 | end
402 | _G.page_scroll = function(top, message)
403 | vim.wo.scrolloff = 0
404 | local move
405 | if top then
406 | local key = vim.api.nvim_replace_termcodes('zM', true, false, true)
407 | move = function() vim.api.nvim_feedkeys(key, 'nx', true) end
408 | else
409 | move = function() vim.api.nvim_feedkeys('z-M', 'nx', false) end
410 | end
411 | _G.page_bound(top, message, move)
412 | vim.wo.scrolloff = 999
413 | end
414 | _G.page_close = function()
415 | local buf = vim.api.nvim_get_current_buf()
416 | if buf ~= vim.b.page_alternate_bufnr and
417 | vim.api.nvim_buf_is_loaded(vim.b.page_alternate_bufnr)
418 | then
419 | vim.api.nvim_set_current_buf(vim.b.page_alternate_bufnr)
420 | end
421 | vim.api.nvim_buf_delete(buf, { force = true })
422 | local exit = true
423 | for _, b in ipairs(vim.api.nvim_list_bufs()) do
424 | local bt = vim.api.nvim_buf_get_option(b, 'buftype')
425 | if bt == "" or bt == "acwrite" or bt == "terminal" or bt == "prompt" then
426 | local bm = vim.api.nvim_buf_get_option(b, 'modified')
427 | if bm then
428 | exit = false
429 | break
430 | end
431 | local bl = vim.api.nvim_buf_get_lines(b, 0, -1, false)
432 | if #bl ~= 0 and bl[1] ~= "" and #bl > 1 then
433 | exit = false
434 | break
435 | end
436 | end
437 | end
438 | if exit then
439 | vim.cmd "qa!"
440 | end
441 | end
442 | local function page_map(key, expr)
443 | vim.api.nvim_buf_set_keymap(0, '', key, expr, { nowait = true })
444 | end
445 | page_map('I', 'lua _G.page_scroll(true, "in the beginning of scroll")')
446 | page_map('A', 'lua _G.page_scroll(false, "at the end of scroll")')
447 | page_map('i', 'lua _G.page_bound(true, "in the beginning")')
448 | page_map('a', 'lua _G.page_bound(false, "at the end")')
449 | page_map('q', 'lua _G.page_close()')
450 | page_map('u', '')
451 | page_map('d', '')
452 | page_map('x', 'G')
453 | ```
454 |
455 | ```lua
456 | --{$notify_closed}
457 | -- Is set only on output buffers
458 |
459 | local closed = 'rpcnotify({channel}, "page_buffer_closed", "{page_id}")'
460 | vim.api.nvim_create_autocmd('BufDelete', {
461 | buffer = 0,
462 | command = 'silent! call ' .. closed
463 | })
464 | ```
465 |
466 | ```lua
467 | --{$pre}
468 | -- Is appended when -q provided
469 |
470 | vim.b.page_query_size = {$query_lines_count}
471 | local def_args = '{channel}, "page_fetch_lines", "{page_id}", '
472 | local def = 'command! -nargs=? Page call rpcnotify(' .. def_args .. ')'
473 | vim.cmd(def)
474 | vim.api.create_autocmd('BufEnter', {
475 | buffer = 0,
476 | command = def,
477 | })
478 |
479 | -- Also if -q provided and no -w provided
480 |
481 | page_map('r', 'call rpcnotify(' .. def_args .. 'b:page_query_size * v:count1)')
482 | page_map('R', 'call rpcnotify(' .. def_args .. '99999)')
483 |
484 | -- If -P provided ({pwd} is $PWD value)
485 |
486 | vim.b.page_lcd_backup = getcwd()
487 | vim.cmd 'lcd {pwd}'
488 | vim.api.nvim_create_autocmd('BufEnter', {
489 | buffer = 0,
490 | command = 'lcd {pwd}'
491 | })
492 | vim.api.nvim_create_autocmd('BufLeave', {
493 | buffer = 0,
494 | command = 'exe "lcd" . b:page_lcd_backup'
495 | })
496 | ```
497 |
498 | ```lua
499 | --{$lua_provided_by_user}
500 | -- Is appended when --e provided
501 |
502 | 'value of --e flag'
503 | ```
504 |
505 | ```lua
506 | --{$cmd_provided_by_user}
507 | -- Is appended when -e provided
508 |
509 | vim.cmd [====[{$command}]====]
510 | ```
511 |
512 | ```lua
513 | --{$after}
514 | -- Is appended only on file buffers
515 |
516 | vim.api.nvim_exec_autocmds('User', {
517 | pattern = 'PageOpenFile',
518 | })
519 | ```
520 |
521 |
522 |
523 | ## Limitations (pager)
524 |
525 | * Only ~100000 lines can be displayed (that's neovim terminal limit)
526 | * No reflow: text that doesnt't fit into window will be lost on resize ([due to data structures inherited from vim](https://github.com/neovim/neovim/issues/2514#issuecomment-580035346))
527 |
528 | ## Installation
529 |
530 | * From binaries
531 | * Grab binary for your platform from [releases](https://github.com/I60R/page/releases) (currently Linux and OSX are supported)
532 |
533 | * Arch Linux:
534 | * Package [page-git](https://aur.archlinux.org/packages/page-git/) is available on AUR
535 | * Or: `git clone git@github.com:I60R/page.git && cd page && makepkg -ef && sudo pacman -U page-git*.pkg.tar.xz`
536 |
537 | * Homebrew:
538 | * Package [page](https://formulae.brew.sh/formula/page) is available on [Homebrew](https://brew.sh/)
539 |
540 | * Manually:
541 | * Install `rustup` from your distribution package manager
542 | * Configure toolchain: `rustup install stable && rustup default stable`
543 | * `git clone git@github.com:I60R/page.git && cd page && cargo install --path .`
544 |
--------------------------------------------------------------------------------
/build_shell_completions_and_man_pages.rs:
--------------------------------------------------------------------------------
1 | use clap_complete::shells::{Zsh, Bash, Fish};
2 | use clap::CommandFactory;
3 |
4 | use std::{fs, path::{PathBuf, Path}};
5 |
6 |
7 | #[cfg(feature = "pager")]
8 | #[allow(dead_code)]
9 | mod pager {
10 | include!("src/pager/cli.rs");
11 | }
12 |
13 | #[cfg(feature = "picker")]
14 | #[allow(dead_code)]
15 | mod picker {
16 | include!("src/picker/cli.rs");
17 | }
18 |
19 |
20 | fn main() -> Result<(), Box> {
21 | let out_dir = PathBuf::from(
22 | std::env::var("OUT_DIR")
23 | .unwrap()
24 | ).join("assets");
25 |
26 | fs::create_dir_all(&out_dir)?;
27 | eprintln!("Assets would be generated in: {}", out_dir.display());
28 |
29 | #[cfg(feature = "pager")]
30 | {
31 | let mut app = pager::Options::command();
32 | clap_complete::generate_to(Zsh , &mut app, "page", &out_dir)?;
33 | clap_complete::generate_to(Bash, &mut app, "page", &out_dir)?;
34 | clap_complete::generate_to(Fish, &mut app, "page", &out_dir)?;
35 |
36 | let page_1 = Path::new(&out_dir)
37 | .join("page.1");
38 | let mut page_1 = fs::File::create(page_1)?;
39 | let man = clap_mangen::Man::new(app);
40 | man.render_title(&mut page_1)?;
41 | man.render_description_section(&mut page_1)?;
42 | let mut options_section = vec![];
43 | man.render_options_section(&mut options_section)?;
44 | let options_section = String::from_utf8(options_section)?
45 | .replace("{n} ~ ~ ~", "")
46 | .replace("{n} ^ ~ ~ ~", "")
47 | .replace("[", "【\\fB")
48 | .replace("]", "\\fR】")
49 | .replace("【\\fBFILE\\fR】", "[FILE]..")
50 | .replace("【\\fB\\fIFILE\\fR\\fR】", "[FILE]..");
51 | use std::io::Write;
52 | write!(page_1, "{options_section}")?;
53 | man.render_authors_section(&mut page_1)?;
54 | }
55 |
56 | #[cfg(feature = "picker")]
57 | {
58 | let mut app = picker::Options::command();
59 | clap_complete::generate_to(Zsh , &mut app, "nv", &out_dir)?;
60 | clap_complete::generate_to(Bash, &mut app, "nv", &out_dir)?;
61 | clap_complete::generate_to(Fish, &mut app, "nv", &out_dir)?;
62 |
63 | let nv_1 = Path::new(&out_dir)
64 | .join("nv.1");
65 | let mut nv_1 = fs::File::create(nv_1)?;
66 | let man = clap_mangen::Man::new(app);
67 | man.render_title(&mut nv_1)?;
68 | man.render_description_section(&mut nv_1)?;
69 | let mut options_section = vec![];
70 | man.render_options_section(&mut options_section)?;
71 | let options_section = String::from_utf8(options_section)?
72 | .replace("{n} ~ ~ ~", "")
73 | .replace("{n} ^ ~ ~ ~", "")
74 | .replace("[", "【\\fB")
75 | .replace("]", "\\fR】")
76 | .replace("【\\fBFILE\\fR】", "[FILE]..")
77 | .replace("【\\fB\\fIFILE\\fR\\fR】", "[FILE]..");
78 | use std::io::Write;
79 | write!(nv_1, "{options_section}")?;
80 | man.render_authors_section(&mut nv_1)?;
81 | }
82 |
83 | Ok(())
84 | }
85 |
--------------------------------------------------------------------------------
/src/connection.rs:
--------------------------------------------------------------------------------
1 | pub use crate::{
2 | io_handler::{
3 | PipeOrSocketHandler,
4 | NotificationFromNeovim
5 | },
6 | io_pipe_or_socket::{
7 | PipeOrSocketWrite as IoWrite,
8 | PipeOrSocketRead as IoRead
9 | }
10 | };
11 | pub use nvim_rs::{
12 | neovim::Neovim,
13 | Buffer,
14 | Window,
15 | Value
16 | };
17 |
18 | use tokio_util::compat::{
19 | TokioAsyncReadCompatExt,
20 | TokioAsyncWriteCompatExt
21 | };
22 |
23 | use std::{
24 | path::Path,
25 | process::ExitStatus
26 | };
27 |
28 |
29 | pub fn init_logger() {
30 | let exec_time = std::time::Instant::now();
31 |
32 | let dispatch = fern::Dispatch::new().format(move |cb, msg, log_record| {
33 | let time = exec_time
34 | .elapsed()
35 | .as_micros();
36 |
37 | let lvl = log_record.level();
38 | let target = log_record.target();
39 |
40 | let mut module = log_record
41 | .module_path()
42 | .unwrap_or_default();
43 | let mut prep = " in ";
44 | if target == module {
45 | module = "";
46 | prep = "";
47 | };
48 |
49 | const BOLD: &str = "\x1B[1m";
50 | const UNDERL: &str = "\x1B[4m";
51 | const GRAY: &str = "\x1B[0;90m";
52 | const CLEAR: &str = "\x1B[0m";
53 |
54 | let mut msg_color = GRAY;
55 | if module.starts_with("page") {
56 | msg_color = "";
57 | };
58 |
59 | cb.finish(format_args!(
60 | "{BOLD}{UNDERL}[ {time:010} | {lvl:5} | \
61 | {target}{prep}{module} ]{CLEAR}\n{msg_color}{msg}{CLEAR}\n",
62 | ));
63 | });
64 |
65 | let log_lvl_filter = std::str::FromStr::from_str(
66 | std::env::var("PAGE_LOG")
67 | .as_deref()
68 | .unwrap_or("warn")
69 | ).expect("Cannot parse $PAGE_LOG value");
70 |
71 | dispatch
72 | .level(log_lvl_filter)
73 | .chain(std::io::stderr())
74 | // .chain(fern::log_file("page.log").unwrap())
75 | // .filter(|f| f.target() != "nvim_rs::neovim")
76 | .apply()
77 | .expect("Cannot initialize logger");
78 | }
79 |
80 |
81 | // If neovim dies unexpectedly it messes the terminal
82 | // so terminal state must be cleaned
83 | pub fn init_panic_hook() {
84 | let default_panic_hook = std::panic::take_hook();
85 |
86 | std::panic::set_hook(Box::new(move |panic_info| {
87 | let try_spawn_reset = std::process::Command::new("reset")
88 | .spawn()
89 | .and_then(|mut child| child.wait());
90 |
91 | match try_spawn_reset {
92 | Ok(exit_code) if exit_code.success() => {}
93 |
94 | Ok(err_exit_code) => {
95 | log::error!(
96 | target: "termreset",
97 | "`reset` exited with status: {err_exit_code}"
98 | );
99 | }
100 | Err(e) => {
101 | log::error!(target: "termreset", "`reset` failed: {e:?}");
102 | }
103 | }
104 |
105 | default_panic_hook(panic_info);
106 | }));
107 | }
108 |
109 |
110 | /// This struct contains all neovim-related data which is
111 | /// required by page after connection with neovim is established
112 | pub struct NeovimConnection>> {
113 | pub nvim_proc: Option>>,
114 | pub nvim_actions: Apis,
115 | pub initial_buf_number: i64,
116 | pub channel: u64,
117 | pub initial_win_and_buf: (Window, Buffer),
118 | pub rx: tokio::sync::mpsc::Receiver,
119 | handle: tokio::task::JoinHandle>>,
120 | }
121 |
122 | /// Connects to parent neovim session or spawns
123 | /// a new neovim process and connects to it through socket.
124 | /// Replacement for `nvim_rs::Session::new_child()`,
125 | /// since it uses --embed flag and steals page stdin
126 | pub async fn open>>(
127 | tmp_dir: &Path,
128 | page_id: u128,
129 | nvim_listen_addr: &Option,
130 | config_path: &Option,
131 | custom_nvim_args: &Option,
132 | print_protection: bool,
133 | ) -> NeovimConnection {
134 |
135 | let (tx, rx) = tokio::sync::mpsc::channel(16);
136 |
137 | let handler = PipeOrSocketHandler {
138 | page_id: page_id.to_string(),
139 | tx
140 | };
141 |
142 | let mut nvim_proc = None;
143 |
144 | let (nvim, handle) = match nvim_listen_addr.as_deref() {
145 | Some(nvim_listen_addr)
146 | if nvim_listen_addr.parse::()
147 | .is_ok() =>
148 | {
149 | let tcp = tokio::net::TcpStream::connect(nvim_listen_addr)
150 | .await
151 | .expect("Cannot connect to neovim at TCP/IP address");
152 |
153 | let (rx, tx) = tokio::io::split(tcp);
154 | let (rx, tx) = (IoRead::Tcp(rx.compat()), IoWrite::Tcp(tx.compat_write()));
155 | let (nvim, io) = Neovim::::new(rx, tx, handler);
156 | let io_handle = tokio::task::spawn(io);
157 |
158 | (nvim, io_handle)
159 | }
160 |
161 | Some(nvim_listen_addr) => {
162 | let ipc = parity_tokio_ipc::Endpoint::connect(nvim_listen_addr)
163 | .await
164 | .expect("Cannot connect to neovim at path");
165 |
166 | let (rx, tx) = tokio::io::split(ipc);
167 | let (rx, tx) = (IoRead::Ipc(rx.compat()), IoWrite::Ipc(tx.compat_write()));
168 | let (nvim, io) = Neovim::::new(rx, tx, handler);
169 | let io_handle = tokio::task::spawn(io);
170 |
171 | (nvim, io_handle)
172 | }
173 |
174 | None => {
175 | let (nvim, io_handle, child) = create_new_neovim_process_ipc(
176 | tmp_dir,
177 | page_id,
178 | config_path,
179 | custom_nvim_args,
180 | print_protection,
181 | handler
182 | )
183 | .await;
184 | nvim_proc = Some(child);
185 |
186 | (nvim, io_handle)
187 | }
188 | };
189 |
190 | let channel = nvim
191 | .get_api_info()
192 | .await
193 | .expect("No API info")
194 | .get(0)
195 | .expect("No channel")
196 | .as_u64()
197 | .expect("Channel not a number");
198 |
199 | let initial_win = nvim
200 | .get_current_win()
201 | .await
202 | .expect("Cannot get initial window");
203 |
204 | let initial_buf = nvim
205 | .get_current_buf()
206 | .await
207 | .expect("Cannot get initial buffer");
208 |
209 | let initial_buf_number = initial_buf
210 | .get_number()
211 | .await
212 | .expect("Cannot get initial buffer number");
213 |
214 | NeovimConnection {
215 | nvim_proc,
216 | nvim_actions: From::from(nvim),
217 | initial_buf_number,
218 | channel,
219 | initial_win_and_buf: (initial_win, initial_buf),
220 | rx,
221 | handle
222 | }
223 | }
224 |
225 |
226 | /// Waits until child neovim closes.
227 | /// If no child neovim process spawned then it's safe to just exit from page
228 | pub async fn close_and_exit>>(
229 | nvim_connection: &mut NeovimConnection
230 | ) -> ! {
231 | log::trace!(target: "exit", "close and exit");
232 |
233 | if let Some(ref mut process) = nvim_connection.nvim_proc {
234 | if !process.is_finished() {
235 | process
236 | .await
237 | .expect("Neovim process was spawned with error")
238 | .expect("Neovim process died unexpectedly");
239 | }
240 | }
241 |
242 | nvim_connection.handle
243 | .abort();
244 |
245 | log::logger()
246 | .flush();
247 |
248 | std::process::exit(0)
249 | }
250 |
251 |
252 | /// Creates a new session using UNIX socket.
253 | /// Also prints protection from shell redirection
254 | /// that could cause some harm (see --help[-W])
255 | async fn create_new_neovim_process_ipc(
256 | tmp_dir: &Path,
257 | page_id: u128,
258 | config: &Option,
259 | custom_args: &Option,
260 | print_protection: bool,
261 | handler: PipeOrSocketHandler
262 | ) -> (
263 | Neovim,
264 | tokio::task::JoinHandle>>,
265 | tokio::task::JoinHandle>
266 | ) {
267 | if print_protection {
268 | print_redirect_protection(tmp_dir);
269 | }
270 |
271 | let nvim_listen_addr = tmp_dir
272 | .join(&format!("socket-{page_id}"));
273 |
274 | let mut nvim_proc = tokio::task::spawn({
275 | let (config, custom_args, nvim_listen_addr) = (
276 | config.clone(),
277 | custom_args.clone(),
278 | nvim_listen_addr.clone()
279 | );
280 | async move {
281 | spawn_child_nvim_process(
282 | &config,
283 | &custom_args,
284 | &nvim_listen_addr
285 | )
286 | }
287 | });
288 |
289 | tokio::time::sleep(std::time::Duration::from_millis(128)).await;
290 |
291 | let mut i = 0;
292 | let e = loop {
293 |
294 | let connection = parity_tokio_ipc::Endpoint::connect(&nvim_listen_addr).await;
295 | match connection {
296 | Ok(ipc) => {
297 | log::trace!(target: "child neovim spawned", "attempts={i}");
298 |
299 | let (rx, tx) = tokio::io::split(ipc);
300 | let (rx, tx) = (IoRead::Ipc(rx.compat()), IoWrite::Ipc(tx.compat_write()));
301 | let (neovim, io) = Neovim::::new(rx, tx, handler);
302 | let io_handle = tokio::task::spawn(io);
303 |
304 | return (neovim, io_handle, nvim_proc)
305 | }
306 |
307 | Err(e) if matches!(e.kind(), std::io::ErrorKind::NotFound) => {
308 | if i == 256 {
309 | break e
310 | }
311 |
312 | use std::task::Poll::{Ready, Pending};
313 | let poll = futures::poll!(std::pin::Pin::new(&mut nvim_proc));
314 |
315 | match poll {
316 | Ready(Err(join_e)) => {
317 | log::error!(target: "child neovim didn't start", "{join_e}");
318 |
319 | break join_e.into()
320 | },
321 | Ready(Ok(child)) => {
322 | log::error!(target: "child neovim finished", "{child:?}");
323 |
324 | break e
325 | },
326 |
327 | Pending => {},
328 | }
329 |
330 | tokio::time::sleep(std::time::Duration::from_millis(16)).await;
331 |
332 | i += 1;
333 | }
334 |
335 | Err(e) => break e
336 | }
337 | };
338 |
339 | panic!("Cannot connect to neovim: attempts={i}, address={nvim_listen_addr:?}, {e:?}");
340 | }
341 |
342 |
343 | /// This is hack to prevent behavior (or bug) in some shells (see --help[-W])
344 | fn print_redirect_protection(tmp_dir: &Path) {
345 | let d = tmp_dir
346 | .join("DO-NOT-REDIRECT-OUTSIDE-OF-NVIM-TERM(--help[-W])");
347 |
348 | if let Err(e) = std::fs::create_dir_all(&d) {
349 | panic!("Cannot create protection directory '{}': {e:?}", d.display())
350 | }
351 |
352 | println!("{}", d.to_string_lossy());
353 | }
354 |
355 | /// Spawns child neovim process on top of page,
356 | /// which further will be connected to page with UNIX socket.
357 | /// In this way neovim UI is displayed properly on top of page,
358 | /// and page as well is able to handle its own input to redirect it
359 | /// unto proper target (which is impossible with methods provided by
360 | /// `neovim_lib`). Also custom neovim config will be picked
361 | /// if it exists on corresponding locations.
362 | fn spawn_child_nvim_process(
363 | config: &Option,
364 | custom_args: &Option,
365 | nvim_listen_addr: &Path
366 | ) -> Result {
367 |
368 | let nvim_args = {
369 | let mut a = String::new();
370 | a += "--cmd 'set shortmess+=I' ";
371 | a += "--listen ";
372 | a += &nvim_listen_addr.to_string_lossy();
373 |
374 | if let Some(config) = config
375 | .clone()
376 | .or_else(default_config_path)
377 | {
378 | a += " ";
379 | a += "-u ";
380 | a += &config;
381 | }
382 |
383 | if let Some(custom_args) = custom_args.as_ref() {
384 | a += " ";
385 | a += custom_args;
386 | }
387 |
388 | shell_words::split(&a)
389 | .expect("Cannot parse neovim arguments")
390 | };
391 |
392 | log::trace!(target: "new neovim process", "Args: {nvim_args:?}");
393 |
394 | let term = current_term();
395 |
396 | std::process::Command::new("nvim")
397 | .args(&nvim_args)
398 | .stdin(term)
399 | .spawn()
400 | .expect("Cannot spawn a child neovim process")
401 | .wait()
402 | }
403 |
404 |
405 | fn current_term() -> std::fs::File {
406 | #[cfg(windows)]
407 | let dev = "CON:";
408 | #[cfg(not(windows))]
409 | let dev = "/dev/tty";
410 |
411 | std::fs::OpenOptions::new()
412 | .read(true)
413 | .open(dev)
414 | .expect("Cannot open current terminal device")
415 | }
416 |
417 |
418 | /// Returns path to custom neovim config if
419 | /// it's present in a corresponding locations
420 | fn default_config_path() -> Option {
421 | use std::path::PathBuf;
422 |
423 | let page_home = std::env::var("XDG_CONFIG_HOME")
424 | .map(|xdg_config_home| {
425 | PathBuf::from(xdg_config_home)
426 | .join("page")
427 | });
428 |
429 | let page_home = page_home.or_else(|_| std::env::var("HOME")
430 | .map(|home| {
431 | PathBuf::from(home)
432 | .join(".config/page")
433 | }));
434 |
435 | log::trace!(target: "config", "directory is: {page_home:?}");
436 |
437 | let Ok(page_home) = page_home else {
438 | return None;
439 | };
440 |
441 | let init_lua = page_home
442 | .join("init.lua");
443 | if init_lua.exists() {
444 | let p = init_lua.to_string_lossy().to_string();
445 | log::trace!(target: "config", "use init.lua");
446 | return Some(p)
447 | }
448 |
449 | let init_vim = page_home
450 | .join("init.vim");
451 | if init_vim.exists() {
452 | let p = init_vim.to_string_lossy().to_string();
453 | log::trace!(target: "config", "use init.vim");
454 | return Some(p)
455 | }
456 |
457 | None
458 | }
459 |
460 |
461 | mod io_pipe_or_socket {
462 | use parity_tokio_ipc::Connection;
463 | use tokio::{
464 | io::{ReadHalf, WriteHalf},
465 | net::TcpStream
466 | };
467 | use tokio_util::compat::Compat;
468 | use std::pin::Pin;
469 |
470 | pub enum PipeOrSocketRead {
471 | Ipc(Compat>),
472 | Tcp(Compat>),
473 | }
474 |
475 | pub enum PipeOrSocketWrite {
476 | Ipc(Compat>),
477 | Tcp(Compat>),
478 | }
479 |
480 | macro_rules! delegate {
481 | ($self:ident => $method:ident($($args:expr),*)) => {
482 | match $self.get_mut() {
483 | Self::Ipc(rw) => Pin::new(rw).$method($($args),*),
484 | Self::Tcp(rw) => Pin::new(rw).$method($($args),*),
485 | }
486 | };
487 | }
488 |
489 | impl futures::AsyncRead for PipeOrSocketRead {
490 | fn poll_read(
491 | self: Pin<&mut Self>,
492 | cx: &mut std::task::Context<'_>,
493 | buf: &mut [u8]
494 | ) -> std::task::Poll> {
495 | delegate!(self => poll_read(cx, buf))
496 | }
497 | }
498 |
499 | impl futures::AsyncWrite for PipeOrSocketWrite {
500 | fn poll_write(
501 | self: Pin<&mut Self>,
502 | cx: &mut std::task::Context<'_>,
503 | buf: &[u8]
504 | ) -> std::task::Poll> {
505 | delegate!(self => poll_write(cx, buf))
506 | }
507 |
508 |
509 | fn poll_flush(
510 | self: Pin<&mut Self>,
511 | cx: &mut std::task::Context<'_>
512 | ) -> std::task::Poll> {
513 | delegate!(self => poll_flush(cx))
514 | }
515 |
516 |
517 | fn poll_close(
518 | self: Pin<&mut Self>,
519 | cx: &mut std::task::Context<'_>
520 | ) -> std::task::Poll> {
521 | delegate!(self => poll_close(cx))
522 | }
523 | }
524 | }
525 |
526 |
527 | mod io_handler {
528 | use super::{io_pipe_or_socket::PipeOrSocketWrite, Neovim, Value};
529 |
530 | /// Receives and collects notifications from neovim side over IPC or TCP/IP
531 | #[derive(Clone)]
532 | pub struct PipeOrSocketHandler {
533 | pub tx: tokio::sync::mpsc::Sender,
534 | pub page_id: String,
535 | }
536 |
537 | #[async_trait::async_trait]
538 | impl nvim_rs::Handler for PipeOrSocketHandler {
539 | type Writer = PipeOrSocketWrite;
540 |
541 | async fn handle_request(
542 | &self,
543 | request: String,
544 | args: Vec,
545 | _: Neovim
546 | ) -> Result {
547 | log::warn!(target: "unhandled", "{request}: {args:?}");
548 |
549 | Ok(Value::from(0))
550 | }
551 |
552 | async fn handle_notify(
553 | &self,
554 | notification: String,
555 | args: Vec,
556 | _: Neovim
557 | ) {
558 | log::trace!(target: "notification", "{}: {:?} ", notification, args);
559 |
560 | let page_id = args
561 | .get(0)
562 | .and_then(Value::as_str);
563 |
564 | let same_page_id = page_id
565 | .map_or(false, |page_id| page_id == self.page_id);
566 | if !same_page_id {
567 | log::warn!(target: "invalid page id", "{page_id:?}");
568 |
569 | return
570 | }
571 |
572 | let notification_from_neovim = match notification.as_str() {
573 | "page_fetch_lines" => {
574 | let count = args.get(1)
575 | .and_then(Value::as_u64);
576 |
577 | if let Some(lines_count) = count {
578 | NotificationFromNeovim::FetchLines(lines_count as usize)
579 | } else {
580 | NotificationFromNeovim::FetchPart
581 | }
582 | },
583 | "page_buffer_closed" => {
584 | NotificationFromNeovim::BufferClosed
585 | },
586 |
587 | unknown => {
588 | log::warn!(target: "unhandled notification", "{unknown}");
589 |
590 | return
591 | }
592 | };
593 |
594 | self.tx
595 | .send(notification_from_neovim)
596 | .await
597 | .expect("Cannot receive notification");
598 | }
599 | }
600 |
601 |
602 | /// This enum represents all notifications
603 | /// that could be sent from page's commands on neovim side
604 | #[derive(Debug)]
605 | pub enum NotificationFromNeovim {
606 | FetchPart,
607 | FetchLines(usize),
608 | BufferClosed,
609 | }
610 | }
611 |
--------------------------------------------------------------------------------
/src/pager/cli.rs:
--------------------------------------------------------------------------------
1 | use clap::{
2 | Parser,
3 | ArgGroup,
4 | ArgAction,
5 | ValueHint,
6 | };
7 |
8 |
9 | /// Pager for neovim inspired by neovim-remote
10 | #[derive(Parser, Debug)]
11 | #[clap(
12 | author,
13 | disable_help_subcommand = true,
14 | allow_negative_numbers = true,
15 | args_override_self = true,
16 | group = splits_arg_group(),
17 | group = back_arg_group(),
18 | group = follow_arg_group(),
19 | group = instance_use_arg_group(),
20 | )]
21 | pub struct Options {
22 | /// Set title for output buffer (to display it in statusline)
23 | #[clap(display_order=10, short='n', env="PAGE_BUFFER_NAME")]
24 | pub name: Option,
25 |
26 | /// TCP/IP socket address or path to named pipe listened
27 | /// by running host neovim process
28 | #[clap(display_order=100, short='a', env="NVIM")]
29 | pub address: Option,
30 |
31 | /// Arguments that will be passed to child neovim process
32 | /// spawned when is missing
33 | #[clap(display_order=101, short='A', env="NVIM_PAGE_ARGS")]
34 | pub arguments: Option,
35 |
36 | /// Config that will be used by child neovim process spawned
37 | /// when is missing [file: $XDG_CONFIG_HOME/page/init.vim]
38 | #[clap(display_order=102, short='c', value_hint=ValueHint::AnyPath)]
39 | pub config: Option,
40 |
41 | /// Run command on output buffer after it was created
42 | /// or connected as instance
43 | #[clap(display_order=106, short='E')]
44 | pub command_post: Option,
45 |
46 | /// Run lua expr on output buffer after it was created
47 | /// or connected as instance {n}
48 | /// ~ ~ ~
49 | #[clap(display_order=107, long="E")]
50 | pub lua_post: Option,
51 |
52 | /// Create output buffer with tag or use existed
53 | /// with replacing its content by text from page's stdin
54 | #[clap(display_order=200, short='i')]
55 | pub instance: Option,
56 |
57 | /// Create output buffer with tag or use existed
58 | /// with appending to its content text from page's stdin
59 | #[clap(display_order=201, short='I')]
60 | pub instance_append: Option,
61 |
62 | /// Close output buffer with tag if it exists
63 | /// [without other flags revokes implied by defalt -o or -p option] {n}
64 | /// ~ ~ ~
65 | #[clap(display_order=202, short='x')]
66 | pub instance_close: Option,
67 |
68 | /// Create and use output buffer (to redirect text from page's stdin)
69 | /// [implied by default unless -x and/or provided without
70 | /// other flags]
71 | #[clap(display_order=0, short='o')]
72 | pub output_open: bool,
73 |
74 | /// Print path of pty device associated with output buffer (to redirect
75 | /// text from commands respecting output buffer size and preserving colors)
76 | /// [implied if page isn't piped unless -x and/or provided without other flags]
77 | #[clap(display_order=2, short='p')]
78 | pub pty_path_print: bool,
79 |
80 | /// Cursor follows content of output buffer as it appears
81 | /// instead of keeping top position (like `tail -f`)
82 | #[clap(display_order=5, short='f')]
83 | pub follow: bool,
84 |
85 | /// Cursor follows content of output and buffers
86 | /// as it appears instead of keeping top position
87 | #[clap(display_order=6, short='F')]
88 | pub follow_all: bool,
89 |
90 | /// Return back to current buffer
91 | #[clap(display_order=8, short='b')]
92 | pub back: bool,
93 |
94 | /// Return back to current buffer and enter into INSERT/TERMINAL mode
95 | #[clap(display_order=9, short='B')]
96 | pub back_restore: bool,
97 |
98 | /// Enable PageConnect PageDisconnect autocommands
99 | #[clap(display_order=103, short='C')]
100 | pub command_auto: bool,
101 |
102 | /// Flush redirection protection that prevents from producing junk
103 | /// and possible overwriting of existed files by invoking commands like
104 | /// `ls > $(NVIM= page -E q)` where the RHS of > operator
105 | /// evaluates not into /path/to/pty as expected but into a bunch
106 | /// of whitespace-separated strings/escape sequences from neovim UI;
107 | /// bad things happens when some shells interpret this as many valid
108 | /// targets for text redirection. The protection is only printing of a path
109 | /// to the existed dummy directory always first before printing
110 | /// of a neovim UI might occur; this makes the first target for text
111 | /// redirection from page's output invalid and disrupts the
112 | /// whole redirection early before other harmful writes might occur.
113 | /// [env: PAGE_REDIRECTION_PROTECT; (0 to disable)] {n}
114 | /// ~ ~ ~
115 | #[clap(display_order=800, short='W')]
116 | pub page_no_protect: bool,
117 |
118 | /// Pagerize output when it exceeds lines
119 | /// (to view `journalctl`) [default: disabled; empty: 90_000] {n}
120 | /// ~ ~ ~
121 | #[clap(display_order=12, short='z')]
122 | pub pagerize: Option