From 9cdf2db64d11312fbe5a322588dec6e381455868 Mon Sep 17 00:00:00 2001 From: Sebastian Kranz Date: Mon, 25 Aug 2014 09:12:16 +0200 Subject: [PATCH 1/3] add cursorId and keyId --- DESCRIPTION | 6 +-- R/ace-editor.R | 88 +++++++++++++++++++++++++++++------------- man/aceEditor.Rd | 17 +++++++- man/getAceModes.Rd | 2 +- man/getAceThemes.Rd | 2 +- man/jsQuote.Rd | 2 +- man/updateAceEditor.Rd | 4 +- shinyAce.Rproj | 1 + 8 files changed, 86 insertions(+), 36 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 945f0a8..aa40a21 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -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 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) License: MIT Depends: R (>= 2.15.0) diff --git a/R/ace-editor.R b/R/ace-editor.R index d035fc6..a0c6836 100644 --- a/R/ace-editor.R +++ b/R/ace-editor.R @@ -23,31 +23,49 @@ #' 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 short-cuts of keys (see example) #' @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="Ctrl-R|Ctrl-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){ + editorVar = paste0("editor__",outputId) + #restore.point("aceEditor") + #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="") } 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='", @@ -55,35 +73,54 @@ aceEditor <- function(outputId, value, mode, theme, vimKeyBinding = FALSE, } 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]] + id = names(keyId)[i] + code = paste0(" + ",editorVar,".commands.addCommand({ + name: '",id,"', + bindKey: {win: '",shortcut,"', mac: '",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(), @@ -101,4 +138,3 @@ aceEditor <- function(outputId, value, mode, theme, vimKeyBinding = FALSE, tags$script(type="text/javascript", HTML(js)) ) } - diff --git a/man/aceEditor.Rd b/man/aceEditor.Rd index c3c8611..0b7e4c3 100644 --- a/man/aceEditor.Rd +++ b/man/aceEditor.Rd @@ -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} @@ -45,6 +46,14 @@ 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 short-cuts of keys (see example)} } \description{ Render an Ace editor on an application page. @@ -53,6 +62,10 @@ 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="Ctrl-R|Ctrl-Shift-Enter"), + wordWrap=TRUE, debounce=10) } } \author{ diff --git a/man/getAceModes.Rd b/man/getAceModes.Rd index e58479f..296e65e 100644 --- a/man/getAceModes.Rd +++ b/man/getAceModes.Rd @@ -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} diff --git a/man/getAceThemes.Rd b/man/getAceThemes.Rd index 2a080de..25bfef4 100644 --- a/man/getAceThemes.Rd +++ b/man/getAceThemes.Rd @@ -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} diff --git a/man/jsQuote.Rd b/man/jsQuote.Rd index e7006b5..26ec81f 100644 --- a/man/jsQuote.Rd +++ b/man/jsQuote.Rd @@ -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} diff --git a/man/updateAceEditor.Rd b/man/updateAceEditor.Rd index 68d6b03..125bbd4 100644 --- a/man/updateAceEditor.Rd +++ b/man/updateAceEditor.Rd @@ -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 diff --git a/shinyAce.Rproj b/shinyAce.Rproj index 3a76475..f7442a1 100644 --- a/shinyAce.Rproj +++ b/shinyAce.Rproj @@ -14,3 +14,4 @@ LaTeX: pdfLaTeX BuildType: Package PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd From 7ec839e0105a7d2b80f3ed847cd7be646ed668c6 Mon Sep 17 00:00:00 2001 From: Sebastian Kranz Date: Tue, 7 Oct 2014 05:52:51 +0200 Subject: [PATCH 2/3] allow different shortcuts for mac and windows --- R/ace-editor.R | 15 ++++++++++++--- man/aceEditor.Rd | 10 ++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/R/ace-editor.R b/R/ace-editor.R index a0c6836..c54ae0c 100644 --- a/R/ace-editor.R +++ b/R/ace-editor.R @@ -25,14 +25,17 @@ #' 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 short-cuts of keys (see example) +#' @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). #' @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="Ctrl-R|Ctrl-Shift-Enter"), +#' 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} @@ -99,11 +102,17 @@ aceEditor <- function(outputId, value, mode, theme, vimKeyBinding = FALSE, 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: {win: '",shortcut,"', mac: '",shortcut,"'}, + bindKey: {", shortcut,"}, exec: function(",editorVar,") { Shiny.onInputChange(\"",id, "\",{ diff --git a/man/aceEditor.Rd b/man/aceEditor.Rd index 0b7e4c3..e95f768 100644 --- a/man/aceEditor.Rd +++ b/man/aceEditor.Rd @@ -53,7 +53,10 @@ aceEditor(outputId, value, mode, theme, vimKeyBinding = FALSE, selected text} \item{keyId}{A list whose names are ID names and whose - elements are the short-cuts of keys (see example)} + 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. @@ -64,7 +67,10 @@ Render an Ace editor on an application page. theme="ambiance") aceEditor("myCodeEditor", "# Enter code", mode="r", - keyId = list(helpKey="F1",runKey="Ctrl-R|Ctrl-Shift-Enter"), + keyId = list(helpKey="F1", + runKey=list(win="Ctrl-R|Ctrl-Shift-Enter", + mac="CMD-ENTER|CMD-SHIFT-ENTER") + ), wordWrap=TRUE, debounce=10) } } From bd026f5918720f3c20faca40021f04cd29fce016 Mon Sep 17 00:00:00 2001 From: Sebastian Kranz Date: Wed, 15 Oct 2014 15:15:05 +0200 Subject: [PATCH 3/3] implemented Jeff Allens suggestions visible changes: renamed keyId to hotkeys --- DESCRIPTION | 2 +- R/ace-editor.R | 31 +++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index aa40a21..8629cb8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -6,7 +6,7 @@ Date: 2014-08-25 Author: Trestle Technology, LLC. Maintainer: Jeff Allen Description: Ace editor bindings to enable a rich text editing environment - within Shiny. (Extension by Sebastian Kranz: add listeners for keys and cursor changes) + within Shiny. License: MIT Depends: R (>= 2.15.0) diff --git a/R/ace-editor.R b/R/ace-editor.R index c54ae0c..12d88f5 100644 --- a/R/ace-editor.R +++ b/R/ace-editor.R @@ -25,14 +25,14 @@ #' 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). +#' @param hotkeys 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). #' @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", +#' hotkeys = list(helpKey="F1", #' runKey=list(win="Ctrl-R|Ctrl-Shift-Enter", #' mac="CMD-ENTER|CMD-SHIFT-ENTER") #' ), @@ -42,9 +42,10 @@ #' @export aceEditor <- function(outputId, value, mode, theme, vimKeyBinding = FALSE, readOnly=FALSE, height="400px", - fontSize=12, debounce=1000, wordWrap=FALSE,showLineNumbers = TRUE,highlightActiveLine=TRUE, selectionId=NULL, cursorId=NULL, keyId=NULL){ + fontSize=12, debounce=1000, wordWrap=FALSE, + showLineNumbers = TRUE,highlightActiveLine=TRUE, + selectionId=NULL, cursorId=NULL, hotkeys=NULL){ editorVar = paste0("editor__",outputId) - #restore.point("aceEditor") #editorVar = "editor" #editorIdVar = paste0("$('#", outputId, "')") js <- paste("var ", editorVar," = ace.edit('",outputId,"');",sep="") @@ -76,7 +77,21 @@ aceEditor <- function(outputId, value, mode, theme, vimKeyBinding = FALSE, } if (!is.null(debounce) && !is.na(as.numeric(debounce))){ - js <- paste(js, "$('#",outputId,"').data('debounce',",debounce,");", sep="") + # 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){ @@ -100,15 +115,15 @@ aceEditor <- function(outputId, value, mode, theme, vimKeyBinding = FALSE, js <- paste(js, curJS, sep="") } - for (i in seq_along(keyId)) { - shortcut = keyId[[i]] + for (i in seq_along(hotkeys)) { + shortcut = hotkeys[[i]] if (is.list(shortcut)) { shortcut = paste0(names(shortcut),": '", shortcut,"'", collapse=", ") } else { shortcut = paste0("win: '",shortcut,"', mac: '",shortcut,"'") } - id = names(keyId)[i] + id = names(hotkeys)[i] code = paste0(" ",editorVar,".commands.addCommand({ name: '",id,"',