diff --git a/docs/Config.md b/docs/Config.md index b22decffd..bf5b6225b 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -131,6 +131,14 @@ For static and editable math fields, when `tabbable` is false, the math field is Static math fields default to `tabbable: false`, Editable math fields default to `tabbable:true`. +## disableAutoSubstitutionInSubscripts + +If `disableAutoSubstitutionInSubscripts` is `false` (default), then typing auto command names such as `int` will expand to `\int`, even in subscripts. + +If `disableAutoSubstitutionInSubscripts` is `true`, then such expansions are disabled in all subscripts, so users can type `A_{point}` without getting `A_{po\int}`. + +If `disableAutoSubstitutionInSubscripts` is `{except: "log"}`, then such expansions are disabled in all subscripts, _except_ for after `\log`, so users can type `\log_{\pi}(x)` again. Just like [`autoCommands`](#autocommands) above, the `except` property should be a string formatted as a space-delimited list of LaTeX commands. + # Handlers Handlers are called after a specified event. They are called directly on the `handlers` object passed in, preserving the `this` value, so you can do stuff like: diff --git a/src/commands/math/basicSymbols.ts b/src/commands/math/basicSymbols.ts index 9211e7ca2..8a6a5eb64 100644 --- a/src/commands/math/basicSymbols.ts +++ b/src/commands/math/basicSymbols.ts @@ -410,11 +410,16 @@ function letterSequenceEndingAtNode(node: NodeRef, maxLength: number) { class Letter extends Variable { letter: string; - /** If this is the last letter of an operatorname (`\operatorname{arcsinh}`) + /** + * If this is the last letter of an operatorname (`\operatorname{arcsinh}`) * or builtin (`\sin`), give its category, based on infixOperatorNames * and prefixOperatorNames. E.g. "for" may be infix and "sin" may be prefix. */ endsCategory?: undefined | 'infix' | 'prefix'; + /** + * Similarly to endsCategory, give the string name, e.g. "sin" or "arcsinh". + */ + endsWord?: string; constructor(ch: string) { super(ch); @@ -502,7 +507,10 @@ class Letter extends Variable { italicize(bool: boolean) { this.isItalic = bool; this.isPartOfOperator = !bool; - if (bool) delete this.endsCategory; + if (bool) { + delete this.endsCategory; + delete this.endsWord; + } this.domFrag().toggleClass('mq-operator-name', !bool); return this; } @@ -584,6 +592,7 @@ class Letter extends Variable { first.ctrlSeq = (isBuiltIn ? '\\' : '\\operatorname{') + first.ctrlSeq; last.ctrlSeq += isBuiltIn ? ' ' : '}'; + last.endsWord = word; if (opts.infixOperatorNames[word]) { last.endsCategory = 'infix'; } else if (opts.prefixOperatorNames[word]) { @@ -740,6 +749,17 @@ baseOptionProcessors.infixOperatorNames = splitWordsIntoDict; Options.prototype.prefixOperatorNames = {}; baseOptionProcessors.prefixOperatorNames = splitWordsIntoDict; +Options.prototype.disableAutoSubstitutionInSubscripts = false; +baseOptionProcessors.disableAutoSubstitutionInSubscripts = function ( + opt: unknown +) { + if (typeof opt === 'boolean') return opt; + if (typeof opt !== 'object' || opt === null || !('except' in opt)) { + throw '"' + opt + '" not an object with property "except"'; + } + return { except: splitWordsIntoDict((opt as any).except) }; +}; + function splitWordsIntoDict(cmds: unknown) { if (typeof cmds !== 'string') { throw '"' + cmds + '" not a space-delimited list'; diff --git a/src/mathquill.d.ts b/src/mathquill.d.ts index ea73df4bc..34751c201 100644 --- a/src/mathquill.d.ts +++ b/src/mathquill.d.ts @@ -127,7 +127,7 @@ declare namespace MathQuill { logAriaAlerts?: boolean; autoParenthesizedFunctions?: string; quietEmptyDelimiters?: string; - disableAutoSubstitutionInSubscripts?: boolean; + disableAutoSubstitutionInSubscripts?: boolean | { except: string }; interpretTildeAsSim?: boolean; handlers?: HandlerOptions>; } diff --git a/src/publicapi.ts b/src/publicapi.ts index ae5696ef5..c05ba8c01 100644 --- a/src/publicapi.ts +++ b/src/publicapi.ts @@ -61,7 +61,8 @@ const processedOptions = { prefixOperatorNames: true, leftRightIntoCmdGoes: true, maxDepth: true, - interpretTildeAsSim: true + interpretTildeAsSim: true, + disableAutoSubstitutionInSubscripts: true }; type ProcessedOption = keyof typeof processedOptions; @@ -122,7 +123,9 @@ class Options { autoCommands: AutoDict; autoParenthesizedFunctions: AutoDict; quietEmptyDelimiters: { [id: string]: any }; - disableAutoSubstitutionInSubscripts?: boolean; + disableAutoSubstitutionInSubscripts?: + | boolean + | { except: { [name in string]?: true } }; interpretTildeAsSim: boolean; handlers?: { fns: HandlerOptions; diff --git a/src/tree.ts b/src/tree.ts index 38eff025e..5dafe7b84 100644 --- a/src/tree.ts +++ b/src/tree.ts @@ -292,10 +292,22 @@ class NodeBase { } shouldIgnoreSubstitutionInSimpleSubscript(options: CursorOptions) { - if (!options.disableAutoSubstitutionInSubscripts) return false; + const opt = options.disableAutoSubstitutionInSubscripts; + if (!opt) return false; if (!this.parent) return false; if (!(this.parent.parent instanceof SupSub)) return false; + // Allow substitution in e.g. log subscripts + const before = this.parent.parent[L]; + if ( + typeof opt === 'object' && + before instanceof Letter && + before.endsWord && + opt.except[before.endsWord] + ) { + return false; + } + // Mathquill is gross. There are many different paths that // create subscripts and sometimes we don't even construct // true instances of `LatexCmds._`. Another problem is that diff --git a/test/unit/autoOperatorNames.test.js b/test/unit/autoOperatorNames.test.js index 1ffe4ebcf..8dd8bf394 100644 --- a/test/unit/autoOperatorNames.test.js +++ b/test/unit/autoOperatorNames.test.js @@ -8,6 +8,10 @@ suite('autoOperatorNames', function () { autoCommands: 'sum int', disableAutoSubstitutionInSubscripts: true }; + const subscriptConfigNoLog = { + autoCommands: 'sum int', + disableAutoSubstitutionInSubscripts: { except: 'log' } + }; setup(function () { mq = MQ.MathField($('').appendTo('#mock')[0]); @@ -78,17 +82,34 @@ suite('autoOperatorNames', function () { assertLatex('int allows operatorname', '\\int_{\\sin}^{ }'); }); + test('works in subscript after log, based on "except" option', function () { + // log subscript without config option + mq.config(subscriptConfig); + mq.typedText('log_'); + mq.typedText('sin'); + assertLatex('subscripts do not turn to operatorname', '\\log_{sin}'); + + // log subscript + mq.latex(''); + mq.config(subscriptConfigNoLog); + mq.typedText('log_'); + mq.typedText('sin'); + assertLatex('log subscript does turn to operatorname', '\\log_{\\sin}'); + }); + test('no auto operator names in simple subscripts when typing', function () { + // normal mq.config(normalConfig); mq.typedText('x_'); mq.typedText('sin'); assertLatex('subscripts turn to operatorname', 'x_{\\sin}'); + + // subscript config mq.latex(''); mq.config(subscriptConfig); mq.typedText('x_'); mq.typedText('sin'); assertLatex('subscripts do not turn to operatorname', 'x_{sin}'); - mq.config(normalConfig); }); test('no auto operator names in simple subscripts when pasting', function () {