blockly 7.20211209.4 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/blockly.d.ts +18963 -18432
  2. package/blockly.min.js +5 -4
  3. package/blockly_compressed.js +4 -3
  4. package/blockly_compressed.js.map +1 -1
  5. package/blocks/blocks.js +47 -0
  6. package/blocks/colour.js +13 -3
  7. package/blocks/lists.js +22 -13
  8. package/blocks/logic.js +13 -3
  9. package/blocks/loops.js +24 -11
  10. package/blocks/math.js +12 -3
  11. package/blocks/procedures.js +45 -32
  12. package/blocks/text.js +22 -13
  13. package/blocks/variables.js +14 -3
  14. package/blocks/variables_dynamic.js +13 -3
  15. package/blocks_compressed.js +1 -1
  16. package/blocks_compressed.js.map +1 -1
  17. package/core/block.js +1869 -1814
  18. package/core/block_drag_surface.js +201 -200
  19. package/core/block_dragger.js +377 -373
  20. package/core/block_svg.js +1593 -1479
  21. package/core/blockly.js +8 -22
  22. package/core/blocks.js +9 -2
  23. package/core/browser_events.js +22 -5
  24. package/core/bubble.js +841 -797
  25. package/core/bubble_dragger.js +213 -206
  26. package/core/bump_objects.js +2 -2
  27. package/core/clipboard.js +9 -9
  28. package/core/comment.js +353 -332
  29. package/core/common.js +46 -17
  30. package/core/component_manager.js +181 -174
  31. package/core/config.js +87 -0
  32. package/core/connection.js +595 -584
  33. package/core/connection_checker.js +242 -244
  34. package/core/connection_db.js +235 -230
  35. package/core/contextmenu.js +9 -6
  36. package/core/contextmenu_items.js +1 -2
  37. package/core/contextmenu_registry.js +93 -89
  38. package/core/css.js +474 -474
  39. package/core/delete_area.js +45 -42
  40. package/core/drag_target.js +57 -56
  41. package/core/dropdowndiv.js +153 -163
  42. package/core/events/events.js +2 -2
  43. package/core/events/events_abstract.js +89 -77
  44. package/core/events/events_block_base.js +37 -36
  45. package/core/events/events_block_change.js +130 -124
  46. package/core/events/events_block_create.js +73 -71
  47. package/core/events/events_block_delete.js +84 -82
  48. package/core/events/events_block_drag.js +50 -49
  49. package/core/events/events_block_move.js +147 -140
  50. package/core/events/events_bubble_open.js +51 -50
  51. package/core/events/events_click.js +48 -44
  52. package/core/events/events_comment_base.js +72 -69
  53. package/core/events/events_comment_change.js +63 -61
  54. package/core/events/events_comment_create.js +44 -42
  55. package/core/events/events_comment_delete.js +42 -40
  56. package/core/events/events_comment_move.js +106 -104
  57. package/core/events/events_marker_move.js +65 -64
  58. package/core/events/events_selected.js +46 -45
  59. package/core/events/events_theme_change.js +36 -35
  60. package/core/events/events_toolbox_item_select.js +46 -45
  61. package/core/events/events_trashcan_open.js +37 -36
  62. package/core/events/events_ui.js +47 -46
  63. package/core/events/events_ui_base.js +30 -29
  64. package/core/events/events_var_base.js +37 -36
  65. package/core/events/events_var_create.js +50 -48
  66. package/core/events/events_var_delete.js +50 -48
  67. package/core/events/events_var_rename.js +51 -49
  68. package/core/events/events_viewport.js +66 -65
  69. package/core/events/utils.js +29 -14
  70. package/core/events/workspace_events.js +49 -55
  71. package/core/extensions.js +4 -3
  72. package/core/field.js +1061 -997
  73. package/core/field_angle.js +462 -442
  74. package/core/field_checkbox.js +194 -182
  75. package/core/field_colour.js +519 -505
  76. package/core/field_dropdown.js +617 -598
  77. package/core/field_image.js +229 -220
  78. package/core/field_label.js +102 -91
  79. package/core/field_label_serializable.js +42 -41
  80. package/core/field_multilineinput.js +372 -358
  81. package/core/field_number.js +272 -253
  82. package/core/field_textinput.js +499 -467
  83. package/core/field_variable.js +458 -420
  84. package/core/flyout_base.js +1005 -952
  85. package/core/flyout_button.js +277 -260
  86. package/core/flyout_horizontal.js +304 -302
  87. package/core/flyout_metrics_manager.js +64 -64
  88. package/core/flyout_vertical.js +306 -300
  89. package/core/generator.js +459 -446
  90. package/core/gesture.js +829 -813
  91. package/core/grid.js +166 -163
  92. package/core/icon.js +168 -159
  93. package/core/inject.js +7 -5
  94. package/core/input.js +257 -248
  95. package/core/insertion_marker_manager.js +655 -624
  96. package/core/internal_constants.js +0 -129
  97. package/core/keyboard_nav/ast_node.js +605 -596
  98. package/core/keyboard_nav/basic_cursor.js +166 -165
  99. package/core/keyboard_nav/cursor.js +99 -97
  100. package/core/keyboard_nav/marker.js +83 -79
  101. package/core/keyboard_nav/tab_navigate_cursor.js +18 -23
  102. package/core/marker_manager.js +153 -141
  103. package/core/menu.js +377 -372
  104. package/core/menuitem.js +223 -217
  105. package/core/metrics_manager.js +403 -390
  106. package/core/mutator.js +468 -437
  107. package/core/names.js +229 -188
  108. package/core/options.js +290 -284
  109. package/core/procedures.js +29 -17
  110. package/core/registry.js +19 -16
  111. package/core/rendered_connection.js +482 -463
  112. package/core/renderers/common/block_rendering.js +9 -3
  113. package/core/renderers/common/constants.js +1119 -1112
  114. package/core/renderers/common/debug.js +14 -0
  115. package/core/renderers/common/debugger.js +338 -316
  116. package/core/renderers/common/drawer.js +380 -370
  117. package/core/renderers/common/i_path_object.js +2 -2
  118. package/core/renderers/common/info.js +626 -618
  119. package/core/renderers/common/marker_svg.js +579 -541
  120. package/core/renderers/common/path_object.js +203 -200
  121. package/core/renderers/common/renderer.js +220 -218
  122. package/core/renderers/geras/constants.js +36 -36
  123. package/core/renderers/geras/drawer.js +155 -147
  124. package/core/renderers/geras/highlight_constants.js +244 -238
  125. package/core/renderers/geras/highlighter.js +231 -179
  126. package/core/renderers/geras/info.js +392 -369
  127. package/core/renderers/geras/measurables/inline_input.js +25 -19
  128. package/core/renderers/geras/measurables/statement_input.js +23 -17
  129. package/core/renderers/geras/path_object.js +106 -121
  130. package/core/renderers/geras/renderer.js +96 -98
  131. package/core/renderers/measurables/base.js +30 -18
  132. package/core/renderers/measurables/bottom_row.js +83 -80
  133. package/core/renderers/measurables/connection.js +22 -15
  134. package/core/renderers/measurables/external_value_input.js +35 -22
  135. package/core/renderers/measurables/field.js +35 -20
  136. package/core/renderers/measurables/hat.js +18 -13
  137. package/core/renderers/measurables/icon.js +24 -17
  138. package/core/renderers/measurables/in_row_spacer.js +15 -13
  139. package/core/renderers/measurables/inline_input.js +43 -33
  140. package/core/renderers/measurables/input_connection.js +41 -28
  141. package/core/renderers/measurables/input_row.js +50 -44
  142. package/core/renderers/measurables/jagged_edge.js +14 -12
  143. package/core/renderers/measurables/next_connection.js +16 -14
  144. package/core/renderers/measurables/output_connection.js +26 -20
  145. package/core/renderers/measurables/previous_connection.js +16 -15
  146. package/core/renderers/measurables/round_corner.js +20 -18
  147. package/core/renderers/measurables/row.js +184 -168
  148. package/core/renderers/measurables/spacer_row.js +38 -23
  149. package/core/renderers/measurables/square_corner.js +18 -16
  150. package/core/renderers/measurables/statement_input.js +23 -20
  151. package/core/renderers/measurables/top_row.js +88 -85
  152. package/core/renderers/minimalist/constants.js +8 -7
  153. package/core/renderers/minimalist/drawer.js +11 -10
  154. package/core/renderers/minimalist/info.js +18 -18
  155. package/core/renderers/minimalist/renderer.js +40 -39
  156. package/core/renderers/thrasos/info.js +258 -248
  157. package/core/renderers/thrasos/renderer.js +20 -20
  158. package/core/renderers/zelos/constants.js +898 -873
  159. package/core/renderers/zelos/drawer.js +186 -169
  160. package/core/renderers/zelos/info.js +502 -479
  161. package/core/renderers/zelos/marker_svg.js +129 -115
  162. package/core/renderers/zelos/measurables/bottom_row.js +31 -30
  163. package/core/renderers/zelos/measurables/inputs.js +22 -21
  164. package/core/renderers/zelos/measurables/row_elements.js +14 -13
  165. package/core/renderers/zelos/measurables/top_row.js +34 -33
  166. package/core/renderers/zelos/path_object.js +181 -180
  167. package/core/renderers/zelos/renderer.js +91 -92
  168. package/core/scrollbar.js +759 -713
  169. package/core/scrollbar_pair.js +250 -245
  170. package/core/serialization/blocks.js +26 -10
  171. package/core/serialization/workspaces.js +3 -2
  172. package/core/shortcut_registry.js +286 -277
  173. package/core/sprites.js +31 -0
  174. package/core/theme.js +135 -141
  175. package/core/theme_manager.js +147 -143
  176. package/core/toolbox/category.js +602 -576
  177. package/core/toolbox/collapsible_category.js +226 -227
  178. package/core/toolbox/separator.js +70 -61
  179. package/core/toolbox/toolbox.js +934 -927
  180. package/core/toolbox/toolbox_item.js +115 -99
  181. package/core/tooltip.js +108 -35
  182. package/core/touch.js +8 -3
  183. package/core/touch_gesture.js +254 -251
  184. package/core/trashcan.js +606 -595
  185. package/core/utils/coordinate.js +97 -95
  186. package/core/utils/dom.js +2 -2
  187. package/core/utils/global.js +2 -0
  188. package/core/utils/rect.js +41 -37
  189. package/core/utils/sentinel.js +25 -0
  190. package/core/utils/size.js +30 -27
  191. package/core/utils/svg.js +18 -16
  192. package/core/variable_map.js +325 -341
  193. package/core/variable_model.js +55 -54
  194. package/core/variables.js +9 -2
  195. package/core/variables_dynamic.js +3 -1
  196. package/core/warning.js +126 -120
  197. package/core/widgetdiv.js +4 -4
  198. package/core/workspace.js +685 -664
  199. package/core/workspace_audio.js +124 -118
  200. package/core/workspace_comment.js +308 -298
  201. package/core/workspace_comment_svg.js +1029 -951
  202. package/core/workspace_drag_surface_svg.js +147 -140
  203. package/core/workspace_dragger.js +70 -71
  204. package/core/workspace_svg.js +2322 -2297
  205. package/core/xml.js +30 -20
  206. package/core/zoom_controls.js +431 -439
  207. package/generators/dart/colour.js +56 -64
  208. package/generators/dart/lists.js +61 -50
  209. package/generators/dart/math.js +160 -148
  210. package/generators/dart/text.js +83 -61
  211. package/generators/javascript/colour.js +37 -34
  212. package/generators/javascript/lists.js +50 -43
  213. package/generators/javascript/math.js +123 -139
  214. package/generators/javascript/text.js +67 -81
  215. package/generators/lua/colour.js +25 -23
  216. package/generators/lua/lists.js +97 -69
  217. package/generators/lua/logic.js +1 -2
  218. package/generators/lua/math.js +182 -144
  219. package/generators/lua/text.js +116 -99
  220. package/generators/php/colour.js +38 -32
  221. package/generators/php/lists.js +109 -89
  222. package/generators/php/math.js +90 -81
  223. package/generators/php/text.js +63 -61
  224. package/generators/python/colour.js +18 -18
  225. package/generators/python/lists.js +38 -30
  226. package/generators/python/loops.js +12 -8
  227. package/generators/python/math.js +104 -106
  228. package/generators/python/text.js +34 -30
  229. package/msg/smn.js +436 -0
  230. package/package.json +7 -6
  231. package/blocks/all.js +0 -23
@@ -39,694 +39,703 @@ const {Workspace} = goog.requireType('Blockly.Workspace');
39
39
  * Class for an AST node.
40
40
  * It is recommended that you use one of the createNode methods instead of
41
41
  * creating a node directly.
42
- * @param {string} type The type of the location.
43
- * Must be in ASTNode.types.
44
- * @param {!IASTNodeLocation} location The position in the AST.
45
- * @param {!ASTNode.Params=} opt_params Optional dictionary of options.
46
- * @constructor
47
42
  * @alias Blockly.ASTNode
48
43
  */
49
- const ASTNode = function(type, location, opt_params) {
50
- if (!location) {
51
- throw Error('Cannot create a node without a location.');
44
+ class ASTNode {
45
+ /**
46
+ * @param {string} type The type of the location.
47
+ * Must be in ASTNode.types.
48
+ * @param {!IASTNodeLocation} location The position in the AST.
49
+ * @param {!ASTNode.Params=} opt_params Optional dictionary of options.
50
+ * @alias Blockly.ASTNode
51
+ */
52
+ constructor(type, location, opt_params) {
53
+ if (!location) {
54
+ throw Error('Cannot create a node without a location.');
55
+ }
56
+
57
+ /**
58
+ * The type of the location.
59
+ * One of ASTNode.types
60
+ * @type {string}
61
+ * @private
62
+ */
63
+ this.type_ = type;
64
+
65
+ /**
66
+ * Whether the location points to a connection.
67
+ * @type {boolean}
68
+ * @private
69
+ */
70
+ this.isConnection_ = ASTNode.isConnectionType_(type);
71
+
72
+ /**
73
+ * The location of the AST node.
74
+ * @type {!IASTNodeLocation}
75
+ * @private
76
+ */
77
+ this.location_ = location;
78
+
79
+ /**
80
+ * The coordinate on the workspace.
81
+ * @type {Coordinate}
82
+ * @private
83
+ */
84
+ this.wsCoordinate_ = null;
85
+
86
+ this.processParams_(opt_params || null);
52
87
  }
53
88
 
54
89
  /**
55
- * The type of the location.
56
- * One of ASTNode.types
57
- * @type {string}
90
+ * Parse the optional parameters.
91
+ * @param {?ASTNode.Params} params The user specified parameters.
58
92
  * @private
59
93
  */
60
- this.type_ = type;
94
+ processParams_(params) {
95
+ if (!params) {
96
+ return;
97
+ }
98
+ if (params.wsCoordinate) {
99
+ this.wsCoordinate_ = params.wsCoordinate;
100
+ }
101
+ }
61
102
 
62
103
  /**
63
- * Whether the location points to a connection.
64
- * @type {boolean}
65
- * @private
104
+ * Gets the value pointed to by this node.
105
+ * It is the callers responsibility to check the node type to figure out what
106
+ * type of object they get back from this.
107
+ * @return {!IASTNodeLocation} The current field, connection, workspace, or
108
+ * block the cursor is on.
66
109
  */
67
- this.isConnection_ = ASTNode.isConnectionType_(type);
110
+ getLocation() {
111
+ return this.location_;
112
+ }
68
113
 
69
114
  /**
70
- * The location of the AST node.
71
- * @type {!IASTNodeLocation}
72
- * @private
115
+ * The type of the current location.
116
+ * One of ASTNode.types
117
+ * @return {string} The type of the location.
73
118
  */
74
- this.location_ = location;
119
+ getType() {
120
+ return this.type_;
121
+ }
75
122
 
76
123
  /**
77
124
  * The coordinate on the workspace.
78
- * @type {Coordinate}
79
- * @private
125
+ * @return {Coordinate} The workspace coordinate or null if the
126
+ * location is not a workspace.
80
127
  */
81
- this.wsCoordinate_ = null;
82
-
83
- this.processParams_(opt_params || null);
84
- };
85
-
86
- /**
87
- * @typedef {{
88
- * wsCoordinate: Coordinate
89
- * }}
90
- */
91
- ASTNode.Params;
92
-
93
- /**
94
- * Object holding different types for an AST node.
95
- * @enum {string}
96
- */
97
- ASTNode.types = {
98
- FIELD: 'field',
99
- BLOCK: 'block',
100
- INPUT: 'input',
101
- OUTPUT: 'output',
102
- NEXT: 'next',
103
- PREVIOUS: 'previous',
104
- STACK: 'stack',
105
- WORKSPACE: 'workspace',
106
- };
107
-
108
- /**
109
- * True to navigate to all fields. False to only navigate to clickable fields.
110
- * @type {boolean}
111
- */
112
- ASTNode.NAVIGATE_ALL_FIELDS = false;
113
-
114
- /**
115
- * The default y offset to use when moving the cursor from a stack to the
116
- * workspace.
117
- * @type {number}
118
- * @private
119
- */
120
- ASTNode.DEFAULT_OFFSET_Y = -20;
121
-
122
- /**
123
- * Whether an AST node of the given type points to a connection.
124
- * @param {string} type The type to check. One of ASTNode.types.
125
- * @return {boolean} True if a node of the given type points to a connection.
126
- * @private
127
- */
128
- ASTNode.isConnectionType_ = function(type) {
129
- switch (type) {
130
- case ASTNode.types.PREVIOUS:
131
- case ASTNode.types.NEXT:
132
- case ASTNode.types.INPUT:
133
- case ASTNode.types.OUTPUT:
134
- return true;
128
+ getWsCoordinate() {
129
+ return this.wsCoordinate_;
135
130
  }
136
- return false;
137
- };
138
131
 
139
- /**
140
- * Create an AST node pointing to a field.
141
- * @param {Field} field The location of the AST node.
142
- * @return {ASTNode} An AST node pointing to a field.
143
- */
144
- ASTNode.createFieldNode = function(field) {
145
- if (!field) {
146
- return null;
132
+ /**
133
+ * Whether the node points to a connection.
134
+ * @return {boolean} [description]
135
+ * @package
136
+ */
137
+ isConnection() {
138
+ return this.isConnection_;
147
139
  }
148
- return new ASTNode(ASTNode.types.FIELD, field);
149
- };
150
140
 
151
- /**
152
- * Creates an AST node pointing to a connection. If the connection has a parent
153
- * input then create an AST node of type input that will hold the connection.
154
- * @param {Connection} connection This is the connection the node will
155
- * point to.
156
- * @return {ASTNode} An AST node pointing to a connection.
157
- */
158
- ASTNode.createConnectionNode = function(connection) {
159
- if (!connection) {
141
+ /**
142
+ * Given an input find the next editable field or an input with a non null
143
+ * connection in the same block. The current location must be an input
144
+ * connection.
145
+ * @return {ASTNode} The AST node holding the next field or connection
146
+ * or null if there is no editable field or input connection after the
147
+ * given input.
148
+ * @private
149
+ */
150
+ findNextForInput_() {
151
+ const location = /** @type {!Connection} */ (this.location_);
152
+ const parentInput = location.getParentInput();
153
+ const block = parentInput.getSourceBlock();
154
+ const curIdx = block.inputList.indexOf(parentInput);
155
+ for (let i = curIdx + 1; i < block.inputList.length; i++) {
156
+ const input = block.inputList[i];
157
+ const fieldRow = input.fieldRow;
158
+ for (let j = 0; j < fieldRow.length; j++) {
159
+ const field = fieldRow[j];
160
+ if (field.isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) {
161
+ return ASTNode.createFieldNode(field);
162
+ }
163
+ }
164
+ if (input.connection) {
165
+ return ASTNode.createInputNode(input);
166
+ }
167
+ }
160
168
  return null;
161
169
  }
162
- const type = connection.type;
163
- if (type === ConnectionType.INPUT_VALUE) {
164
- return ASTNode.createInputNode(connection.getParentInput());
165
- } else if (
166
- type === ConnectionType.NEXT_STATEMENT && connection.getParentInput()) {
167
- return ASTNode.createInputNode(connection.getParentInput());
168
- } else if (type === ConnectionType.NEXT_STATEMENT) {
169
- return new ASTNode(ASTNode.types.NEXT, connection);
170
- } else if (type === ConnectionType.OUTPUT_VALUE) {
171
- return new ASTNode(ASTNode.types.OUTPUT, connection);
172
- } else if (type === ConnectionType.PREVIOUS_STATEMENT) {
173
- return new ASTNode(ASTNode.types.PREVIOUS, connection);
174
- }
175
- return null;
176
- };
177
170
 
178
- /**
179
- * Creates an AST node pointing to an input. Stores the input connection as the
180
- * location.
181
- * @param {Input} input The input used to create an AST node.
182
- * @return {ASTNode} An AST node pointing to a input.
183
- */
184
- ASTNode.createInputNode = function(input) {
185
- if (!input || !input.connection) {
171
+ /**
172
+ * Given a field find the next editable field or an input with a non null
173
+ * connection in the same block. The current location must be a field.
174
+ * @return {ASTNode} The AST node pointing to the next field or
175
+ * connection or null if there is no editable field or input connection
176
+ * after the given input.
177
+ * @private
178
+ */
179
+ findNextForField_() {
180
+ const location = /** @type {!Field} */ (this.location_);
181
+ const input = location.getParentInput();
182
+ const block = location.getSourceBlock();
183
+ const curIdx = block.inputList.indexOf(/** @type {!Input} */ (input));
184
+ let fieldIdx = input.fieldRow.indexOf(location) + 1;
185
+ for (let i = curIdx; i < block.inputList.length; i++) {
186
+ const newInput = block.inputList[i];
187
+ const fieldRow = newInput.fieldRow;
188
+ while (fieldIdx < fieldRow.length) {
189
+ if (fieldRow[fieldIdx].isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) {
190
+ return ASTNode.createFieldNode(fieldRow[fieldIdx]);
191
+ }
192
+ fieldIdx++;
193
+ }
194
+ fieldIdx = 0;
195
+ if (newInput.connection) {
196
+ return ASTNode.createInputNode(newInput);
197
+ }
198
+ }
186
199
  return null;
187
200
  }
188
- return new ASTNode(ASTNode.types.INPUT, input.connection);
189
- };
190
201
 
191
- /**
192
- * Creates an AST node pointing to a block.
193
- * @param {Block} block The block used to create an AST node.
194
- * @return {ASTNode} An AST node pointing to a block.
195
- */
196
- ASTNode.createBlockNode = function(block) {
197
- if (!block) {
202
+ /**
203
+ * Given an input find the previous editable field or an input with a non null
204
+ * connection in the same block. The current location must be an input
205
+ * connection.
206
+ * @return {ASTNode} The AST node holding the previous field or
207
+ * connection.
208
+ * @private
209
+ */
210
+ findPrevForInput_() {
211
+ const location = /** @type {!Connection} */ (this.location_);
212
+ const parentInput = location.getParentInput();
213
+ const block = parentInput.getSourceBlock();
214
+ const curIdx = block.inputList.indexOf(parentInput);
215
+ for (let i = curIdx; i >= 0; i--) {
216
+ const input = block.inputList[i];
217
+ if (input.connection && input !== parentInput) {
218
+ return ASTNode.createInputNode(input);
219
+ }
220
+ const fieldRow = input.fieldRow;
221
+ for (let j = fieldRow.length - 1; j >= 0; j--) {
222
+ const field = fieldRow[j];
223
+ if (field.isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) {
224
+ return ASTNode.createFieldNode(field);
225
+ }
226
+ }
227
+ }
198
228
  return null;
199
229
  }
200
- return new ASTNode(ASTNode.types.BLOCK, block);
201
- };
202
230
 
203
- /**
204
- * Create an AST node of type stack. A stack, represented by its top block, is
205
- * the set of all blocks connected to a top block, including the top block.
206
- * @param {Block} topBlock A top block has no parent and can be found
207
- * in the list returned by workspace.getTopBlocks().
208
- * @return {ASTNode} An AST node of type stack that points to the top
209
- * block on the stack.
210
- */
211
- ASTNode.createStackNode = function(topBlock) {
212
- if (!topBlock) {
231
+ /**
232
+ * Given a field find the previous editable field or an input with a non null
233
+ * connection in the same block. The current location must be a field.
234
+ * @return {ASTNode} The AST node holding the previous input or field.
235
+ * @private
236
+ */
237
+ findPrevForField_() {
238
+ const location = /** @type {!Field} */ (this.location_);
239
+ const parentInput = location.getParentInput();
240
+ const block = location.getSourceBlock();
241
+ const curIdx = block.inputList.indexOf(
242
+ /** @type {!Input} */ (parentInput));
243
+ let fieldIdx = parentInput.fieldRow.indexOf(location) - 1;
244
+ for (let i = curIdx; i >= 0; i--) {
245
+ const input = block.inputList[i];
246
+ if (input.connection && input !== parentInput) {
247
+ return ASTNode.createInputNode(input);
248
+ }
249
+ const fieldRow = input.fieldRow;
250
+ while (fieldIdx > -1) {
251
+ if (fieldRow[fieldIdx].isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) {
252
+ return ASTNode.createFieldNode(fieldRow[fieldIdx]);
253
+ }
254
+ fieldIdx--;
255
+ }
256
+ // Reset the fieldIdx to the length of the field row of the previous
257
+ // input.
258
+ if (i - 1 >= 0) {
259
+ fieldIdx = block.inputList[i - 1].fieldRow.length - 1;
260
+ }
261
+ }
213
262
  return null;
214
263
  }
215
- return new ASTNode(ASTNode.types.STACK, topBlock);
216
- };
217
264
 
218
- /**
219
- * Creates an AST node pointing to a workspace.
220
- * @param {!Workspace} workspace The workspace that we are on.
221
- * @param {Coordinate} wsCoordinate The position on the workspace
222
- * for this node.
223
- * @return {ASTNode} An AST node pointing to a workspace and a position
224
- * on the workspace.
225
- */
226
- ASTNode.createWorkspaceNode = function(workspace, wsCoordinate) {
227
- if (!wsCoordinate || !workspace) {
228
- return null;
265
+ /**
266
+ * Navigate between stacks of blocks on the workspace.
267
+ * @param {boolean} forward True to go forward. False to go backwards.
268
+ * @return {ASTNode} The first block of the next stack or null if there
269
+ * are no blocks on the workspace.
270
+ * @private
271
+ */
272
+ navigateBetweenStacks_(forward) {
273
+ let curLocation = this.getLocation();
274
+ if (curLocation.getSourceBlock) {
275
+ curLocation = /** @type {!IASTNodeLocationWithBlock} */ (curLocation)
276
+ .getSourceBlock();
277
+ }
278
+ if (!curLocation || !curLocation.workspace) {
279
+ return null;
280
+ }
281
+ const curRoot = curLocation.getRootBlock();
282
+ const topBlocks = curRoot.workspace.getTopBlocks(true);
283
+ for (let i = 0; i < topBlocks.length; i++) {
284
+ const topBlock = topBlocks[i];
285
+ if (curRoot.id === topBlock.id) {
286
+ const offset = forward ? 1 : -1;
287
+ const resultIndex = i + offset;
288
+ if (resultIndex === -1 || resultIndex === topBlocks.length) {
289
+ return null;
290
+ }
291
+ return ASTNode.createStackNode(topBlocks[resultIndex]);
292
+ }
293
+ }
294
+ throw Error(
295
+ 'Couldn\'t find ' + (forward ? 'next' : 'previous') + ' stack?!');
229
296
  }
230
- const params = {wsCoordinate: wsCoordinate};
231
- return new ASTNode(ASTNode.types.WORKSPACE, workspace, params);
232
- };
233
297
 
234
- /**
235
- * Gets the parent connection on a block.
236
- * This is either an output connection, previous connection or undefined.
237
- * If both connections exist return the one that is actually connected
238
- * to another block.
239
- * @param {!Block} block The block to find the parent connection on.
240
- * @return {Connection} The connection connecting to the parent of the
241
- * block.
242
- * @private
243
- */
244
- const getParentConnection = function(block) {
245
- let topConnection = block.outputConnection;
246
- if (!topConnection ||
247
- (block.previousConnection && block.previousConnection.isConnected())) {
248
- topConnection = block.previousConnection;
298
+ /**
299
+ * Finds the top most AST node for a given block.
300
+ * This is either the previous connection, output connection or block
301
+ * depending on what kind of connections the block has.
302
+ * @param {!Block} block The block that we want to find the top
303
+ * connection on.
304
+ * @return {!ASTNode} The AST node containing the top connection.
305
+ * @private
306
+ */
307
+ findTopASTNodeForBlock_(block) {
308
+ const topConnection = getParentConnection(block);
309
+ if (topConnection) {
310
+ return /** @type {!ASTNode} */ (
311
+ ASTNode.createConnectionNode(topConnection));
312
+ } else {
313
+ return /** @type {!ASTNode} */ (ASTNode.createBlockNode(block));
314
+ }
249
315
  }
250
- return topConnection;
251
- };
252
316
 
253
- /**
254
- * Creates an AST node for the top position on a block.
255
- * This is either an output connection, previous connection, or block.
256
- * @param {!Block} block The block to find the top most AST node on.
257
- * @return {ASTNode} The AST node holding the top most position on the
258
- * block.
259
- */
260
- ASTNode.createTopNode = function(block) {
261
- let astNode;
262
- const topConnection = getParentConnection(block);
263
- if (topConnection) {
264
- astNode = ASTNode.createConnectionNode(topConnection);
265
- } else {
266
- astNode = ASTNode.createBlockNode(block);
317
+ /**
318
+ * Get the AST node pointing to the input that the block is nested under or if
319
+ * the block is not nested then get the stack AST node.
320
+ * @param {Block} block The source block of the current location.
321
+ * @return {ASTNode} The AST node pointing to the input connection or
322
+ * the top block of the stack this block is in.
323
+ * @private
324
+ */
325
+ getOutAstNodeForBlock_(block) {
326
+ if (!block) {
327
+ return null;
328
+ }
329
+ // If the block doesn't have a previous connection then it is the top of the
330
+ // substack.
331
+ const topBlock = block.getTopStackBlock();
332
+ const topConnection = getParentConnection(topBlock);
333
+ // If the top connection has a parentInput, create an AST node pointing to
334
+ // that input.
335
+ if (topConnection && topConnection.targetConnection &&
336
+ topConnection.targetConnection.getParentInput()) {
337
+ return ASTNode.createInputNode(
338
+ topConnection.targetConnection.getParentInput());
339
+ } else {
340
+ // Go to stack level if you are not underneath an input.
341
+ return ASTNode.createStackNode(topBlock);
342
+ }
267
343
  }
268
- return astNode;
269
- };
270
344
 
271
- /**
272
- * Parse the optional parameters.
273
- * @param {?ASTNode.Params} params The user specified parameters.
274
- * @private
275
- */
276
- ASTNode.prototype.processParams_ = function(params) {
277
- if (!params) {
278
- return;
279
- }
280
- if (params.wsCoordinate) {
281
- this.wsCoordinate_ = params.wsCoordinate;
345
+ /**
346
+ * Find the first editable field or input with a connection on a given block.
347
+ * @param {!Block} block The source block of the current location.
348
+ * @return {ASTNode} An AST node pointing to the first field or input.
349
+ * Null if there are no editable fields or inputs with connections on the
350
+ * block.
351
+ * @private
352
+ */
353
+ findFirstFieldOrInput_(block) {
354
+ const inputs = block.inputList;
355
+ for (let i = 0; i < inputs.length; i++) {
356
+ const input = inputs[i];
357
+ const fieldRow = input.fieldRow;
358
+ for (let j = 0; j < fieldRow.length; j++) {
359
+ const field = fieldRow[j];
360
+ if (field.isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) {
361
+ return ASTNode.createFieldNode(field);
362
+ }
363
+ }
364
+ if (input.connection) {
365
+ return ASTNode.createInputNode(input);
366
+ }
367
+ }
368
+ return null;
282
369
  }
283
- };
284
370
 
285
- /**
286
- * Gets the value pointed to by this node.
287
- * It is the callers responsibility to check the node type to figure out what
288
- * type of object they get back from this.
289
- * @return {!IASTNodeLocation} The current field, connection, workspace, or
290
- * block the cursor is on.
291
- */
292
- ASTNode.prototype.getLocation = function() {
293
- return this.location_;
294
- };
295
-
296
- /**
297
- * The type of the current location.
298
- * One of ASTNode.types
299
- * @return {string} The type of the location.
300
- */
301
- ASTNode.prototype.getType = function() {
302
- return this.type_;
303
- };
371
+ /**
372
+ * Finds the source block of the location of this node.
373
+ * @return {Block} The source block of the location, or null if the node
374
+ * is of type workspace.
375
+ */
376
+ getSourceBlock() {
377
+ if (this.getType() === ASTNode.types.BLOCK) {
378
+ return /** @type {Block} */ (this.getLocation());
379
+ } else if (this.getType() === ASTNode.types.STACK) {
380
+ return /** @type {Block} */ (this.getLocation());
381
+ } else if (this.getType() === ASTNode.types.WORKSPACE) {
382
+ return null;
383
+ } else {
384
+ return /** @type {IASTNodeLocationWithBlock} */ (this.getLocation())
385
+ .getSourceBlock();
386
+ }
387
+ }
304
388
 
305
- /**
306
- * The coordinate on the workspace.
307
- * @return {Coordinate} The workspace coordinate or null if the
308
- * location is not a workspace.
309
- */
310
- ASTNode.prototype.getWsCoordinate = function() {
311
- return this.wsCoordinate_;
312
- };
389
+ /**
390
+ * Find the element to the right of the current element in the AST.
391
+ * @return {ASTNode} An AST node that wraps the next field, connection,
392
+ * block, or workspace. Or null if there is no node to the right.
393
+ */
394
+ next() {
395
+ switch (this.type_) {
396
+ case ASTNode.types.STACK:
397
+ return this.navigateBetweenStacks_(true);
398
+
399
+ case ASTNode.types.OUTPUT: {
400
+ const connection = /** @type {!Connection} */ (this.location_);
401
+ return ASTNode.createBlockNode(connection.getSourceBlock());
402
+ }
403
+ case ASTNode.types.FIELD:
404
+ return this.findNextForField_();
313
405
 
314
- /**
315
- * Whether the node points to a connection.
316
- * @return {boolean} [description]
317
- * @package
318
- */
319
- ASTNode.prototype.isConnection = function() {
320
- return this.isConnection_;
321
- };
406
+ case ASTNode.types.INPUT:
407
+ return this.findNextForInput_();
322
408
 
323
- /**
324
- * Given an input find the next editable field or an input with a non null
325
- * connection in the same block. The current location must be an input
326
- * connection.
327
- * @return {ASTNode} The AST node holding the next field or connection
328
- * or null if there is no editable field or input connection after the given
329
- * input.
330
- * @private
331
- */
332
- ASTNode.prototype.findNextForInput_ = function() {
333
- const location = /** @type {!Connection} */ (this.location_);
334
- const parentInput = location.getParentInput();
335
- const block = parentInput.getSourceBlock();
336
- const curIdx = block.inputList.indexOf(parentInput);
337
- for (let i = curIdx + 1; i < block.inputList.length; i++) {
338
- const input = block.inputList[i];
339
- const fieldRow = input.fieldRow;
340
- for (let j = 0; j < fieldRow.length; j++) {
341
- const field = fieldRow[j];
342
- if (field.isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) {
343
- return ASTNode.createFieldNode(field);
409
+ case ASTNode.types.BLOCK: {
410
+ const block = /** @type {!Block} */ (this.location_);
411
+ const nextConnection = block.nextConnection;
412
+ return ASTNode.createConnectionNode(nextConnection);
413
+ }
414
+ case ASTNode.types.PREVIOUS: {
415
+ const connection = /** @type {!Connection} */ (this.location_);
416
+ return ASTNode.createBlockNode(connection.getSourceBlock());
417
+ }
418
+ case ASTNode.types.NEXT: {
419
+ const connection = /** @type {!Connection} */ (this.location_);
420
+ const targetConnection = connection.targetConnection;
421
+ return ASTNode.createConnectionNode(targetConnection);
344
422
  }
345
423
  }
346
- if (input.connection) {
347
- return ASTNode.createInputNode(input);
348
- }
424
+
425
+ return null;
349
426
  }
350
- return null;
351
- };
352
427
 
353
- /**
354
- * Given a field find the next editable field or an input with a non null
355
- * connection in the same block. The current location must be a field.
356
- * @return {ASTNode} The AST node pointing to the next field or
357
- * connection or null if there is no editable field or input connection
358
- * after the given input.
359
- * @private
360
- */
361
- ASTNode.prototype.findNextForField_ = function() {
362
- const location = /** @type {!Field} */ (this.location_);
363
- const input = location.getParentInput();
364
- const block = location.getSourceBlock();
365
- const curIdx = block.inputList.indexOf(/** @type {!Input} */ (input));
366
- let fieldIdx = input.fieldRow.indexOf(location) + 1;
367
- for (let i = curIdx; i < block.inputList.length; i++) {
368
- const newInput = block.inputList[i];
369
- const fieldRow = newInput.fieldRow;
370
- while (fieldIdx < fieldRow.length) {
371
- if (fieldRow[fieldIdx].isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) {
372
- return ASTNode.createFieldNode(fieldRow[fieldIdx]);
428
+ /**
429
+ * Find the element one level below and all the way to the left of the current
430
+ * location.
431
+ * @return {ASTNode} An AST node that wraps the next field, connection,
432
+ * workspace, or block. Or null if there is nothing below this node.
433
+ */
434
+ in() {
435
+ switch (this.type_) {
436
+ case ASTNode.types.WORKSPACE: {
437
+ const workspace = /** @type {!Workspace} */ (this.location_);
438
+ const topBlocks = workspace.getTopBlocks(true);
439
+ if (topBlocks.length > 0) {
440
+ return ASTNode.createStackNode(topBlocks[0]);
441
+ }
442
+ break;
443
+ }
444
+ case ASTNode.types.STACK: {
445
+ const block = /** @type {!Block} */ (this.location_);
446
+ return this.findTopASTNodeForBlock_(block);
447
+ }
448
+ case ASTNode.types.BLOCK: {
449
+ const block = /** @type {!Block} */ (this.location_);
450
+ return this.findFirstFieldOrInput_(block);
451
+ }
452
+ case ASTNode.types.INPUT: {
453
+ const connection = /** @type {!Connection} */ (this.location_);
454
+ const targetConnection = connection.targetConnection;
455
+ return ASTNode.createConnectionNode(targetConnection);
373
456
  }
374
- fieldIdx++;
375
- }
376
- fieldIdx = 0;
377
- if (newInput.connection) {
378
- return ASTNode.createInputNode(newInput);
379
457
  }
458
+
459
+ return null;
380
460
  }
381
- return null;
382
- };
383
461
 
384
- /**
385
- * Given an input find the previous editable field or an input with a non null
386
- * connection in the same block. The current location must be an input
387
- * connection.
388
- * @return {ASTNode} The AST node holding the previous field or
389
- * connection.
390
- * @private
391
- */
392
- ASTNode.prototype.findPrevForInput_ = function() {
393
- const location = /** @type {!Connection} */ (this.location_);
394
- const parentInput = location.getParentInput();
395
- const block = parentInput.getSourceBlock();
396
- const curIdx = block.inputList.indexOf(parentInput);
397
- for (let i = curIdx; i >= 0; i--) {
398
- const input = block.inputList[i];
399
- if (input.connection && input !== parentInput) {
400
- return ASTNode.createInputNode(input);
401
- }
402
- const fieldRow = input.fieldRow;
403
- for (let j = fieldRow.length - 1; j >= 0; j--) {
404
- const field = fieldRow[j];
405
- if (field.isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) {
406
- return ASTNode.createFieldNode(field);
462
+ /**
463
+ * Find the element to the left of the current element in the AST.
464
+ * @return {ASTNode} An AST node that wraps the previous field,
465
+ * connection, workspace or block. Or null if no node exists to the left.
466
+ * null.
467
+ */
468
+ prev() {
469
+ switch (this.type_) {
470
+ case ASTNode.types.STACK:
471
+ return this.navigateBetweenStacks_(false);
472
+
473
+ case ASTNode.types.OUTPUT:
474
+ return null;
475
+
476
+ case ASTNode.types.FIELD:
477
+ return this.findPrevForField_();
478
+
479
+ case ASTNode.types.INPUT:
480
+ return this.findPrevForInput_();
481
+
482
+ case ASTNode.types.BLOCK: {
483
+ const block = /** @type {!Block} */ (this.location_);
484
+ const topConnection = getParentConnection(block);
485
+ return ASTNode.createConnectionNode(topConnection);
486
+ }
487
+ case ASTNode.types.PREVIOUS: {
488
+ const connection = /** @type {!Connection} */ (this.location_);
489
+ const targetConnection = connection.targetConnection;
490
+ if (targetConnection && !targetConnection.getParentInput()) {
491
+ return ASTNode.createConnectionNode(targetConnection);
492
+ }
493
+ break;
494
+ }
495
+ case ASTNode.types.NEXT: {
496
+ const connection = /** @type {!Connection} */ (this.location_);
497
+ return ASTNode.createBlockNode(connection.getSourceBlock());
407
498
  }
408
499
  }
500
+
501
+ return null;
409
502
  }
410
- return null;
411
- };
412
503
 
413
- /**
414
- * Given a field find the previous editable field or an input with a non null
415
- * connection in the same block. The current location must be a field.
416
- * @return {ASTNode} The AST node holding the previous input or field.
417
- * @private
418
- */
419
- ASTNode.prototype.findPrevForField_ = function() {
420
- const location = /** @type {!Field} */ (this.location_);
421
- const parentInput = location.getParentInput();
422
- const block = location.getSourceBlock();
423
- const curIdx = block.inputList.indexOf(
424
- /** @type {!Input} */ (parentInput));
425
- let fieldIdx = parentInput.fieldRow.indexOf(location) - 1;
426
- for (let i = curIdx; i >= 0; i--) {
427
- const input = block.inputList[i];
428
- if (input.connection && input !== parentInput) {
429
- return ASTNode.createInputNode(input);
430
- }
431
- const fieldRow = input.fieldRow;
432
- while (fieldIdx > -1) {
433
- if (fieldRow[fieldIdx].isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) {
434
- return ASTNode.createFieldNode(fieldRow[fieldIdx]);
504
+ /**
505
+ * Find the next element that is one position above and all the way to the
506
+ * left of the current location.
507
+ * @return {ASTNode} An AST node that wraps the next field, connection,
508
+ * workspace or block. Or null if we are at the workspace level.
509
+ */
510
+ out() {
511
+ switch (this.type_) {
512
+ case ASTNode.types.STACK: {
513
+ const block = /** @type {!Block} */ (this.location_);
514
+ const blockPos = block.getRelativeToSurfaceXY();
515
+ // TODO: Make sure this is in the bounds of the workspace.
516
+ const wsCoordinate =
517
+ new Coordinate(blockPos.x, blockPos.y + ASTNode.DEFAULT_OFFSET_Y);
518
+ return ASTNode.createWorkspaceNode(block.workspace, wsCoordinate);
519
+ }
520
+ case ASTNode.types.OUTPUT: {
521
+ const connection = /** @type {!Connection} */ (this.location_);
522
+ const target = connection.targetConnection;
523
+ if (target) {
524
+ return ASTNode.createConnectionNode(target);
525
+ }
526
+ return ASTNode.createStackNode(connection.getSourceBlock());
527
+ }
528
+ case ASTNode.types.FIELD: {
529
+ const field = /** @type {!Field} */ (this.location_);
530
+ return ASTNode.createBlockNode(field.getSourceBlock());
531
+ }
532
+ case ASTNode.types.INPUT: {
533
+ const connection = /** @type {!Connection} */ (this.location_);
534
+ return ASTNode.createBlockNode(connection.getSourceBlock());
535
+ }
536
+ case ASTNode.types.BLOCK: {
537
+ const block = /** @type {!Block} */ (this.location_);
538
+ return this.getOutAstNodeForBlock_(block);
539
+ }
540
+ case ASTNode.types.PREVIOUS: {
541
+ const connection = /** @type {!Connection} */ (this.location_);
542
+ return this.getOutAstNodeForBlock_(connection.getSourceBlock());
543
+ }
544
+ case ASTNode.types.NEXT: {
545
+ const connection = /** @type {!Connection} */ (this.location_);
546
+ return this.getOutAstNodeForBlock_(connection.getSourceBlock());
435
547
  }
436
- fieldIdx--;
437
- }
438
- // Reset the fieldIdx to the length of the field row of the previous input.
439
- if (i - 1 >= 0) {
440
- fieldIdx = block.inputList[i - 1].fieldRow.length - 1;
441
548
  }
442
- }
443
- return null;
444
- };
445
549
 
446
- /**
447
- * Navigate between stacks of blocks on the workspace.
448
- * @param {boolean} forward True to go forward. False to go backwards.
449
- * @return {ASTNode} The first block of the next stack or null if there
450
- * are no blocks on the workspace.
451
- * @private
452
- */
453
- ASTNode.prototype.navigateBetweenStacks_ = function(forward) {
454
- let curLocation = this.getLocation();
455
- if (curLocation.getSourceBlock) {
456
- curLocation = /** @type {!IASTNodeLocationWithBlock} */ (curLocation)
457
- .getSourceBlock();
458
- }
459
- if (!curLocation || !curLocation.workspace) {
460
550
  return null;
461
551
  }
462
- const curRoot = curLocation.getRootBlock();
463
- const topBlocks = curRoot.workspace.getTopBlocks(true);
464
- for (let i = 0; i < topBlocks.length; i++) {
465
- const topBlock = topBlocks[i];
466
- if (curRoot.id === topBlock.id) {
467
- const offset = forward ? 1 : -1;
468
- const resultIndex = i + offset;
469
- if (resultIndex === -1 || resultIndex === topBlocks.length) {
470
- return null;
471
- }
472
- return ASTNode.createStackNode(topBlocks[resultIndex]);
552
+
553
+ /**
554
+ * Whether an AST node of the given type points to a connection.
555
+ * @param {string} type The type to check. One of ASTNode.types.
556
+ * @return {boolean} True if a node of the given type points to a connection.
557
+ * @private
558
+ */
559
+ static isConnectionType_(type) {
560
+ switch (type) {
561
+ case ASTNode.types.PREVIOUS:
562
+ case ASTNode.types.NEXT:
563
+ case ASTNode.types.INPUT:
564
+ case ASTNode.types.OUTPUT:
565
+ return true;
473
566
  }
567
+ return false;
474
568
  }
475
- throw Error('Couldn\'t find ' + (forward ? 'next' : 'previous') + ' stack?!');
476
- };
477
569
 
478
- /**
479
- * Finds the top most AST node for a given block.
480
- * This is either the previous connection, output connection or block depending
481
- * on what kind of connections the block has.
482
- * @param {!Block} block The block that we want to find the top
483
- * connection on.
484
- * @return {!ASTNode} The AST node containing the top connection.
485
- * @private
486
- */
487
- ASTNode.prototype.findTopASTNodeForBlock_ = function(block) {
488
- const topConnection = getParentConnection(block);
489
- if (topConnection) {
490
- return /** @type {!ASTNode} */ (
491
- ASTNode.createConnectionNode(topConnection));
492
- } else {
493
- return /** @type {!ASTNode} */ (ASTNode.createBlockNode(block));
570
+ /**
571
+ * Create an AST node pointing to a field.
572
+ * @param {Field} field The location of the AST node.
573
+ * @return {ASTNode} An AST node pointing to a field.
574
+ */
575
+ static createFieldNode(field) {
576
+ if (!field) {
577
+ return null;
578
+ }
579
+ return new ASTNode(ASTNode.types.FIELD, field);
494
580
  }
495
- };
496
581
 
497
- /**
498
- * Get the AST node pointing to the input that the block is nested under or if
499
- * the block is not nested then get the stack AST node.
500
- * @param {Block} block The source block of the current location.
501
- * @return {ASTNode} The AST node pointing to the input connection or
502
- * the top block of the stack this block is in.
503
- * @private
504
- */
505
- ASTNode.prototype.getOutAstNodeForBlock_ = function(block) {
506
- if (!block) {
582
+ /**
583
+ * Creates an AST node pointing to a connection. If the connection has a
584
+ * parent input then create an AST node of type input that will hold the
585
+ * connection.
586
+ * @param {Connection} connection This is the connection the node will
587
+ * point to.
588
+ * @return {ASTNode} An AST node pointing to a connection.
589
+ */
590
+ static createConnectionNode(connection) {
591
+ if (!connection) {
592
+ return null;
593
+ }
594
+ const type = connection.type;
595
+ if (type === ConnectionType.INPUT_VALUE) {
596
+ return ASTNode.createInputNode(connection.getParentInput());
597
+ } else if (
598
+ type === ConnectionType.NEXT_STATEMENT && connection.getParentInput()) {
599
+ return ASTNode.createInputNode(connection.getParentInput());
600
+ } else if (type === ConnectionType.NEXT_STATEMENT) {
601
+ return new ASTNode(ASTNode.types.NEXT, connection);
602
+ } else if (type === ConnectionType.OUTPUT_VALUE) {
603
+ return new ASTNode(ASTNode.types.OUTPUT, connection);
604
+ } else if (type === ConnectionType.PREVIOUS_STATEMENT) {
605
+ return new ASTNode(ASTNode.types.PREVIOUS, connection);
606
+ }
507
607
  return null;
508
608
  }
509
- // If the block doesn't have a previous connection then it is the top of the
510
- // substack.
511
- const topBlock = block.getTopStackBlock();
512
- const topConnection = getParentConnection(topBlock);
513
- // If the top connection has a parentInput, create an AST node pointing to
514
- // that input.
515
- if (topConnection && topConnection.targetConnection &&
516
- topConnection.targetConnection.getParentInput()) {
517
- return ASTNode.createInputNode(
518
- topConnection.targetConnection.getParentInput());
519
- } else {
520
- // Go to stack level if you are not underneath an input.
521
- return ASTNode.createStackNode(topBlock);
522
- }
523
- };
524
609
 
525
- /**
526
- * Find the first editable field or input with a connection on a given block.
527
- * @param {!Block} block The source block of the current location.
528
- * @return {ASTNode} An AST node pointing to the first field or input.
529
- * Null if there are no editable fields or inputs with connections on the block.
530
- * @private
531
- */
532
- ASTNode.prototype.findFirstFieldOrInput_ = function(block) {
533
- const inputs = block.inputList;
534
- for (let i = 0; i < inputs.length; i++) {
535
- const input = inputs[i];
536
- const fieldRow = input.fieldRow;
537
- for (let j = 0; j < fieldRow.length; j++) {
538
- const field = fieldRow[j];
539
- if (field.isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) {
540
- return ASTNode.createFieldNode(field);
541
- }
542
- }
543
- if (input.connection) {
544
- return ASTNode.createInputNode(input);
610
+ /**
611
+ * Creates an AST node pointing to an input. Stores the input connection as
612
+ * the location.
613
+ * @param {Input} input The input used to create an AST node.
614
+ * @return {ASTNode} An AST node pointing to a input.
615
+ */
616
+ static createInputNode(input) {
617
+ if (!input || !input.connection) {
618
+ return null;
545
619
  }
620
+ return new ASTNode(ASTNode.types.INPUT, input.connection);
546
621
  }
547
- return null;
548
- };
549
622
 
550
- /**
551
- * Finds the source block of the location of this node.
552
- * @return {Block} The source block of the location, or null if the node
553
- * is of type workspace.
554
- */
555
- ASTNode.prototype.getSourceBlock = function() {
556
- if (this.getType() === ASTNode.types.BLOCK) {
557
- return /** @type {Block} */ (this.getLocation());
558
- } else if (this.getType() === ASTNode.types.STACK) {
559
- return /** @type {Block} */ (this.getLocation());
560
- } else if (this.getType() === ASTNode.types.WORKSPACE) {
561
- return null;
562
- } else {
563
- return /** @type {IASTNodeLocationWithBlock} */ (this.getLocation())
564
- .getSourceBlock();
623
+ /**
624
+ * Creates an AST node pointing to a block.
625
+ * @param {Block} block The block used to create an AST node.
626
+ * @return {ASTNode} An AST node pointing to a block.
627
+ */
628
+ static createBlockNode(block) {
629
+ if (!block) {
630
+ return null;
631
+ }
632
+ return new ASTNode(ASTNode.types.BLOCK, block);
565
633
  }
566
- };
567
634
 
568
- /**
569
- * Find the element to the right of the current element in the AST.
570
- * @return {ASTNode} An AST node that wraps the next field, connection,
571
- * block, or workspace. Or null if there is no node to the right.
572
- */
573
- ASTNode.prototype.next = function() {
574
- switch (this.type_) {
575
- case ASTNode.types.STACK:
576
- return this.navigateBetweenStacks_(true);
577
-
578
- case ASTNode.types.OUTPUT: {
579
- const connection = /** @type {!Connection} */ (this.location_);
580
- return ASTNode.createBlockNode(connection.getSourceBlock());
635
+ /**
636
+ * Create an AST node of type stack. A stack, represented by its top block, is
637
+ * the set of all blocks connected to a top block, including the top
638
+ * block.
639
+ * @param {Block} topBlock A top block has no parent and can be found
640
+ * in the list returned by workspace.getTopBlocks().
641
+ * @return {ASTNode} An AST node of type stack that points to the top
642
+ * block on the stack.
643
+ */
644
+ static createStackNode(topBlock) {
645
+ if (!topBlock) {
646
+ return null;
581
647
  }
582
- case ASTNode.types.FIELD:
583
- return this.findNextForField_();
584
-
585
- case ASTNode.types.INPUT:
586
- return this.findNextForInput_();
648
+ return new ASTNode(ASTNode.types.STACK, topBlock);
649
+ }
587
650
 
588
- case ASTNode.types.BLOCK: {
589
- const block = /** @type {!Block} */ (this.location_);
590
- const nextConnection = block.nextConnection;
591
- return ASTNode.createConnectionNode(nextConnection);
592
- }
593
- case ASTNode.types.PREVIOUS: {
594
- const connection = /** @type {!Connection} */ (this.location_);
595
- return ASTNode.createBlockNode(connection.getSourceBlock());
596
- }
597
- case ASTNode.types.NEXT: {
598
- const connection = /** @type {!Connection} */ (this.location_);
599
- const targetConnection = connection.targetConnection;
600
- return ASTNode.createConnectionNode(targetConnection);
651
+ /**
652
+ * Creates an AST node pointing to a workspace.
653
+ * @param {!Workspace} workspace The workspace that we are on.
654
+ * @param {Coordinate} wsCoordinate The position on the workspace
655
+ * for this node.
656
+ * @return {ASTNode} An AST node pointing to a workspace and a position
657
+ * on the workspace.
658
+ */
659
+ static createWorkspaceNode(workspace, wsCoordinate) {
660
+ if (!wsCoordinate || !workspace) {
661
+ return null;
601
662
  }
663
+ const params = {wsCoordinate: wsCoordinate};
664
+ return new ASTNode(ASTNode.types.WORKSPACE, workspace, params);
602
665
  }
603
666
 
604
- return null;
605
- };
667
+ /**
668
+ * Creates an AST node for the top position on a block.
669
+ * This is either an output connection, previous connection, or block.
670
+ * @param {!Block} block The block to find the top most AST node on.
671
+ * @return {ASTNode} The AST node holding the top most position on the
672
+ * block.
673
+ */
674
+ static createTopNode(block) {
675
+ let astNode;
676
+ const topConnection = getParentConnection(block);
677
+ if (topConnection) {
678
+ astNode = ASTNode.createConnectionNode(topConnection);
679
+ } else {
680
+ astNode = ASTNode.createBlockNode(block);
681
+ }
682
+ return astNode;
683
+ }
684
+ }
606
685
 
607
686
  /**
608
- * Find the element one level below and all the way to the left of the current
609
- * location.
610
- * @return {ASTNode} An AST node that wraps the next field, connection,
611
- * workspace, or block. Or null if there is nothing below this node.
687
+ * @typedef {{
688
+ * wsCoordinate: Coordinate
689
+ * }}
612
690
  */
613
- ASTNode.prototype.in = function() {
614
- switch (this.type_) {
615
- case ASTNode.types.WORKSPACE: {
616
- const workspace = /** @type {!Workspace} */ (this.location_);
617
- const topBlocks = workspace.getTopBlocks(true);
618
- if (topBlocks.length > 0) {
619
- return ASTNode.createStackNode(topBlocks[0]);
620
- }
621
- break;
622
- }
623
- case ASTNode.types.STACK: {
624
- const block = /** @type {!Block} */ (this.location_);
625
- return this.findTopASTNodeForBlock_(block);
626
- }
627
- case ASTNode.types.BLOCK: {
628
- const block = /** @type {!Block} */ (this.location_);
629
- return this.findFirstFieldOrInput_(block);
630
- }
631
- case ASTNode.types.INPUT: {
632
- const connection = /** @type {!Connection} */ (this.location_);
633
- const targetConnection = connection.targetConnection;
634
- return ASTNode.createConnectionNode(targetConnection);
635
- }
636
- }
691
+ ASTNode.Params;
637
692
 
638
- return null;
693
+ /**
694
+ * Object holding different types for an AST node.
695
+ * @enum {string}
696
+ */
697
+ ASTNode.types = {
698
+ FIELD: 'field',
699
+ BLOCK: 'block',
700
+ INPUT: 'input',
701
+ OUTPUT: 'output',
702
+ NEXT: 'next',
703
+ PREVIOUS: 'previous',
704
+ STACK: 'stack',
705
+ WORKSPACE: 'workspace',
639
706
  };
640
707
 
641
708
  /**
642
- * Find the element to the left of the current element in the AST.
643
- * @return {ASTNode} An AST node that wraps the previous field,
644
- * connection, workspace or block. Or null if no node exists to the left.
645
- * null.
709
+ * True to navigate to all fields. False to only navigate to clickable fields.
710
+ * @type {boolean}
646
711
  */
647
- ASTNode.prototype.prev = function() {
648
- switch (this.type_) {
649
- case ASTNode.types.STACK:
650
- return this.navigateBetweenStacks_(false);
651
-
652
- case ASTNode.types.OUTPUT:
653
- return null;
654
-
655
- case ASTNode.types.FIELD:
656
- return this.findPrevForField_();
657
-
658
- case ASTNode.types.INPUT:
659
- return this.findPrevForInput_();
660
-
661
- case ASTNode.types.BLOCK: {
662
- const block = /** @type {!Block} */ (this.location_);
663
- const topConnection = getParentConnection(block);
664
- return ASTNode.createConnectionNode(topConnection);
665
- }
666
- case ASTNode.types.PREVIOUS: {
667
- const connection = /** @type {!Connection} */ (this.location_);
668
- const targetConnection = connection.targetConnection;
669
- if (targetConnection && !targetConnection.getParentInput()) {
670
- return ASTNode.createConnectionNode(targetConnection);
671
- }
672
- break;
673
- }
674
- case ASTNode.types.NEXT: {
675
- const connection = /** @type {!Connection} */ (this.location_);
676
- return ASTNode.createBlockNode(connection.getSourceBlock());
677
- }
678
- }
712
+ ASTNode.NAVIGATE_ALL_FIELDS = false;
679
713
 
680
- return null;
681
- };
714
+ /**
715
+ * The default y offset to use when moving the cursor from a stack to the
716
+ * workspace.
717
+ * @type {number}
718
+ * @private
719
+ */
720
+ ASTNode.DEFAULT_OFFSET_Y = -20;
682
721
 
683
722
  /**
684
- * Find the next element that is one position above and all the way to the left
685
- * of the current location.
686
- * @return {ASTNode} An AST node that wraps the next field, connection,
687
- * workspace or block. Or null if we are at the workspace level.
723
+ * Gets the parent connection on a block.
724
+ * This is either an output connection, previous connection or undefined.
725
+ * If both connections exist return the one that is actually connected
726
+ * to another block.
727
+ * @param {!Block} block The block to find the parent connection on.
728
+ * @return {Connection} The connection connecting to the parent of the
729
+ * block.
730
+ * @private
688
731
  */
689
- ASTNode.prototype.out = function() {
690
- switch (this.type_) {
691
- case ASTNode.types.STACK: {
692
- const block = /** @type {!Block} */ (this.location_);
693
- const blockPos = block.getRelativeToSurfaceXY();
694
- // TODO: Make sure this is in the bounds of the workspace.
695
- const wsCoordinate =
696
- new Coordinate(blockPos.x, blockPos.y + ASTNode.DEFAULT_OFFSET_Y);
697
- return ASTNode.createWorkspaceNode(block.workspace, wsCoordinate);
698
- }
699
- case ASTNode.types.OUTPUT: {
700
- const connection = /** @type {!Connection} */ (this.location_);
701
- const target = connection.targetConnection;
702
- if (target) {
703
- return ASTNode.createConnectionNode(target);
704
- }
705
- return ASTNode.createStackNode(connection.getSourceBlock());
706
- }
707
- case ASTNode.types.FIELD: {
708
- const field = /** @type {!Field} */ (this.location_);
709
- return ASTNode.createBlockNode(field.getSourceBlock());
710
- }
711
- case ASTNode.types.INPUT: {
712
- const connection = /** @type {!Connection} */ (this.location_);
713
- return ASTNode.createBlockNode(connection.getSourceBlock());
714
- }
715
- case ASTNode.types.BLOCK: {
716
- const block = /** @type {!Block} */ (this.location_);
717
- return this.getOutAstNodeForBlock_(block);
718
- }
719
- case ASTNode.types.PREVIOUS: {
720
- const connection = /** @type {!Connection} */ (this.location_);
721
- return this.getOutAstNodeForBlock_(connection.getSourceBlock());
722
- }
723
- case ASTNode.types.NEXT: {
724
- const connection = /** @type {!Connection} */ (this.location_);
725
- return this.getOutAstNodeForBlock_(connection.getSourceBlock());
726
- }
732
+ const getParentConnection = function(block) {
733
+ let topConnection = block.outputConnection;
734
+ if (!topConnection ||
735
+ (block.previousConnection && block.previousConnection.isConnected())) {
736
+ topConnection = block.previousConnection;
727
737
  }
728
-
729
- return null;
738
+ return topConnection;
730
739
  };
731
740
 
732
741
  exports.ASTNode = ASTNode;