From d3a63a0be8622f7ef285fa97db565c46df43b5c4 Mon Sep 17 00:00:00 2001 From: jocs Date: Fri, 17 Nov 2023 21:01:08 +0800 Subject: [PATCH 1/3] feat: delete right by delete key --- .../commands/commands/core-editing.command.ts | 40 +++++++-- .../mutations/core-editing.mutation.ts | 2 + .../mutations/functions/update-apply.ts | 2 + .../delete-left-input.controller.ts | 83 ++++++++++++++++--- packages/base-docs/src/doc-plugin.ts | 5 +- .../src/shortcuts/core-editing.shortcut.ts | 8 +- .../src/basics/document-node-tools.ts | 2 +- packages/core/src/shared/doc-tool.ts | 5 +- 8 files changed, 124 insertions(+), 23 deletions(-) diff --git a/packages/base-docs/src/commands/commands/core-editing.command.ts b/packages/base-docs/src/commands/commands/core-editing.command.ts index a29260b3cd9..751c251a92c 100644 --- a/packages/base-docs/src/commands/commands/core-editing.command.ts +++ b/packages/base-docs/src/commands/commands/core-editing.command.ts @@ -26,6 +26,12 @@ export const DeleteLeftCommand: ICommand = { handler: async () => true, }; +export const DeleteRightCommand: ICommand = { + id: 'doc.command.delete-right', + type: CommandType.COMMAND, + handler: async () => true, +}; + export const BreakLineCommand: ICommand = { id: 'doc.command.break-line', type: CommandType.COMMAND, @@ -97,9 +103,15 @@ export const InsertCommand: ICommand = { }, }; +export enum DeleteDirection { + LEFT, + RIGHT, +} + export interface IDeleteCommandParams { unitId: string; range: ITextRange; + direction: DeleteDirection; segmentId?: string; } @@ -117,7 +129,8 @@ export const DeleteCommand: ICommand = { const commandService = accessor.get(ICommandService); const undoRedoService = accessor.get(IUndoRedoService); - const { range, segmentId, unitId } = params; + const { range, segmentId, unitId, direction } = params; + const { collapsed, startOffset } = range; const doMutation: IMutationInfo = { id: RichTextEditingMutation.id, params: { @@ -126,7 +139,24 @@ export const DeleteCommand: ICommand = { }, }; - doMutation.params!.mutations.push(...getRetainAndDeleteFromReplace(range, segmentId)); + if (collapsed) { + if (startOffset > 0) { + doMutation.params!.mutations.push({ + t: 'r', + len: direction === DeleteDirection.LEFT ? startOffset - 1 : startOffset, + segmentId, + }); + } + + doMutation.params!.mutations.push({ + t: 'd', + len: 1, + line: 0, + segmentId, + }); + } else { + doMutation.params!.mutations.push(...getRetainAndDeleteFromReplace(range, segmentId)); + } const result = commandService.syncExecuteCommand< IRichTextEditingMutationParams, @@ -155,7 +185,7 @@ export interface IUpdateCommandParams { } /** - * The command to update text properties. + * The command to update text properties, mainly used in BACKSPACE. */ export const UpdateCommand: ICommand = { id: 'doc.command.update-text', @@ -312,7 +342,7 @@ export function getRetainAndDeleteFromReplace( const dos: Array = []; const textStart = startOffset + (collapsed ? -1 : 0) - memoryCursor; - const textEnd = endOffset - 1 - memoryCursor; + const textEnd = endOffset - memoryCursor; if (textStart > 0) { dos.push({ @@ -324,7 +354,7 @@ export function getRetainAndDeleteFromReplace( dos.push({ t: 'd', - len: textEnd - textStart + 1, + len: textEnd - textStart, line: 0, segmentId, }); diff --git a/packages/base-docs/src/commands/mutations/core-editing.mutation.ts b/packages/base-docs/src/commands/mutations/core-editing.mutation.ts index 032d734787c..f2740ddb1d4 100644 --- a/packages/base-docs/src/commands/mutations/core-editing.mutation.ts +++ b/packages/base-docs/src/commands/mutations/core-editing.mutation.ts @@ -77,6 +77,8 @@ export const RichTextEditingMutation: IMutation { @@ -59,12 +65,21 @@ export class DeleteLeftInputController extends Disposable { return; } - this._deleteFunction(); + switch (command.id) { + case DeleteLeftCommand.id: + this._deleteLeftFunction(); + break; + case DeleteRightCommand.id: + this._deleteRightFunction(); + break; + default: + throw new Error('Unknown command'); + } }) ); } - private _deleteFunction() { + private _deleteLeftFunction() { const activeRange = this._textSelectionRenderManager.getActiveRange(); const skeleton = this._docSkeletonManagerService.getCurrent()?.skeleton; @@ -75,20 +90,24 @@ export class DeleteLeftInputController extends Disposable { const docsModel = this._currentUniverService.getCurrentUniverDocInstance(); - const { startOffset, collapsed, segmentId, style, startNodePosition } = activeRange; + const { startOffset, collapsed, segmentId, style } = activeRange; - const preSpan = skeleton.findSpanByPosition(startNodePosition); + // No need to delete when the cursor is at the first position of the first paragraph. + if (startOffset === 0 && collapsed) { + return; + } - const preIsBullet = hasListSpan(preSpan); + const preSpan = skeleton.findNodeByCharIndex(startOffset); + // is in bullet list? + const preIsBullet = hasListSpan(preSpan); + // is in indented paragraph? const preIsIndent = isIndentBySpan(preSpan, docsModel.body); let cursor = startOffset; - cursor--; - - if (collapsed === false) { - cursor += 1; + if (collapsed === true) { + cursor--; } const span = skeleton.findNodeByCharIndex(cursor); @@ -111,6 +130,7 @@ export class DeleteLeftInputController extends Disposable { if (preIsBullet === true) { const paragraphStyle = paragraph.paragraphStyle; + if (paragraphStyle) { updateParagraph.paragraphStyle = paragraphStyle; } @@ -128,7 +148,7 @@ export class DeleteLeftInputController extends Disposable { this._commandService.executeCommand(UpdateCommand.id, { unitId: docsModel.getUnitId(), - body: { + updateBody: { dataStream: '', paragraphs: [{ ...updateParagraph }], }, @@ -153,6 +173,7 @@ export class DeleteLeftInputController extends Disposable { unitId: docsModel.getUnitId(), range: activeRange, segmentId, + direction: DeleteDirection.LEFT, }); } @@ -173,6 +194,44 @@ export class DeleteLeftInputController extends Disposable { ]); } + private _deleteRightFunction() { + const activeRange = this._textSelectionRenderManager.getActiveRange(); + + const skeleton = this._docSkeletonManagerService.getCurrent()?.skeleton; + + if (activeRange == null || skeleton == null) { + return; + } + + const docsModel = this._currentUniverService.getCurrentUniverDocInstance(); + + const { startOffset, collapsed, segmentId, style } = activeRange; + + // No need to delete when the cursor is at the last position of the last paragraph. + if (startOffset === docsModel.getBodyModel().getBody().dataStream.length - 2 && collapsed) { + return; + } + + this._commandService.executeCommand(DeleteCommand.id, { + unitId: docsModel.getUnitId(), + range: activeRange, + segmentId, + direction: DeleteDirection.RIGHT, + }); + + skeleton?.calculate(); + + // move selection + this._textSelectionManagerService.replaceTextRanges([ + { + startOffset, + endOffset: startOffset, + collapsed: true, + style, + }, + ]); + } + private _getDocObject() { return getDocObject(this._currentUniverService, this._renderManagerService); } diff --git a/packages/base-docs/src/doc-plugin.ts b/packages/base-docs/src/doc-plugin.ts index 45add0100fb..534a03faa87 100644 --- a/packages/base-docs/src/doc-plugin.ts +++ b/packages/base-docs/src/doc-plugin.ts @@ -17,6 +17,7 @@ import { CoverCommand, DeleteCommand, DeleteLeftCommand, + DeleteRightCommand, IMEInputCommand, InsertCommand, UpdateCommand, @@ -52,7 +53,7 @@ import { enUS } from './locale'; import { DocClipboardService, IDocClipboardService } from './services/clipboard/clipboard.service'; import { DocSkeletonManagerService } from './services/doc-skeleton-manager.service'; import { TextSelectionManagerService } from './services/text-selection-manager.service'; -import { BreakLineShortcut, DeleteLeftShortcut } from './shortcuts/core-editing.shortcut'; +import { BreakLineShortcut, DeleteLeftShortcut, DeleteRightShortcut } from './shortcuts/core-editing.shortcut'; import { MoveCursorDownShortcut, MoveCursorLeftShortcut, @@ -106,6 +107,7 @@ export class DocPlugin extends Plugin { MoveCursorOperation, MoveSelectionOperation, DeleteLeftCommand, + DeleteRightCommand, SetInlineFormatBoldCommand, SetInlineFormatItalicCommand, SetInlineFormatUnderlineCommand, @@ -139,6 +141,7 @@ export class DocPlugin extends Plugin { MoveSelectionLeftShortcut, MoveSelectionRightShortcut, DeleteLeftShortcut, + DeleteRightShortcut, BreakLineShortcut, ].forEach((shortcut) => { this._injector.get(IShortcutService).registerShortcut(shortcut); diff --git a/packages/base-docs/src/shortcuts/core-editing.shortcut.ts b/packages/base-docs/src/shortcuts/core-editing.shortcut.ts index 45c5a3f27e8..470f92e3414 100644 --- a/packages/base-docs/src/shortcuts/core-editing.shortcut.ts +++ b/packages/base-docs/src/shortcuts/core-editing.shortcut.ts @@ -1,7 +1,7 @@ import { IShortcutItem, KeyCode } from '@univerjs/base-ui'; import { FOCUSING_DOC } from '@univerjs/core'; -import { BreakLineCommand, DeleteLeftCommand } from '../commands/commands/core-editing.command'; +import { BreakLineCommand, DeleteLeftCommand, DeleteRightCommand } from '../commands/commands/core-editing.command'; export const BreakLineShortcut: IShortcutItem = { id: BreakLineCommand.id, @@ -14,3 +14,9 @@ export const DeleteLeftShortcut: IShortcutItem = { preconditions: (contextService) => contextService.getContextValue(FOCUSING_DOC), binding: KeyCode.BACKSPACE, }; + +export const DeleteRightShortcut: IShortcutItem = { + id: DeleteRightCommand.id, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_DOC), + binding: KeyCode.DELETE, +}; diff --git a/packages/base-render/src/basics/document-node-tools.ts b/packages/base-render/src/basics/document-node-tools.ts index 4a10b2d3d78..4de1ddc68b0 100644 --- a/packages/base-render/src/basics/document-node-tools.ts +++ b/packages/base-render/src/basics/document-node-tools.ts @@ -19,7 +19,7 @@ export function isIndentBySpan(span: Nullable, body?: IDo if (paragraph == null) { return false; } - const paragraphStyle = paragraph.paragraphStyle; + const { paragraphStyle } = paragraph; if (paragraphStyle == null) { return false; diff --git a/packages/core/src/shared/doc-tool.ts b/packages/core/src/shared/doc-tool.ts index 0bca8f0f20c..918dab5b125 100644 --- a/packages/core/src/shared/doc-tool.ts +++ b/packages/core/src/shared/doc-tool.ts @@ -62,9 +62,8 @@ export function checkParagraphHasIndentByStyle(paragraphStyle?: IParagraphStyle) } if ( - ((paragraphStyle?.indentStart == null || paragraphStyle?.indentStart === 0) && - paragraphStyle?.hanging == null) || - paragraphStyle?.hanging === 0 + ((paragraphStyle.indentStart == null || paragraphStyle.indentStart === 0) && paragraphStyle.hanging == null) || + paragraphStyle.hanging === 0 ) { return false; } From 1ad06aaa17c8d41feb52988d5425978f85b8ed1c Mon Sep 17 00:00:00 2001 From: jocs Date: Fri, 17 Nov 2023 21:09:08 +0800 Subject: [PATCH 2/3] refactor: remove debug codes --- .../base-docs/src/commands/commands/core-editing.command.ts | 4 ++-- .../base-docs/src/commands/mutations/core-editing.mutation.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/base-docs/src/commands/commands/core-editing.command.ts b/packages/base-docs/src/commands/commands/core-editing.command.ts index 751c251a92c..a86de69f7e6 100644 --- a/packages/base-docs/src/commands/commands/core-editing.command.ts +++ b/packages/base-docs/src/commands/commands/core-editing.command.ts @@ -338,10 +338,10 @@ export function getRetainAndDeleteFromReplace( segmentId: string = '', memoryCursor: number = 0 ): Array { - const { startOffset, endOffset, collapsed } = range; + const { startOffset, endOffset } = range; const dos: Array = []; - const textStart = startOffset + (collapsed ? -1 : 0) - memoryCursor; + const textStart = startOffset - memoryCursor; const textEnd = endOffset - memoryCursor; if (textStart > 0) { diff --git a/packages/base-docs/src/commands/mutations/core-editing.mutation.ts b/packages/base-docs/src/commands/mutations/core-editing.mutation.ts index f2740ddb1d4..032d734787c 100644 --- a/packages/base-docs/src/commands/mutations/core-editing.mutation.ts +++ b/packages/base-docs/src/commands/mutations/core-editing.mutation.ts @@ -77,8 +77,6 @@ export const RichTextEditingMutation: IMutation Date: Mon, 20 Nov 2023 11:34:27 +0800 Subject: [PATCH 3/3] refactor: rename some files --- .../src/commands/commands/core-editing.command.ts | 1 + ...left-input.controller.ts => delete.controller.ts} | 12 ++++++------ packages/base-docs/src/doc-plugin.ts | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) rename packages/base-docs/src/controllers/{delete-left-input.controller.ts => delete.controller.ts} (96%) diff --git a/packages/base-docs/src/commands/commands/core-editing.command.ts b/packages/base-docs/src/commands/commands/core-editing.command.ts index a86de69f7e6..0ba7fc58cc1 100644 --- a/packages/base-docs/src/commands/commands/core-editing.command.ts +++ b/packages/base-docs/src/commands/commands/core-editing.command.ts @@ -20,6 +20,7 @@ import { RichTextEditingMutation, } from '../mutations/core-editing.mutation'; +// TODO: @JOCS, do not use command as event bus. export const DeleteLeftCommand: ICommand = { id: 'doc.command.delete-left', type: CommandType.COMMAND, diff --git a/packages/base-docs/src/controllers/delete-left-input.controller.ts b/packages/base-docs/src/controllers/delete.controller.ts similarity index 96% rename from packages/base-docs/src/controllers/delete-left-input.controller.ts rename to packages/base-docs/src/controllers/delete.controller.ts index 1e306478020..29adf6f87fc 100644 --- a/packages/base-docs/src/controllers/delete-left-input.controller.ts +++ b/packages/base-docs/src/controllers/delete.controller.ts @@ -31,8 +31,8 @@ import { import { DocSkeletonManagerService } from '../services/doc-skeleton-manager.service'; import { TextSelectionManagerService } from '../services/text-selection-manager.service'; -@OnLifecycle(LifecycleStages.Rendered, DeleteLeftInputController) -export class DeleteLeftInputController extends Disposable { +@OnLifecycle(LifecycleStages.Rendered, DeleteController) +export class DeleteController extends Disposable { private _onInputSubscription: Nullable; constructor( @@ -67,10 +67,10 @@ export class DeleteLeftInputController extends Disposable { switch (command.id) { case DeleteLeftCommand.id: - this._deleteLeftFunction(); + this._handleDeleteLeft(); break; case DeleteRightCommand.id: - this._deleteRightFunction(); + this._handleDeleteRight(); break; default: throw new Error('Unknown command'); @@ -79,7 +79,7 @@ export class DeleteLeftInputController extends Disposable { ); } - private _deleteLeftFunction() { + private _handleDeleteLeft() { const activeRange = this._textSelectionRenderManager.getActiveRange(); const skeleton = this._docSkeletonManagerService.getCurrent()?.skeleton; @@ -194,7 +194,7 @@ export class DeleteLeftInputController extends Disposable { ]); } - private _deleteRightFunction() { + private _handleDeleteRight() { const activeRange = this._textSelectionRenderManager.getActiveRange(); const skeleton = this._docSkeletonManagerService.getCurrent()?.skeleton; diff --git a/packages/base-docs/src/doc-plugin.ts b/packages/base-docs/src/doc-plugin.ts index 534a03faa87..75ef2399a2e 100644 --- a/packages/base-docs/src/doc-plugin.ts +++ b/packages/base-docs/src/doc-plugin.ts @@ -38,7 +38,7 @@ import { MoveCursorOperation, MoveSelectionOperation } from './commands/operatio import { SetDocZoomRatioOperation } from './commands/operations/set-doc-zoom-ratio.operation'; import { SetTextSelectionsOperation } from './commands/operations/text-selection.operation'; import { DocClipboardController } from './controllers/clipboard.controller'; -import { DeleteLeftInputController } from './controllers/delete-left-input.controller'; +import { DeleteController } from './controllers/delete.controller'; import { DocRenderController } from './controllers/doc-render.controller'; import { FloatingObjectController } from './controllers/floating-object.controller'; import { IMEInputController } from './controllers/ime-input.controller'; @@ -183,7 +183,7 @@ export class DocPlugin extends Plugin { [TextSelectionController], [NormalInputController], [IMEInputController], - [DeleteLeftInputController], + [DeleteController], [InlineFormatController], [DocClipboardController], [LineBreakInputController],