# File src/library/utils/R/completion.R # Part of the R package, https://www.R-project.org # # Copyright (C) 2006-2022 The R Core Team # Copyright (C) 2006-2022 Deepayan Sarkar # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # A copy of the GNU General Public License is available at # https://www.R-project.org/Licenses/ ### Note: By default, we try not to do things that might be slow due ### to network latency (think NFS). For example, retrieving a list of ### available packages is potentially slow, and is thus disabled ### initially. ### Status: I'm mostly happy with things. The only obvious ### improvement I can think of is figuring out when we are in ### continuation mode (R prompt == "+") and make use of previous lines ### in that case. I haven't found a way to do that. ### Note: sprintf seems faster than paste based on naive benchmarking: ## > system.time(for (i in 1L:100000L) sprintf("foo%sbar%d", letters, 1L:26L) ) ## user system total user.children system.children ## 4.796 0.088 4.887 0.000 0.000 ## > system.time(for (i in 1L:100000L) paste("foo", letters, "bar", 1L:26L) ) ## user system total user.children system.children ## 8.300 0.028 8.336 0.000 0.000 ### so will change all pastes to sprintf. However, we need to be ### careful because 0 length components in sprintf will cause errors. ## Should compare with paste0() and if you compare with a pure format string, it may change: if(FALSE) { ii <- seq_len(1e5) ; ilet <- seq_along(letters) system.time(for (i in ii) sprintf("%s%s%s%d", "foo", letters, "bar", ilet) )[1] # 1.101 system.time(for (i in ii) paste0( "foo", letters, "bar", ilet) )[1] # 0.936 ## But for the most frequent case, sprintf() is still faster: system.time(for (i in ii) sprintf("^%s", letters)) # 0.344 system.time(for (i in ii) paste0 ("^", letters)) # 0.444 } ## [July 2013] First attempt to support fuzzy matching, if ## rc.settings(fuzzy=TRUE), based on suggestion from Rasmus Baath. ## Initially, this replaces use of grep() to find matches by ## findMatches(), which behaves differently depending on the 'fuzzy' ## setting. This does not affect basic object name completion, which ## is done using apropos(). For that, we need to write a fuzzy ## version of apropos (which is not that difficult; just loop through ## everything in the search path). findExactMatches <- function(pattern, values) { grep(pattern, values, value = TRUE) } ### agrep() version ## ## findFuzzyMatches <- function(pattern, values) ## { ## ## Try exact matches first, and return them if found ## ans <- findExactMatches(pattern, values) ## if (length(ans) == 0) { ## fuzzies <- ## agrep(pattern, values, max.distance = 2, ## ignore.case = TRUE, fixed = FALSE, value = TRUE) ## ## Multiple inconsistent matches will lead to more deletion ## ## than reasonable. To avoid this, we find distances, and ## ## return the one with minimum distance. However, if minimum ## ## distance is not unique, this will still delete. ## ## E.g., a = list(.foobar = 1, foo.bar = 2) ; a$foob ## if (length(fuzzies) == 0) character(0) ## else { ## fdist <- adist(pattern, fuzzies, ignore.case=TRUE, partial = TRUE, fixed = FALSE) ## fmin <- which(fdist == min(fdist)) ## fuzzies[fmin] ## } ## } ## else ## ans ## } ### normalizing version (from Rasmus Baath) ## findFuzzyMatches <- function(pattern, values) { ## FIXME: option to allow experimentation, remove eventually if (!is.null(ffun <- rc.getOption("fuzzy.match.fun"))) { return (ffun(pattern, values)) } ## Try exact matches first, and return them if found exact.matches <- findExactMatches(pattern, values) if (length(exact.matches) == 0) { ## Removes "\\." and "_" in the pattern excluding the anchor ## (^) and the first character but does not removes "\\." and ## "_" if it is the last character. normalizedPattern <- gsub("(? 0] } ## Accessing the help system: should allow anything with an index entry. ## This just looks at packages on the search path. matchAvailableTopics <- function(prefix, text) { .readAliases <- function(path) { if(file.exists(f <- file.path(path, "help", "aliases.rds"))) names(readRDS(f)) else if(file.exists(f <- file.path(path, "help", "AnIndex"))) ## aliases.rds was introduced before 2.10.0, as can phase this out scan(f, what = list("", ""), sep = "\t", quote = "", na.strings = "", quiet = TRUE)[[1L]] else character() } if (length(text) != 1L || text == "") return (character()) ## Update list of help topics if necessary pkgpaths <- searchpaths()[startsWith(search(), "package:")] if (!identical(basename(pkgpaths), .CompletionEnv[["attached_packages"]])) { assign("attached_packages", basename(pkgpaths), envir = .CompletionEnv) assign("help_topics", unique(unlist(lapply(pkgpaths, .readAliases))), envir = .CompletionEnv) } aliases <- .CompletionEnv[["help_topics"]] ans <- findMatches(sprintf("^%s", makeRegexpSafe(text)), aliases) if (nzchar(prefix)) { ## FIXME: This is a little unsafe. We are not protecting ## prefix to make sure that we do not get any special ## characters (like ? or + or *). However, these are unlikely ## in practice. tmp <- grep(sprintf("-%s$", prefix), ans, value = TRUE) if (length(tmp)) substring(tmp, 1, nchar(tmp) - nchar(prefix) - 1L) else character(0) } else ans } ## this is for requests of the form ?suffix[TAB] or prefix?suffix[TAB] helpCompletions <- function(prefix = "", suffix) { ## Do not attempt to complete ?? (help.search) or ??? (invalid) if (prefix %in% c("?", "??")) return (character(0)) nc <- if (.CompletionEnv$settings[["help"]]) matchAvailableTopics(prefix, suffix) else normalCompletions(suffix, check.mode = FALSE) if (length(nc)) sprintf("%s?%s", prefix, nc) else character() } specialCompletions <- function(text, spl) { ## we'll only try to complete after the last special operator, and ## assume that everything before is meaningfully complete. A more ## sophisticated version of this function may decide to do things ## differently. ## Note that this will involve evaluations, which may have side ## effects. This (side-effects) would not happen normally (except ## of lazy loaded symbols, which most likely would have been ## evaluated shortly anyway), because explicit function calls ## (with parentheses) are not evaluated. In any case, these ## evaluations won't happen if settings$ops==FALSE ## spl (locations of matches) is guaranteed to be non-empty wm <- which.max(spl) op <- names(spl)[wm] opStart <- spl[wm] opEnd <- opStart + nchar(op) if (opStart < 1) return(character()) # shouldn't happen prefix <- substr(text, 1L, opStart - 1L) suffix <- substr(text, opEnd, 1000000L) if (op == "?") return(helpCompletions(prefix, suffix)) if (opStart <= 1) return(character()) # not meaningful ## ( breaks words, so prefix should not involve function calls, ## and thus, hopefully no side-effects. comps <- specialOpCompletionsHelper(op, suffix, prefix) if (length(comps) == 0L) comps <- "" sprintf("%s%s%s", prefix, op, comps) } ## completions on special keywords (subset of those in gram.c). Some ## issues with parentheses: e.g. mode(get("repeat")) is "function", so ## it is normally completed with a left-paren appended, but that is ## not normal usage. Putting it here means that both 'repeat' and ## 'repeat(' will be valid completions (as they should be) keywordCompletions <- function(text) { ## FIXME: Will not allow fuzzy completions, as this adds too much ## noise in normalCompletions. Should revisit later once we ## figure out a way to suppress fuzzy matching if there is at ## least one exact match. findExactMatches(sprintf("^%s", makeRegexpSafe(text)), c("NULL", "NA", "TRUE", "FALSE", "Inf", "NaN", "NA_integer_", "NA_real_", "NA_character_", "NA_complex_", "repeat ", "in ", "next ", "break ", "else ")) } ## 'package' environments in the search path. These will be completed ## with a :: (Use of this is function is replaced by ## loadedPackageCompletions below, which also completes packages ## loaded, but not necessarily attached). attachedPackageCompletions <- function(text, add = rc.getOption("package.suffix")) { ## FIXME: Will not allow fuzzy completions. See comment in keywordCompletions() above if (.CompletionEnv$settings[["ns"]]) { s <- grep("^package", search(), value = TRUE) comps <- findExactMatches(sprintf("^%s", makeRegexpSafe(text)), substr(s, 9L, 1000000L)) if (length(comps) && !is.null(add)) sprintf("%s%s", comps, add) else comps } else character() } loadedPackageCompletions <- function(text, add = rc.getOption("package.suffix")) { ## FIXME: Will not allow fuzzy completions. See comment in keywordCompletions() above if (.CompletionEnv$settings[["ns"]]) { s <- loadedNamespaces() comps <- findExactMatches(sprintf("^%s", makeRegexpSafe(text)), s) if (length(comps) && !is.null(add)) sprintf("%s%s", comps, add) else comps } else character() } ## this provides the most basic completion, looking for completions in ## the search path using apropos, plus keywords. Plus completion on ## attached/loaded packages if settings$ns == TRUE normalCompletions <- function(text, check.mode = TRUE, add.fun = rc.getOption("function.suffix")) { ## use apropos or equivalent if (text == "") character() ## too many otherwise else { comps <- if (.CompletionEnv$settings[["fuzzy"]]) fuzzyApropos(sprintf("^%s", makeRegexpSafe(text))) else apropos(sprintf("^%s", makeRegexpSafe(text)), ignore.case = FALSE, dot_internals = TRUE) if (.CompletionEnv$settings[["func"]] && check.mode && !is.null(add.fun)) { which.function <- sapply(comps, function(s) exists(s, mode = "function")) if (any(which.function)) comps[which.function] <- sprintf("%s%s", comps[which.function], add.fun) ##sprintf("\033[31m%s\033[0m%s", comps[which.function], add.fun) } c(comps, keywordCompletions(text), loadedPackageCompletions(text)) } } ## completion on function arguments. This involves the most work (as ## we need to look back in the line buffer to figure out which ## function we are inside, if any), and is also potentially intensive ## when many functions match the function that we are supposedly in ## (which will happen for common generic functions like print (we are ## very optimistic here, erring on the side of ## whatever-the-opposite-of-caution-is (our justification being that ## erring on the side of caution is practically useless and not erring ## at all is expensive to the point of being impossible (we really ## don't want to evaluate the dotplot() call in "print(dotplot(x), ## positi[TAB] )" )))) ## this defines potential function name boundaries breakRE <- "[^\\.\\w]" ## breakRE <- "[ \t\n \\\" '`><=-%;,&}\\\?\\\+\\\{\\\|\\\(\\\)\\\*]" ## for some special functions like library, data, etc, normal ## completion is rarely meaningful, especially for the first argument. ## Unfortunately, knowing whether the token being completed is the ## first arg of such a function involves more work than we would ## normally want to do. On the other hand, inFunction() below already ## does most of this work, so we will add a piece of code (mostly ## irrelevant to its primary purpose) to indicate this. The following ## two functions are just wrappers to access and modify this ## information. setIsFirstArg <- function(v) .CompletionEnv[["isFirstArg"]] <- v getIsFirstArg <- function() .CompletionEnv[["isFirstArg"]] inFunction <- function(line = .CompletionEnv[["linebuffer"]], cursor = .CompletionEnv[["start"]]) { ## are we inside a function? Yes if the number of ( encountered ## going backwards exceeds number of ). In that case, we would ## also like to know what function we are currently inside ## (ideally, also what arguments to it have already been supplied, ## but let's not dream that far ahead). parens <- sapply(c("(", ")"), function(s) gregexpr(s, substr(line, 1L, cursor), fixed = TRUE)[[1L]], simplify = FALSE) ## remove -1's parens <- lapply(parens, function(x) x[x > 0]) ## The naive algo is as follows: set counter = 0; go backwards ## from cursor, set counter-- when a ) is encountered, and ## counter++ when a ( is encountered. We are inside a function ## that starts at the first ( with counter > 0. temp <- data.frame(i = c(parens[["("]], parens[[")"]]), c = rep.int(c(1, -1), lengths(parens))) if (nrow(temp) == 0) return(character()) temp <- temp[order(-temp$i), , drop = FALSE] ## order backwards wp <- which(cumsum(temp$c) > 0) if (length(wp)) # inside a function { ## return guessed name of function, letting someone else ## decide what to do with that name index <- temp$i[wp[1L]] prefix <- substr(line, 1L, index - 1L) suffix <- substr(line, index + 1L, cursor + 1L) ## note in passing whether we are the first argument (no '=' ## and no ',' in suffix) if ((length(grep("=", suffix, fixed = TRUE)) == 0L) && (length(grep(",", suffix, fixed = TRUE)) == 0L)) setIsFirstArg(TRUE) if ((length(grep("=", suffix, fixed = TRUE))) && (length(grep(",", substr(suffix, tail.default(gregexpr("=", suffix, fixed = TRUE)[[1L]], 1L), 1000000L), fixed = TRUE)) == 0L)) { ## we are on the wrong side of a = to be an argument, so ## we don't care even if we are inside a function return(character()) } else ## guess function name { possible <- suppressWarnings(strsplit(prefix, breakRE, perl = TRUE))[[1L]] possible <- possible[nzchar(possible)] if (length(possible)) return(tail.default(possible, 1)) else return(character()) } } else # not inside function { return(character()) } } argNames <- function(fname, use.arg.db = .CompletionEnv$settings[["argdb"]]) { if (use.arg.db) args <- .FunArgEnv[[fname]] if (!is.null(args)) return(args) ## else args <- do.call(argsAnywhere, list(fname)) if (is.null(args)) character() else if (is.list(args)) unlist(lapply(args, function(f) names(formals(f)))) else names(formals(args)) } specialFunctionArgs <- function(fun, text) { ## certain special functions have special possible arguments. ## This is primarily applicable to library and require, for which ## rownames(installed.packages()). This is disabled by default, ## because the first call to installed.packages() can be time ## consuming, e.g. on a network file system. However, the results ## are cached, so subsequent calls are not that expensive. switch(EXPR = fun, library = , require = { if (.CompletionEnv$settings[["ipck"]]) { findMatches(sprintf("^%s", makeRegexpSafe(text)), rownames(installed.packages())) } else character() }, data = { if (.CompletionEnv$settings[["data"]]) { findMatches(sprintf("^%s", makeRegexpSafe(text)), data()$results[, "Item"]) } else character() }, ## otherwise, character()) } functionArgs <- function(fun, text, S3methods = .CompletionEnv$settings[["S3"]], S4methods = FALSE, keep.dots = .CompletionEnv$settings[["dots"]], add.args = rc.getOption("funarg.suffix")) { if (length(fun) < 1L || any(fun == "")) return(character()) specialFunArgs <- specialFunctionArgs(fun, text) if (S3methods && exists(fun, mode = "function")) fun <- c(fun, tryCatch(methods(fun), warning = function(w) {}, error = function(e) {})) if (S4methods) warning("cannot handle S4 methods yet") allArgs <- unique(unlist(lapply(fun, argNames))) ans <- findMatches(sprintf("^%s", makeRegexpSafe(text)), allArgs) ## Handle dots specially. If present, drop if keep.dots=FALSE ## obviously, but even otherwise move it to the end and don't add ## a suffix. whereDots <- ans == "..." if (any(whereDots)) ans <- ans[!whereDots] if (length(ans) && !is.null(add.args)) ans <- sprintf("%s%s", ans, add.args) if (keep.dots && any(whereDots)) ans <- c(ans, "...") c(specialFunArgs, ans) } ## Note: Inside the C code, we redefine ## rl_attempted_completion_function rather than ## rl_completion_entry_function, which means that if ## retrieveCompletions() returns a length-0 result, by default the ## fallback filename completion mechanism will be used. This is not ## quite the best way to go, as in certain (most) situations filename ## completion will definitely be inappropriate even if no valid R ## completions are found. We could return "" as the only completion, ## but that produces an irritating blank line on ## list-possible-completions (or whatever the correct name is). ## Instead (since we don't want to reinvent the wheel), we use the ## following scheme: If the character just preceding our token is " or ## ', we immediately go to file name completion. If not, we do our ## stuff, and disable file name completion (using ## .Call("RCSuppressFileCompletion")) even if we don't find any ## matches. ## Note that under this scheme, filename completion will fail ## (possibly in unexpected ways) if the partial name contains 'unusual ## characters', namely ones that have been set (see C code) to cause a ## word break because doing so is meaningful in R syntax (e.g. "+", ## "-" ("/" is exempt (and treated specially below) because of its ## ubiquitousness in UNIX file names (where this package is most ## likely to be used)) ## decide whether to fall back on filename completion. Yes if the ## number of quotes between the cursor and the beginning of the line ## is an odd number. ## FIXME: should include backtick (`)? May be useful, but needs more ## thought; e.g., should imply not-filename, but rather variable ## names. Must cooperate with the if (isInsideQuotes()) branch in ## .completeToken(). isInsideQuotes <- fileCompletionPreferred <- function() { (.CompletionEnv[["start"]] > 0 && { ## yes if the number of quote signs to the left is odd linebuffer <- .CompletionEnv[["linebuffer"]] lbss <- head.default(unlist(strsplit(linebuffer, "")), .CompletionEnv[["end"]]) ((sum(lbss == "'") %% 2 == 1) || (sum(lbss == '"') %% 2 == 1)) }) } ## File name completion, used if settings$quotes == TRUE. Front ends ## that can do filename completion themselves should probably not use ## this if they can do a better job. correctFilenameToken <- function() { ## Helper function ## If a file name contains spaces, the token will only have the ## part after the last space. This function tries to recover the ## complete initial part. ## Find part between last " or ' linebuffer <- .CompletionEnv[["linebuffer"]] lbss <- head.default(unlist(strsplit(linebuffer, "")), .CompletionEnv[["end"]]) whichDoubleQuote <- lbss == '"' whichSingleQuote <- lbss == "'" insideDoubleQuote <- (sum(whichDoubleQuote) %% 2 == 1) insideSingleQuote <- (sum(whichSingleQuote) %% 2 == 1) loc.start <- if (insideDoubleQuote && insideSingleQuote) { ## Should not happen, but if it does, should take whichever comes later max(which(whichDoubleQuote), which(whichSingleQuote)) } else if (insideDoubleQuote) max(which(whichDoubleQuote)) else if (insideSingleQuote) max(which(whichSingleQuote)) else ## should not happen, abort non-intrusively .CompletionEnv[["start"]] substring(linebuffer, loc.start + 1L, .CompletionEnv[["end"]]) } fileCompletions <- function(token) { ## uses Sys.glob (conveniently introduced in 2.5.0) ## token may not start just after the begin quote, e.g., if spaces ## are included. Get 'correct' partial file name by looking back ## to begin quote pfilename <- correctFilenameToken() ## This may come from an illegal string like "C:\Prog". Try to parse it: pfilename <- tryCatch(eval(str2expression(paste0('"', token, '"'))), error = identity) if (inherits(pfilename, "error")) return(character()) ## Sys.glob doesn't work without expansion. Is that intended? pfilename.expanded <- path.expand(pfilename) comps <- Sys.glob(sprintf("%s*", pfilename.expanded), dirmark = TRUE) ## If there is only one completion (and it's a directory), also ## include files inside in list of completions. This is not ## particularly useful, but without this, readline tends to add an ## end-quote (if sole completion) which is irritating if one is ## actually looking for something inside the directory. Note that ## we don't actually test to see if it's a directory, because if ## it is not, list.files() will simply return character(0). if (length(comps) == 1 && endsWith(comps, "/")) { filesInside <- list.files(comps, all.files = TRUE, full.names = FALSE, no.. = TRUE) if (length(filesInside)) comps <- c(comps, file.path(comps, filesInside)) } ## for things that only extend beyond the cursor, need to ## 'unexpand' path if (pfilename.expanded != pfilename) comps <- sub(path.expand("~"), "~", comps, fixed = TRUE) ## for tokens that were non-trivially corrected by adding prefix, ## need to delete extra part if (pfilename != token) comps <- substring(comps, nchar(pfilename) - nchar(token) + 1L, 1000L) ## In Win32, we often have backslashes in names. Also possible on ## Unix, though unlikely. Add escapes for those and for quotes. comps <- gsub("([\\\\'\"])", "\\\\\\1", comps) comps } ## .completeToken() is the primary interface, and does the actual ## completion when called from C code. .completeToken <- function(custom = TRUE) { ## Allow override by user-specified function custom.completer <- rc.getOption("custom.completer") if (custom && is.function(custom.completer)) return (custom.completer(.CompletionEnv)) text <- .CompletionEnv[["token"]] if (isInsideQuotes()) { ## If we're in here, that means we think the cursor is inside ## quotes. In most cases, this means that standard filename ## completion is more appropriate, but probably not if we're ## trying to access things of the form x["foo... or x$"foo... ## The following tries to figure this out, but it won't work ## in all cases (e.g. x[, "foo"]) ## We assume that whoever determines our token boundaries ## considers quote signs as a breaking symbol. ## If the 'quotes' setting is FALSE, we will make no attempt to ## do filename completion (this is likely to happen with ## front-ends that are capable of doing their own file name ## completion; such front-ends can fall back to their native ## file completion when rc.status("fileName") is TRUE. if (.CompletionEnv$settings[["quotes"]]) { ## ## This was used to make a guess whether we are in ## ## special situations like ::, ?, [, etc. But from R ## ## 3.0.0 we re-evaluate the token based from the ## ## begin-quote, so this is postponed. This part can be ## ## deleted once this is stable enough. ## st <- .CompletionEnv[["start"]] ## probablyNotFilename <- ## ((st > 2L && ## ((prequote <- substr(.CompletionEnv[["linebuffer"]], st-1L, st-1L)) %in% c("?", "[", ":", "$"))) || ## (st == 2L && ## ((prequote <- substr(.CompletionEnv[["linebuffer"]], st-1L, st-1L)) %in% c("?"))) ## ) ## FIXME|TODO: readline (and maybe other backends) will ## usually use a fixed set of breakpoints to detect ## tokens. If we are handling quotes ourselves, the more ## likely correct token is everything from the last ## unclosed quote onwards (which may include spaces, ## punctuations, etc. that would normally cause breaks). ## We already do this when we guess the token ourselves ## (e.g., for Windows) (and also in the fileCompletions() ## call below using correctFilenameToken()), and can ## re-use that here. The problem is that for other ## backends a token may already have been determined, and ## that's what we will need to use. We can still fake it ## by using the correct token but substracting the extra ## part when providing completions, but that will need ## some work. ## Related to that: if we implement that, should also ## check before for '?' and move to help completion ## if so. ### str(correctFilenameToken()) ### str(.guessTokenFromLine(update = FALSE)) ## TODO: For extra credit, we could also allow for ## spaces like in 'package ? grid', but will leave ## that for the future (maybe some regexp magic will ## make this simple) fullToken <- .guessTokenFromLine(update = FALSE) probablyHelp <- (fullToken$start >= 2L && ((substr(.CompletionEnv[["linebuffer"]], fullToken$start-1L, fullToken$start-1L)) == "?")) if (probablyHelp) { fullToken$prefix <- .guessTokenFromLine(end = fullToken$start - 2, update = FALSE)$token } probablyName <- ((fullToken$start > 2L && ((substr(.CompletionEnv[["linebuffer"]], fullToken$start-1L, fullToken$start-1L)) == "$")) || (fullToken$start > 3L && ((substr(.CompletionEnv[["linebuffer"]], fullToken$start-2L, fullToken$start-1L)) == "[["))) probablyNamespace <- (fullToken$start > 3L && ((substr(.CompletionEnv[["linebuffer"]], fullToken$start-2L, fullToken$start-1L)) %in% c("::"))) ## in anticipation that we will handle this eventually: ## TODO: Detect cases where backtick has already been inserted by the user, and handle appropriately ## probablyBacktick <- (fullToken$start >= 1L && ## ((substr(.CompletionEnv[["linebuffer"]], ## fullToken$start, ## fullToken$start)) %in% c("`"))) probablySpecial <- probablyHelp || probablyName || probablyNamespace ## str(list(probablyHelp = probablyHelp, ## probablyName = probablyName, ## probablyNamespace = probablyNamespace, ## probablyBacktick = probablyBacktick, ## probablySpecial = probablySpecial)) ## For now, we only handle probablyHelp, and just decline ## to do filename completion if any of the other special ## situations are detected (but don't try to complete). tentativeCompletions <- if (probablyHelp) { substring(helpCompletions(fullToken$prefix, fullToken$token), 2L + nchar(fullToken$prefix), 1000L) # drop initial "prefix + ?" } else if (!probablySpecial) # probably a filename fileCompletions(fullToken$token) # FIXME: but not if probablyBacktick ## Give a custom completer function a chance to add to this list else if (is.function(fcustomQuote <- rc.getOption("custom.quote.completer"))) fcustomQuote(fullToken) .setFileComp(FALSE) ## str(c(fullToken, list(comps = tentativeCompletions))) ## Adjust for self-computed token .CompletionEnv[["comps"]] <- substring(tentativeCompletions, 1L + nchar(fullToken$token) - nchar(text), 1000L) } else { .CompletionEnv[["comps"]] <- character() .setFileComp(TRUE) } } else { .setFileComp(FALSE) setIsFirstArg(FALSE) # might be changed by inFunction() call ## make a guess at what function we are inside guessedFunction <- if (.CompletionEnv$settings[["args"]]) inFunction(.CompletionEnv[["linebuffer"]], .CompletionEnv[["start"]]) else "" .CompletionEnv[["fguess"]] <- guessedFunction ## if this is not "", then we want to add possible arguments ## of that function(s) (methods etc). Should be character() ## if nothing matches fargComps <- functionArgs(guessedFunction, text) if (getIsFirstArg() && length(guessedFunction) && guessedFunction %in% c("library", "require", "data")) { .CompletionEnv[["comps"]] <- fargComps ## don't try anything else return() } ## Is there an arithmetic operator in there in there? If so, ## work on the part after that and append to prefix before ## returning. It would have been easier if these were ## word-break characters, but that potentially interferes with ## filename completion. ## lastArithOp <- tail(gregexpr("/", text, fixed = TRUE)[[1L]], 1) lastArithOp <- tail.default(gregexpr("[\"'^/*+-]", text)[[1L]], 1) if (haveArithOp <- (lastArithOp > 0)) { prefix <- substr(text, 1L, lastArithOp) text <- substr(text, lastArithOp + 1L, 1000000L) } spl <- specialOpLocs(text) comps <- if (length(spl)) specialCompletions(text, spl) else { ## should we append a left-paren for functions? ## Usually yes, but not when inside certain special ## functions which often take other functions as ## arguments appendFunctionSuffix <- !any(guessedFunction %in% c("help", "args", "formals", "example", "do.call", "environment", "page", "apply", "sapply", "lapply", "tapply", "mapply", "methods", "fix", "edit")) normalCompletions(text, check.mode = appendFunctionSuffix) } if (haveArithOp && length(comps)) { comps <- paste0(prefix, comps) } comps <- c(fargComps, comps) ## Give a custom completer function a chance to add to this list if (is.function(fcustom <- rc.getOption("custom.argument.completer"))) comps <- c(fcustom(guessedFunction, text), comps) .CompletionEnv[["comps"]] <- comps } } ## support functions that attempt to provide tools useful specifically ## for the Windows Rgui. ## Note: even though these are unexported functions, changes in the ## API should be noted in man/rcompgen.Rd .win32consoleCompletion <- function(linebuffer, cursorPosition, check.repeat = TRUE, minlength = -1) { isRepeat <- ## is TAB being pressed repeatedly with this combination? if (check.repeat) (linebuffer == .CompletionEnv[["linebuffer"]] && cursorPosition == .CompletionEnv[["end"]]) else TRUE .assignLinebuffer(linebuffer) .assignEnd(cursorPosition) .guessTokenFromLine() token <- .CompletionEnv[["token"]] comps <- if (nchar(token, type = "chars") < minlength) character() else { .completeToken() .retrieveCompletions() } ## FIXME: no idea how much of this is MBCS-safe if (length(comps) == 0L) { ## no completions addition <- "" possible <- character() } else if (length(comps) == 1L) { ## FIXME (maybe): in certain cases the completion may be ## shorter than the token (e.g. when trying to complete on an ## impossible name inside a list). It's debatable what the ## behaviour should be in this case, but readline and Emacs ## actually delete part of the token (at least currently). To ## achieve this in Rgui one would need to do somewhat more ## work than I'm ready to do right now (especially since it's ## not clear that this is the right thing to do to begin ## with). So, in this case, I'll just pretend that no ## completion was found. addition <- substr(comps, nchar(token, type = "chars") + 1L, 100000L) possible <- character() } else if (length(comps) > 1L) { ## more than one completion. The right thing to is to extend ## the line by the unique part if any, and list the multiple ## possibilities otherwise. additions <- substr(comps, nchar(token, type = "chars") + 1L, 100000L) if (length(table(substr(additions, 1L, 1L))) > 1L) { ## no unique substring addition <- "" possible <- if (isRepeat) capture.output(cat(format(comps, justify = "left"), fill = TRUE)) else character() } else { ## need to figure out maximal unique substr keepUpto <- 1 while (length(table(substr(additions, 1L, keepUpto))) == 1L) keepUpto <- keepUpto + 1L addition <- substr(additions[1L], 1L, keepUpto - 1L) possible <- character() } } list(addition = addition, possible = possible, comps = paste(comps, collapse = " ")) } ## usage: ## .addFunctionInfo(foo = c("arg1", "arg2"), bar = c("a", "b")) .addFunctionInfo <- function(...) { dots <- list(...) for (nm in names(dots)) .FunArgEnv[[nm]] <- dots[[nm]] } .initialize.argdb <- function() { ## lattice lattice.common <- c("data", "allow.multiple", "outer", "auto.key", "aspect", "panel", "prepanel", "scales", "strip", "groups", "xlab", "xlim", "ylab", "ylim", "drop.unused.levels", "...", "default.scales", "subscripts", "subset", "formula", "cond", "aspect", "as.table", "between", "key", "legend", "page", "main", "sub", "par.strip.text", "layout", "skip", "strip", "strip.left", "xlab.default", "ylab.default", "xlab", "ylab", "panel", "xscale.components", "yscale.components", "axis", "index.cond", "perm.cond", "...", "par.settings", "plot.args", "lattice.options") densityplot <- c("plot.points", "ref", "groups", "jitter.amount", "bw", "adjust", "kernel", "weights", "window", "width", "give.Rkern", "n", "from", "to", "cut", "na.rm") panel.xyplot <- c("type", "groups", "pch", "col", "col.line", "col.symbol", "font", "fontfamily", "fontface", "lty", "cex", "fill", "lwd", "horizontal") .addFunctionInfo(xyplot.formula = c(lattice.common, panel.xyplot), densityplot.formula = c(lattice.common, densityplot)) ## grid grid.clip <- c("x", "y", "width", "height", "just", "hjust", "vjust", "default.units", "name", "vp") grid.curve <- c("x1", "y1", "x2", "y2", "default.units", "curvature", "angle", "ncp", "shape", "square", "squareShape", "inflect", "arrow", "open", "debug", "name", "gp", "vp") grid.polyline <- c("x", "y", "id", "id.lengths", "default.units", "arrow", "name", "gp", "vp") grid.xspline <- c("x", "y", "id", "id.lengths", "default.units", "shape", "open", "arrow", "repEnds", "name", "gp", "vp") .addFunctionInfo(grid.clip = grid.clip, grid.curve = grid.curve, grid.polyline = grid.polyline, grid.xspline = grid.xspline) ## par, options par <- c("xlog", "ylog", "adj", "ann", "ask", "bg", "bty", "cex", "cex.axis", "cex.lab", "cex.main", "cex.sub", "cin", "col", "col.axis", "col.lab", "col.main", "col.sub", "cra", "crt", "csi", "cxy", "din", "err", "family", "fg", "fig", "fin", "font", "font.axis", "font.lab", "font.main", "font.sub", "gamma", "lab", "las", "lend", "lheight", "ljoin", "lmitre", "lty", "lwd", "mai", "mar", "mex", "mfcol", "mfg", "mfrow", "mgp", "mkh", "new", "oma", "omd", "omi", "pch", "pin", "plt", "ps", "pty", "smo", "srt", "tck", "tcl", "usr", "xaxp", "xaxs", "xaxt", "xpd", "yaxp", "yaxs", "yaxt", "page", "ylbias") options <- unique(c( names(.Options), ## + options not yet initialized when preparing utils "bitmapType", "citation.bibtex.max", "contrasts", "demo.ask", "device", "device.ask.default", "editor", "example.ask", "help.search.types", "help.try.all.packages", "HTTPUserAgent", "internet.info", "locatorBell", "mailer", "menu.graphics", "na.action", "pkgType", "repos", "show.coef.Pvalues", "show.signif.stars", "str", "str.dendrogram.last", "ts.eps", "ts.S.compat", "unzip", "windowsTimeout", ## + options unset by default (or OS-specific) "mc.cores", "dvipscmd", "warn.FPU", "askYesNo", "BioC_mirror", "ccaddress", "checkPackageLicense", "conflicts.policy", "de.cellwidth", "deparse.max.lines", "digits.secs", "download.file.extra", "download.file.method", "error", "help.htmlmath", "help.htmltoc", "help.ports", "help_type", "install.lock", "install.packages.check.source", "install.packages.compile.from.source", "interrupt", "Ncpus", "save.defaults", "save.image.defaults", "setWidthOnResize", "show.error.locations", "show.nls.convergence", "SweaveHooks", "SweaveSyntax", "topLevelEnvironment", "traceback.max.lines", "url.method", "warning.expression" )) .addFunctionInfo(par = par, options = options) ## read.csv etc (... passed to read.table) readTable <- c("file", "header", "sep", "quote", "dec", "numerals", "row.names", "col.names", "as.is", "na.strings", "colClasses", "nrows", "skip", "check.names", "fill", "strip.white", "blank.lines.skip", "comment.char", "allowEscapes", "flush", "stringsAsFactors", "fileEncoding", "encoding", "text", "skipNul") .addFunctionInfo(read.csv = readTable, read.csv2 = readTable, read.delim = readTable, read.delim2 = readTable) ## c() because it is primitive c(...) and there is no proper ## c.default even though docs say it is ## c.default(..., recursive = FALSE, use.names = TRUE) .addFunctionInfo(c = c("recursive", "use.names")) } .CompletionEnv <- new.env(hash = FALSE) ## needed to save some overhead in .win32consoleCompletion assign("linebuffer", "", envir = .CompletionEnv) assign("end", 1, envir = .CompletionEnv) assign("settings", list(ops = TRUE, ns = TRUE, args = TRUE, dots = TRUE, func = FALSE, ipck = FALSE, S3 = TRUE, data = TRUE, help = TRUE, argdb = TRUE, fuzzy = FALSE, backtick = FALSE, files = TRUE, # FIXME: deprecate in favour of quotes quotes = TRUE), envir = .CompletionEnv) assign("options", list(package.suffix = "::", funarg.suffix = "=", function.suffix = "("), envir = .CompletionEnv) ## These keeps track of attached packages and available help topics. ## Needs updating only when packages are attached. assign("attached_packages", character(0), envir = .CompletionEnv) assign("help_topics", character(0), envir = .CompletionEnv) .FunArgEnv <- new.env(hash = TRUE, parent = emptyenv()) .initialize.argdb()