Skip to content

Commit

Permalink
Allow substitution in subscripts after log, based on config
Browse files Browse the repository at this point in the history
Also, documented the config disableAutoSubstitutionInSubscripts
  • Loading branch information
jared-hughes committed Oct 16, 2024
1 parent f71f190 commit 5ab5501
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 7 deletions.
8 changes: 8 additions & 0 deletions docs/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
24 changes: 22 additions & 2 deletions src/commands/math/basicSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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]) {
Expand Down Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion src/mathquill.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ declare namespace MathQuill {
logAriaAlerts?: boolean;
autoParenthesizedFunctions?: string;
quietEmptyDelimiters?: string;
disableAutoSubstitutionInSubscripts?: boolean;
disableAutoSubstitutionInSubscripts?: boolean | { except: string };
interpretTildeAsSim?: boolean;
handlers?: HandlerOptions<BaseMathQuill<$>>;
}
Expand Down
7 changes: 5 additions & 2 deletions src/publicapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ const processedOptions = {
prefixOperatorNames: true,
leftRightIntoCmdGoes: true,
maxDepth: true,
interpretTildeAsSim: true
interpretTildeAsSim: true,
disableAutoSubstitutionInSubscripts: true
};
type ProcessedOption = keyof typeof processedOptions;

Expand Down Expand Up @@ -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;
Expand Down
14 changes: 13 additions & 1 deletion src/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 22 additions & 1 deletion test/unit/autoOperatorNames.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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($('<span></span>').appendTo('#mock')[0]);
Expand Down Expand Up @@ -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 () {
Expand Down

0 comments on commit 5ab5501

Please sign in to comment.