diff --git a/.eslintignore b/.eslintignore index 3fc5feac7d8..b321d6a2b5a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -583,10 +583,15 @@ packages/editor/CodeMirror/CodeMirrorControl.js packages/editor/CodeMirror/configFromSettings.js packages/editor/CodeMirror/createEditor.test.js packages/editor/CodeMirror/createEditor.js +packages/editor/CodeMirror/editorCommands/duplicateLine.test.js +packages/editor/CodeMirror/editorCommands/duplicateLine.js packages/editor/CodeMirror/editorCommands/editorCommands.js packages/editor/CodeMirror/editorCommands/insertLineAfter.test.js packages/editor/CodeMirror/editorCommands/insertLineAfter.js +packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js +packages/editor/CodeMirror/editorCommands/sortSelectedLines.js packages/editor/CodeMirror/editorCommands/supportsCommand.js +packages/editor/CodeMirror/editorCommands/swapLine.test.js packages/editor/CodeMirror/editorCommands/swapLine.js packages/editor/CodeMirror/getScrollFraction.js packages/editor/CodeMirror/markdown/codeBlockLanguages/allLanguages.js diff --git a/.gitignore b/.gitignore index 272bd9d2007..d7d4e657697 100644 --- a/.gitignore +++ b/.gitignore @@ -563,10 +563,15 @@ packages/editor/CodeMirror/CodeMirrorControl.js packages/editor/CodeMirror/configFromSettings.js packages/editor/CodeMirror/createEditor.test.js packages/editor/CodeMirror/createEditor.js +packages/editor/CodeMirror/editorCommands/duplicateLine.test.js +packages/editor/CodeMirror/editorCommands/duplicateLine.js packages/editor/CodeMirror/editorCommands/editorCommands.js packages/editor/CodeMirror/editorCommands/insertLineAfter.test.js packages/editor/CodeMirror/editorCommands/insertLineAfter.js +packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js +packages/editor/CodeMirror/editorCommands/sortSelectedLines.js packages/editor/CodeMirror/editorCommands/supportsCommand.js +packages/editor/CodeMirror/editorCommands/swapLine.test.js packages/editor/CodeMirror/editorCommands/swapLine.js packages/editor/CodeMirror/getScrollFraction.js packages/editor/CodeMirror/markdown/codeBlockLanguages/allLanguages.js diff --git a/packages/editor/CodeMirror/CodeMirrorControl.test.ts b/packages/editor/CodeMirror/CodeMirrorControl.test.ts index 7eeb7270be5..726c02f1d3a 100644 --- a/packages/editor/CodeMirror/CodeMirrorControl.test.ts +++ b/packages/editor/CodeMirror/CodeMirrorControl.test.ts @@ -57,4 +57,23 @@ describe('CodeMirrorControl', () => { expect(control.execCommand('myTestCommand')).toBe('test'); expect(command).toHaveBeenCalledTimes(1); }); + + it('should toggle comments', () => { + const control = createEditorControl('Hello\nWorld\n'); + control.select(1, 5); + + control.execCommand('toggleComment'); + expect(control.getValue()).toBe('\nWorld\n'); + + control.execCommand('toggleComment'); + expect(control.getValue()).toBe('Hello\nWorld\n'); + }); + + it('should delete line', () => { + const control = createEditorControl('Hello\nWorld\n'); + control.setCursor(1, 0); + + control.execCommand('deleteLine'); + expect(control.getValue()).toBe('Hello\n'); + }); }); diff --git a/packages/editor/CodeMirror/editorCommands/duplicateLine.test.ts b/packages/editor/CodeMirror/editorCommands/duplicateLine.test.ts new file mode 100644 index 00000000000..c299d9b9945 --- /dev/null +++ b/packages/editor/CodeMirror/editorCommands/duplicateLine.test.ts @@ -0,0 +1,31 @@ +import { EditorView } from '@codemirror/view'; +import { EditorSelection } from '@codemirror/state'; +import duplicateLine from './duplicateLine'; + +describe('duplicateLine', () => { + it('should duplicate line', () => { + const initialText = 'Hello\nWorld\n'; + const editorView = new EditorView({ + doc: initialText, + selection: EditorSelection.cursor(0), + }); + + duplicateLine(editorView); + + const result = editorView.state.doc.toString(); + expect(result).toBe('Hello\nHello\nWorld\n'); + }); + + it('should duplicate range', () => { + const initialText = 'Hello\nWorld\n'; + const editorView = new EditorView({ + doc: initialText, + selection: EditorSelection.range(0, 8), + }); + + duplicateLine(editorView); + + const result = editorView.state.doc.toString(); + expect(result).toBe('Hello\nWoHello\nWorld\n'); + }); +}); diff --git a/packages/editor/CodeMirror/editorCommands/duplicateLine.ts b/packages/editor/CodeMirror/editorCommands/duplicateLine.ts new file mode 100644 index 00000000000..87e6fe5209b --- /dev/null +++ b/packages/editor/CodeMirror/editorCommands/duplicateLine.ts @@ -0,0 +1,34 @@ +import { EditorSelection } from '@codemirror/state'; +import { Command, EditorView } from '@codemirror/view'; + +const duplicateLine: Command = (editor: EditorView) => { + const state = editor.state; + const doc = state.doc; + + const transaction = state.changeByRange(range => { + const currentLine = doc.lineAt(range.anchor); + + let text, insertPos, selectionRange; + if (range.empty) { + text = `\n${currentLine.text}`; + insertPos = currentLine.to; + selectionRange = EditorSelection.cursor(currentLine.to + text.length); + } else { + text = doc.slice(range.from, range.to); + insertPos = range.to; + selectionRange = EditorSelection.range(range.to, range.to + text.length); + } + return { + range: selectionRange, + changes: [{ + from: insertPos, + to: insertPos, + insert: text, + }], + }; + }); + + editor.dispatch(transaction); + return true; +}; +export default duplicateLine; diff --git a/packages/editor/CodeMirror/editorCommands/editorCommands.ts b/packages/editor/CodeMirror/editorCommands/editorCommands.ts index 2f0166095c0..a2e995b3a9c 100644 --- a/packages/editor/CodeMirror/editorCommands/editorCommands.ts +++ b/packages/editor/CodeMirror/editorCommands/editorCommands.ts @@ -1,6 +1,6 @@ import { EditorView } from '@codemirror/view'; import { EditorCommandType, ListType } from '../../types'; -import { undo, redo, selectAll, indentSelection, cursorDocStart, cursorDocEnd, cursorLineStart, cursorLineEnd, deleteToLineStart, deleteToLineEnd, undoSelection, redoSelection, cursorPageDown, cursorPageUp, cursorCharRight, cursorCharLeft, insertNewlineAndIndent, cursorLineDown, cursorLineUp } from '@codemirror/commands'; +import { undo, redo, selectAll, indentSelection, cursorDocStart, cursorDocEnd, cursorLineStart, cursorLineEnd, deleteToLineStart, deleteToLineEnd, undoSelection, redoSelection, cursorPageDown, cursorPageUp, cursorCharRight, cursorCharLeft, insertNewlineAndIndent, cursorLineDown, cursorLineUp, toggleComment, deleteLine } from '@codemirror/commands'; import { decreaseIndent, increaseIndent, toggleBolded, toggleCode, @@ -8,6 +8,8 @@ import { toggleList, toggleMath, } from '../markdown/markdownCommands'; import swapLine, { SwapLineDirection } from './swapLine'; +import duplicateLine from './duplicateLine'; +import sortSelectedLines from './sortSelectedLines'; import { closeSearchPanel, findNext, findPrevious, openSearchPanel, replaceAll, replaceNext } from '@codemirror/search'; export type EditorCommandFunction = (editor: EditorView)=> void; @@ -22,6 +24,9 @@ const editorCommands: Record = { [EditorCommandType.ToggleItalicized]: toggleItalicized, [EditorCommandType.ToggleCode]: toggleCode, [EditorCommandType.ToggleMath]: toggleMath, + [EditorCommandType.ToggleComment]: toggleComment, + [EditorCommandType.DuplicateLine]: duplicateLine, + [EditorCommandType.SortSelectedLines]: sortSelectedLines, [EditorCommandType.ToggleNumberedList]: toggleList(ListType.OrderedList), [EditorCommandType.ToggleBulletedList]: toggleList(ListType.UnorderedList), [EditorCommandType.ToggleCheckList]: toggleList(ListType.CheckList), @@ -39,6 +44,7 @@ const editorCommands: Record = { }, [EditorCommandType.DeleteToLineEnd]: deleteToLineEnd, [EditorCommandType.DeleteToLineStart]: deleteToLineStart, + [EditorCommandType.DeleteLine]: deleteLine, [EditorCommandType.IndentMore]: increaseIndent, [EditorCommandType.IndentLess]: decreaseIndent, [EditorCommandType.IndentAuto]: indentSelection, diff --git a/packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.ts b/packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.ts new file mode 100644 index 00000000000..ec333f41542 --- /dev/null +++ b/packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.ts @@ -0,0 +1,18 @@ +import { EditorView } from '@codemirror/view'; +import { EditorSelection } from '@codemirror/state'; +import sortSelectedLines from './sortSelectedLines'; + +describe('sortSelectedLines', () => { + it('should sort selected lines', () => { + const initialText = 'World\nHello\n'; + const editorView = new EditorView({ + doc: initialText, + selection: EditorSelection.range(0, 8), + }); + + sortSelectedLines(editorView); + + const result = editorView.state.doc.toString(); + expect(result).toBe('Hello\nWorld\n'); + }); +}); diff --git a/packages/editor/CodeMirror/editorCommands/sortSelectedLines.ts b/packages/editor/CodeMirror/editorCommands/sortSelectedLines.ts new file mode 100644 index 00000000000..ab56f645559 --- /dev/null +++ b/packages/editor/CodeMirror/editorCommands/sortSelectedLines.ts @@ -0,0 +1,32 @@ +import { EditorSelection } from '@codemirror/state'; +import { Command, EditorView } from '@codemirror/view'; + +const sortSelectedLines: Command = (editor: EditorView) => { + const state = editor.state; + const doc = state.doc; + + const transaction = state.changeByRange(range => { + const startLine = doc.lineAt(range.from); + const endLine = doc.lineAt(range.to); + + const lines = []; + for (let j = startLine.number; j <= endLine.number; j++) { + lines.push(doc.line(j).text); + } + + const sortedText = lines.sort().join('\n'); + + return { + range: EditorSelection.cursor(startLine.from + sortedText.length), + changes: [{ + from: startLine.from, + to: endLine.to, + insert: sortedText, + }], + }; + }); + + editor.dispatch(transaction); + return true; +}; +export default sortSelectedLines; diff --git a/packages/editor/CodeMirror/editorCommands/swapLine.test.ts b/packages/editor/CodeMirror/editorCommands/swapLine.test.ts new file mode 100644 index 00000000000..917bebe9e0e --- /dev/null +++ b/packages/editor/CodeMirror/editorCommands/swapLine.test.ts @@ -0,0 +1,32 @@ +import { EditorView } from '@codemirror/view'; +import { EditorSelection } from '@codemirror/state'; +import swapLine, { SwapLineDirection } from './swapLine'; + + +describe('swapLine', () => { + it('should swap line down', () => { + const initialText = 'Hello\nWorld\nJoplin\n'; + const editorView = new EditorView({ + doc: initialText, + selection: EditorSelection.cursor(0), + }); + + swapLine(SwapLineDirection.Down)(editorView); + + const result = editorView.state.doc.toString(); + expect(result).toBe('World\nHello\nJoplin\n'); + }); + + it('should swap line up', () => { + const initialText = 'Hello\nWorld\nJoplin\n'; + const editorView = new EditorView({ + doc: initialText, + selection: EditorSelection.cursor(6), + }); + + swapLine(SwapLineDirection.Up)(editorView); + + const result = editorView.state.doc.toString(); + expect(result).toBe('World\nHello\nJoplin\n'); + }); +}); diff --git a/packages/editor/types.ts b/packages/editor/types.ts index d8bef62ac6e..e0b1eb540ae 100644 --- a/packages/editor/types.ts +++ b/packages/editor/types.ts @@ -15,6 +15,9 @@ export enum EditorCommandType { ToggleItalicized = 'textItalic', ToggleCode = 'textCode', ToggleMath = 'textMath', + ToggleComment = 'toggleComment', + DuplicateLine = 'duplicateLine', + SortSelectedLines = 'sortSelectedLines', ToggleNumberedList = 'textNumberedList', ToggleBulletedList = 'textBulletedList', @@ -37,6 +40,7 @@ export enum EditorCommandType { // Editing and navigation commands ScrollSelectionIntoView = 'scrollSelectionIntoView', + DeleteLine = 'deleteLine', DeleteToLineEnd = 'killLine', DeleteToLineStart = 'delLineLeft', IndentMore = 'indentMore',