Skip to content

Commit 49f40e4

Browse files
authored
Merge pull request #93 from lizlooney/pr_mutate_callers
Update parameters for call block when method args are changed.
2 parents f0900b1 + bccb189 commit 49f40e4

File tree

2 files changed

+137
-53
lines changed

2 files changed

+137
-53
lines changed

src/blocks/mrc_call_python_function.ts

Lines changed: 133 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { createFieldNonEditableText } from '../fields/FieldNonEditableText';
2828
import { getAllowedTypesForSetCheck, getOutputCheck } from './utils/python';
2929
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
3030
import { MRC_STYLE_FUNCTIONS } from '../themes/styles'
31+
import { ClassMethodDefExtraState } from './mrc_class_method_def'
3132

3233
// A block to call a python function.
3334

@@ -57,7 +58,8 @@ interface CallPythonFunctionMixin extends CallPythonFunctionMixinType {
5758
mrcImportModule: string,
5859
mrcActualFunctionName: string,
5960
mrcExportedFunction: boolean,
60-
maybeRenameProcedure(this: CallPythonFunctionBlock, oldName: string, legalName: string): void;
61+
renameMethod(this: CallPythonFunctionBlock, oldName: string, newName: string): void;
62+
mutateMethod(this: CallPythonFunctionBlock, defBlockExtraState: ClassMethodDefExtraState): void;
6163
}
6264
type CallPythonFunctionMixinType = typeof CALL_PYTHON_FUNCTION;
6365

@@ -219,59 +221,95 @@ const CALL_PYTHON_FUNCTION = {
219221
this.setNextStatement(true, null);
220222
this.setOutput(false);
221223
}
222-
// Add the dummy input.
223-
switch (this.mrcFunctionKind) {
224-
case FunctionKind.MODULE:
225-
this.appendDummyInput()
226-
.appendField('call')
227-
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_MODULE_OR_CLASS_NAME)
228-
.appendField('.')
229-
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_FUNCTION_NAME);
230-
break;
231-
case FunctionKind.STATIC:
232-
this.appendDummyInput()
233-
.appendField('call')
234-
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_MODULE_OR_CLASS_NAME)
235-
.appendField('.')
236-
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_FUNCTION_NAME);
237-
break;
238-
case FunctionKind.CONSTRUCTOR:
239-
this.appendDummyInput()
240-
.appendField('create')
241-
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_MODULE_OR_CLASS_NAME);
242-
break;
243-
case FunctionKind.INSTANCE:
244-
this.appendDummyInput()
245-
.appendField('call')
246-
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_MODULE_OR_CLASS_NAME)
247-
.appendField('.')
248-
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_FUNCTION_NAME);
249-
break;
250-
case FunctionKind.INSTANCE_WITHIN:
251-
this.appendDummyInput()
252-
.appendField('call')
253-
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_FUNCTION_NAME);
254-
break;
255-
default:
256-
throw new Error('mrcFunctionKind has unexpected value: ' + mrcFunctionKind)
224+
225+
if (!this.getInput('TITLE')) {
226+
// Add the dummy input.
227+
switch (this.mrcFunctionKind) {
228+
case FunctionKind.MODULE:
229+
this.appendDummyInput('TITLE')
230+
.appendField('call')
231+
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_MODULE_OR_CLASS_NAME)
232+
.appendField('.')
233+
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_FUNCTION_NAME);
234+
break;
235+
case FunctionKind.STATIC:
236+
this.appendDummyInput('TITLE')
237+
.appendField('call')
238+
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_MODULE_OR_CLASS_NAME)
239+
.appendField('.')
240+
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_FUNCTION_NAME);
241+
break;
242+
case FunctionKind.CONSTRUCTOR:
243+
this.appendDummyInput('TITLE')
244+
.appendField('create')
245+
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_MODULE_OR_CLASS_NAME);
246+
break;
247+
case FunctionKind.INSTANCE:
248+
this.appendDummyInput('TITLE')
249+
.appendField('call')
250+
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_MODULE_OR_CLASS_NAME)
251+
.appendField('.')
252+
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_FUNCTION_NAME);
253+
break;
254+
case FunctionKind.INSTANCE_WITHIN: {
255+
const input = this.getInput('TITLE');
256+
if (!input) {
257+
this.appendDummyInput('TITLE')
258+
.appendField('call')
259+
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_FUNCTION_NAME);
260+
}
261+
break;
262+
}
263+
default:
264+
throw new Error('mrcFunctionKind has unexpected value: ' + mrcFunctionKind)
265+
}
257266
}
258-
// Add input sockets for the arguments.
267+
268+
// Update input sockets for the arguments.
259269
for (let i = 0; i < this.mrcArgs.length; i++) {
260-
const input = this.appendValueInput('ARG' + i)
261-
.setAlign(Blockly.inputs.Align.RIGHT)
262-
.appendField(this.mrcArgs[i].name);
270+
const argName = this.mrcArgs[i].name;
271+
let argInput = this.getInput('ARG' + i);
272+
const argField = this.getField('ARGNAME' + i);
273+
if (argInput && argField) {
274+
// Ensure argument name is up to date. No need to fire a change event.
275+
Blockly.Events.disable();
276+
try {
277+
argField.setValue(argName);
278+
} finally {
279+
Blockly.Events.enable();
280+
}
281+
} else {
282+
// Add new input.
283+
argInput = this.appendValueInput('ARG' + i)
284+
.setAlign(Blockly.inputs.Align.RIGHT)
285+
.appendField(argName, 'ARGNAME' + i);
286+
}
263287
if (this.mrcArgs[i].type) {
264-
input.setCheck(getAllowedTypesForSetCheck(this.mrcArgs[i].type));
288+
argInput.setCheck(getAllowedTypesForSetCheck(this.mrcArgs[i].type));
265289
}
266290
}
267-
},
268-
maybeRenameProcedure: function(this: CallPythonFunctionBlock, oldName: string, newName: string): void {
269-
if (this.mrcFunctionKind === FunctionKind.INSTANCE_WITHIN) {
270-
if (this.getFieldValue(pythonUtils.FIELD_FUNCTION_NAME) == oldName) {
271-
this.setFieldValue(newName, pythonUtils.FIELD_FUNCTION_NAME);
272-
}
291+
// Remove deleted inputs.
292+
for (let i = this.mrcArgs.length; this.getInput('ARG' + i); i++) {
293+
this.removeInput('ARG' + i);
273294
}
274-
}
295+
},
296+
renameMethod: function(this: CallPythonFunctionBlock, newName: string): void {
297+
this.setFieldValue(newName, pythonUtils.FIELD_FUNCTION_NAME);
298+
},
299+
mutateMethod: function(
300+
this: CallPythonFunctionBlock,
301+
defBlockExtraState: ClassMethodDefExtraState
302+
): void {
303+
this.mrcReturnType = defBlockExtraState.returnType;
304+
this.mrcArgs = [];
305+
defBlockExtraState.params.forEach((param) => {
306+
this.mrcArgs.push({
307+
'name': param.name,
308+
'type': param.type,
309+
});
310+
});
311+
this.updateBlock_();
312+
},
275313
};
276314

277315
export const setup = function() {
@@ -356,3 +394,50 @@ function generateCodeForArguments(
356394
}
357395
return code;
358396
}
397+
398+
function getMethodCallers(workspace: Blockly.Workspace, name: string): Blockly.Block[] {
399+
return workspace.getBlocksByType('mrc_call_python_function').filter((block) => {
400+
const callBlock = block as CallPythonFunctionBlock;
401+
return (
402+
callBlock.mrcFunctionKind === FunctionKind.INSTANCE_WITHIN &&
403+
callBlock.getFieldValue(pythonUtils.FIELD_FUNCTION_NAME) === name
404+
);
405+
});
406+
}
407+
408+
export function renameMethodCallers(workspace: Blockly.Workspace, oldName: string, newName: string): void {
409+
for (const block of getMethodCallers(workspace, oldName)) {
410+
(block as CallPythonFunctionBlock).renameMethod(newName);
411+
}
412+
}
413+
414+
export function mutateMethodCallers(
415+
workspace: Blockly.Workspace, methodName: string, defBlockExtraState: ClassMethodDefExtraState) {
416+
const oldRecordUndo = Blockly.Events.getRecordUndo();
417+
418+
for (const block of getMethodCallers(workspace, methodName)) {
419+
const callBlock = block as CallPythonFunctionBlock;
420+
// Get the extra state before changing the call block.
421+
const oldExtraState = callBlock.saveExtraState();
422+
423+
// Apply the changes.
424+
callBlock.mutateMethod(defBlockExtraState);
425+
426+
// Get the extra state after changing the call block.
427+
const newExtraState = callBlock.saveExtraState();
428+
if (oldExtraState !== newExtraState) {
429+
// Fire a change event, but don't record it as an undoable action.
430+
Blockly.Events.setRecordUndo(false);
431+
Blockly.Events.fire(
432+
new (Blockly.Events.get(Blockly.Events.BLOCK_CHANGE))(
433+
callBlock,
434+
'mutation',
435+
null,
436+
oldExtraState,
437+
newExtraState,
438+
),
439+
);
440+
Blockly.Events.setRecordUndo(oldRecordUndo);
441+
}
442+
}
443+
}

src/blocks/mrc_class_method_def.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import * as ChangeFramework from './utils/change_framework'
2626
import { getLegalName } from './utils/python';
2727
import { Order } from 'blockly/python';
2828
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
29-
import { CallPythonFunctionBlock } from './mrc_call_python_function'
29+
import { renameMethodCallers, mutateMethodCallers } from './mrc_call_python_function'
3030

3131
export const BLOCK_NAME = 'mrc_class_method_def';
3232

@@ -146,6 +146,7 @@ const CLASS_METHOD_DEF = {
146146
});
147147
});
148148
this.updateBlock_();
149+
mutateMethodCallers(this.workspace, this.getFieldValue('NAME'), this.saveExtraState());
149150
},
150151
/**
151152
* Update the block to reflect the newly loaded extra state.
@@ -186,7 +187,7 @@ const CLASS_METHOD_DEF = {
186187
paramBlock.nextConnection && paramBlock.nextConnection.targetBlock();
187188
}
188189
this.mrcUpdateParams();
189-
//Blockly.Procedures.mutateCallers(this);
190+
mutateMethodCallers(this.workspace, this.getFieldValue('NAME'), this.saveExtraState());
190191
},
191192
decompose: function (this: ClassMethodDefBlock, workspace: Blockly.Workspace) {
192193
// This is a special sub-block that only gets created in the mutator UI.
@@ -238,9 +239,7 @@ const CLASS_METHOD_DEF = {
238239
const oldName = nameField.getValue();
239240
if (oldName !== name && oldName !== legalName) {
240241
// Rename any callers.
241-
for (const block of this.workspace.getBlocksByType('mrc_call_python_function')) {
242-
(block as CallPythonFunctionBlock).maybeRenameProcedure(oldName, legalName);
243-
}
242+
renameMethodCallers(this.workspace, oldName, legalName);
244243
}
245244
return legalName;
246245
},

0 commit comments

Comments
 (0)