Skip to content

Commit

Permalink
[IMP] Composer: Implement range selection/edition toggle
Browse files Browse the repository at this point in the history
Currently, the only way to select a range in a formula is to write some
text such that the next argument could be a range (e.g. write '=sum(').
If you edit an existing formula, you need to delete a range argument in
order to 'activate' the range selection mode. While you can use the
graphical tool to move the range around, it can be bothersome to find
which range is where and then having to move it around.

This revision adds the possibility to switch to a range selection mode
when the cursor is on a reference by pressing F2. This allows the user
to click on a range in the composer, switch to 'selecting' mode and then
cllick on another range to replace the current one.
Pressing the shortcut again will switch back to 'editing' mode which can
be helpful to quickly edit formulas.

Task: 4105364
  • Loading branch information
rrahir committed Sep 24, 2024
1 parent b2e40ea commit 01747c5
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 13 deletions.
55 changes: 45 additions & 10 deletions src/components/composer/composer/abstract_composer_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export abstract class AbstractComposerStore extends SpreadsheetStore {
"stopComposerRangeSelection",
"cancelEdition",
"cycleReferences",
"toggleEditionMode",
"changeComposerCursorSelection",
"replaceComposerCursorSelection",
] as const;
Expand Down Expand Up @@ -227,6 +228,49 @@ export abstract class AbstractComposerStore extends SpreadsheetStore {
this.setCurrentContent(updated.content, updated.selection);
}

toggleEditionMode() {
if (this.editionMode === "inactive") return;
// todo: look at token at cursor or the next token as well
const start = Math.min(this.selectionStart, this.selectionEnd);
const end = Math.max(this.selectionStart, this.selectionEnd);
const refToken = [...this.currentTokens]
.reverse()
.find((tk) => tk.end >= start && end >= tk.start && tk.type === "REFERENCE");

if (this.editionMode === "editing" && refToken) {
// startcomposerRangeSelection to register the listener
const currentSheetId = this.getters.getActiveSheetId();
const { sheetName, xc } = splitReference(refToken.value);
const sheetId = this.getters.getSheetIdByName(sheetName);
if (sheetId && sheetId !== currentSheetId) {
this.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: currentSheetId, sheetIdTo: sheetId });
}
// move cursor to the right part of the token
this.selectionStart = this.selectionEnd = refToken.end;
const zone = this.getters.getRangeFromSheetXC(this.sheetId, xc).zone;
this.captureSelection(zone);
this.editionMode = "selecting";
} else {
this.editionMode = "editing";
}
}

private captureSelection(zone: Zone, col?: HeaderIndex, row?: HeaderIndex) {
this.model.selection.capture(
this,
{
cell: { col: col || zone.left, row: row || zone.right },
zone,
},
{
handleEvent: this.handleEvent.bind(this),
release: () => {
this._stopEdition();
},
}
);
}

private isSelectionValid(length: number, start: number, end: number): boolean {
return start >= 0 && start <= length && end >= 0 && end <= length;
}
Expand Down Expand Up @@ -267,16 +311,7 @@ export abstract class AbstractComposerStore extends SpreadsheetStore {
this.setContent(str || this.initialContent, selection);
this.colorIndexByRange = {};
const zone = positionToZone({ col: this.col, row: this.row });
this.model.selection.capture(
this,
{ cell: { col: this.col, row: this.row }, zone },
{
handleEvent: this.handleEvent.bind(this),
release: () => {
this._stopEdition();
},
}
);
this.captureSelection(zone, col, row);
}

protected _stopEdition() {
Expand Down
21 changes: 19 additions & 2 deletions src/components/composer/composer/composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const functions = functionRegistry.content;
const ASSISTANT_WIDTH = 300;

export const selectionIndicatorClass = "selector-flag";
const referenceSelectedClass = "underlined-ref";
const selectionIndicatorColor = "#a9a9a9";
const selectionIndicator = "␣";

Expand Down Expand Up @@ -79,6 +80,9 @@ css/* scss */ `
content: "${selectionIndicator}";
color: ${selectionIndicatorColor};
}
&.${referenceSelectedClass} {
text-decoration: underline;
}
}
}
}
Expand Down Expand Up @@ -221,7 +225,7 @@ export class Composer extends Component<CellComposerProps, SpreadsheetChildEnv>
"Alt+Enter": this.processNewLineEvent,
"Ctrl+Enter": this.processNewLineEvent,
Escape: this.processEscapeKey,
F2: () => console.warn("Not implemented"),
F2: (ev: KeyboardEvent) => this.toggleEditionMode(ev),
F4: (ev: KeyboardEvent) => this.processF4Key(ev),
Tab: (ev: KeyboardEvent) => this.processTabKey(ev, "right"),
"Shift+Tab": (ev: KeyboardEvent) => this.processTabKey(ev, "left"),
Expand Down Expand Up @@ -369,6 +373,12 @@ export class Composer extends Component<CellComposerProps, SpreadsheetChildEnv>
this.processContent();
}

private toggleEditionMode(ev: KeyboardEvent) {
ev.stopPropagation();
this.props.composerStore.toggleEditionMode();
this.processContent();
}

private processNumpadDecimal(ev: KeyboardEvent) {
ev.stopPropagation();
ev.preventDefault();
Expand Down Expand Up @@ -620,7 +630,14 @@ export class Composer extends Component<CellComposerProps, SpreadsheetChildEnv>
break;
case "REFERENCE":
const { xc, sheetName } = splitReference(token.value);
result.push({ value: token.value, color: this.rangeColor(xc, sheetName) || "#000" });
result.push({
value: token.value,
color: this.rangeColor(xc, sheetName) || "#000",
class:
tokenAtCursor === token && this.props.composerStore.editionMode === "selecting"
? referenceSelectedClass
: undefined,
});
break;
case "SYMBOL":
const value = token.value;
Expand Down
2 changes: 1 addition & 1 deletion src/components/composer/content_editable_helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class ContentEditableHelper {
let startNode = this.findChildAtCharacterIndex(start);
let endNode = this.findChildAtCharacterIndex(end);
range.setStart(startNode.node, startNode.offset);
selection.extend(endNode.node, endNode.offset);
range.setEnd(endNode.node, endNode.offset);
}
}

Expand Down
9 changes: 9 additions & 0 deletions tests/composer/composer_component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,15 @@ describe("composer", () => {
await nextTick();
expect(composerStore.currentContent).toBe("5.,");
});

test("Pressing F2 will toggle edition mode on ranges", async () => {
await startComposition("=A1+A2");
expect(composerStore.editionMode).toBe("editing");
await keyDown({ key: "F2" });
expect(composerStore.editionMode).toBe("selecting");
await keyDown({ key: "F2" });
expect(composerStore.editionMode).toBe("editing");
});
});

describe("composer formula color", () => {
Expand Down
49 changes: 49 additions & 0 deletions tests/composer/composer_store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1385,4 +1385,53 @@ describe("edition", () => {
},
]);
});

describe("Toggling edition", () => {
test("toggling edition mode on a reference", () => {
const { store } = makeStoreWithModel(model, CellComposerStore);
store.startEdition("=A1+A2");
expect(store.editionMode).toBe("editing");
// select A1
store.changeComposerCursorSelection(2, 2);
store.toggleEditionMode();
expect(store.editionMode).toBe("selecting");
store.toggleEditionMode();
expect(store.editionMode).toBe("editing");

// select A2
store.changeComposerCursorSelection(5, 5);
store.toggleEditionMode();
expect(store.editionMode).toBe("selecting");
});

test("toggling edition mode on a range moves the cursor to the end of the range", () => {
const { store } = makeStoreWithModel(model, CellComposerStore);
store.startEdition("=A1:B2");
expect(store.editionMode).toBe("editing");
store.changeComposerCursorSelection(2, 2);
store.toggleEditionMode();
expect(store.editionMode).toBe("selecting");
store.stopComposerRangeSelection();
expect(store.composerSelection).toEqual({ start: 6, end: 6 });
});

test("toggling edition mode on a string", () => {
const { store } = makeStoreWithModel(model, CellComposerStore);
store.startEdition("=sum(A1)");
expect(store.editionMode).toBe("editing");
store.toggleEditionMode();
expect(store.editionMode).toBe("editing");
store.changeComposerCursorSelection(2, 2);
expect(store.tokenAtCursor?.value).toBe("sum");
store.toggleEditionMode();
expect(store.editionMode).toBe("editing");
});

test("toggling edition mode when inactive does nothing", () => {
const { store } = makeStoreWithModel(model, CellComposerStore);
expect(store.editionMode).toBe("inactive");
store.toggleEditionMode();
expect(store.editionMode).toBe("inactive");
});
});
});

0 comments on commit 01747c5

Please sign in to comment.