1# Copyright (C) 2021 Brodie Gaslam
2#
3# This file is part of "diffobj - Diffs for R Objects"
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# Go to <https://www.r-project.org/Licenses/GPL-2> for a copy of the license.
16
17#' @include html.R
18#' @include finalizer.R
19#' @include pager.R
20
21NULL
22
23# maybe this shouldn't be an S4 class since the function slot doesn't work
24# for classed functions (e.g. the ones produced by crayon)
25
26#' Functions Used for Styling Diff Components
27#'
28#' Except for \code{container} every function specified here should be
29#' vectorized and apply formatting to each element in a character vectors.  The
30#' functions must accept at least one argument and require no more than one
31#' argument.  The text to be formatted will be passed as a character vector
32#' as the first argument to each function.
33#'
34#' These functions are applied in post processing steps.  The \code{diff*}
35#' methods do not do any of the formatting.  Instead, the formatting is done
36#' only if the user requests to \code{show} the object.  Internally, \code{show}
37#' first converts the object to a character vector using \code{as.character},
38#' which applies every formatting function defined here except for
39#' \code{container}.  Then \code{show} applies \code{container} before
40#' forwarding the result to the screen or pager.
41#'
42#' @note the slots are set to class \dQuote{ANY} to allow classed functions
43#'   such as those defined in the \code{crayon} package.  Despite this seemingly
44#'   permissive slot definition, only functions are allowed in the slots by
45#'   the validation functions.
46#' @param container function used primarily by HTML styles to generate an
47#'   outermost \code{DIV} that allows for CSS targeting of its contents
48#'   (see \code{\link{cont_f}} for a function generator appropriate for use
49#'   here)
50#' @param line function
51#' @param line.insert function
52#' @param line.delete function
53#' @param line.match function
54#' @param line.guide function formats guide lines (see \code{\link{guides}})
55#' @param text function
56#' @param text.insert function
57#' @param text.delete function
58#' @param text.match function
59#' @param text.guide function formats guide lines (see \code{\link{guides}})
60#' @param gutter function
61#' @param gutter.insert function
62#' @param gutter.delete function
63#' @param gutter.match function
64#' @param gutter.guide function
65#' @param gutter.pad function
66#' @param header function to format each hunk header with
67#' @param banner function to format entire banner
68#' @param banner.insert function to format insertion banner
69#' @param banner.delete function to format deletion banner
70#' @param meta function format meta information lines
71#' @param context.sep function to format the separator used to visually
72#'   distinguish the A and B hunks in \dQuote{context} \code{mode}
73#' @return a StyleFuns S4 object
74#' @seealso \code{\link{Style}}
75#' @rdname StyleFuns
76#' @export StyleFuns
77#' @exportClass StyleFuns
78
79StyleFuns <- setClass(
80  "StyleFuns",
81  slots=c(
82    container="ANY", row="ANY",
83    line="ANY", line.insert="ANY", line.delete="ANY", line.match="ANY",
84    line.guide="ANY", line.fill="ANY",
85    text="ANY", text.insert="ANY", text.delete="ANY", text.match="ANY",
86    text.guide="ANY", text.fill="ANY",
87    banner="ANY", banner.insert="ANY", banner.delete="ANY",
88    gutter="ANY",
89    gutter.insert="ANY", gutter.insert.ctd="ANY",
90    gutter.delete="ANY", gutter.delete.ctd="ANY",
91    gutter.match="ANY", gutter.match.ctd="ANY",
92    gutter.guide="ANY", gutter.guide.ctd="ANY",
93    gutter.fill="ANY", gutter.fill.ctd="ANY",
94    gutter.context.sep="ANY", gutter.context.sep.ctd="ANY",
95    gutter.pad="ANY",
96    word.insert="ANY", word.delete="ANY",
97    context.sep="ANY", header="ANY", meta="ANY", trim="ANY"
98  ),
99  prototype=list(
100    container=identity, row=identity,
101    banner=identity, banner.insert=identity, banner.delete=identity,
102    line=identity, line.insert=identity, line.delete=identity,
103    line.match=identity, line.guide=identity, line.fill=identity,
104    text=identity, text.insert=identity, text.delete=identity,
105    text.match=identity, text.guide=identity, text.fill=identity,
106    gutter=identity, gutter.pad=identity,
107    gutter.insert=identity, gutter.insert.ctd=identity,
108    gutter.delete=identity, gutter.delete.ctd=identity,
109    gutter.match=identity, gutter.match.ctd=identity,
110    gutter.guide=identity, gutter.guide.ctd=identity,
111    gutter.fill=identity, gutter.fill.ctd=identity,
112    gutter.context.sep=identity, gutter.context.sep.ctd=identity,
113    word.insert=identity, word.delete=identity,
114    header=identity,
115    context.sep=identity,
116    meta=identity,
117    trim=identity
118  ),
119  validity=function(object){
120    for(i in slotNames(object)) {
121      if(!is.function(slot(object, i)))
122        return(paste0("Argument `", i, "` should be a function."))
123      if(has_non_def_formals(tail(formals(slot(object, i)), -1L)))
124        return(
125          paste0(
126            "Argument `", i,
127            "` may not have non-default formals argument after the first."
128          ) )
129    }
130    TRUE
131  }
132)
133StyleFunsAnsi <- setClass(
134  "StyleFunsAnsi", contains="StyleFuns",
135  prototype=list(
136    word.insert=crayon::green, word.delete=crayon::red,
137    gutter.insert=crayon::green, gutter.insert.ctd=crayon::green,
138    gutter.delete=crayon::red, gutter.delete.ctd=crayon::red,
139    gutter.guide=crayon::silver, gutter.guide.ctd=crayon::silver,
140    gutter.fill=crayon::silver, gutter.fill.ctd=crayon::silver,
141    gutter.context.sep=crayon::silver, gutter.context.sep.ctd=crayon::silver,
142    header=crayon::cyan,
143    meta=crayon::silver,
144    line.guide=crayon::silver,
145    context.sep=crayon::silver,
146    trim=crayon::silver
147  )
148)
149#' Character Tokens Used in Diffs
150#'
151#' Various character tokens are used throughout diffs to provide visual cues.
152#' For example, gutters will contain characters that denote deletions and
153#' insertions (\code{<} and \code{>} by default).
154#'
155#' @param gutter.insert character(1L) text to use as visual cue to indicate
156#'   whether a diff line is an insertion, defaults to \dQuote{> }
157#' @param gutter.insert.ctd character(1L) if a diff line is wrapped, the
158#'   visual cue shifts to this character to indicate wrapping occured
159#' @param gutter.delete character(1L) see \code{gutter.insert} above
160#' @param gutter.delete.ctd character(1L) see \code{gutter.insert.ctd} above
161#' @param gutter.match character(1L) see \code{gutter.insert} above
162#' @param gutter.match.ctd character(1L) see \code{gutter.insert.ctd} above
163#' @param gutter.guide character(1L) see \code{gutter.insert} above
164#' @param gutter.guide.ctd character(1L) see \code{gutter.insert.ctd} above
165#' @param gutter.fill character(1L) see \code{gutter.insert} above
166#' @param gutter.fill.ctd character(1L) see \code{gutter.insert.ctd} above
167#' @param gutter.pad character(1L) separator between gutter characters and the
168#'   rest of a line in a diff
169#' @param pad.col character(1L) separator between columns in side by side mode
170#' @return a StyleText S4 object
171#' @seealso \code{\link{Style}}
172#' @rdname StyleText
173#' @export StyleText
174#' @exportClass StyleText
175
176StyleText <- setClass(
177  "StyleText",
178  slots=c(
179    gutter.insert="character", gutter.insert.ctd="character",
180    gutter.delete="character", gutter.delete.ctd="character",
181    gutter.match="character", gutter.match.ctd="character",
182    gutter.guide="character", gutter.guide.ctd="character",
183    gutter.fill="character", gutter.fill.ctd="character",
184    gutter.context.sep="character", gutter.context.sep.ctd="character",
185    gutter.pad="character",
186    context.sep="character",
187    pad.col="character",
188    line.break="character"
189  ),
190  prototype=list(
191    gutter.insert=">", gutter.insert.ctd=":",
192    gutter.delete="<", gutter.delete.ctd=":",
193    gutter.match=" ", gutter.match.ctd=" ",
194    gutter.guide="~", gutter.guide.ctd="~",
195    gutter.fill="~", gutter.fill.ctd="~",
196    gutter.context.sep="~", gutter.context.sep.ctd="~",
197    gutter.pad=" ", context.sep="----------",
198    pad.col="  ",
199    line.break="\n"
200  ),
201  validity=function(object){
202    for(i in slotNames(object)) if(!is.chr.1L(slot(object, i)))
203      return(paste0("Argument `", i, "` must be character(1L) and not NA."))
204    TRUE
205  }
206)
207#' Styling Information for Summaries
208#'
209#' @export
210#' @rdname StyleSummary
211#' @slot container function applied to entire summary
212#' @slot body function applied to everything except the actual map portion of
213#'   the summary
214#' @slot detail function applied to section showing how many deletions /
215#'   insertions, etc. occurred
216#' @slot map function applied to the map portion of the summary
217
218StyleSummary <- setClass("StyleSummary",
219  slots=c(container="ANY", body="ANY", map="ANY", detail="ANY"),
220  prototype=list(
221    container=function(x) sprintf("\n%s\n", paste0(x, collapse="")),
222    body=identity,
223    detail=function(x) sprintf("\n%s\n", paste0("  ", x, collapse="")),
224    map=function(x) sprintf("\n%s", paste0("  ", x, collapse="\n"))
225  ),
226  validity=function(object) {
227    fun.slots <- c("container", "body", "map", "detail")
228    for(i in fun.slots) {
229      if(!isTRUE(is.one.arg.fun(slot(object, i))))
230        return(
231          "Slot ", i, " must contain a function that accepts at least one ",
232          "argument and requires no more than one argument."
233        )
234    }
235    TRUE
236  }
237)
238#' @rdname StyleSummary
239#' @export
240
241StyleSummaryHtml <- setClass("StyleSummaryHtml", contains="StyleSummary",
242  prototype=list(
243    container=function(x) div_f("diffobj-summary")(paste0(x, collapse="")),
244    body=div_f("body"),
245    detail=div_f("detail"),
246    map=div_f("map")
247) )
248#' Customize Appearance of Diff
249#'
250#' S4 objects that expose the formatting controls for \code{Diff}
251#' objects.  Many predefined formats are defined as classes that extend the
252#' base \code{Style} class.  You may fine tune styles by either extending
253#' the pre-defined classes, or modifying an instance thereof.
254#'
255#' @section Pre-defined Classes:
256#'
257#' Pre-defined classes are used to populate the \code{\link{PaletteOfStyles}}
258#' object, which in turn allows the \code{diff*} methods to pick the
259#' appropriate \code{Style} for each combination of the \code{format},
260#' \code{color.mode}, and \code{brightness} parameters when the \code{style}
261#' parameter is set to \dQuote{auto}.  The following classes are pre-defined:
262#'
263#' \itemize{
264#'   \item \code{StyleRaw}: No styles applied
265#'   \item \code{StyleAnsi8NeutralRgb}
266#'   \item \code{StyleAnsi8NeutralYb}
267#'   \item \code{StyleAnsi256LightRgb}
268#'   \item \code{StyleAnsi256LightYb}
269#'   \item \code{StyleAnsi256DarkRgb}
270#'   \item \code{StyleAnsi256DarkYb}
271#'   \item \code{StyleHtmlLightRgb}
272#'   \item \code{StyleHtmlLightYb}
273#' }
274#' Each of these classes has an associated constructor function with the
275#' same name (see examples).  Objects instantiated from these classes
276#' may also be used directly as the value for the \code{style} parameter to the
277#' \code{diff*} methods. This will override the automatic selection process
278#' that uses \code{\link{PaletteOfStyles}}.  If you wish to tweak an
279#' auto-selected style rather than explicitly specify one, pass a parameter
280#' list instead of a \code{Style} objects as the \code{style} parameter to the
281#' \code{diff*} methods (see examples).
282#'
283#' There are predefined classes for most combinations of
284#' \code{format/color.mode/brightness}, but not all.  For example, there are
285#' only \dQuote{light} \code{brightness} defined for the \dQuote{html}
286#' \code{format}, and those classes are re-used for all possible
287#' \code{brightness} values, and the 8 color ANSI neutral classes are used
288#' for the 256 color neutral selections as well.
289#'
290#' To get a preview of what a style looks like just instantiate
291#' an object; the \code{show} method will output a trivial diff to screen with
292#' styles applied.  Note that for ANSI styles of the dark and light variety
293#' the show method colors the terminal background and foregrounds in compatible
294#' colors.  In normal usage the terminal background and foreground colors are
295#' left untouched so you should not expect light styles to look good on dark
296#' background and vice versa even if they render correctly when showing the
297#' style object.
298#'
299#' @section Style Structure:
300#'
301#' Most of the customization is done by specifying functions that operate on
302#' character vectors and return a modified character vector of the same length.
303#' The intended use case is to pass \code{crayon} functions such as
304#' \code{crayon::red}, although you may pass any function of your liking
305#' that behaves as described.  Formatting functions are expected to return their
306#' inputs formatted in such a way that their \emph{display} width is unchanged.
307#' If your formatting functions change display width output may not render
308#' properly, particularly when using \code{mode="sidebyside"}.
309#'
310#' The visual representation of the diff has many nested components.  The
311#' functions you specify here will be applied starting with the innermost ones.
312#' A schematic of the various component that represent an inserted line follows
313#' (note \dQuote{insert} abbreviated to \dQuote{ins}, and \dQuote{gutter}
314#' abbreviated to \dQuote{gtr}):
315#' \preformatted{+- line ---------------------------------------------------+
316#' |+- line.ins ---------------------------------------------+|
317#' ||+- gtr ------------------------++- text ---------------+||
318#' |||+- gtr.ins ---++- gtr.pad ---+||+- text.ins ---------+|||
319#' ||||             ||             ||||      +- word.ins -+||||
320#' |||| gtr.ins.txt || gtr.pad.txt |||| DIFF | TEXT HERE  |||||
321#' ||||             ||             ||||      +------------+||||
322#' |||+-------------++-------------+||+--------------------+|||
323#' ||+------------------------------++----------------------+||
324#' |+--------------------------------------------------------+|
325#' +----------------------------------------------------------+
326#' }
327#' A similar model applies to deleted and matching lines.  The boxes represent
328#' functions.  \code{gutter.insert.txt} represents the text to use in the gutter
329#' and is not a function. \code{DIFF TEXT HERE} is text from the objects being
330#' diffed, with the portion that has different words inside the
331#' \code{word.insert}.  \code{gutter.pad} and \code{gutter.pad.txt} are used to
332#' separate the gutter from the text and usually end up resolving to a space.
333#'
334#' Most of the functions defined here default to \code{\link{identity}}, but
335#' you are given the flexibility to fully format the diff.  See
336#' \code{\link{StyleFuns}} and \code{\link{StyleText}} for a full listing of
337#' the adjustable elements.
338#'
339#' In side-by-side mode there are two \dQuote{lines} per screen line, each with
340#' the structure described here.
341#'
342#' The structure described here may change in the future.
343#'
344#' @section HTML Styles:
345#'
346#' If you use a \code{Style} that inherits from \code{StyleHtml} the
347#' diff will be wrapped in HTML tags, styled with CSS, and output to
348#' \code{getOption("viewer")} if your IDE supports it (e.g. Rstudio), or
349#' directly to the browser otherwise, assuming that the default
350#' \code{\link{Pager}} or a correctly configured pager that inherits from
351#' \code{\link{PagerBrowser}} is in effect.  Otherwise, the raw HTML will be
352#' output to your terminal.
353#'
354#' By default HTML output sent to the viewer/browser is a full stand-alone
355#' webpage with CSS styles to format and color the diff, and JS code to
356#' handle scaling.  The CSS and JS is read from the
357#' \link[=webfiles]{default files} and injected into the HTML to simplify
358#' packaging of the output.  You can customize the CSS and JS by using the
359#' \code{css} and \code{js} arguments respectively, but read the rest of this
360#' documentation section if you plan on doing so.
361#'
362#' Should you want to capture the HTML output for use elsewhere, you can do
363#' so by using \code{as.character} on the return value of the \code{diff*}
364#' methods.  If you want the raw HTML without any of the headers, CSS, and
365#' JS use \code{html.ouput="diff.only"} when you instantiate the
366#' \code{StyleHtml} object (see examples), or disable the \code{\link{Pager}}.
367#' Another option is \code{html.output="diff.w.style"} which will add
368#' \code{<style>} tags with the CSS, but without wrapping those in \code{<head>}
369#' tags. This last option results in illegal HTML with a \code{<style>} block
370#' outside of the \code{<head>} block, but appears to work and is useful if you
371#' want to embed HTML someplace but do not have access to the headers.
372#'
373#' If you wish to modify the CSS styles you should do so cautiously.  The
374#' HTML and CSS work well together out of the box, but may not take to kindly
375#' to modifications.  The safest changes you can make are to the colors of the
376#' scheme.  You also probably should not modify the functions in the
377#' \code{@funs} slot of the \code{StyleHtml} object.  If you want to provide
378#' your own custom styles make a copy of the file at the location returned by
379#' \code{diffobj_css()}, modify it to your liking, and pass the location of your
380#' modified sheet back via the \code{css} argument (see examples).
381#'
382#' The javascript controls the scaling of the output such that its width fits
383#' in the viewport.  If you wish to turn of this behavior you can do so via the
384#' \code{scale} argument.  You may need to modify the javascript if you modify
385#' the \code{@funs} functions, but otherwise you are probably best off leaving
386#' the javascript untouched.  You can provide the location of a modified
387#' javascript file via the \code{js} argument.
388#'
389#' Both the CSS and JS files can be specified via options,
390#' \dQuote{diffobj.html.css}, and \dQuote{diffobj.html.js} respectively.
391#'
392#' If you define your own custom \code{StyleHtml} object you may want to modify
393#' the slot \code{@funs@container}.  This slot contains a function that is
394#' applied to the entire diff output.  For example, \code{StyleHtmlLightRgb}
395#' uses \code{@funs@container <- cont_f("light", "rgb")}.  \code{cont_f} returns
396#' a function that accepts a character vector as an argument and returns
397#' that value wrapped in a \code{DIV} block with class
398#' \dQuote{"diffobj-container light rgb"}.  This allows the CSS style sheet to
399#' target the \code{Diff} elements with the correct styles.
400#'
401#' @section Modifying Style Parameters Directly:
402#'
403#' Often you will want to specify some of the style parameters (e.g.
404#' \code{scale} for html styles) while still relying on the default style
405#' selection to pick the specific style.  You can do so by passing a list to the
406#' \code{style} parameter of the \code{\link[=diffPrint]{diff*}} methods.
407#' See examples.
408#'
409#' @section New Classes:
410#'
411#' You can in theory create entirely new classes that extent \code{Style}.  For
412#' example you could generate a class that renders the diff in \code{grid}
413#' graphics.  Note however that we have not tested such extensions and it is
414#' possible there is some embedded code that will misbehave with such a new
415#' class.
416#'
417#' @rdname Style
418#' @export Style
419#' @exportClass Style
420#' @param funs a \code{\link{StyleFuns}} object that contains all the functions
421#'   represented above
422#' @param text a \code{\link{StyleText}} object that contains the non-content
423#'   text used by the diff (e.g. \code{gutter.insert.txt})
424#' @param summary a \code{\link{StyleSummary}} object that contains formatting
425#'   functions and other meta data for rendering summaries
426#' @param wrap TRUE or FALSE, whether the text should be hard wrapped to fit in
427#'   the console
428#' @param pad TRUE or FALSE, whether text should be right padded
429#' @param pager what type of \code{\link{Pager}} to use
430#' @param nchar.fun function to use to count characters; intended mostly for
431#'   internal use (used only for gutters as of version 0.2.0).
432#' @param wrap TRUE or FALSE, whether text should be hard wrapped at
433#'   \code{disp.width}
434#' @param na.sub what character value to substitute for NA elements; NA elements
435#'   are generated when lining up side by side diffs by adding padding rows; by
436#'   default the text styles replace these with a blank character string, and
437#'   the HTML styles leave them as NA for the HTML formatting functions to deal
438#'   with
439#' @param blank sub what character value to replace blanks with; needed in
440#'   particular for HTML rendering (uses \code{"&nbsp;"}) to prevent lines from
441#'   collapsing
442#' @param disp.width how many columns the text representation of the objects to
443#'   diff is allowed to take up before it is hard wrapped (assuming \code{wrap}
444#'   is TRUE).  See param \code{disp.width} for \code{\link{diffPrint}}.
445#' @param finalizer function that accepts at least two parameters and requires
446#'   no more than two parameters, will receive as the first parameter the
447#'   the object to render (either a \code{Diff} or a \code{DiffSummary}
448#'   object), and the text representation of that object as the second
449#'   argument.  This allows final modifications to the character output so that
450#'   it is displayed correctly by the pager.  For example, \code{StyleHtml}
451#'   objects use it to generate HTML headers if the \code{Diff} is destined to
452#'   be displayed in a browser.  The object themselves are passed along to
453#'   provide information about the paging device and other contextual data to
454#'   the function.
455#' @param html.output (\code{StyleHtml} objects only) one of:
456#'   \itemize{
457#'     \item \dQuote{page}: Include all HTML/CSS/JS required to create a
458#'       stand-alone web page with the diff; in this mode the diff string will
459#'       be re-encoded with \code{\link{enc2utf8}} and the HTML page encoding
460#'       will be declared as UTF-8.
461#'     \item \dQuote{diff.w.style}: The CSS and HTML, but without any of the
462#'       outer tags that would make it a proper HTML page (i.e. no
463#'       \code{<html>/<head>} tags or the like) and without the JS; note that
464#'       technically this is illegal HTML since we have \code{<style>} tags
465#'       floating outside of \code{<head>} tags, but it seems to work in most
466#'       browsers.
467#'     \item \dQuote{diff.only}: Like \dQuote{diff.w.style}, but without the CSS
468#'     \item \dQuote{auto}: Pick one of the above based on \code{Pager}, will
469#'        chose \dQuote{page} if the pager is of type \code{PagerBrowser} (as in
470#'        that case the output is destined to be displayed in a browser like
471#'        device), or \dQuote{diff.only} if it is not.
472#'   }
473#' @param escape.html.entities (\code{StyleHtml} objects only) TRUE (default)
474#'   or FALSE, whether to escape HTML entities in the input
475#' @param scale (\code{StyleHtml} objects only) TRUE (default) or FALSE,
476#'   whether to scale HTML output to fit to the viewport
477#' @param css (\code{StyleHtml} objects only) path to file containing CSS styles
478#'   to style HTML output with
479#' @param js (\code{StyleHtml} objects only) path to file containing Javascript
480#'   used for scaling output to viewports.
481#' @return Style S4 object
482#' @examples
483#' \dontrun{
484#' ## Create a new style based on existing style by changing
485#' ## gutter symbols and guide color; see `?StyleFuns` and
486#' ## `?StyleText` for a full list of adjustable elements
487#' my.style <- StyleAnsi8NeutralYb()
488#' my.style   ## `show` method gives you a preview of the style
489#' my.style@text@gutter.insert <- "+++"
490#' my.style@text@gutter.delete <- "---"
491#' my.style@funs@text.guide <- crayon::green
492#' my.style   ## Notice gutters and guide color
493#'
494#' ## Provide a custom style sheet; here we assume there is a style sheet at
495#' ## `HOME/web/mycss.css`
496#' my.css <- file.path(path.expand("~"), "web", "mycss.css")
497#' diffPrint(1:5, 2:6, style=StyleHtmlLightYb(css=my.css))
498#'
499#' ## Turn of scaling; notice how we pass a list to `style`
500#' ## and we do not need to specify a specific style
501#' diffPrint(letters, letters[-5], format="html", style=list(scale=FALSE))
502#'
503#' ## Alternatively we can do the same by specifying a style, but we must
504#' ## give an exact html style instead of relying on preferences to pick
505#' ## one for us
506#' my.style <- StyleHtmlLightYb(scale=FALSE)
507#' diffPrint(letters, letters[-5], style=my.style)
508#' }
509#' ## Return only the raw HTML without any of the headers
510#' as.character(
511#'   diffPrint(1:5, 2:6, format="html", style=list(html.output="diff.only"))
512#' )
513
514Style <- setClass("Style", contains="VIRTUAL",
515  slots=c(
516    funs="StyleFuns",
517    text="StyleText",
518    summary="StyleSummary",
519    nchar.fun="ANY",
520    wrap="logical",
521    pad="logical",
522    finalizer="function",
523    pager="Pager",
524    na.sub="character",
525    blank.sub="character",
526    disp.width="integer"
527  ),
528  prototype=list(
529    funs=StyleFuns(),
530    text=StyleText(),
531    wrap=TRUE,
532    pad=TRUE,
533    pager=PagerOff(),
534    finalizer=function(x, y) y,
535    na.sub="",
536    blank.sub="",
537    disp.width=0L,
538    nchar.fun=nchar2  # even raw input can have SGR in it
539  ),
540  validity=function(object){
541    # ## no longer true with nchar2 and support sgr parameter
542    # if(!isTRUE(is.one.arg.fun(object@nchar.fun))) {
543    #   return(paste0(
544    #     "Slot `nchar.fun` should be a function with at least one argument that ",
545    #     "doesn't require more than one argument"
546    #   ) )
547    # }
548    if(!is.TF(object@wrap))
549      return("Slot `wrap` must be TRUE or FALSE")
550    if(!is.TF(object@pad))
551      return("Slot `pad` must be TRUE or FALSE")
552    if(length(object@na.sub) != 1L)
553      return("Slot `na.sub` must be character(1L)")
554    if(length(object@blank.sub) != 1L)
555      return("Slot `na.sub` must be character(1L)")
556    if(!is.int.1L(object@disp.width) || object@disp.width < 0L)
557      return("Slot `disp.width` must be integer(1L), positive, and not NA")
558    fin.args <- formals(object@finalizer)
559    if(length(fin.args) < 2L)
560      return(
561        "Slot `finalizer` must be a function with at least two parameters."
562      )
563    if(length(fin.args) > 2L && has_non_def_formals(tail(fin.args, -2L)))
564      return(
565        paste0(
566          "Slot `finalizer` must be a function with no non-default parameters ",
567          "other than the first two."
568    ) )
569  }
570)
571setClass("Light", contains="VIRTUAL")
572setClass("Dark", contains="VIRTUAL")
573setClass("Neutral", contains="VIRTUAL")
574
575setClass("Raw", contains="VIRTUAL")
576setClass("Ansi", contains="VIRTUAL")
577setClass("Html", contains="VIRTUAL")
578
579setClass("Rgb", contains="VIRTUAL")
580setClass("Yb", contains="VIRTUAL")
581
582#' @export StyleRaw
583#' @exportClass StyleRaw
584#' @rdname Style
585
586StyleRaw <- setClass(
587  "StyleRaw", contains=c("Style", "Raw")
588)
589setMethod(
590  "initialize", "StyleRaw",
591  function(.Object, ...) {
592    .Object@pager <- if(pager_is_less())
593      PagerSystemLess() else PagerSystem()
594    callNextMethod(.Object, ...)
595})
596
597#' @export StyleAnsi
598#' @exportClass StyleAnsi
599#' @rdname Style
600
601StyleAnsi <- setClass(
602  "StyleAnsi", contains=c("StyleRaw", "Ansi"),
603  prototype=list(
604    funs=StyleFunsAnsi(),
605    nchar.fun=nchar2
606  )
607)
608#' @export StyleAnsi8NeutralRgb
609#' @exportClass StyleAnsi8NeutralRgb
610#' @rdname Style
611
612StyleAnsi8NeutralRgb <- setClass(
613  "StyleAnsi8NeutralRgb", contains=c("StyleAnsi", "Neutral", "Rgb")
614)
615#' @export StyleAnsi8NeutralYb
616#' @exportClass StyleAnsi8NeutralYb
617#' @rdname Style
618
619StyleAnsi8NeutralYb <- setClass(
620  "StyleAnsi8NeutralYb", contains=c("StyleAnsi", "Neutral", "Yb"),
621  prototype=list(
622    funs=StyleFunsAnsi(
623      word.insert=crayon::blue, word.delete=crayon::yellow,
624      gutter.insert=crayon::blue,
625      gutter.insert.ctd=crayon::blue,
626      gutter.delete=crayon::yellow,
627      gutter.delete.ctd=crayon::yellow
628  ) )
629)
630#' @export StyleAnsi256LightRgb
631#' @exportClass StyleAnsi256LightRgb
632#' @rdname Style
633
634StyleAnsi256LightRgb <- setClass(
635  "StyleAnsi256LightRgb", contains=c("StyleAnsi", "Light", "Rgb"),
636  prototype=list(
637    funs=StyleFunsAnsi(
638      text.insert=crayon::make_style(
639        rgb(4, 5, 4, maxColorValue=5), bg=TRUE, colors=256
640      ),
641      text.delete=crayon::make_style(
642        rgb(5, 4, 4, maxColorValue=5), bg=TRUE, colors=256
643      ),
644      text.fill=crayon::make_style(
645        rgb(20, 20, 20, maxColorValue=23), bg=TRUE, grey=TRUE, colors=256
646      ),
647      word.insert=crayon::make_style(
648        rgb(2, 4, 2, maxColorValue=5), bg=TRUE, colors=256
649      ),
650      word.delete=crayon::make_style(
651        rgb(4, 2, 2, maxColorValue=5), bg=TRUE, colors=256
652      ),
653      gutter.insert=crayon::make_style(
654        rgb(0, 3, 0, maxColorValue=5), colors=256
655      ),
656      gutter.insert.ctd=crayon::make_style(
657        rgb(0, 3, 0, maxColorValue=5), colors=256
658      ),
659      gutter.delete=crayon::make_style(
660        rgb(3, 0, 0, maxColorValue=5), colors=256
661      ),
662      gutter.delete.ctd=crayon::make_style(
663        rgb(3, 0, 0, maxColorValue=5), colors=256
664      ),
665      header=crayon::make_style(
666        rgb(0, 3, 3, maxColorValue=5), colors=256
667      )
668) ) )
669
670#' @export StyleAnsi256LightYb
671#' @exportClass StyleAnsi256LightYb
672#' @rdname Style
673
674StyleAnsi256LightYb <- setClass(
675  "StyleAnsi256LightYb", contains=c("StyleAnsi", "Light", "Yb"),
676  prototype=list(
677    funs=StyleFunsAnsi(
678      text.insert=crayon::make_style(
679        rgb(3, 3, 5, maxColorValue=5), bg=TRUE, colors=256
680      ),
681      text.delete=crayon::make_style(
682        rgb(4, 4, 2, maxColorValue=5), bg=TRUE, colors=256
683      ),
684      text.fill=crayon::make_style(
685        rgb(20, 20, 20, maxColorValue=23), bg=TRUE, grey=TRUE, colors=256
686      ),
687      word.insert=crayon::make_style(
688        rgb(2, 2, 4, maxColorValue=5), bg=TRUE, colors=256
689      ),
690      word.delete=crayon::make_style(
691        rgb(3, 3, 1, maxColorValue=5), bg=TRUE, colors=256
692      ),
693      gutter.insert=crayon::make_style(
694        rgb(0, 0, 3, maxColorValue=5), colors=256
695      ),
696      gutter.insert.ctd=crayon::make_style(
697        rgb(0, 0, 3, maxColorValue=5), colors=256
698      ),
699      gutter.delete=crayon::make_style(
700        rgb(2, 1, 0, maxColorValue=5), colors=256
701      ),
702      gutter.delete.ctd=crayon::make_style(
703        rgb(2, 1, 0, maxColorValue=5), colors=256
704      ),
705      header=crayon::make_style(
706        rgb(0, 3, 3, maxColorValue=5), colors=256
707      )
708) ) )
709#' @export StyleAnsi256DarkRgb
710#' @exportClass StyleAnsi256DarkRgb
711#' @rdname Style
712
713StyleAnsi256DarkRgb <- setClass(
714  "StyleAnsi256DarkRgb", contains=c("StyleAnsi", "Dark", "Rgb"),
715  prototype=list(
716    funs=StyleFunsAnsi(
717      text.insert=crayon::make_style(
718        rgb(0, 1, 0, maxColorValue=5), bg=TRUE, colors=256
719      ),
720      text.delete=crayon::make_style(
721        rgb(1, 0, 0, maxColorValue=5), bg=TRUE, colors=256
722      ),
723      word.insert=crayon::make_style(
724        rgb(0, 3, 0, maxColorValue=5), bg=TRUE, colors=256
725      ),
726      word.delete=crayon::make_style(
727        rgb(3, 0, 0, maxColorValue=5), bg=TRUE, colors=256
728      ),
729      gutter.insert=crayon::make_style(
730        rgb(0, 2, 0, maxColorValue=5), colors=256
731      ),
732      gutter.insert.ctd=crayon::make_style(
733        rgb(0, 2, 0, maxColorValue=5), colors=256
734      ),
735      gutter.delete=crayon::make_style(
736        rgb(2, 0, 0, maxColorValue=5), colors=256
737      ),
738      gutter.delete.ctd=crayon::make_style(
739        rgb(2, 0, 0, maxColorValue=5), colors=256
740      ),
741      gutter.guide=crayon::make_style(
742        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
743      ),
744      gutter.guide.ctd=crayon::make_style(
745        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
746      ),
747      line.guide=crayon::make_style(
748        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
749      ),
750      gutter.fill=crayon::make_style(
751        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
752      ),
753      gutter.fill.ctd=crayon::make_style(
754        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
755      ),
756      text.fill=crayon::make_style(
757        rgb(2, 2, 2, maxColorValue=23), bg=TRUE, grey=TRUE, colors=256
758      ),
759      gutter.context.sep=crayon::make_style(
760        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
761      ),
762      gutter.context.sep.ctd=crayon::make_style(
763        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
764      ),
765      context.sep=crayon::make_style(
766        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
767      ),
768      meta=crayon::make_style(
769        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
770      ),
771      trim=crayon::make_style(
772        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
773      )
774) ) )
775#' @export StyleAnsi256DarkYb
776#' @exportClass StyleAnsi256DarkYb
777#' @rdname Style
778
779StyleAnsi256DarkYb <- setClass(
780  "StyleAnsi256DarkYb", contains=c("StyleAnsi", "Dark", "Yb"),
781  prototype=list(
782    funs=StyleFunsAnsi(
783      text.insert=crayon::make_style(
784        rgb(0, 0, 2, maxColorValue=5), bg=TRUE, colors=256
785      ),
786      text.delete=crayon::make_style(
787        rgb(1, 1, 0, maxColorValue=5), bg=TRUE, colors=256
788      ),
789      word.insert=crayon::make_style(
790        rgb(0, 0, 5, maxColorValue=5), bg=TRUE, colors=256
791      ),
792      word.delete=crayon::make_style(
793        rgb(3, 2, 0, maxColorValue=5), bg=TRUE, colors=256
794      ),
795      gutter.insert=crayon::make_style(
796        rgb(0, 0, 3, maxColorValue=5), colors=256
797      ),
798      gutter.insert.ctd=crayon::make_style(
799        rgb(0, 0, 3, maxColorValue=5), colors=256
800      ),
801      gutter.delete=crayon::make_style(
802        rgb(1, 1, 0, maxColorValue=5), colors=256
803      ),
804      gutter.delete.ctd=crayon::make_style(
805        rgb(1, 1, 0, maxColorValue=5), colors=256
806      ),
807      header=crayon::make_style(
808        rgb(0, 3, 3, maxColorValue=5), colors=256
809      ),
810      gutter.guide=crayon::make_style(
811        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
812      ),
813      gutter.guide.ctd=crayon::make_style(
814        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
815      ),
816      line.guide=crayon::make_style(
817        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
818      ),
819      gutter.fill=crayon::make_style(
820        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
821      ),
822      gutter.fill.ctd=crayon::make_style(
823        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
824      ),
825      text.fill=crayon::make_style(
826        rgb(2, 2, 2, maxColorValue=23), bg=TRUE, grey=TRUE, colors=256
827      ),
828      gutter.context.sep=crayon::make_style(
829        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
830      ),
831      gutter.context.sep.ctd=crayon::make_style(
832        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
833      ),
834      context.sep=crayon::make_style(
835        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
836      ),
837      meta=crayon::make_style(
838        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
839      ),
840      trim=crayon::make_style(
841        rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256
842      )
843) ) )
844#' Return Location of Default HTML Support Files
845#'
846#' File location for default CSS and JS files.  Note that these files are read
847#' and injected into the output HTML rather than referenced to simplify serving.
848#'
849#' @aliases diffobj_js
850#' @name webfiles
851#' @rdname webfiles
852#' @return path to the default CSS or JS file
853#' @examples
854#' diffobj_css()
855#' diffobj_js()
856
857NULL
858
859#' @export
860#' @rdname webfiles
861
862diffobj_css <- function()
863  file.path(system.file(package="diffobj"), "css", "diffobj.css")
864
865#' @export
866#' @rdname webfiles
867
868diffobj_js <- function()
869  file.path(system.file(package="diffobj"), "script", "diffobj.js")
870
871#' @export StyleHtml
872#' @exportClass StyleHtml
873#' @rdname Style
874
875StyleHtml <- setClass(
876  "StyleHtml", contains=c("Style", "Html"),
877  slots=c(
878    css="character", html.output="character", escape.html.entities="logical",
879    js="character", scale="logical"
880  ),
881  prototype=list(
882    funs=StyleFuns(
883      container=cont_f(),
884      row=div_f("diffobj-row"),
885      banner.insert=div_f("insert"),
886      banner.delete=div_f("delete"),
887      banner=div_f("diffobj-line banner"),
888      line.insert=div_f("insert"),
889      line.delete=div_f("delete"),
890      line.match=div_f("diffobj-match"),
891      line.guide=div_f("diffobj-guide"),
892      line.fill=div_f("diffobj-fill"),
893      line=div_f("diffobj-line"),
894      text.insert=div_f("insert"),
895      text.delete=div_f("delete"),
896      text.match=div_f("diffobj-match"),
897      text.guide=div_f("diffobj-guide"),
898      text.fill=div_f("diffobj-fill"),
899      text=div_f("diffobj-text"),
900      gutter.insert=div_f("insert"),
901      gutter.delete=div_f("delete"),
902      gutter.match=div_f("diffobj-match"),
903      gutter.guide=div_f("diffobj-guide"),
904      gutter.fill=div_f("diffobj-fill"),
905      gutter.pad=div_f("pad"),
906      gutter.context.sep=div_f(c("context_sep", "ctd")),
907      gutter.insert.ctd=div_f(c("insert", "ctd")),
908      gutter.delete.ctd=div_f(c("delete", "ctd")),
909      gutter.match.ctd=div_f(c("diffobj-match", "ctd")),
910      gutter.guide.ctd=div_f(c("diffobj-guide", "ctd")),
911      gutter.fill.ctd=div_f(c("diffobj-fill", "ctd")),
912      gutter.context.sep.ctd=div_f(c("context_sep", "ctd")),
913      gutter=div_f("diffobj-gutter"),
914      context.sep=div_f("context_sep"),
915      word.insert=span_f(c("diffobj-word", "insert")),
916      word.delete=span_f(c("diffobj-word", "delete")),
917      trim=span_f("diffobj-trim"),
918      header=div_f(c("diffobj-header"))
919    ),
920    text=StyleText(
921      gutter.insert="&gt;",
922      gutter.delete="&lt;",
923      gutter.match="&nbsp;",
924      line.break="<br />",
925      pad.col=""
926    ),
927    summary=StyleSummaryHtml(),
928    pager=PagerBrowser(),
929    wrap=FALSE,
930    pad=FALSE,
931    nchar.fun=nchar_html,  # only used in gutter
932    escape.html.entities=TRUE,
933    na.sub="&nbsp;",
934    blank.sub="&nbsp;",
935    disp.width=80L,
936    html.output="auto",
937    css="",
938    js="",
939    finalizer=finalizeHtml,
940    scale=TRUE
941  ),
942  validity=function(object) {
943    if(!is.chr.1L(object@css))
944      return("slot `css` must be character(1L)")
945    if(!is.chr.1L(object@js))
946      return("slot `js` must be character(1L)")
947    if(!is.chr.1L(object@html.output))
948      return("slot `html.output` must be character(1L)")
949    if(!is.TF(object@escape.html.entities))
950      return("slot `escape.html.entities` must be TRUE or FALSE.")
951    if(!is.TF(object@scale))
952      return("slot `scale` must be TRUE or FALSE.")
953    if(!identical(object@wrap, FALSE))
954      return("slot `wrap` must be FALSE for `styleHtml` objects.")
955    TRUE
956  }
957)
958# construct with default values specified via options
959
960setMethod("initialize", "StyleHtml",
961  function(
962    .Object, css=getOption("diffobj.html.css"),
963    js=getOption("diffobj.html.js"),
964    html.output=getOption("diffobj.html.output", default='auto'),
965    escape.html.entities=getOption("diffobj.html.escape.html.entities"),
966    scale=getOption("diffobj.html.scale", default=TRUE),
967    ...
968  ) {
969    # Had some problems with R 3.1 where it appears that the initialize methods
970    # are triggered on load, but before `.onLoad` is called, and as such options
971    # are not available.  This is why we have this logic here.
972
973    if(is.null(css)) css <- diffobj_css()
974    if(is.null(js)) js <- diffobj_js()
975
976    if(!isTRUE(css.err <- is.one.file.name(css)))
977      stop("Argument `css` ", css.err)
978    if(!isTRUE(js.err <- is.one.file.name(js)))
979      stop("Argument `js` ", js.err)
980
981    if(!is.TF(scale))
982      stop("Argument `scale` must be TRUE or FALSE")
983    valid.html.output <- c("auto", "page", "diff.only", "diff.w.style")
984    if(!string_in(html.output, valid.html.output))
985      stop("Argument `html.output` must be in `", dep(valid.html.output), "`.")
986
987    callNextMethod(
988      .Object, css=css, html.output=html.output, js=js, scale=scale, ...
989    )
990} )
991#' @export StyleHtmlLightRgb
992#' @exportClass StyleHtmlLightRgb
993#' @rdname Style
994
995StyleHtmlLightRgb <- setClass(
996  "StyleHtmlLightRgb", contains=c("StyleHtml", "Light", "Rgb")
997)
998setMethod("initialize", "StyleHtmlLightRgb",
999  function(.Object, ...) {
1000    .Object@funs@container <- cont_f(c("light", "rgb"))
1001    callNextMethod(.Object, ...)
1002  }
1003)
1004#' @export StyleHtmlLightYb
1005#' @exportClass StyleHtmlLightYb
1006#' @rdname Style
1007
1008StyleHtmlLightYb <- setClass(
1009  "StyleHtmlLightYb", contains=c("StyleHtml", "Light", "Yb"),
1010)
1011setMethod("initialize", "StyleHtmlLightYb",
1012  function(.Object, ...) {
1013    .Object@funs@container <- cont_f(c("light", "yb"))
1014    callNextMethod(.Object, ...)
1015  }
1016)
1017# Helper structure for constructing our defaults array
1018
1019.dfs.dims <- list(
1020  format=c("raw", "ansi8", "ansi256", "html"),
1021  brightness=c("neutral", "light", "dark"),
1022  color.mode=c("rgb", "yb")  # add b/w?
1023)
1024.dfs.dims.sizes <- vapply(.dfs.dims, length, integer(1L))
1025.dfs.arr <- array(
1026  vector("list", prod(.dfs.dims.sizes)), dim=.dfs.dims.sizes, dimnames=.dfs.dims
1027)
1028
1029#' Class for Tracking Default Styles by Style Type
1030#'
1031#' Provides a mechanism for specifying a style based on the style properties
1032#' along dimensions of format, brightness, and color.  This allows a user to
1033#' request a style that meets a certain description (e.g. a \dQuote{light}
1034#' scheme in \dQuote{ansi256} format), without having to provide a specific
1035#' \code{\link{Style}} object.
1036#'
1037#' @section An Array of Styles:
1038#'
1039#' A \code{PaletteOfStyles} object is an \dQuote{array} containing either
1040#' \dQuote{classRepresentation} objects that extend \code{StyleHtml} or are
1041#' instances of objects that inherit from \code{StyleHtml}.  The \code{diff*}
1042#' methods then pick an object/class from this array based on the values of
1043#' the \code{format}, \code{brightness}, and \code{color.mode} parameters.
1044#'
1045#' For the most part the distinction between actual \code{Style} objects vs
1046#' \dQuote{classRepresentation} ones is academic, except that with the latter
1047#' you can control the instantiation by providing a parameter list as the
1048#' \code{style} argument to the \code{diff*} methods. This is not an option with
1049#' already instantiated objects.  See examples.
1050#'
1051#' @section Dimensions:
1052#'
1053#' There are three general orthogonal dimensions of styles that can be used when
1054#' rendering diffs: the type of format, the \dQuote{brightness} of the output,
1055#' and whether the colors used are distinguishable if you assume reds and greens
1056#' are not distinguishable.  Defaults for the intersections each of these
1057#' dimensions are encoded as a three dimensional list.  This list is just an
1058#' atomic vector of type \dQuote{list} with a length 3 \code{dim} attribute.
1059#'
1060#' The array/list dimensions are:
1061#' \itemize{
1062#'   \item \code{format}: the format type, one of \dQuote{raw},
1063#'     \dQuote{ansi8}, \dQuote{ansi256}, or \dQuote{html}
1064#'   \item \code{brightness}: whether the colors are bright or not, which
1065#'     allows user to chose a scheme that is compatible with their console,
1066#'     one of: \dQuote{light}, \dQuote{dark}, \dQuote{normal}
1067#'   \item \code{color.mode}: \dQuote{rgb} for full color or \dQuote{yb} for
1068#'     dichromats (yb stands for Yellow Blue).
1069#' }
1070#'
1071#' Each of these dimensions can be specified directly via the corresponding
1072#' parameters to the \code{diff*} methods.
1073#'
1074#' @section Methods:
1075#'
1076#' \code{PaletteOfStyles} objects have The following methods implemented:
1077#' \itemize{
1078#'   \item \code{[}, \code{[<-}, \code{[[}
1079#'   \item show
1080#'   \item summary
1081#'   \item dimnames
1082#' }
1083#' @section Structural Details:
1084#'
1085#' The array/list is stored in the \code{data} slot of
1086#' \code{PaletteOfStyles} objects.  Subsetting methods are provided so you
1087#' may operate directly on the S4 object as you would on a regular array.
1088#'
1089#' The array/list must be fully populated with objects that are or inherit
1090#' \code{Style}, or are \dQuote{classRepresentation} objects (i.e. those of
1091#' the type returned by \code{\link{getClassDef}}) that extend \code{Style}.
1092#' By default the array is populated only with \dQuote{classRepresentation}
1093#' objects as that allows the list form of the \code{style} parameter to the
1094#' \code{diff*} methods.  If there is a particular combination of coordinates
1095#' that does not have a corresponding defined style a reasonable substitution
1096#' must be provided.  For example, this package only defines \dQuote{light}
1097#' HTML styles, so it simply uses that style for all the possible
1098#' \code{brightness} values.
1099#'
1100#' There is no explicit check that the objects in the list comply with the
1101#' descriptions implied by their coordinates, although the default object
1102#' provided by the package does comply for the most part.  One check that is
1103#' carried out is that any element that has a \dQuote{html} value in the
1104#' \code{format} dimension extends \code{StyleHtml}.
1105#'
1106#' While the list may only have the three dimensions described, you can add
1107#' values to the dimensions provided the values described above are the first
1108#' ones in each of their corresponding dimensions.  For example, if you wanted
1109#' to allow for styles that would render in \code{grid} graphics, you could
1110#' generate a default list with a \dQuote{"grid"} value appended to the values
1111#' of the \code{format} dimension.
1112#'
1113#' @export PaletteOfStyles
1114#' @exportClass PaletteOfStyles
1115#' @examples
1116#' \dontrun{
1117#' ## Look at all "ansi256" styles (assumes compatible terminal)
1118#' PaletteOfStyles()["ansi256",,]
1119#' }
1120#' ## Generate the default style object palette, and replace
1121#' ## the ansi256 / light / rgb style with our modified one
1122#' ## which for illustrative purposes is the raw style
1123#' my.pal <- PaletteOfStyles()
1124#' my.style <- StyleRaw()   # See `?Style` for custom styles
1125#' my.style@funs@word.delete <- function(x) sprintf("--%s--", x)
1126#' my.pal["ansi256", "light", "rgb"] <- list(my.style) # note `list()`
1127#' ## Output has no format now for format/color.mode/brightness
1128#' ## we modified ...
1129#' ## `pager="off"` for CRAN compliance; you may omit in normal use
1130#' diffPrint(
1131#'    1:3, 2:5, format="ansi256", color.mode="rgb", brightness="light",
1132#'    palette.of.styles=my.pal, pager="off", disp.width=80
1133#' )
1134#' ## If so desired, set our new style palette as the default
1135#' ## one; could also pass directly as argument to `diff*` funs
1136#' \dontrun{
1137#' options(diffobj.palette=defs)
1138#' }
1139
1140PaletteOfStyles <- setClass(
1141  "PaletteOfStyles",
1142  slots=c(data="array"),
1143  validity=function(object) {
1144    dat <- object@data
1145    valid.names <- names(.dfs.dims)
1146    if(!is.list(dat))
1147      return("Slot `data` must be a dimensioned list")
1148    if(
1149      !is.list(dimnames(dat)) ||
1150      !identical(names(dimnames(dat)), valid.names) ||
1151      !all(vapply(dimnames(dat), is.character, logical(1L))) ||
1152      anyNA(unlist(dat))
1153    )
1154      return(
1155        paste0(
1156          "`dimnames` for default styles must be a list with names `",
1157          paste0(deparse(valid.names), collapse=""), "` and contain only ",
1158          "character vectors with no NA values."
1159      ) )
1160
1161    if(
1162      !all(
1163        vapply(
1164          valid.names,
1165          function(x) identical(
1166            .dfs.dims[[x]], head(dimnames(dat)[[x]], length(.dfs.dims[[x]]))
1167          ),
1168          logical(1L)
1169    ) ) )
1170      return("Style dimension names do not contain all required values")
1171
1172    # May be either style objects or Style Class definitions
1173
1174    style.def <- getClassDef("Style", package="diffobj")
1175    are.styles <- vapply(dat, is, logical(1L), "Style")
1176    are.styles.def <- logical(length(are.styles))
1177    are.styles.def[!are.styles] <- vapply(
1178      dat[!are.styles],
1179      function(x) is(x, "classRepresentation") && extends(x, style.def),
1180      logical(1L)
1181    )
1182    if(!all(are.styles | are.styles.def))
1183      return(
1184        paste0(
1185          "Styles may only contain objects that inherit from `Style` or class ",
1186          "definitions that extend `Style`"
1187      ) )
1188    if(
1189      !all(
1190        vapply(
1191          dat["html", ,],
1192          function(x)
1193            is(x, "classRepresentation") && extends(x, "StyleHtml") ||
1194            is(x, "StyleHtml"),
1195          logical(1L)
1196        )
1197      )
1198    )
1199      return("Styles classifed as HTML must extend `StyleHtml`")
1200    TRUE
1201  }
1202)
1203setMethod("initialize", "PaletteOfStyles",
1204  function(.Object, ...) {
1205    .dfs.arr["raw", , ] <- list(
1206      getClassDef("StyleRaw", package="diffobj", inherits=FALSE)
1207    )
1208    .dfs.arr["ansi8", , "rgb"] <- list(
1209      getClassDef("StyleAnsi8NeutralRgb", package="diffobj", inherits=FALSE)
1210    )
1211    .dfs.arr["ansi8", , "yb"] <- list(
1212      getClassDef("StyleAnsi8NeutralYb", package="diffobj", inherits=FALSE)
1213    )
1214    .dfs.arr["ansi256", "neutral", "rgb"] <- list(
1215      getClassDef("StyleAnsi8NeutralRgb", package="diffobj", inherits=FALSE)
1216    )
1217    .dfs.arr["ansi256", "neutral", "yb"] <- list(
1218      getClassDef("StyleAnsi8NeutralYb", package="diffobj", inherits=FALSE)
1219    )
1220    .dfs.arr["ansi256", "light", "rgb"] <- list(
1221      getClassDef("StyleAnsi256LightRgb", package="diffobj", inherits=FALSE)
1222    )
1223    .dfs.arr["ansi256", "light", "yb"] <- list(
1224      getClassDef("StyleAnsi256LightYb", package="diffobj", inherits=FALSE)
1225    )
1226    .dfs.arr["ansi256", "dark", "rgb"] <- list(
1227      getClassDef("StyleAnsi256DarkRgb", package="diffobj", inherits=FALSE)
1228    )
1229    .dfs.arr["ansi256", "dark", "yb"] <- list(
1230      getClassDef("StyleAnsi256DarkYb", package="diffobj", inherits=FALSE)
1231    )
1232    .dfs.arr["html", , "rgb"] <- list(
1233      getClassDef("StyleHtmlLightRgb", package="diffobj", inherits=FALSE)
1234    )
1235    .dfs.arr["html", , "yb"] <- list(
1236      getClassDef("StyleHtmlLightYb", package="diffobj", inherits=FALSE)
1237    )
1238    .Object@data <- .dfs.arr
1239    callNextMethod(.Object, ...)
1240  }
1241)
1242#' @rdname Extract_PaletteOfStyles
1243
1244setReplaceMethod(
1245  "[", signature=c(x="PaletteOfStyles"),
1246  function(x, i, j, ..., value) {
1247    x@data[i, j, ...] <- value
1248    validObject(x)
1249    x
1250} )
1251#' @rdname Extract_PaletteOfStyles
1252
1253setMethod(
1254  "[", signature=c(x="PaletteOfStyles"),
1255  function(x, i, j, ..., drop=FALSE) {
1256    x@data <- x@data[i, j, ..., drop=drop]
1257    x
1258  }
1259)
1260#' Extract/Replace a Style Class or Object from PaletteOfStyles
1261#'
1262#' @rdname Extract_PaletteOfStyles
1263#' @seealso \code{\link{diffPrint}} for explanations of \code{format},
1264#'   \code{brightness}, and \code{color.mode}
1265#' @param x a \code{\link{PaletteOfStyles}} object
1266#' @param i numeric, or character corresponding to a valid style \code{format}
1267#' @param j numeric, or character corresponding to a valid style
1268#'   \code{brightness}
1269#' @param ... pass a numeric or character corresponding to a valid
1270#'   \code{color.mode}
1271#' @param exact passed on to generic
1272#' @param drop TRUE or FALSE, whether to drop dimensions, defaults to FALSE,
1273#'   which is different than generic
1274#' @param value a \emph{list} of \code{\link{Style}} class or
1275#'   \code{\link{Style}} objects
1276#' @return a \code{\link{Style}} \code{ClassRepresentation} object or
1277#'    \code{\link{Style}} object for \code{[[}, and a list of the same for
1278#'    \code{[}
1279#' @examples
1280#' pal <- PaletteOfStyles()
1281#' pal[["ansi256", "light", "rgb"]]
1282#' pal["ansi256", "light", ]
1283#' pal["ansi256", "light", "rgb"] <- list(StyleAnsi8NeutralRgb())
1284
1285setMethod(
1286  "[[", signature=c(x="PaletteOfStyles"),
1287  function(x, i, j, ..., exact=TRUE) {
1288    x@data[[i, j, ..., exact=exact]]
1289  }
1290)
1291#' Retrieve Dimnames for PaletteOfStyles Objects
1292#'
1293#' @param x a \code{\link{PaletteOfStyles}} object
1294#' @return list the dimension names
1295#' dimnames(PaletteOfStyles())
1296
1297setMethod("dimnames", "PaletteOfStyles", function(x) dimnames(x@data))
1298
1299# Matrices used for show methods for styles
1300
1301.mx1 <- .mx2 <- matrix(1:50, ncol=2)
1302.mx2[c(6L, 40L)] <- 99L
1303.mx2 <- .mx2[-7L,]
1304
1305#' Show Method for Style Objects
1306#'
1307#' Display a small sample diff with the Style object styles applied.  For
1308#' ANSI light and dark styles, will also temporarily set the background and
1309#' foreground colors to ensure they are compatible with the style, even though
1310#' this is not done in normal output (i.e. if you intend on using a
1311#' \dQuote{light} style, you should set your terminal background color to be
1312#' light or expect sub-optimal rendering).
1313#'
1314#' @param object a \code{Style} S4 object
1315#' @return NULL, invisibly
1316#' @examples
1317#' show(StyleAnsi256LightYb())  # assumes ANSI colors supported
1318
1319setMethod("show", "Style",
1320  function(object) {
1321    cat(sprintf("Object of class `%s`:\n\n", class(object)))
1322    d.p <- diffPrint(
1323      .mx1, .mx2, context=1, line.limit=7L,
1324      style=object, pager=PagerOff(),
1325      tar.banner="diffobj:::.mx1", cur.banner="diffobj:::.mx2",
1326      sgr.supported=if(is(object, "Ansi")) TRUE
1327    )
1328    d.txt <- capture.output(show(d.p))
1329    if(is(object, "Ansi")) {
1330      old.opt <- options(crayon.enabled=TRUE)
1331      on.exit(options(old.opt))
1332      pad.width <- max(nchar2(d.txt, sgr.supported=TRUE))
1333      d.txt <- rpad(d.txt, width=pad.width, sgr.supported=TRUE)
1334      bgWhite <- crayon::make_style(rgb(1, 1, 1), bg=TRUE, colors=256)
1335      white <- crayon::make_style(rgb(1, 1, 1), colors=256)
1336      if(is(object, "Light")) {
1337        d.txt <- bgWhite(crayon::black(d.txt))
1338      } else if (is(object, "Dark")) {
1339        d.txt <- crayon::bgBlack(white(d.txt))
1340      }
1341      options(old.opt)
1342      on.exit(NULL)
1343      if(is(object, "Light") || is(object, "Dark")) {
1344        d.txt <- c(
1345          d.txt, "",
1346          strwrap(
1347            paste0(
1348              "Default bg and fg colors forced to appropriate colors for ",
1349              "scheme; this does not happen in actual use."
1350            ),
1351            width=pad.width + 20L
1352    ) ) } }
1353    cat(d.txt, sep="\n")
1354    invisible(NULL)
1355} )
1356#' @rdname show-Style-method
1357
1358setMethod("show", "StyleHtml",
1359  function(object) {
1360    cat(sprintf("Class `%s` sample output:\n\n", class(object)))
1361    cat("[Object Renders in HTML]\n")
1362    invisible(NULL)
1363} )
1364#' Display a PaletteOfStyles
1365#'
1366#' @param object a \code{\link{PaletteOfStyles}} object
1367#' @return NULL, invisibly
1368
1369setMethod("show", "PaletteOfStyles",
1370  function(object) {
1371    fmt <- dimnames(object)$format
1372    brt <- dimnames(object)$brightness
1373    clr <- dimnames(object)$color.mode
1374
1375    for(f in fmt) {
1376      for(b in brt) {
1377        for(c in clr) {
1378          obj <- object[[f, b, c]]
1379          if(is(obj, "classRepresentation")) obj <- new(obj)
1380          txt <- capture.output(show(obj))
1381          cat(
1382            sprintf("\nformat: %s, brightness: %s, color.mode: %s\n\n", f, b, c)
1383          )
1384          cat(paste0("  ", txt), sep="\n")
1385} } } } )
1386#' Display a Summarized Version of a PaletteOfStyles
1387#'
1388#' @param object a \code{\link{PaletteOfStyles}} object
1389#' @param ... unused, for compatibility with generic
1390#' @return character representation showing classes and/or objects in
1391#'   PaletteOfStyles
1392#' summary(PaletteOfStyles())
1393
1394setMethod("summary", "PaletteOfStyles",
1395  function(object, ...)
1396    apply(
1397      object@data,
1398      1:3,
1399      function(x)
1400        if(is(x[[1L]], "classRepresentation"))
1401          paste0("class: ", x[[1L]]@className) else
1402          paste0("object: ", class(x[[1L]]))
1403    )
1404)
1405
1406# Helper function to render output for vignette
1407
1408display_ansi_256_styles <- function() {
1409  styles <- lapply(
1410    list(
1411      StyleAnsi8NeutralYb(), StyleAnsi8NeutralRgb(),
1412      StyleAnsi256DarkYb(), StyleAnsi256DarkRgb(),
1413      StyleAnsi256LightYb(), StyleAnsi256LightRgb()
1414    ),
1415    function(x) capture.output(show(x))[3:9]
1416  )
1417  names <- c("Neutral", "Dark", "Light")
1418  cat("\n")
1419  lapply(
1420    1:3,
1421    function(x) {
1422      cat(paste0(" ", names[x]), "\n\n")
1423      cat(paste("   ", styles[[x * 2 - 1]], " ", styles[[x * 2]]), sep="\n")
1424      cat("\n")
1425    }
1426  )
1427  invisible(NULL)
1428}
1429