Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shortcut and cursor listeners #16

Merged
merged 3 commits into from
Oct 23, 2014
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Package: shinyAce
Type: Package
Title: Ace editor bindings for Shiny
Version: 0.1.0
Date: 2013-10-10
Version: 0.1.1
Date: 2014-08-25
Author: Trestle Technology, LLC.
Maintainer: Jeff Allen <jeff@trestletech.com>
Description: Ace editor bindings to enable a rich text editing environment
within Shiny.
within Shiny. (Extension by Sebastian Kranz: add listeners for keys and cursor changes)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know that CRAN would like this here. Can you remove that line and either add a contributors section to the README or I can after we merge?

License: MIT
Depends:
R (>= 2.15.0)
Expand Down
97 changes: 71 additions & 26 deletions R/ace-editor.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,67 +23,113 @@
#' of every keystroke as it happens.
#' @param wordWrap If set to \code{TRUE}, Ace will enable word wrapping.
#' Default value is \code{FALSE}.
#' @param cursorId The ID associated with a cursor change.
#' @param selectionId The ID associated with a change of selected text
#' @param keyId A list whose names are ID names and whose elements are the shortcuts of keys. Shortcuts can either be a simple string or a list with elements 'win' and 'mac' that that specifies different shortcuts for win and mac (see example).
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure keyId communicates what this param really does. Can we consider a name change? A couple of options:

  • hotkeys
  • shortcuts
  • keyBindings

I personally like hotkeys best, but I'm not sure if that's internationally understood. What do you think? I'm open to alternatives.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, can you provide a reference to the syntax of these keyboard shortcuts in the @param documentation? I assume it's a jQuery convention?

#' @import shiny
#' @examples \dontrun{
#' aceEditor("myEditor", "Initial text for editor here", mode="r",
#' theme="ambiance")
#'
#' aceEditor("myCodeEditor", "# Enter code", mode="r",
#' keyId = list(helpKey="F1",
#' runKey=list(win="Ctrl-R|Ctrl-Shift-Enter",
#' mac="CMD-ENTER|CMD-SHIFT-ENTER")
#' ),
#' wordWrap=TRUE, debounce=10)
#' }
#' @author Jeff Allen \email{jeff@@trestletech.com}
#' @export
aceEditor <- function(outputId, value, mode, theme, vimKeyBinding = FALSE,
readOnly=FALSE, height="400px",
fontSize=12, debounce=1000, wordWrap=FALSE, selectionId=NULL){
js <- paste("var editor = ace.edit('",outputId,"');",sep="")
fontSize=12, debounce=1000, wordWrap=FALSE,showLineNumbers = TRUE,highlightActiveLine=TRUE, selectionId=NULL, cursorId=NULL, keyId=NULL){
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicky, but can I try to keep lines to < 80 chars wide. Can we wrap this one?

editorVar = paste0("editor__",outputId)
#restore.point("aceEditor")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extraneous comments can be trimmed.

#editorVar = "editor"
#editorIdVar = paste0("$('#", outputId, "')")
js <- paste("var ", editorVar," = ace.edit('",outputId,"');",sep="")
if (!missing(theme)){
js <- paste(js, "editor.setTheme('ace/theme/",theme,"');",sep="")
js <- paste(js, "", editorVar,".setTheme('ace/theme/",theme,"');",sep="")
}
if (vimKeyBinding){
js <- paste(js, "editor.setKeyboardHandler('ace/keyboard/vim');",sep="")
js <- paste(js, "", editorVar,".setKeyboardHandler('ace/keyboard/vim');",sep="")
}
if (!missing(mode)){
js <- paste(js, "editor.getSession().setMode('ace/mode/",mode,"');", sep="")
js <- paste(js, "", editorVar,".getSession().setMode('ace/mode/",mode,"');", sep="")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this block still be in there?

}
if (!missing(value)){
js <- paste(js, "editor.setValue(", jsQuote(value), ", -1);", sep="")
js <- paste(js, "", editorVar,".setValue(", jsQuote(value), ", -1);", sep="")
}
if (!showLineNumbers) {
js <- paste(js, "", editorVar,".renderer.setShowGutter(false);", sep="")
}
if (!highlightActiveLine) {
js <- paste(js, "", editorVar,".setHighlightActiveLine(false);", sep="")
}

if (readOnly){
js <- paste(js, "editor.setReadOnly(", jsQuote(readOnly), ");", sep="")
js <- paste(js, "", editorVar,".setReadOnly(", jsQuote(readOnly), ");", sep="")
}
if (!is.null(fontSize) && !is.na(as.numeric(fontSize))){
js <- paste(js, "document.getElementById('",outputId,"').style.fontSize='",
as.numeric(fontSize), "px'; ", sep="")
}

if (!is.null(debounce) && !is.na(as.numeric(debounce))){
# I certainly hope there's a more reasonable way to compare versions with an
# extra field in them...
re <- regexpr("^\\d+\\.\\d+\\.\\d+", packageVersion("shiny"))
shinyVer <- substr(packageVersion("shiny"), 0, attr(re, "match.length"))

minorVer <- as.integer(substr(packageVersion("shiny"),
attr(re, "match.length")+2,
nchar(packageVersion("shiny"))))
comp <- compareVersion(shinyVer, "0.9.1")
if (comp < 0 || (comp == 0 && minorVer < 9004)){
warning(
"Shiny version 0.9.1.9004 required to use input debouncing in shinyAce.")
}
js <- paste(js, "$('#",outputId,"').data('debounce',",debounce,");", sep="")
}

if (wordWrap){
js <- paste(js, "editor.getSession().setUseWrapMode(true);", sep="")
js <- paste(js, "", editorVar,".getSession().setUseWrapMode(true);", sep="")
}

js <- paste(js, "$('#", outputId, "').data('aceEditor',editor);", sep="")
js <- paste(js, "$('#", outputId, "').data('aceEditor',", editorVar,");", sep="")

if (!is.null(selectionId)){
selectJS <- paste("editor.getSelection().on(\"changeSelection\", function(){
selectJS <- paste("", editorVar,".getSelection().on(\"changeSelection\", function(){
Shiny.onInputChange(\"",selectionId,
"\",editor.getCopyText());})",
"\",", editorVar,".getCopyText());})",
sep="")
js <- paste(js, selectJS, sep="")
}

if (!is.null(cursorId)){
curJS <- paste("\n", editorVar,".getSelection().on(\"changeCursor\", function(){
Shiny.onInputChange(\"",cursorId,
"\",", editorVar,".selection.getCursor() );}\n);",
sep="")
js <- paste(js, curJS, sep="")
}

for (i in seq_along(keyId)) {
shortcut = keyId[[i]]
if (is.list(shortcut)) {
shortcut = paste0(names(shortcut),": '", shortcut,"'", collapse=", ")
} else {
shortcut = paste0("win: '",shortcut,"', mac: '",shortcut,"'")
}

id = names(keyId)[i]
code = paste0("
",editorVar,".commands.addCommand({
name: '",id,"',
bindKey: {", shortcut,"},
exec: function(",editorVar,") {
Shiny.onInputChange(\"",id,
"\",{
editorId : '",outputId,"',
selection: ", editorVar,".session.getTextRange(",editorVar,".getSelectionRange()),
cursor : ", editorVar,".selection.getCursor(),
randNum : Math.random()
});
},
readOnly: true // false if this command should not apply in readOnly mode
});
")
js = paste0(js, code)
}



tagList(
singleton(tags$head(
initResourcePaths(),
Expand All @@ -101,4 +147,3 @@ aceEditor <- function(outputId, value, mode, theme, vimKeyBinding = FALSE,
tags$script(type="text/javascript", HTML(js))
)
}

23 changes: 21 additions & 2 deletions man/aceEditor.Rd
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
% Generated by roxygen2 (4.0.0): do not edit by hand
% Generated by roxygen2 (4.0.1): do not edit by hand
\name{aceEditor}
\alias{aceEditor}
\title{Render Ace}
\usage{
aceEditor(outputId, value, mode, theme, vimKeyBinding = FALSE,
readOnly = FALSE, height = "400px", fontSize = 12, debounce = 1000,
wordWrap = FALSE)
wordWrap = FALSE, showLineNumbers = TRUE, highlightActiveLine = TRUE,
selectionId = NULL, cursorId = NULL, keyId = NULL)
}
\arguments{
\item{outputId}{The ID associated with this element}
Expand Down Expand Up @@ -45,6 +46,17 @@ aceEditor(outputId, value, mode, theme, vimKeyBinding = FALSE,

\item{wordWrap}{If set to \code{TRUE}, Ace will enable
word wrapping. Default value is \code{FALSE}.}

\item{cursorId}{The ID associated with a cursor change.}

\item{selectionId}{The ID associated with a change of
selected text}

\item{keyId}{A list whose names are ID names and whose
elements are the shortcuts of keys. Shortcuts can either
be a simple string or a list with elements 'win' and
'mac' that that specifies different shortcuts for win and
mac (see example).}
}
\description{
Render an Ace editor on an application page.
Expand All @@ -53,6 +65,13 @@ Render an Ace editor on an application page.
\dontrun{
aceEditor("myEditor", "Initial text for editor here", mode="r",
theme="ambiance")

aceEditor("myCodeEditor", "# Enter code", mode="r",
keyId = list(helpKey="F1",
runKey=list(win="Ctrl-R|Ctrl-Shift-Enter",
mac="CMD-ENTER|CMD-SHIFT-ENTER")
),
wordWrap=TRUE, debounce=10)
}
}
\author{
Expand Down
2 changes: 1 addition & 1 deletion man/getAceModes.Rd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
% Generated by roxygen2 (4.0.0): do not edit by hand
% Generated by roxygen2 (4.0.1): do not edit by hand
\name{getAceModes}
\alias{getAceModes}
\title{Get available modes}
Expand Down
2 changes: 1 addition & 1 deletion man/getAceThemes.Rd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
% Generated by roxygen2 (4.0.0): do not edit by hand
% Generated by roxygen2 (4.0.1): do not edit by hand
\name{getAceThemes}
\alias{getAceThemes}
\title{Get available themes}
Expand Down
2 changes: 1 addition & 1 deletion man/jsQuote.Rd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
% Generated by roxygen2 (4.0.0): do not edit by hand
% Generated by roxygen2 (4.0.1): do not edit by hand
\name{jsQuote}
\alias{jsQuote}
\title{Escape a JS String}
Expand Down
4 changes: 2 additions & 2 deletions man/updateAceEditor.Rd
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
% Generated by roxygen2 (4.0.0): do not edit by hand
% Generated by roxygen2 (4.0.1): do not edit by hand
\name{updateAceEditor}
\alias{updateAceEditor}
\title{Update Ace Editor}
\usage{
updateAceEditor(session, editorId, value, theme, readOnly, mode, fontSize,
wordWrap)
wordWrap, border = c("normal", "alert", "flash"))
}
\arguments{
\item{session}{The Shiny session to whom the editor
Expand Down
1 change: 1 addition & 0 deletions shinyAce.Rproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ LaTeX: pdfLaTeX

BuildType: Package
PackageInstallArgs: --no-multiarch --with-keep.source
PackageRoxygenize: rd