Skip to content

Commit 0f3254f

Browse files
chore: simplify inheritance for Line Cursor class (#45)
1 parent 5361fa9 commit 0f3254f

File tree

2 files changed

+122
-16
lines changed

2 files changed

+122
-16
lines changed

src/line_cursor.ts

Lines changed: 114 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
*/
1515

1616
import * as Blockly from 'blockly/core';
17-
import {ASTNode, BasicCursor} from 'blockly/core';
17+
import {ASTNode, Marker} from 'blockly/core';
1818

1919
/**
2020
* Class for a line cursor.
2121
*/
22-
export class LineCursor extends BasicCursor {
22+
export class LineCursor extends Marker {
23+
override type = 'cursor';
24+
2325
/**
2426
* Constructor for a line cursor.
2527
*/
@@ -33,14 +35,13 @@ export class LineCursor extends BasicCursor {
3335
*
3436
* @returns The next node, or null if the current node is
3537
* not set or there is no next value.
36-
* @override
3738
*/
3839
next(): ASTNode | null {
3940
const curNode = this.getCurNode();
4041
if (!curNode) {
4142
return null;
4243
}
43-
let newNode = this.getNextNode_(curNode, this.validLineNode);
44+
let newNode = this.getNextNode(curNode, this.validLineNode);
4445

4546
// Skip the input or next value if there is a connected block.
4647
if (
@@ -49,7 +50,7 @@ export class LineCursor extends BasicCursor {
4950
newNode.getType() == ASTNode.types.NEXT) &&
5051
(newNode.getLocation() as Blockly.Connection).targetBlock()
5152
) {
52-
newNode = this.getNextNode_(newNode, this.validLineNode);
53+
newNode = this.getNextNode(newNode, this.validLineNode);
5354
}
5455
if (newNode) {
5556
this.setCurNode(newNode);
@@ -64,12 +65,12 @@ export class LineCursor extends BasicCursor {
6465
* @returns The next node, or null if the current node is
6566
* not set or there is no next value.
6667
*/
67-
override in(): ASTNode | null {
68+
in(): ASTNode | null {
6869
const curNode = this.getCurNode();
6970
if (!curNode) {
7071
return null;
7172
}
72-
const newNode = this.getNextNode_(curNode, this.validInLineNode);
73+
const newNode = this.getNextNode(curNode, this.validInLineNode);
7374

7475
if (newNode) {
7576
this.setCurNode(newNode);
@@ -83,40 +84,41 @@ export class LineCursor extends BasicCursor {
8384
* @returns The previous node, or null if the current node
8485
* is not set or there is no previous value.
8586
*/
86-
override prev(): ASTNode | null {
87+
prev(): ASTNode | null {
8788
const curNode = this.getCurNode();
8889
if (!curNode) {
8990
return null;
9091
}
91-
let newNode = this.getPreviousNode_(curNode, this.validLineNode);
92+
let newNode = this.getPreviousNode(curNode, this.validLineNode);
9293

9394
if (
9495
newNode &&
9596
(newNode.getType() == ASTNode.types.INPUT ||
9697
newNode.getType() == ASTNode.types.NEXT) &&
9798
(newNode.getLocation() as Blockly.Connection).targetBlock()
9899
) {
99-
newNode = this.getPreviousNode_(newNode, this.validLineNode);
100+
newNode = this.getPreviousNode(newNode, this.validLineNode);
100101
}
101102

102103
if (newNode) {
103104
this.setCurNode(newNode);
104105
}
105106
return newNode;
106107
}
108+
107109
/**
108110
* Moves the cursor to the previous input connection or field in the pre order
109111
* traversal.
110112
*
111113
* @returns The previous node, or null if the current node
112114
* is not set or there is no previous value.
113115
*/
114-
override out(): ASTNode | null {
116+
out(): ASTNode | null {
115117
const curNode = this.getCurNode();
116118
if (!curNode) {
117119
return null;
118120
}
119-
const newNode = this.getPreviousNode_(curNode, this.validInLineNode);
121+
const newNode = this.getPreviousNode(curNode, this.validInLineNode);
120122

121123
if (newNode) {
122124
this.setCurNode(newNode);
@@ -308,6 +310,106 @@ export class LineCursor extends BasicCursor {
308310
}
309311
return newNode;
310312
}
313+
314+
/**
315+
* Uses pre order traversal to navigate the Blockly AST. This will allow
316+
* a user to easily navigate the entire Blockly AST without having to go in
317+
* and out levels on the tree.
318+
*
319+
* @param node The current position in the AST.
320+
* @param isValid A function true/false depending on whether the given node
321+
* should be traversed.
322+
* @returns The next node in the traversal.
323+
*/
324+
private getNextNode(
325+
node: ASTNode | null,
326+
isValid: (p1: ASTNode | null) => boolean,
327+
): ASTNode | null {
328+
if (!node) {
329+
return null;
330+
}
331+
const newNode = node.in() || node.next();
332+
if (isValid(newNode)) {
333+
return newNode;
334+
} else if (newNode) {
335+
return this.getNextNode(newNode, isValid);
336+
}
337+
const siblingOrParent = this.findSiblingOrParent(node.out());
338+
if (isValid(siblingOrParent)) {
339+
return siblingOrParent;
340+
} else if (siblingOrParent) {
341+
return this.getNextNode(siblingOrParent, isValid);
342+
}
343+
return null;
344+
}
345+
346+
/**
347+
* Reverses the pre order traversal in order to find the previous node. This
348+
* will allow a user to easily navigate the entire Blockly AST without having
349+
* to go in and out levels on the tree.
350+
*
351+
* @param node The current position in the AST.
352+
* @param isValid A function true/false depending on whether the given node
353+
* should be traversed.
354+
* @returns The previous node in the traversal or null if no previous node
355+
* exists.
356+
*/
357+
private getPreviousNode(
358+
node: ASTNode | null,
359+
isValid: (p1: ASTNode | null) => boolean,
360+
): ASTNode | null {
361+
if (!node) {
362+
return null;
363+
}
364+
let newNode: ASTNode | null = node.prev();
365+
366+
if (newNode) {
367+
newNode = this.getRightMostChild(newNode);
368+
} else {
369+
newNode = node.out();
370+
}
371+
if (isValid(newNode)) {
372+
return newNode;
373+
} else if (newNode) {
374+
return this.getPreviousNode(newNode, isValid);
375+
}
376+
return null;
377+
}
378+
379+
/**
380+
* From the given node find either the next valid sibling or parent.
381+
*
382+
* @param node The current position in the AST.
383+
* @returns The parent AST node or null if there are no valid parents.
384+
*/
385+
private findSiblingOrParent(node: ASTNode | null): ASTNode | null {
386+
if (!node) {
387+
return null;
388+
}
389+
const nextNode = node.next();
390+
if (nextNode) {
391+
return nextNode;
392+
}
393+
return this.findSiblingOrParent(node.out());
394+
}
395+
396+
/**
397+
* Get the right most child of a node.
398+
*
399+
* @param node The node to find the right most child of.
400+
* @returns The right most child of the given node, or the node if no child
401+
* exists.
402+
*/
403+
private getRightMostChild(node: ASTNode | null): ASTNode | null {
404+
if (!node!.in()) {
405+
return node;
406+
}
407+
let newNode = node!.in();
408+
while (newNode && newNode.next()) {
409+
newNode = newNode.next();
410+
}
411+
return this.getRightMostChild(newNode);
412+
}
311413
}
312414

313415
export const registrationName = 'LineCursor';

src/navigation_controller.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@
1313
import './gesture_monkey_patch';
1414

1515
import * as Blockly from 'blockly/core';
16-
import {ASTNode, ShortcutRegistry, BlockSvg, WorkspaceSvg, ICopyData} from 'blockly/core';
16+
import {
17+
ASTNode,
18+
ShortcutRegistry,
19+
BlockSvg,
20+
WorkspaceSvg,
21+
ICopyData,
22+
} from 'blockly/core';
1723
import {utils as BlocklyUtils} from 'blockly/core';
1824

1925
import * as Constants from './constants';
@@ -822,9 +828,7 @@ export class NavigationController {
822828
if (!cursor) {
823829
return false;
824830
}
825-
const sourceBlock = cursor
826-
.getCurNode()
827-
.getSourceBlock() as BlockSvg;
831+
const sourceBlock = cursor.getCurNode().getSourceBlock() as BlockSvg;
828832
// Delete or backspace.
829833
// Stop the browser from going back to the previous page.
830834
// Do this first to prevent an error in the delete code from resulting

0 commit comments

Comments
 (0)