diff --git a/DESCRIPTION b/DESCRIPTION index 945f0a8..8629cb8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ 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 diff --git a/R/ace-editor.R b/R/ace-editor.R index d035fc6..12d88f5 100644 --- a/R/ace-editor.R +++ b/R/ace-editor.R @@ -23,31 +23,53 @@ #' 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 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", +#' hotkeys = 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, hotkeys=NULL){ + editorVar = paste0("editor__",outputId) + #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 +77,74 @@ 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... + # 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")))) + 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.") + "Shiny version 0.9.1.9004 required to use input debouncing in shinyAce.") } - js <- paste(js, "$('#",outputId,"').data('debounce',",debounce,");", sep="") + + 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(hotkeys)) { + shortcut = hotkeys[[i]] + if (is.list(shortcut)) { + shortcut = paste0(names(shortcut),": '", shortcut,"'", collapse=", ") + } else { + shortcut = paste0("win: '",shortcut,"', mac: '",shortcut,"'") + } + + id = names(hotkeys)[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(), @@ -101,4 +162,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..e95f768 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,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. @@ -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{ 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