-
-
Notifications
You must be signed in to change notification settings - Fork 65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhance Matrix Shortcut with Multi-selection #394
Open
Testudinidae
wants to merge
16
commits into
artisticat1:main
Choose a base branch
from
Testudinidae:feature/matrix-shortcut
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
dbd1298
feat: refactor and add multi-selection support
Testudinidae 867e078
feat: trim whitespace surrounding cursor on Tab and Enter
Testudinidae 845c056
feat: make trimming whitespace optional
Testudinidae b93b132
feat: trim extra '&' at end of line on Enter
Testudinidae bc92438
feat: disable auto '\\' insertion after '\hline' on Enter
Testudinidae 836846f
feat: make disabling '\\' insertion after '\hline' optional
Testudinidae 6f4f89b
refactor: split isWithinEnvironment into isWithinEnvironment and getE…
Testudinidae d4c0422
Merge branch 'feature/matrix-shortcuts-multi-selection' into feature/…
Testudinidae 40ef922
feat: trim empty line when exiting an environment with Shift+Enter
Testudinidae 0b97c4c
feat: make trimming empty line after environment optional
Testudinidae acf6688
feat: add line break when exiting an environment with Shift+Enter
Testudinidae fac502f
feat: make adding line break after environment optional
Testudinidae 7f21a4b
Merge branch 'feature/matrix-shortcuts-add-line-break-after-environme…
Testudinidae f53e552
feat: make trimming alignment optional
Testudinidae dc78f1a
Merge branch 'feature/matrix-shortcut-hline-skip' into feature/matrix…
Testudinidae 46f495b
Merge branch 'feature/matrix-shortcuts-after-environment' into featur…
Testudinidae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,174 @@ | ||
import { EditorView } from "@codemirror/view"; | ||
import { setCursor } from "src/utils/editor_utils"; | ||
import { getLatexSuiteConfig } from "src/snippets/codemirror/config"; | ||
import { EditorSelection, SelectionRange } from "@codemirror/state"; | ||
import { Context } from "src/utils/context"; | ||
import { setCursor, replaceRange } from "src/utils/editor_utils"; | ||
import { getLatexSuiteConfig } from "src/snippets/codemirror/config"; | ||
import { tabout } from "src/features/tabout"; | ||
|
||
|
||
const ALIGNMENT = " & "; | ||
const LINE_BREAK = " \\\\\n" | ||
const LINE_BREAK_INLINE = " \\\\ " | ||
const END_LINE_BREAK = LINE_BREAK.trimEnd(); | ||
let trimWhitespace = false; | ||
let trimAlignment = false; | ||
let hlineLineBreakEnabled = false; | ||
let trimEmptyLineAfterEnv = false; | ||
let addLineBreakAfterEnv = false; | ||
|
||
|
||
const isLineBreak = (separator: string) => { | ||
return separator.contains("\\\\"); | ||
} | ||
|
||
|
||
const isMultiLineBreak = (separator: string): boolean => { | ||
return separator.contains("\\\\") && separator.contains("\n"); | ||
} | ||
|
||
|
||
const isHline = (line: string): boolean => { | ||
return line.trimEnd().endsWith("\\hline"); | ||
} | ||
|
||
|
||
const generateSeparatorChange = (separator: string, view: EditorView, range: SelectionRange): { from: number, to: number, insert: string } => { | ||
const d = view.state.doc; | ||
|
||
const fromLine = d.lineAt(range.from); | ||
const textBeforeFrom = d.sliceString(fromLine.from, range.from).trimStart(); // Preserve indents | ||
|
||
const toLine = d.lineAt(range.from); | ||
const textAfterTo = d.sliceString(range.to, toLine.to); | ||
|
||
let from = range.from; | ||
let to = range.to; | ||
|
||
if (!hlineLineBreakEnabled && isMultiLineBreak(separator) && isHline(textBeforeFrom)) { | ||
separator = "\n"; | ||
} | ||
|
||
if (trimWhitespace) { | ||
// If at the beginning of the line | ||
if (textBeforeFrom === "") { | ||
separator = separator.match(/^[ \t]*([\s\S]*)$/)[1]; | ||
} | ||
|
||
// Extend selection to include trailing whitespace before `from` | ||
if (trimAlignment && isLineBreak(separator)) { | ||
from -= textBeforeFrom.match(/\s*\&?\s*$/)[0].length; | ||
} | ||
else { | ||
from -= textBeforeFrom.match(/\s*$/)[0].length; | ||
} | ||
to += textAfterTo.match(/^\s*/)[0].length; // Extend selection to include leading whitespace after `to` | ||
} | ||
|
||
// Insert indents | ||
const leadingIndents = fromLine.text.match(/^\s*/)[0]; | ||
separator = separator.replaceAll("\n", `\n${leadingIndents}`); | ||
|
||
return { from: from, to: to, insert: separator }; | ||
} | ||
|
||
|
||
const applySeparator = (separator: string, view: EditorView) => { | ||
const sel = view.state.selection; | ||
const changes = sel.ranges.map(range => generateSeparatorChange(separator, view, range)); | ||
|
||
const tempTransaction = view.state.update({ changes }); | ||
|
||
const newSelection = EditorSelection.create( | ||
changes.map(({ from, to, insert }) => | ||
EditorSelection.cursor(tempTransaction.changes.mapPos(from) + insert.length) | ||
), | ||
sel.mainIndex | ||
); | ||
|
||
view.dispatch(view.state.update({ changes, selection: newSelection })); | ||
} | ||
|
||
|
||
export const runMatrixShortcuts = (view: EditorView, ctx: Context, key: string, shiftKey: boolean): boolean => { | ||
const settings = getLatexSuiteConfig(view); | ||
|
||
// Check whether we are inside a matrix / align / case environment | ||
let isInsideAnEnv = false; | ||
|
||
let env; | ||
for (const envName of settings.matrixShortcutsEnvNames) { | ||
const env = { openSymbol: "\\begin{" + envName + "}", closeSymbol: "\\end{" + envName + "}" }; | ||
env = { openSymbol: "\\begin{" + envName + "}", closeSymbol: "\\end{" + envName + "}" }; | ||
|
||
isInsideAnEnv = ctx.isWithinEnvironment(ctx.pos, env); | ||
if (isInsideAnEnv) break; | ||
} | ||
|
||
if (!isInsideAnEnv) return false; | ||
|
||
// Take main cursor since ctx.mode takes the main cursor, weird behaviour is expected with multicursor because of this. | ||
trimWhitespace = settings.matrixShortcutsTrimWhitespace; | ||
trimAlignment = settings.matrixShortcutsTrimAlignment; | ||
hlineLineBreakEnabled = settings.matrixShortcutsHlineLineBreakEnabled; | ||
|
||
if (key === "Tab" && view.state.selection.main.empty) { | ||
view.dispatch(view.state.replaceSelection(" & ")); | ||
applySeparator(ALIGNMENT, view); | ||
|
||
return true; | ||
} | ||
|
||
else if (key === "Enter") { | ||
if (shiftKey && ctx.mode.blockMath) { | ||
// Move cursor to end of next line | ||
const d = view.state.doc; | ||
if (shiftKey) { | ||
if (ctx.mode.inlineMath) { | ||
tabout(view, ctx); | ||
} | ||
else { | ||
// Move cursor to end of next line | ||
let d = view.state.doc; | ||
const envBound = ctx.getEnvironmentBound(ctx.pos, env); | ||
const envText = d.sliceString(envBound.start, envBound.end); | ||
|
||
const nextLineNo = d.lineAt(ctx.pos).number + 1; | ||
const nextLine = d.line(nextLineNo); | ||
trimEmptyLineAfterEnv = settings.matrixShortcutsTrimEmptyLineAfterEnv; | ||
addLineBreakAfterEnv = settings.matrixShortcutsAddLineBreakAfterEnv; | ||
|
||
setCursor(view, nextLine.to); | ||
} | ||
else if (shiftKey && ctx.mode.inlineMath) { | ||
tabout(view, ctx); | ||
} | ||
else if (ctx.mode.blockMath) { | ||
const d = view.state.doc; | ||
const lineText = d.lineAt(ctx.pos).text; | ||
const matchIndents = lineText.match(/^\s*/); | ||
const leadingIndents = matchIndents ? matchIndents[0] : ""; | ||
let line = d.lineAt(ctx.pos); | ||
let lineNo = line.number; | ||
let nextLine = d.line(lineNo + 1); | ||
let newPos = nextLine.to; | ||
|
||
view.dispatch(view.state.replaceSelection(` \\\\\n${leadingIndents}`)); | ||
if (newPos > envBound.end) { | ||
if (trimEmptyLineAfterEnv && line.text.trim() === "") { | ||
replaceRange(view, line.from, nextLine.from, ""); | ||
|
||
d = view.state.doc; | ||
lineNo--; | ||
line = d.line(lineNo); | ||
nextLine = d.line(lineNo + 1); | ||
newPos = nextLine.to; | ||
} | ||
|
||
if (addLineBreakAfterEnv && !envText.trimEnd().endsWith("\\\\")) { | ||
setCursor(view, line.to); | ||
|
||
applySeparator(END_LINE_BREAK, view); | ||
|
||
d = view.state.doc; | ||
nextLine = d.line(lineNo + 1); | ||
newPos = nextLine.to; | ||
} | ||
} | ||
|
||
setCursor(view, newPos); | ||
} | ||
} | ||
else { | ||
view.dispatch(view.state.replaceSelection(" \\\\ ")); | ||
if (ctx.mode.inlineMath) { | ||
applySeparator(LINE_BREAK_INLINE, view); | ||
} | ||
else { | ||
applySeparator(LINE_BREAK, view); | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
else { | ||
return false; | ||
} | ||
|
||
return false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry to be nitpicky but why was this removed. Weird behavior still arises when the main cursor is in different environment then the other cursors.
For example if nothing is selected in the main cursor but something is selected in another cursor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to apologize—I believe all features should be discussed to find the most convenient solution for everyone. I'm not sure what you mean by "weird behavior." I think in multi-selection cases, all selections should behave consistently—either all selected lines increase indentation or all selections are replaced with " & ", always following the main selection. Other potentially strange behaviors, like some selections being outside the environment or even outside the math block, shouldn’t be an issue because users using multi-selection should clearly know what they are doing. I hope you can provide more specific examples of what you mean by "weird behavior."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My original thought was that a cursor according to its "environment" (like math, code or text).
So any cursor that doesn't follow the behaviour of its environment is messy behaviour, but since I couldn't give a use case I just called it weird behaviour.
Cause if you have the following code where | is the cursor
then either the lines are indented or the selected text is replaced with a &, which can be weird but also no use case.
The main cursor was indeed taken to be consistent with the rest of the plugin and put the comment there if someone wanted
ctx
cover multicursor.But I still can't think of a use case, why someone would use cursors in different environments besides accidents, so it can be left out.
hope this explained it better.