Skip to content

I60R/page

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Page

Rust Build Lines Of Code

Allows you to redirect text into neovim. You can set it as $PAGER to view logs, diffs, various command outputs.

ANSI escape sequences will be interpreted by :term buffer, which makes page noticeably faster than vimpager and nvimpager. And text will be displayed instantly as it arrives - no need to wait until EOF.

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. That's by utilizing $NVIM variable like neovim-remote does.

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.

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.

Usage

  • under regular terminal

usage under regular terminal

  • under neovim's terminal

usage under neovim's terminal


CLI

expand page --help
Usage: page [OPTIONS] [FILE]...

Arguments:
  [FILE]...  Open provided file in separate buffer [without other flags revokes implied by default -o or -p
             option]

Options:
  -o                         Create and use output buffer (to redirect text from page's stdin) [implied by
                             default unless -x and/or <FILE> provided without other flags]
  -O [<NOOPEN_LINES>]        Prefetch <NOOPEN_LINES> from page's stdin: if all input fits then print it to
                             stdout and exit without neovim usage (to emulate `less --quit-if-one-screen`)
                             [empty: term height - 3 (space for prompt); negative: term height -
                             <NOOPEN_LINES>; 0: disabled and default; ignored with -o, -p, -x and when page
                             isn't piped]
  -p                         Print path of pty device associated with output buffer (to redirect text from
                             commands respecting output buffer size and preserving colors) [implied if page
                             isn't piped unless -x and/or <FILE> provided without other flags]
  -P                         Set $PWD as working directory at output buffer (to navigate paths with `gf`)
  -q [<QUERY_LINES>]         Read no more than <QUERY_LINES> from page's stdin: next lines should be
                             fetched by invoking :Page <QUERY> command or 'r'/'R' keypress on neovim side
                             [empty: term height - 2 (space for tab and buffer lines); negative: term
                             height - <QUERY_LINES>; 0: disabled and default; <QUERY> is optional and
                             defaults to <QUERY_LINES>; doesn't take effect on <FILE> buffers]
  -f                         Cursor follows content of output buffer as it appears instead of keeping top
                             position (like `tail -f`)
  -F                         Cursor follows content of output and <FILE> buffers as it appears instead of
                             keeping top position
  -t <FILETYPE>              Set filetype on output buffer (to enable syntax highlighting) [pager: default;
                             not works with text echoed by -O]
  -b                         Return back to current buffer
  -B                         Return back to current buffer and enter into INSERT/TERMINAL mode
  -n <NAME>                  Set title for output buffer (to display it in statusline) [env:
                             PAGE_BUFFER_NAME=]
  -w                         Do not remap i, I, a, A, u, d, x, q (and r, R with -q) keys [wouldn't unmap on
                             connected instance output buffer]
  -z [<PAGERIZE>]            Pagerize output when it exceeds <PAGERIZE> lines (to view `journalctl`)
                             [default: disabled; empty: 100_000]
                              ~ ~ ~

                              ~ ~ ~
  -a <ADDRESS>               TCP/IP socked address or path to named pipe listened by running host neovim
                             process [env: NVIM=/run/user/1000/nvim.9389.0]
  -A <ARGUMENTS>             Arguments that will be passed to child neovim process spawned when <ADDRESS>
                             is missing [env: NVIM_PAGE_ARGS=]
  -c <CONFIG>                Config that will be used by child neovim process spawned when <ADDRESS> is
                             missing [file:$XDG_CONFIG_HOME/page/init.vim]
  -C                         Enable PageConnect PageDisconnect autocommands
  -e <COMMAND>               Run command  on output buffer after it was created
      --e <LUA>              Run lua expr on output buffer after it was created
  -E <COMMAND_POST>          Run command  on output buffer after it was created or connected as instance
      --E <LUA_POST>         Run lua expr on output buffer after it was created or connected as instance
                              ~ ~ ~
  -i <INSTANCE>              Create output buffer with <INSTANCE> tag or use existed with replacing its
                             content by text from page's stdin
  -I <INSTANCE_APPEND>       Create output buffer with <INSTANCE_APPEND> tag or use existed with appending
                             to its content text from page's stdin
  -x <INSTANCE_CLOSE>        Close  output buffer with <INSTANCE_CLOSE> tag if it exists [without other
                             flags revokes implied by defalt -o or -p option]
                              ~ ~ ~
  -W                         Flush redirection protection that prevents from producing junk and possible
                             overwriting of existed files by invoking commands like `ls > $(NVIM= page -E
                             q)` where the RHS of > operator evaluates not into /path/to/pty as expected
                             but into a bunch of whitespace-separated strings/escape sequences from neovim
                             UI; bad things happens when some shells interpret this as many valid targets
                             for text redirection. The protection is only printing of a path to the existed
                             dummy directory always first before printing of a neovim UI might occur; this
                             makes the first target for text redirection from page's output invalid and
                             disrupts the whole redirection early before other harmful writes might occur.
                             [env:PAGE_REDIRECTION_PROTECT; (0 to disable)]
                              ~ ~ ~
  -l...                      Split left  with ratio: window_width  * 3 / (<l-PROVIDED> + 1)
  -r...                      Split right with ratio: window_width  * 3 / (<r-PROVIDED> + 1)
  -u...                      Split above with ratio: window_height * 3 / (<u-PROVIDED> + 1)
  -d...                      Split below with ratio: window_height * 3 / (<d-PROVIDED> + 1)
  -L <SPLIT_LEFT_COLS>       Split left  and resize to <SPLIT_LEFT_COLS>  columns
  -R <SPLIT_RIGHT_COLS>      Split right and resize to <SPLIT_RIGHT_COLS> columns
  -U <SPLIT_ABOVE_ROWS>      Split above and resize to <SPLIT_ABOVE_ROWS> rows
  -D <SPLIT_BELOW_ROWS>      Split below and resize to <SPLIT_BELOW_ROWS> rows
                              ^
  -+                         With any of -r -l -u -d -R -L -U -D open floating window instead of split [to
                             not overwrite data in the current terminal]
                              ~ ~ ~
  -h, --help                 Print help information
expand nv --help
Usage: nv [OPTIONS] [FILE]...

Arguments:
  [FILE]...  Open provided files as editable [if none provided nv opens last modified file in currend
             directory]

Options:
  -o                          Open non-text files including directories, binaries, images etc
  -O [<RECURSE_DEPTH>]        Ignoring [FILE] open all text files in the current directory and recursively
                              open all text files in its subdirectories [0: disabled and default; empty:
                              defaults to 1 and implied if no <RECURSE_DEPTH> provided; <RECURSE_DEPTH>:
                              also opens in subdirectories at this level of depth]
  -v                          Open in `page` instead (just postfix shortcut)
                               ~ ~ ~
  -f                          Open each [FILE] at last line
  -p <PATTERN>                Open and search for a specified <PATTERN>
  -P <PATTERN_BACKWARDS>      Open and search backwars for a specified <PATTERN_BACKWARDS>
  -b                          Return back to current buffer
  -B                          Return back to current buffer and enter into INSERT/TERMINAL mode
  -k                          Keep `nv` process until buffer is closed (for editing git commit message)
  -K                          Keep `nv` process until first write occur, then close buffer and neovim if
                              it was spawned by `nv`
                               ~ ~ ~
  -a <ADDRESS>                TCP/IP socket address or path to named pipe listened by running host neovim
                              process [env: NVIM=/run/user/1000/nvim.604327.0]
  -A <ARGUMENTS>              Arguments that will be passed to child neovim process spawned when <ADDRESS>
                              is missing [env: NVIM_PAGE_PICKER_ARGS=]
  -c <CONFIG>                 Config that will be used by child neovim process spawned when <ADDRESS> is
                              missing [file: $XDG_CONFIG_HOME/page/init.vim]
  -t <FILETYPE>               Override filetype on each [FILE] buffer (to enable custom syntax highlighting
                              [text: default]
                               ~ ~ ~
  -e <COMMAND>                Run command  on each [FILE] buffer after it was created
      --e <LUA>               Run lua expr on each [FILE] buffer after it was created
  -x <COMMAND_ONLY>           Just run command  with ignoring all other options
      --x <LUA_ONLY>          Just run lua expr with ignoring all other options
                               ~ ~ ~
  -l...                       Split left  with ratio: window_width  * 3 / (<l-PROVIDED> + 1)
  -r...                       Split right with ratio: window_width  * 3 / (<r-PROVIDED> + 1)
  -u...                       Split above with ratio: window_height * 3 / (<u-PROVIDED> + 1)
  -d...                       Split below with ratio: window_height * 3 / (<d-PROVIDED> + 1)
  -L <SPLIT_LEFT_COLS>        Split left  and resize to <SPLIT_LEFT_COLS>  columns
  -R <SPLIT_RIGHT_COLS>       Split right and resize to <SPLIT_RIGHT_COLS> columns
  -U <SPLIT_ABOVE_ROWS>       Split above and resize to <SPLIT_ABOVE_ROWS> rows
  -D <SPLIT_BELOW_ROWS>       Split below and resize to <SPLIT_BELOW_ROWS> rows
                               ^
  -+                          With any of -r -l -u -d -R -L -U -D open floating window instead of split
                              [to not overwrite data in the current terminal]
                               ~ ~ ~
  -h, --help                  Print help information

Note: page and nv may be unergonomic to type so I suggest users to create alias like p and v

nvim/init.lua customizations

-- Opacity of popup window spawned with -+ option
vim.g.page_popup_winblend = 25

nvim/init.lua customizations (pager only)

Statusline appearance:

-- String that will append to buffer name
vim.g.page_icon_pipe = '|' -- When piped
vim.g.page_icon_redirect = '>' -- When exposes pty device
vim.g.page_icon_instance = '$' -- When `-i, -I` flags provided

Autocommand hooks:

-- Will run once when output buffer is created
vim.api.create_autocmd('User', {
    pattern = 'PageOpen',
    callback = lua_function,
})

-- Will run once when file buffer is created
vim.api.create_autocmd('User', {
    pattern = 'PageOpenFile',
    callback = lua_function,
})

Only with -C option provided:

-- will run always when output buffer is created
-- and also when `page` connects to instance `-i, -I` buffers:
vim.api.create_autocmd('User', {
    pattern = 'PageConnect',
    callback = lua_function,
})

-- Will run when page process exits
vim.api.create_autocmd('User', {
    pattern = 'PageDisconnect',
    callback = lua_function,
})

Shell hacks

To use as $PAGER without scrollback overflow:

export PAGER="page -q 90000"

# Alternatively

export PAGER="page -z 90000" # will pagerize output

# And you can combine both

export PAGER="page -q 90000 -z 90000"

To configure:

export PAGER="page -WfC -q 90000 -z 90000" # some sensible flags
alias page="$PAGER"

# Usage
ls | page -q 100 # you can specify the same flag multiple times:
                 # last provided will override previous

To use as $MANPAGER:

export MANPAGER="page -t man"

# Alternatively, to pick a bit better `man` highlighting:

man () {
    PROGRAM="${@[-1]}"
    SECTION="${@[-2]}"
    page -W "man://$PROGRAM${SECTION:+($SECTION)}"
}

To set nv as popup git commit message editor:

# Will spawn popup editor and exit on first write
git config --global core.editor "nv -K -+-R 80 -B"

To cd into directory passed to nv

nv() {
    #stdin_is_term #one_argument   #it's_dir
    if [ -t 1 ] && [ 1 -eq $# ] && [ -d $1 ]; then
        cd $1
    else
        nv $*
    fi
}

compdef _nv nv # if you have completions installed

To automatically lcd into terminal's directory:

chpwd () {
    [ ! -z "$NVIM" ] && nv -x "lcd $PWD"
}

To circumvent neovim config picking:

page -c NONE

# Alternatively, to override neovim config create this file:

touch $XDG_CONFIG_HOME/page/init.lua # init.vim is also supported

To set output buffer name as first two words from invoked command (zsh only):

preexec () {
    if [ -z "$NVIM" ]; then
        export PAGE_BUFFER_NAME="page"
    else
        WORDS=(${1// *|*})
        export PAGE_BUFFER_NAME="${WORDS[@]:0:2}"
    fi
}

Buffer defaults (pager)

expand

These commands are run on each page buffer creation:

vim.b.page_alternate_bufnr = {$initial_buf_nr}
if vim.wo.scrolloff > 999 or vim.wo.scrolloff < 0 then
    vim.g.page_scrolloff_backup = 0
else
    vim.g.page_scrolloff_backup = vim.wo.scrolloff
end
vim.bo.scrollback, vim.wo.scrolloff, vim.wo.signcolumn, vim.wo.number =
    100000, 999, 'no', false
{$filetype}
{$edit}
vim.api.nvim_create_autocmd('BufEnter', {
    buffer = 0,
    callback = function() vim.wo.scrolloff = 999 end
})
vim.api.nvim_create_autocmd('BufLeave', {
    buffer = 0,
    callback = function() vim.wo.scrolloff = vim.g.page_scrolloff_backup end
})
{$notify_closed}
{$pre}
vim.cmd 'silent doautocmd User PageOpen | redraw'
{$lua_provided_by_user}
{$cmd_provided_by_user}
{$after}

Where:

--{$initial_buf_nr}
-- Is always set on all buffers created by page

'number of parent :term buffer or -1 when page isn't spawned from :term'
--{$filetype}
-- Is set only on output buffers.
-- On files buffers filetypes are detected automatically.

vim.bo.filetype='value of -t argument or "pager"'
--{$edit}
-- Is appended when no -w option provided

vim.bo.modifiable = false
_G.page_echo_notification = function(message)
    vim.defer_fn(function()
        local msg = "-- [PAGE] " .. message .. " --"
        vim.api.nvim_echo({{ msg, 'Comment' }, }, false, {})
        vim.cmd 'au CursorMoved <buffer> ++once echo'
    end, 64)
end
_G.page_bound = function(top, message, move)
    local row, col, search
    if top then
        row, col, search = 1, 1, { '\\S', 'c' }
    else
        row, col, search = 9999999999, 9999999999, { '\\S', 'bc' }
    end
    vim.api.nvim_call_function('cursor', { row, col })
    vim.api.nvim_call_function('search', search)
    if move ~= nil then move() end
    _G.page_echo_notification(message)
end
_G.page_scroll = function(top, message)
    vim.wo.scrolloff = 0
    local move
    if top then
        local key = vim.api.nvim_replace_termcodes('z<CR>M', true, false, true)
        move = function() vim.api.nvim_feedkeys(key, 'nx', true) end
    else
        move = function() vim.api.nvim_feedkeys('z-M', 'nx', false) end
    end
    _G.page_bound(top, message, move)
    vim.wo.scrolloff = 999
end
_G.page_close = function()
    local buf = vim.api.nvim_get_current_buf()
    if buf ~= vim.b.page_alternate_bufnr and
        vim.api.nvim_buf_is_loaded(vim.b.page_alternate_bufnr)
    then
        vim.api.nvim_set_current_buf(vim.b.page_alternate_bufnr)
    end
    vim.api.nvim_buf_delete(buf, { force = true })
    local exit = true
    for _, b in ipairs(vim.api.nvim_list_bufs()) do
        local bt = vim.api.nvim_buf_get_option(b, 'buftype')
        if bt == "" or bt == "acwrite" or bt == "terminal" or bt == "prompt" then
            local bm = vim.api.nvim_buf_get_option(b, 'modified')
            if bm then
                exit = false
                break
            end
            local bl = vim.api.nvim_buf_get_lines(b, 0, -1, false)
            if #bl ~= 0 and bl[1] ~= "" and #bl > 1 then
                exit = false
                break
            end
        end
    end
    if exit then
        vim.cmd "qa!"
    end
end
local function page_map(key, expr)
    vim.api.nvim_buf_set_keymap(0, '', key, expr, { nowait = true })
end
page_map('I', '<CMD>lua _G.page_scroll(true, "in the beginning of scroll")<CR>')
page_map('A', '<CMD>lua _G.page_scroll(false, "at the end of scroll")<CR>')
page_map('i', '<CMD>lua _G.page_bound(true, "in the beginning")<CR>')
page_map('a', '<CMD>lua _G.page_bound(false, "at the end")<CR>')
page_map('q', '<CMD>lua _G.page_close()<CR>')
page_map('u', '<C-u>')
page_map('d', '<C-d>')
page_map('x', 'G')
--{$notify_closed}
-- Is set only on output buffers

local closed = 'rpcnotify({channel}, "page_buffer_closed", "{page_id}")'
vim.api.nvim_create_autocmd('BufDelete', {
    buffer = 0,
    command = 'silent! call ' .. closed
})
--{$pre}
-- Is appended when -q provided

vim.b.page_query_size = {$query_lines_count}
local def_args = '{channel}, "page_fetch_lines", "{page_id}", '
local def = 'command! -nargs=? Page call rpcnotify(' .. def_args .. '<args>)'
vim.cmd(def)
vim.api.create_autocmd('BufEnter', {
    buffer = 0,
    command = def,
})

-- Also if -q provided and no -w provided

page_map('r', '<CMD>call rpcnotify(' .. def_args .. 'b:page_query_size * v:count1)<CR>')
page_map('R', '<CMD>call rpcnotify(' .. def_args .. '99999)<CR>')

-- If -P provided ({pwd} is $PWD value)

vim.b.page_lcd_backup = getcwd()
vim.cmd 'lcd {pwd}'
vim.api.nvim_create_autocmd('BufEnter', {
    buffer = 0,
    command = 'lcd {pwd}'
})
vim.api.nvim_create_autocmd('BufLeave', {
    buffer = 0,
    command = 'exe "lcd" . b:page_lcd_backup'
})
--{$lua_provided_by_user}
-- Is appended when --e provided

'value of --e flag'
--{$cmd_provided_by_user}
-- Is appended when -e provided

vim.cmd [====[{$command}]====]
--{$after}
-- Is appended only on file buffers

vim.api.nvim_exec_autocmds('User', {
    pattern = 'PageOpenFile',
})

Limitations (pager)

Installation

  • From binaries

    • Grab binary for your platform from releases (currently Linux and OSX are supported)
  • Arch Linux:

    • Package page-git is available on AUR
    • Or: git clone git@github.com:I60R/page.git && cd page && makepkg -ef && sudo pacman -U page-git*.pkg.tar.xz
  • Homebrew:

  • Manually:

    • Install rustup from your distribution package manager
    • Configure toolchain: rustup install stable && rustup default stable
    • git clone git@github.com:I60R/page.git && cd page && cargo install --path .