Keyboard driven pager of data.tables

Open
#2,893 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Feature
Clarity
Needs clarification
Activity status
Stale
Tech stack
r
Domain
cli

Research direction

Start with the pager function and its num_lines and num_cols helpers in the issue body. Assess how terminal width is detected and how horizontal movement could coexist with the existing j/k and g/G commands. Done would mean a defined, tested pager behavior for wide data.tables, including terminal resizing and horizontal scrolling.

Written by the indexing model from the issue text.

Description

print

I wrote this because R's built-in page is SLOW for large data.tables (also, apparently, page doesn't do what I thought it did). Combine that with the fact that I'm not a big user of the various R gui's (e.g. RStudio), but I do, occasionally, want to see my actual data, and I decided to create a little pager for data.tables. I've been using this hack, which is a less-like interface for data.tables for a while now. Unfortunately, it only scrolls up and down, not left and right, which works for me as my data is usually long and narrow. But it's definitely a shortcoming.

To get horizontal scrolling working, what should probably be done is only print the columns that fit in the width of the terminal with some indicator that there are more columns to the left/right, with maybe an option to "freeze" the key columns so they don't scroll off the page when moving right (though that assumes the key columns are already organized to be to the left of the data.table). This would prevent the extra columns from being printed below the others, which otherwise really screws with the utility of the pager.

In any case, I don't think this is clean enough to warrant a pull request (I suspect the way I'm getting the number of rows/columns for the terminal is linux specific), and I have no idea how to make test cases for it. But it's been sitting in my collection of scripts for a while and thought I should share it. If someone has some ideas on how to get the width of that data.table in at least the current range and horizontal scrolling working, that might make it more worthy of inclusion.

num_lines = function(default=24) {
    x = Sys.getenv("LINES")
    if (grepl('^[0-9]+$', x)) {
        return(as.integer(x))
    } else {
        x = system2('tput', 'lines', stdout=T)
        if (grepl('^[0-9]+$', x)) {
            return(as.integer(x))
        } else {
            return(default)
        }
    }
}

num_cols = function(default=80) {
    x = Sys.getenv("COLUMNS")
    if (grepl('^[0-9]+$', x)) {
        return(as.integer(x))
    } else {
        x = system2('tput', 'cols', stdout=T)
        if (grepl('^[0-9]+$', x)) {
            return(as.integer(x))
        } else {
            return(default)
        }
    }
}

#' A pager built for data.table
#'
#' There are 4 basic commands: j, k, G, g.
#' k/j: move up and down a page at a time
#' G/g: move to the bottom/top of the object
#' q: quit
#'
#' k/j can be modified with a number. by default, the pager will go down one
#' page. If you provide a number along with j/k it will move that number of
#' rows down. So "1j" would move just 1 row down instead of a full page.
#' "500j" would move down 500 rows. The counting starts from the top row.
#' Similary for up using 'k'. This will not wrap around/go beyond the beginning
#' or end of the object.
#' 
#' A simple enter implies the previous command, or just 'j' if no command has
#' yet been entered
pager = function(dt) {
    cur_width = options()$width
    options(width=num_cols())
    bufferlines = 3 # take into account header row and such
    termheight = num_lines() - bufferlines
    ndt = nrow(dt)
    if (ndt < termheight) {
        print(dt)
        return(NULL)
    }
    state = 1
    command = 'j'
    num = termheight
    while (TRUE) {
        # use data.frame so that the row names with the actual row numbers are printed
        dt2 = as.data.frame(dt[state:(state+termheight),])
        rownames(dt2) = state:(state+termheight)
        print(dt2)
        if (state + termheight >= ndt) {
            r = readline("End of Object. ")
        } else {
            r = readline(': ')
        }
        if (r == '') { # repeat previous command
            r = sprintf('%s%s', num, command)
        }
        termheight = num_lines() - bufferlines # in case terminal resized
        # set state for next print
        if (tolower(r) == 'q') {
            break
        } else if (r == 'g') {
            state = 1
            command = 'g'
            num = ''
        } else if (r == 'G') {
            state = ndt - termheight
            command = 'G'
            num = ''
        } else if (grepl('j', r, ignore.case=T)) {
            command = 'j'
            num = gsub('j', '', r, ignore.case=T)
            if (num == '') {
                num = termheight + 1
            } else {
                num = as.integer(num)
            }
            if (num + state + termheight >= ndt) {
                state = ndt - termheight
            } else {
                state = state + num
            }
        } else if (grepl('k', r, ignore.case=T)) {
            command = 'k'
            num = gsub('k', '', r, ignore.case=T)
            if (num == '') {
                num = termheight + 1
            } else {
                num = as.integer(num)
            }
            if (state - num < 1) {
                state = 1
            } else {
                state = state - num
            }
        } else {
            cat('illegal option.\n')
            break
        }
    }
    options(width=cur_width)
}
Dominant language
R
Stars
3.9k
Forks
1.1k
Avg merge
14h 4m
Merged PRs (30d)
4

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from Rdatatable/data.table

All issues in Rdatatable/data.table

Similar issues

More R issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.