Skip to content

Desktop: Codemirror clean up list indent code #3581

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

Merged
merged 2 commits into from
Aug 1, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default function useListIdent(CodeMirror: any) {
function getIndentLevel(cm: any, line: number) {
const tokens = cm.getLineTokens(line);
let indentLevel = 0;
if (tokens.length > 0 && tokens[0].string.match(/\s/)) {
if (tokens.length > 0 && tokens[0].string.match(/^\s/)) {
indentLevel = tokens[0].string.length;
}

Expand Down Expand Up @@ -63,79 +63,103 @@ export default function useListIdent(CodeMirror: any) {
if (cm.getOption('disableInput')) return CodeMirror.Pass;

const ranges = cm.listSelections();
for (let i = 0; i < ranges.length; i++) {
const { anchor, head } = ranges[i];

const line = cm.getLine(anchor.line);

// This is an actual selection and we should indent
if (isSelection(anchor, head) || !isListItem(line)) {
cm.execCommand('defaultTab');
} else {
if (olLineNumber(line)) {
const tokens = cm.getLineTokens(anchor.line);
const { start, end } = getListSpan(tokens, line);
// Resets numbered list to 1.
cm.replaceRange('1. ', { line: anchor.line, ch: start }, { line: anchor.line, ch: end });
cm.operation(() => {
for (let i = 0; i < ranges.length; i++) {
const { anchor, head } = ranges[i];

const line = cm.getLine(anchor.line);

// This is an actual selection and we should indent
if (isSelection(anchor, head)) {
cm.execCommand('defaultTab');
// This will apply to all selections so it makes sense to stop processing here
// this is an edge case for users because there is no clear intended behavior
// if the use multicursor with a mix of selected and not selected
break;
} else if (!isListItem(line) || !isEmptyListItem(line)) {
cm.replaceRange('\t', anchor, head);
} else {
if (olLineNumber(line)) {
const tokens = cm.getLineTokens(anchor.line);
const { start, end } = getListSpan(tokens, line);
// Resets numbered list to 1.
cm.replaceRange('1. ', { line: anchor.line, ch: start }, { line: anchor.line, ch: end });
}

cm.indentLine(anchor.line, 'add');
}

cm.indentLine(anchor.line, 'add');
}
}
});
};

CodeMirror.commands.smartListUnindent = function(cm: any) {
if (cm.getOption('disableInput')) return CodeMirror.Pass;

const ranges = cm.listSelections();
for (let i = 0; i < ranges.length; i++) {
const { anchor, head } = ranges[i];

const line = cm.getLine(anchor.line);

// This is an actual selection and we should unindent
if (isSelection(anchor, head) || !isListItem(line)) {
cm.execCommand('indentLess');
} else {
const newToken = newListToken(cm, anchor.line);
const tokens = cm.getLineTokens(anchor.line);
const { start, end } = getListSpan(tokens, line);
cm.operation(() => {
for (let i = 0; i < ranges.length; i++) {
const { anchor, head } = ranges[i];

const line = cm.getLine(anchor.line);

// This is an actual selection and we should unindent
if (isSelection(anchor, head)) {
cm.execCommand('indentLess');
// This will apply to all selections so it makes sense to stop processing here
// this is an edge case for users because there is no clear intended behavior
// if the use multicursor with a mix of selected and not selected
break;
} else if (!isListItem(line) || !isEmptyListItem(line)) {
cm.indentLine(anchor.line, 'subtract');
} else {
const newToken = newListToken(cm, anchor.line);
const tokens = cm.getLineTokens(anchor.line);
const { start, end } = getListSpan(tokens, line);

cm.replaceRange(newToken, { line: anchor.line, ch: start }, { line: anchor.line, ch: end });
cm.replaceRange(newToken, { line: anchor.line, ch: start }, { line: anchor.line, ch: end });

cm.indentLine(anchor.line, 'subtract');
cm.indentLine(anchor.line, 'subtract');
}
}
}
});
};

CodeMirror.commands.insertListElement = function(cm: any) {
if (cm.getOption('disableInput')) return CodeMirror.Pass;

const ranges = cm.listSelections();
for (let i = 0; i < ranges.length; i++) {
const { anchor } = ranges[i];
if (ranges.length === 0) return;
const { anchor } = ranges[0];

// Only perform the extra smart code if there is a single cursor
// otherwise fallback on the default codemirror behavior
if (ranges.length === 1) {
const line = cm.getLine(anchor.line);

if (isEmptyListItem(line)) {
const tokens = cm.getLineTokens(anchor.line);
// A empty list item with an indent will have whitespace as the first token
if (tokens.length > 1 && tokens[0].string.match(/\s/)) {
if (tokens.length > 1 && tokens[0].string.match(/^\s/)) {
cm.execCommand('smartListUnindent');
} else {
cm.replaceRange('', { line: anchor.line, ch: 0 }, anchor);
}
} else {
// Disable automatic indent for html/xml outside of codeblocks
const state = cm.getTokenAt(anchor).state;
const mode = cm.getModeAt(anchor);
// html/xml inside of a codeblock is fair game for auto-indent
if (mode.name !== 'xml' || state.overlay.codeBlock) {
cm.execCommand('newlineAndIndentContinueMarkdownList');
} else {
cm.replaceSelection('\n');
}
return;
}
}

// Disable automatic indent for html/xml outside of codeblocks
const state = cm.getTokenAt(anchor).state;
const mode = cm.getModeAt(anchor);

// html/xml inside of a codeblock is fair game for auto-indent
// for states who's mode is xml, having the localState property means they are within a code block
if (mode.name !== 'xml' || !!state.outer.localState) {
cm.execCommand('newlineAndIndentContinueMarkdownList');
} else {
cm.replaceSelection('\n');
}
};
}