Merge pull request #1071 from marisaleung/develop

VariableMap and functions added.
This commit is contained in:
marisaleung
2017-05-03 13:20:05 -07:00
committed by GitHub
11 changed files with 982 additions and 106 deletions

View File

@@ -137,7 +137,7 @@ Blockly.FieldVariable.dropdownCreate = function() {
if (this.sourceBlock_ && this.sourceBlock_.workspace) {
// Get a copy of the list, so that adding rename and new variable options
// doesn't modify the workspace's list.
var variableList = this.sourceBlock_.workspace.variableList.slice(0);
var variableList = this.sourceBlock_.workspace.getVariablesOfType('');
} else {
var variableList = [];
}

View File

@@ -86,7 +86,7 @@ Blockly.Variables.allUsedVariables = function(root) {
* Find all variables that the user has created through the workspace or
* toolbox. For use by generators.
* @param {!Blockly.Workspace} root The workspace to inspect.
* @return {!Array.<string>} Array of variable names.
* @return {!Array.<Blockly.VariableModel>} Array of variable models.
*/
Blockly.Variables.allVariables = function(root) {
if (root instanceof Blockly.Block) {
@@ -94,8 +94,9 @@ Blockly.Variables.allVariables = function(root) {
console.warn('Deprecated call to Blockly.Variables.allVariables ' +
'with a block instead of a workspace. You may want ' +
'Blockly.Variables.allUsedVariables');
return {};
}
return root.variableList;
return root.getAllVariables();
};
/**
@@ -104,7 +105,7 @@ Blockly.Variables.allVariables = function(root) {
* @return {!Array.<!Element>} Array of XML block elements.
*/
Blockly.Variables.flyoutCategory = function(workspace) {
var variableList = workspace.variableList;
var variableList = workspace.getVariablesOfType('');
variableList.sort(goog.string.caseInsensitiveCompare);
var xmlList = [];
@@ -169,7 +170,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
* @return {string} New variable name.
*/
Blockly.Variables.generateUniqueName = function(workspace) {
var variableList = workspace.variableList;
var variableList = workspace.getAllVariables();
var newName = '';
if (variableList.length) {
var nameSuffix = 1;
@@ -179,7 +180,7 @@ Blockly.Variables.generateUniqueName = function(workspace) {
while (!newName) {
var inUse = false;
for (var i = 0; i < variableList.length; i++) {
if (variableList[i].toLowerCase() == potName) {
if (variableList[i].name.toLowerCase() == potName) {
// This potential name is already used.
inUse = true;
break;
@@ -222,13 +223,21 @@ Blockly.Variables.createVariable = function(workspace, opt_callback) {
Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, defaultName,
function(text) {
if (text) {
if (workspace.variableIndexOf(text) != -1) {
if (workspace.getVariable(text)) {
Blockly.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1',
text.toLowerCase()),
function() {
promptAndCheckWithAlert(text); // Recurse
});
} else {
}
else if (!Blockly.Procedures.isLegalName_(text, workspace)) {
Blockly.alert(Blockly.Msg.PROCEDURE_ALREADY_EXISTS.replace('%1',
text.toLowerCase()),
function() {
promptAndCheckWithAlert(text); // Recurse
});
}
else {
workspace.createVariable(text);
if (opt_callback) {
opt_callback(text);

View File

@@ -74,12 +74,15 @@ Blockly.Workspace = function(opt_options) {
* @private
*/
this.blockDB_ = Object.create(null);
/*
* @type {!Array.<string>}
* A list of all of the named variables in the workspace, including variables
/**
* @type {!Object<string, !Array.<Blockly.VariableModel>>}
* A map from variable type to list of variable names. The lists contain all
* of the named variables in the workspace, including variables
* that are not currently in use.
* @private
*/
this.variableList = [];
this.variableMap_ = Object.create(null);
};
/**
@@ -115,19 +118,20 @@ Blockly.Workspace.SCAN_ANGLE = 3;
/**
* Add a block to the list of top blocks.
* @param {!Blockly.Block} block Block to remove.
* @param {!Blockly.Block} block Block to add.
*/
Blockly.Workspace.prototype.addTopBlock = function(block) {
this.topBlocks_.push(block);
if (this.isFlyout) {
// This is for the (unlikely) case where you have a variable in a block in
// an always-open flyout. It needs to be possible to edit the block in the
// flyout, so the contents of the dropdown need to be correct.
var variables = Blockly.Variables.allUsedVariables(block);
for (var i = 0; i < variables.length; i++) {
if (this.variableList.indexOf(variables[i]) == -1) {
this.variableList.push(variables[i]);
}
if (!this.isFlyout) {
return;
}
// This is for the (unlikely) case where you have a variable in a block in
// an always-open flyout. It needs to be possible to edit the block in the
// flyout, so the contents of the dropdown need to be correct.
var variableNames = Blockly.Variables.allUsedVariables(block);
for (var i = 0, name; name = variableNames[i]; i++) {
if (!this.getVariable(name)) {
this.createVariable(name);
}
}
};
@@ -191,84 +195,176 @@ Blockly.Workspace.prototype.clear = function() {
if (!existingGroup) {
Blockly.Events.setGroup(false);
}
this.variableList.length = 0;
this.variableMap_ = Object.create(null);
};
/**
* Walk the workspace and update the list of variables to only contain ones in
* Walk the workspace and update the map of variables to only contain ones in
* use on the workspace. Use when loading new workspaces from disk.
* @param {boolean} clearList True if the old variable list should be cleared.
* @param {boolean} clear True if the old variable map should be cleared.
*/
Blockly.Workspace.prototype.updateVariableList = function(clearList) {
Blockly.Workspace.prototype.updateVariableStore = function(clear) {
// TODO: Sort
if (!this.isFlyout) {
// Update the list in place so that the flyout's references stay correct.
if (clearList) {
this.variableList.length = 0;
if (this.isFlyout) {
return;
}
var variableNames = Blockly.Variables.allUsedVariables(this);
var varList = [];
for (var i = 0, name; name = variableNames[i]; i++) {
// Get variable model with the used variable name.
var tempVar = this.getVariable(name);
if (tempVar) {
varList.push({'name': tempVar.name, 'type': tempVar.type,
'id': tempVar.getId()});
}
var allVariables = Blockly.Variables.allUsedVariables(this);
for (var i = 0; i < allVariables.length; i++) {
this.createVariable(allVariables[i]);
else {
varList.push({'name': name, 'type': null, 'id': null});
// TODO(marisaleung): Use variable.type and variable.getId() once variable
// instances are storing more than just name.
}
}
if (clear) {
this.variableMap_ = Object.create(null);
}
// Update the list in place so that the flyout's references stay correct.
for (var i = 0, varDict; varDict = varList[i]; i++) {
if (!this.getVariable(varDict.name)) {
this.createVariable(varDict.name, varDict.type, varDict.id);
}
}
};
/**
* Rename a variable by updating its name in the variable list.
* Rename a variable by updating its name in the variable map. Identify the
* variable to rename with the given variable.
* TODO: #468
* @param {string} oldName Variable to rename.
* @param {?Blockly.VariableModel} variable Variable to rename.
* @param {string} newName New variable name.
*/
Blockly.Workspace.prototype.renameVariable = function(oldName, newName) {
// Find the old name in the list.
var variableIndex = this.variableIndexOf(oldName);
var newVariableIndex = this.variableIndexOf(newName);
Blockly.Workspace.prototype.renameVariableInternal_ = function(variable, newName) {
var newVariable = this.getVariable(newName);
var oldCase;
var variableIndex = -1;
var newVariableIndex = -1;
var type;
// We might be renaming to an existing name but with different case. If so,
// we will also update all of the blocks using the new name to have the
// correct case.
if (newVariableIndex != -1 &&
this.variableList[newVariableIndex] != newName) {
var oldCase = this.variableList[newVariableIndex];
// If they are different types, throw an error.
if (variable && newVariable && variable.type != newVariable.type) {
throw Error('Variable "' + oldName + '" is type "' + variable.type +
'" and variable "' + newName + '" is type "' + newVariable.type +
'". Both must be the same type.');
}
if (variable || newVariable) {
type = (variable || newVariable).type;
}
else {
type = '';
}
var variableList = this.getVariablesOfType(type);
if (variable) {
variableIndex = variableList.indexOf(variable);
}
if (newVariable){
newVariableIndex = variableList.indexOf(newVariable);
}
// Find if newVariable case is different.
if (newVariableIndex != -1 && newVariable.name != newName) {
oldCase = newVariable.name;
}
Blockly.Events.setGroup(true);
var blocks = this.getAllBlocks();
// Iterate through every block.
// Iterate through every block and update name.
for (var i = 0; i < blocks.length; i++) {
blocks[i].renameVar(oldName, newName);
blocks[i].renameVar(variable.name, newName);
if (oldCase) {
blocks[i].renameVar(oldCase, newName);
}
}
Blockly.Events.setGroup(false);
if (variableIndex == newVariableIndex ||
if (variableIndex == -1 && newVariableIndex == -1) {
this.createVariable(newName, '');
console.log('Tried to rename an non-existent variable.');
}
else if (variableIndex == newVariableIndex ||
variableIndex != -1 && newVariableIndex == -1) {
// Only changing case, or renaming to a completely novel name.
this.variableList[variableIndex] = newName;
this.variableMap_[type][variableIndex].name = newName;
} else if (variableIndex != -1 && newVariableIndex != -1) {
// Renaming one existing variable to another existing variable.
// The case might have changed, so we update the destination ID.
this.variableList[newVariableIndex] = newName;
this.variableList.splice(variableIndex, 1);
} else {
this.variableList.push(newName);
console.log('Tried to rename an non-existent variable.');
this.variableMap_[type][newVariableIndex].name = newName;
this.variableMap_[type].splice(variableIndex, 1);
}
};
/**
* Create a variable with the given name.
* Rename a variable by updating its name in the variable map. Identify the
* variable to rename with the given name.
* TODO: #468
* @param {string} name The new variable's name.
* @param {string} oldName Variable to rename.
* @param {string} newName New variable name.
*/
Blockly.Workspace.prototype.createVariable = function(name) {
var index = this.variableIndexOf(name);
if (index == -1) {
this.variableList.push(name);
Blockly.Workspace.prototype.renameVariable = function(oldName, newName) {
// Warning: Prefer to use renameVariableById.
var variable = this.getVariable(oldName);
this.renameVariableInternal_(variable, newName);
};
/**
* Rename a variable by updating its name in the variable map. Identify the
* variable to rename with the given id.
* @param {string} id Id of the variable to rename.
* @param {string} newName New variable name.
*/
Blockly.Workspace.prototype.renameVariableById = function(id, newName) {
var variable = this.getVariableById(id);
this.renameVariableInternal_(variable, newName);
}
/**
* Create a variable with a given name, optional type, and optional id.
* @param {!string} name The name of the variable. This must be unique across
* variables and procedures.
* @param {?string} opt_type The type of the variable like 'int' or 'string'.
* Does not need to be unique. Field_variable can filter variables based on
* their type. This will default to '' which is a specific type.
* @param {?string} opt_id The unique id of the variable. This will default to
* a UUID.
*/
Blockly.Workspace.prototype.createVariable = function(name, opt_type, opt_id) {
var variable = this.getVariable(name);
if (variable) {
if (opt_type && variable.type != opt_type) {
throw Error('Variable "' + name + '" is already in use and its type is "'
+ variable.type + '" which conflicts with the passed in ' +
'type, "' + opt_type + '".');
}
if (opt_id && variable.getId() != opt_id) {
throw Error('Variable "' + name + '" is already in use and its id is "'
+ variable.getId() + '" which conflicts with the passed in ' +
'id, "' + opt_id + '".');
}
return;
}
if (opt_id && this.getVariableById(opt_id)) {
throw Error('Variable id, "' + opt_id + '", is already in use.');
}
opt_id = opt_id || Blockly.utils.genUid();
opt_type = opt_type || '';
variable = new Blockly.VariableModel(name, opt_type, opt_id);
// If opt_type is not a key, create a new list.
if (!this.variableMap_[opt_type]) {
this.variableMap_[opt_type] = [variable];
}
// Else append the variable to the preexisting list.
else {
this.variableMap_[opt_type].push(variable);
}
};
@@ -297,15 +393,11 @@ Blockly.Workspace.prototype.getVariableUses = function(name) {
};
/**
* Delete a variables and all of its uses from this workspace. May prompt the
* user for confirmation.
* Delete a variable by the passed in name and all of its uses from this
* workspace. May prompt the user for confirmation.
* @param {string} name Name of variable to delete.
*/
Blockly.Workspace.prototype.deleteVariable = function(name) {
var variableIndex = this.variableIndexOf(name);
if (variableIndex == -1) {
return;
}
// Check whether this variable is a function parameter before deleting.
var uses = this.getVariableUses(name);
for (var i = 0, block; block = uses[i]; i++) {
@@ -321,6 +413,7 @@ Blockly.Workspace.prototype.deleteVariable = function(name) {
}
var workspace = this;
var variable = workspace.getVariable(name);
if (uses.length > 1) {
// Confirm before deleting multiple blocks.
Blockly.confirm(
@@ -328,29 +421,49 @@ Blockly.Workspace.prototype.deleteVariable = function(name) {
replace('%2', name),
function(ok) {
if (ok) {
workspace.deleteVariableInternal_(name);
workspace.deleteVariableInternal_(variable);
}
});
} else {
// No confirmation necessary for a single block.
this.deleteVariableInternal_(name);
this.deleteVariableInternal_(variable);
}
};
/**
* Delete a variables by the passed in id and all of its uses from this
* workspace. May prompt the user for confirmation.
* @param {string} id Id of variable to delete.
*/
Blockly.Workspace.prototype.deleteVariableById = function(id) {
var variable = this.getVariableById(id);
if (variable) {
this.deleteVariableInternal_(variable);
}
};
/**
* Deletes a variable and all of its uses from this workspace without asking the
* user for confirmation.
* @param {Blockly.VariableModel} variable Variable to delete.
* @private
*/
Blockly.Workspace.prototype.deleteVariableInternal_ = function(name) {
var uses = this.getVariableUses(name);
var variableIndex = this.variableIndexOf(name);
Blockly.Workspace.prototype.deleteVariableInternal_ = function(variable) {
var uses = this.getVariableUses(variable.name);
Blockly.Events.setGroup(true);
for (var i = 0; i < uses.length; i++) {
uses[i].dispose(true, false);
}
Blockly.Events.setGroup(false);
this.variableList.splice(variableIndex, 1);
var type = variable.type;
for (var i = 0, tempVar; tempVar = this.variableMap_[type][i]; i++) {
if (Blockly.Names.equals(tempVar.name, variable.name)) {
delete this.variableMap_[type][i];
this.variableMap_[type].splice(i, 1);
return;
}
}
};
/**
@@ -359,14 +472,48 @@ Blockly.Workspace.prototype.deleteVariableInternal_ = function(name) {
* @param {string} name The name to check for.
* @return {number} The index of the name in the variable list, or -1 if it is
* not present.
* @deprecated April 2017
*/
Blockly.Workspace.prototype.variableIndexOf = function(name) {
for (var i = 0, varname; varname = this.variableList[i]; i++) {
if (Blockly.Names.equals(varname, name)) {
return i;
console.warn(
'Deprecated call to Blockly.Workspace.prototype.variableIndexOf');
return -1;
};
/**
* Find the variable by the given name and return it. Return null if it is not
* found.
* @param {!string} name The name to check for.
* @return {?Blockly.VariableModel} the variable with the given name.
*/
Blockly.Workspace.prototype.getVariable = function(name) {
var keys = Object.keys(this.variableMap_);
for (var i = 0; i < keys.length; i++ ) {
var key = keys[i];
for (var j = 0, variable; variable = this.variableMap_[key][j]; j++) {
if (Blockly.Names.equals(variable.name, name)) {
return variable;
}
}
}
return -1;
return null;
};
/**
* Find the variable by the given id and return it. Return null if it is not
* found.
* @param {!string} id The id to check for.
* @return {?Blockly.VariableModel} The variable with the given id.
*/
Blockly.Workspace.prototype.getVariableById = function(id) {
for (var key of Object.keys(this.variableMap_)) {
for (var variable of this.variableMap_[key]) {
if (variable.getId() == id) {
return variable;
}
}
}
return null;
};
/**
@@ -504,6 +651,43 @@ Blockly.Workspace.prototype.allInputsFilled = function(opt_shadowBlocksAreFilled
return true;
};
/**
* Find the variable with the specified type. If type is null, return list of
* variables with empty string type.
* @param {?string} type Type of the variables to find.
* @return {Array.<Blockly.VariableModel>} The sought after variables of the
* passed in type. An empty array if none are found.
*/
Blockly.Workspace.prototype.getVariablesOfType = function(type) {
type = type || '';
var variable_list = this.variableMap_[type];
if (variable_list) {
return variable_list;
}
return [];
};
/**
* Return all variable types.
* @return {!Array.<string>} List of variable types.
*/
Blockly.Workspace.prototype.getVariableTypes = function() {
return Object.keys(this.variableMap_);
};
/**
* Return all variables of all types.
* @return {!Array.<Blockly.VariableModel>} List of variable models.
*/
Blockly.Workspace.prototype.getAllVariables = function() {
var all_variables = [];
var keys = Object.keys(this.variableMap_);
for (var i = 0; i < keys.length; i++ ) {
all_variables = all_variables.concat(this.variableMap_[keys[i]]);
}
return all_variables;
};
/**
* Database of all workspaces.
* @private

View File

@@ -331,7 +331,7 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) {
}
Blockly.Field.stopCache();
workspace.updateVariableList(false);
workspace.updateVariableStore(false);
// Re-enable workspace resizing.
if (workspace.setResizesEnabled) {
workspace.setResizesEnabled(true);