[visual] Apply style to the content of color commands (#13726)

GitOrigin-RevId: 4ae8b745618e91b487d17c357cdb0e697038b3a3
This commit is contained in:
Alf Eaton 2023-07-18 11:52:57 +01:00 committed by Copybot
parent ca9593e74c
commit 08c82a24a9
6 changed files with 148 additions and 2 deletions

View file

@ -46,7 +46,12 @@ import { InlineGraphicsWidget } from './visual-widgets/inline-graphics'
import getMeta from '../../../../utils/meta'
import { EditableGraphicsWidget } from './visual-widgets/editable-graphics'
import { EditableInlineGraphicsWidget } from './visual-widgets/editable-inline-graphics'
import { CloseBrace, OpenBrace } from '../../lezer-latex/latex.terms.mjs'
import {
CloseBrace,
OpenBrace,
ShortTextArgument,
TextArgument,
} from '../../lezer-latex/latex.terms.mjs'
import { FootnoteWidget } from './visual-widgets/footnote'
import { getListItems } from '../toolbar/lists'
import { TildeWidget } from './visual-widgets/tilde'
@ -868,6 +873,24 @@ export const atomicDecorations = (options: Options) => {
const { name, label } = result
theoremEnvironments.set(name, label)
}
} else if (
nodeRef.type.is('TextColorCommand') ||
nodeRef.type.is('ColorBoxCommand')
) {
if (shouldDecorate(state, nodeRef)) {
const colorArgumentNode = nodeRef.node.getChild(ShortTextArgument)
const contentArgumentNode = nodeRef.node.getChild(TextArgument)
if (colorArgumentNode && contentArgumentNode) {
// command name and opening brace
decorations.push(
...decorateArgumentBraces(
new BraceWidget(),
contentArgumentNode,
nodeRef.from
)
)
}
}
} else if (nodeRef.type.is('UnknownCommand')) {
// a command that's not defined separately by the grammar
const commandNode = nodeRef.node

View file

@ -10,6 +10,7 @@ import { getUnstarredEnvironmentName } from '../../utils/tree-operations/environ
import { centeringNodeForEnvironment } from '../../utils/tree-operations/figure'
import { parseTheoremStyles } from '../../utils/tree-operations/theorems'
import { Tree } from '@lezer/common'
import { parseColorArguments } from '../../utils/tree-operations/colors'
/**
* A view plugin that decorates ranges of text with Mark decorations.
@ -105,6 +106,40 @@ export const markDecorations = ViewPlugin.define(
)
}
}
} else if (nodeRef.type.is('TextColorCommand')) {
const result = parseColorArguments(state, nodeRef.node)
if (result) {
const { color, from, to } = result
// decorate the content
decorations.push(
Decoration.mark({
class: 'ol-cm-textcolor',
inclusive: true,
attributes: {
style: `color: ${color}`,
},
}).range(from, to)
)
}
} else if (nodeRef.type.is('ColorBoxCommand')) {
const result = parseColorArguments(state, nodeRef.node)
if (result) {
const { color, from, to } = result
// decorate the content
decorations.push(
Decoration.mark({
class: 'ol-cm-colorbox',
inclusive: true,
attributes: {
style: `background-color: ${color}`,
},
}).range(from, to)
)
}
} else if (nodeRef.type.is('$Environment')) {
const environmentName = getUnstarredEnvironmentName(
nodeRef.node,

View file

@ -87,7 +87,9 @@
BibliographyCtrlSeq,
BibliographyStyleCtrlSeq,
AuthorCtrlSeq,
MaketitleCtrlSeq
MaketitleCtrlSeq,
TextColorCtrlSeq,
ColorBoxCtrlSeq
}
@external specialize {EnvName} specializeEnvName from "./tokens.mjs" {
@ -239,6 +241,12 @@ KnownCommand {
UsePackageCtrlSeq optionalWhitespace? OptionalArgument?
PackageArgument
} |
TextColorCommand {
TextColorCtrlSeq optionalWhitespace? ShortTextArgument optionalWhitespace? TextArgument
} |
ColorBoxCommand {
ColorBoxCtrlSeq optionalWhitespace? ShortTextArgument optionalWhitespace? TextArgument
} |
HrefCommand {
HrefCtrlSeq optionalWhitespace? UrlArgument ShortTextArgument
} |

View file

@ -68,6 +68,8 @@ import {
CenteringCtrlSeq,
ListEnvName,
MaketitleCtrlSeq,
TextColorCtrlSeq,
ColorBoxCtrlSeq,
} from './latex.terms.mjs'
function nameChar(ch) {
@ -639,6 +641,8 @@ const otherKnowncommands = {
'\\bibliography': BibliographyCtrlSeq,
'\\bibliographystyle': BibliographyStyleCtrlSeq,
'\\maketitle': MaketitleCtrlSeq,
'\\textcolor': TextColorCtrlSeq,
'\\colorbox': ColorBoxCtrlSeq,
}
// specializer for control sequences
// return new tokens for specific control sequences

View file

@ -0,0 +1,58 @@
import { EditorState } from '@codemirror/state'
import { SyntaxNode } from '@lezer/common'
import {
LongArg,
ShortArg,
ShortTextArgument,
TextArgument,
} from '../../lezer-latex/latex.terms.mjs'
// basic color definitions from the xcolor package
// https://github.com/latex3/xcolor/blob/849682246582946835d28c8f9b2081ff2c340e09/xcolor.dtx#L7051-L7093
const colors = new Map<string, string>([
['red', 'rgb(255,0,0)'],
['green', 'rgb(0,255,0)'],
['blue', 'rgb(0,0,255)'],
['brown', 'rgb(195,127,63)'],
['lime', 'rgb(195,255,0)'],
['orange', 'rgb(255,127,0)'],
['pink', 'rgb(255,195,195)'],
['purple', 'rgb(195,0,63)'],
['teal', 'rgb(0,127,127)'],
['violet', 'rgb(127,0,127)'],
['cyan', 'rgb(0,255,255)'],
['magenta', 'rgb(255,0,255)'],
['yellow', 'rgb(255,255,0)'],
['olive', 'rgb(127,127,0)'],
['black', 'rgb(0,0,0)'],
['darkgray', 'rgb(63,63,63)'],
['gray', 'rgb(127,127,127)'],
['lightgray', 'rgb(195,195,195)'],
['white', 'rgb(255,255,255)'],
])
export const parseColorArguments = (
state: EditorState,
node: SyntaxNode
): { color: string; from: number; to: number } | undefined => {
const colorArgumentNode = node.getChild(ShortTextArgument)?.getChild(ShortArg)
const contentArgumentNode = node.getChild(TextArgument)?.getChild(LongArg)
if (colorArgumentNode && contentArgumentNode) {
const { from, to } = contentArgumentNode
if (to > from) {
const colorName = state
.sliceDoc(colorArgumentNode.from, colorArgumentNode.to)
.trim()
if (colorName) {
const color = colors.get(colorName)
if (color) {
return { color, from, to }
}
}
}
}
}

View file

@ -566,6 +566,24 @@ describe('<CodeMirrorEditor/> in Visual mode', function () {
)
})
describe('decorates color commands', function () {
it('decorates textcolor', function () {
cy.get('@first-line').type('\\textcolor{{}red}{{}foo}')
cy.get('.ol-cm-textcolor')
.should('have.length', 1)
.should('have.text', 'foo')
.should('have.attr', 'style', 'color: rgb(255,0,0)')
})
it('decorates colorbox', function () {
cy.get('@first-line').type('\\colorbox{{}yellow}{{}foo}')
cy.get('.ol-cm-colorbox')
.should('have.length', 1)
.should('have.text', 'foo')
.should('have.attr', 'style', 'background-color: rgb(255,255,0)')
})
})
describe('handling of special characters', function () {
it('decorates a tilde with a non-breaking space', function () {
cy.get('@first-line').type('Test~test')