r-lib / R6

Encapsulated object-oriented programming for R
https://R6.r-lib.org
Other
412 stars 57 forks source link

Add function to get completions from generator object #63

Closed wch closed 7 years ago

wch commented 9 years ago

Closes #47.

@kevinushey is something like this what you had in mind? I wasn't sure how to provide types, so if you have thoughts on that, please let me know. If you're querying the generator for completions that are available for instances, it should be possible to tell whether it's a function or not, but beyond that I don't think you can do much.

kevinushey commented 9 years ago

This looks like what we need -- I'll test a bit more thoroughly when I'm working on completions again, but if this effectively means that e.g.

R6:::.instanceCompletions(shiny:::ShinySession, "$")

provides the expected completions, then I think this is what we need.

kevinushey commented 9 years ago

What about returning the completions as a list of lists, one list for each completion, with elements as e.g.:

list(name = <name>, type = <type>)

? Or event:

list(name = <name>, type = <type>, args = <args>)

where args gives the name of possible arguments to a function named <name>. It's okay if args is empty / doesn't exist for non-functions.

wch commented 9 years ago

Is it OK for .instanceCompletions to not be exported? If so, that's slightly simpler.

Presently, it gives a result like this:

AC <- R6Class("AC",
  public = list(
    x = 1,
    getx = function() self$x
  ),
  active = list(
    y2 = function() private$y * 2
  ),
  private = list(
    y = 1
  )
)

.instanceCompletions(AC, "$")
# [1] "y2"   "x"    "getx"

Those completions are useful for self$, and for any instantiations of AC. But it might also be useful to have completions for private$. Any thoughts on that? I could add a type="public" or "private" option if that makes sense. I'm open to any suggestions.

As for returning a list of lists, I can do that. However, I think the type would have to be either function, non-function, or active. For non-functions, the value (and therefore type) that's used in creating the generator isn't necessary the same as what ends up in instantiated objects. For example, we do stuff like this in Shiny:

MyClass <- R6Class("MyClass",
  public = list(
    x = "Map",
    initialize = function() {
      self$x <- Map$new()
    }
  )
)

The initial value of x is never used; in this case we used a string to indicate what it will be. But sometimes we use NULL -- and any arbitrary value would be OK.

kevinushey commented 9 years ago

I think it's fine if it's not exported.

What about having an explicit placeholder function for such entries, e.g.

MyClass <- R6Class("MyClass",
  public = list(
    x = placeholder("Map"),
    initialize = function() {
      self$x <- Map$new()
    }
  )
)

The placeholder function would just return some object with attributes that signal the possible types of objects, e.g.

placeholder <- function(...) {
  result <- ''
  attr(result, "placeholder") <- unlist(list(...))
  result
}

For the common case of deferred initialization of a single object, this would allow us to get an appropriate type; I don't think we could do better for objects that could be of multiple types, though.

In the end, all the IDE really needs is:

  1. The value, giving the text that will actually be inserted when a completion candidate is accepted,
  2. Whether we're inserting a function -- if we are, we might auto-insert parentheses following () and, if it has a positive number of arguments, might place the cursor inside as e.g. foo(|) or, if it takes no arguments, foo()|.

For completions of the argument names of a particular class attached to an R6 function, e.g. if we had

session$sendCustomMessage(|)

and we wanted to autocomplete arguments from sendCustomMessage, then we'd need to know the arguments as well.

wch commented 9 years ago

OK, I've updated it so that it returns a list of lists. Each of the sublists is something like:

list(name = "add", type = "function", args = c("x", "y'))

With R6 classes, it's possible to tell whether a member is a function or a non-function (active bindings are counted as non-functions right now). But if it's a non-function, it's not easy to have more information for reasons we've discussed. I'll think more about doing something like your placeholder() function - that could be useful in cases where the member is itself an R6 object, so you can have completions of chains, like foo$map$|.

You now can tell the function whether you want public members or private members. Public members are appropriate completions for session$ (if you already know that session <- ShinySession$new()) when you're working outside of the class definition, and self$ when you're inside. Private members are appropriate completions for private$ when you're working inside.

Here's what the returned data looks like:

> str(.instanceCompletions(shiny:::ShinySession, "public"))
List of 44
 $ :List of 3
  ..$ name: chr "progressStack"
  ..$ type: NULL
  ..$ args: NULL
 $ :List of 3
  ..$ name: chr "input"
  ..$ type: NULL
  ..$ args: NULL
 $ :List of 3
  ..$ name: chr "output"
  ..$ type: NULL
  ..$ args: NULL
 $ :List of 3
  ..$ name: chr "clientData"
  ..$ type: NULL
  ..$ args: NULL

...
...

  ..$ name: chr "setShowcase"
  ..$ type: chr "function"
  ..$ args: chr "value"
 $ :List of 3
  ..$ name: chr "defineOutput"
  ..$ type: chr "function"
  ..$ args: chr [1:3] "name" "func" "label"
 $ :List of 3
  ..$ name: chr "flushOutput"
  ..$ type: chr "function"
  ..$ args: NULL
 $ :List of 3
  ..$ name: chr "showProgress"
  ..$ type: chr "function"
  ..$ args: chr "id"

...