Custom autocomplete functions in R possible? - r

I am looking to create a custom function in R that will allow the user to call the function and then it will produce an auto complete pipeline ready for them to edit, this way they can jump into quickly customizing the standard pipeline instead of copy pasting from an old script or retyping. How can I go about setting up this sort of autocomplete:
#pseudo code what I type---
seq.Date(1,2,by = "days") %>%
pblapply(function(x){
read.fst(list.files(as.character(x), as.data.table = T) %>%
group_by(x) %>%
count()
}) %>% rbindlist()
#how can I write a function so that when I call that function, it ouputs an autocomplete
#of the above so that I can go ahead and start just customizing the code? Something like this
my_autocomplete_function = function(x) {
print(
"
seq.Date(as.Date(Sys.Date()),as.Date(Sys.Date()+1),by = 'days') %>%
pbapply::pblapply(function(x){
fst::read.fst(list.files(as.character(x), as.data.table = T)) %>%
#begin filtering and grouping by below custom
group_by()
}) %>% rbindlist()
")
}
#I can just print the function and copy paste the text from the output in my console to my script
my_autocomplete_function()
#but I rather it just autocomplete and appear in the script if possible?

Putting text into the command line will probably be a function of the interface you are using to run R - is it plain R, Rstudio, etc?
One possibility might be to use the clipr package and put the code into the clipboard, then prompt the user to hit their "paste" button to get it on the command line. For example this function which creates a little code string:
> writecode = function(x){
code = paste0("print(",x,")")
clipr::write_clip(code)
message("Code ready to paste")}
Use it like this:
> writecode("foo")
Code ready to paste
Then when I hit Ctrl-V to paste, I see this:
> print(foo)
I can then edit that line. Any string will do:
> writecode("bar")
Code ready to paste
[ctrl-V]
> print(bar)
Its one extra key for your user to press, but having a chunk of code appear on the command line with no prompting might be quite surprising for a user.

I spend my day reading the source code for utils auto completion. The linebuffer only contains the code for ... one line, so that can't do fully what your looking for here, but it is quite customizable. Maybe the rstudio source code for autocompletion has more answers. Here is an example to write and activate your own custom auto completer. It is not well suited for packages as any new pacakge could overwrite the settings.
Below a
#load this function into custom.completer setting to activate
rc.options("custom.completer" = function(x) {
#perhaps debug it, it will kill your rsession sometimes
#browser()
###default completion###
##activating custom deactivates anything else
#however you can run utils auto completer also like this
#rstudio auto completation is not entirely the same as utils
f = rc.getOption("custom.completer")
rc.options("custom.completer" = NULL)
#function running base auto complete.
#It will dump suggestion into mutable .CompletionEnv$comps
utils:::.completeToken() #inspect this function to learn more about completion
rc.options("custom.completer" = f)
###your custom part###
##pull this environment holding all input/output needed
.CompletionEnv = utils:::.CompletionEnv
#perhaps read the 'token'. Also 'linebuffer', 'start' & 'end' are also useful
token = .CompletionEnv$token
#generate a new completion or multiple...
your_comps = paste0(token,c("$with_tomato_sauce","$with_apple_sauce"))
#append your suggestions to the vanilla suggestions/completions
.CompletionEnv$comps = c(your_comps,.CompletionEnv$comps)
print(.CompletionEnv$comps)
#no return used
NULL
})
xxx<tab>
NB! currently rstudio IDE will backtick quote any suggestion which is not generated by them. I want to raise an issue on that someday.
Bonus info: A .DollarNames.mys3class-method can be very useful and works with both rstudio and utils out of the box e.g.
#' #export
.DollarNames.mys3class = function(x, pattern = "") {
#x is the .CompletionEnv
c("apple","tomato")
}

Related

Printing check mark is triggering warning when running R cmd checks

I‘m writing a simple function that reads multiple files from the user, then it prints into the terminal (print) file name and yes or no next to it, if it was valid/invalid. I would like to use x/✓symbols.
The problem however, since the check symbol is not in ascii, R cmd is throwing a warning. I tried several approaches (charToRaw, intToUtf8, symbol(“\326”) yet non is working with simple print in terminal.
As an example:
Df <- data.frame(file = myfiles, status = “x”)
Df$status[1]= “✓”
print (Df)
Any idea? Thanks
The cli package offers a way to print these with cli_alert_success and cli_alert_danger. Presumably, you have some more complicated check as to if the file was valid. Store that as a boolean instead of the explicit character.
Df <- data.frame(file = myfiles, isValid = myValidityCheckFx(myfiles))
purrr::walk2(
Df$file, Df$isValid,
~if(.y) cli::cli_alert_success(.x) else cli::cli_alert_danger(.x)
)

Is there an Rstudio shortcut key for ``?

I use it a lot on Rmarkdow for referring to code, so I created an Addin, but wanted to know if there's a shortcut. If it isn't the case, how could I do to configure the addin so when calling it, the position of the caret or cursor stands between both symbols, exactly as it happens when using "" or () in RStudio.
insertInAddin <- function() { rstudioapi::insertText("``") } is the code I used for the Add-in
I'm looking help understanding how to setup
rstudioapi::setCursorPosition()
and document_position() inside the location argument of insertText.
You can use the shrtcts package for this task. It lets you assign Keyboard Shortcuts to arbitrary R Code.
Create a new R Markdown Snippet which can also be used on its own by typing e.g. in (for inline code font) and press Shift+Tab:
snippet in
`${1}`$0
Use the command shrtcts::edit_shortcuts() in the RStudio Console to open the file where you define your custom shortcuts.
Paste the following code inside that file (set your preferred keybinding in the #shortcut line). Note that the inserted text in the second line of the function must match the new Snippet from Step 1:
#' Code Font
#'
#' #description
#' If Editor has selection, transform current selection to code font.
#' If Editor has no selection, write between backticks.
#' #interactive
#' #shortcut Cmd+E
function() {
if (rstudioapi::selectionGet()$value == "") {
rstudioapi::insertText("in")
rstudioapi::executeCommand("insertSnippet") |>
capture.output() |>
invisible()
} else {
# Gets The Active Document
ctx <- rstudioapi::getActiveDocumentContext()
# Checks that a document is active
if (!is.null(ctx)) {
# Extracts selection as a string
selected_text <- ctx$selection[[1]]$text
# modify string
selected_text <- stringr::str_glue("`{selected_text}`")
# replaces selection with string
rstudioapi::modifyRange(ctx$selection[[1]]$range, selected_text)
}
}
}
This solution uses the native pipe |> and thus requires R 4.1.
You can of course just define separate variables in each line or use the magrittr pipe if you use earlier versions of R.
Further the stringr::str_glue() command can be easily replaced with a base R solution to avoid dependencies.
Use the command shrtcts::add_rstudio_shortcuts(set_keyboard_shortcuts = TRUE) in the RStudio Console to add the new shortcut with its assigned keybinding. Then restart RStudio.
Now you can use e.g. cmd+e without selection to place your cursor within backticks and press Tab to continue writing after the second backtick.
Or you can select text and then press cmd+e to surround the selected text by backticks.
The solution above can be easily generalized for bold and italic text in RMarkdown documents or to write within Dollar signs to add Inline Latex Math.
You can check RMarkdown Code Chunks and RStudio Shortcuts.
The shortcut you're looking for is: Ctrl+Alt+I.
Or you can create your own Code Snippet. To do this, follow the instructions on this page: Code Snippets in the RStudio IDE.

How to start at beginning of output when using str()

Is there any way when using the str() function in R to start at the beginning of the output provided rather than the end after the function is run?
Basically I'd like a faster way to get to the beginning of the output rather than scrolling manual back up through the output. This would be especially useful when looking at the structure of larger objects like spatial data.
A variation of the answer linked to by Dason (https://stackoverflow.com/a/3837885/1017276) would be to redirect the output to a browser.
view_str <- function(x)
{
tmp <- tempfile(fileext = ".html")
x <- capture.output(str(x))
write(paste0(x, collapse = "<br/>"),
file = tmp)
viewer <- getOption("viewer")
if (!is.null(viewer)) # If in RStudio, use RStudio's browser
{
viewer(tmp)
}
else{ # Otherwise use the system's default browser
utils::browseURL(tmp)
}
}
view_str(mtcars)

Is there a way to page break the output in console [duplicate]

Is there an equivalent to the unix less command that can be used within the R console?
There is also page() which displays a representation of an object in a pager, like less.
dat <- data.frame(matrix(rnorm(1000), ncol = 10))
page(dat, method = "print")
Not really. There are the commands
head() and tail() for showing the beginning and end of objects
print() for explicitly showing an object, and just its name followed by return does the same
summary() for concise summary that depends on the object
str() for its structure
and more. An equivalent for less would be a little orthogonal to the language and system. Where the Unix shell offers you less to view the content of a file (which is presumed to be ascii-encoded), it cannot know about all types.
R is different in that it knows about the object types which is why summary() -- as well as the whole modeling framework -- are more appropriate.
Follow-up edit: Another possibility is provided by edit() as well as edit.data.frame().
I save the print output to a file and then read it using an editor or less.
Type the following in R
sink("Routput.txt")
print(varname)
sink()
Then in a shell:
less Routput.txt
If the file is already on disk, then you can use file.show
You might like my little toy here:
short <- function(x=seq(1,20),numel=4,skipel=0,ynam=deparse(substitute(x))) {
ynam<-as.character(ynam)
#clean up spaces
ynam<-gsub(" ","",ynam)
#unlist goes by columns, so transpose to get what's expected
if(is.list(x)) x<-unlist(t(x))
if(2*numel >= length(x)) {
print(x)
}
else {
frist=1+skipel
last=numel+skipel
cat(paste(ynam,'[',frist,'] thru ',ynam,'[',last,']\n',sep=""))
print(x[frist:last])
cat(' ... \n')
cat(paste(ynam,'[',length(x)-numel-skipel+1,'] thru ', ynam, '[', length(x)-skipel,']\n',sep=""))
print(x[(length(x)-numel-skipel+1):(length(x)-skipel)])
}
}
blahblah copyright by me, not Disney blahblah free for use, reuse, editing, sprinkling on your Wheaties, etc.

R sometimes does not save my history

I have a program in R. Sometimes when I save history, they do not write into my history file. I lost some histories a few times and this really drive me crazy.
Any recommendation on how to avoid this?
First check your working directory (getwd()). savehistory() saves the history in the current working directory. And to be honest, you better specify the filename, as the default is .History. Say :
savehistory('C:/MyWorkingDir/MySession.RHistory')
which allows you to :
loadhistory('C:/MyWorkingDir/MySession.RHistory')
So the history is not lost, it's just in a place and under a name you weren't aware of. See also ?history.
To clarify : the history is no more than a text file containing all commands of that current session. So it's a nice log of what you've done, but I almost never use it. I construct my "analysis log" myself by using scripts, as hinted in another answer.
#Stedy has provided a workable solution to your immediate question. I would encourage you to learn how to use .R files and a proper text editor, or use an integrated development environment (see this SO page for suggestions). You can then source() in your .R file so that you can consistently replicate your analysis.
For even better replicability, invest the time into learning Sweave. You'll be glad you did.
Check the Rstudio_Desktop/history_database file - it stores every command for any working directory.
See here for more details How to save the whole sequence of commands from a specific day to a file?
Logging your console on a regular basis to **dated* files is handy. The package TeachingDemos has a great function for logging your console session, but it's written as a singleton, which is problematic for automatic logging, since you wouldn't be able to use that function to create teaching demo's if you use it for logging. I re-used that function using a bit of meta-programming to make a copy of that functionality that I include in the .First function in my local .Rprofile, as follows:
.Logger <- (function(){
# copy local versions of the txtStart,
locStart <- TeachingDemos::txtStart
locStop <- TeachingDemos::txtStop
locR2txt <- TeachingDemos:::R2txt
# creat a local environment and link it to each function
.e. <- new.env()
.e.$R2txt.vars <- new.env()
environment(locStart) <- .e.
environment(locStop) <- .e.
environment(locR2txt) <- .e.
# reference the local functions in the calls to `addTaskCallback`
# and `removeTaskCallback`
body(locStart)[[length(body(locStart))-1]] <-
substitute(addTaskCallback(locR2txt, name='locR2txt'))
body(locStop)[[2]] <-
substitute(removeTaskCallback('locR2txt'))
list(start=function(logDir){
op <- options()
locStart(file.path(logDir,format(Sys.time(), "%Y_%m_%d_%H_%M_%S.txt")),
results=FALSE)
options(op)
}, stop = function(){
op <- options()
locStop()
options(op)
})
})()
.First <- function(){
if( interactive() ){
# JUST FOR FUN
cat("\nWelcome",Sys.info()['login'],"at", date(), "\n")
if('fortunes' %in% utils::installed.packages()[,1] )
print(fortunes::fortune())
# CONSTANTS
TIME <- Sys.time()
logDir <- "~/temp/Rconsole.logfiles"
# CREATE THE TEMP DIRECORY IF IT DOES NOT ALREADY EXIST
dir.create(logDir, showWarnings = FALSE)
# DELETE FILES OLDER THAN A WEEK
for(fname in list.files(logDir))
if(difftime(TIME,
file.info(file.path(logDir,fname))$mtime,
units="days") > 7 )
file.remove(file.path(logDir,fname))
# sink() A COPY OF THE TERMINAL OUTPUT TO A DATED LOG FILE
if('TeachingDemos' %in% utils::installed.packages()[,1] )
.Logger$start(logDir)
else
cat('install package `TeachingDemos` to enable console logging')
}
}
.Last <- function(){
.Logger$stop()
}
This causes a copy of the terminal contents to be copied to a dated log file. The nice thing about having dated files is that if you use multiple R sessions the log files won't conflict, unless you start multiple interactive sessions in the same second).

Resources