Concrete.UI.SearchReplaceDialog = Class.create(Concrete.UI.AbstractDialog, { initialize: function($super, options) { var dialogElement = this._createDomElement(); $super(dialogElement, options); this.propertyInput = dialogElement.down(".property_input"); Element.insert(this.propertyInput, { after: "
" }); this.propertyOptions = []; new Autocompleter.Local(this.propertyInput, this.propertyInput.next().down(), this.propertyOptions, { partialSearch: true, fullSearch: true, minChars: 0, partialChars: 0, choices: 100 }) this.searchInput = dialogElement.down(".search_input"); this.replaceInput = dialogElement.down(".replace_input"); this.findButton = dialogElement.down(".find_button"); this.replaceButton = dialogElement.down(".replace_button"); this.replaceFindButton = dialogElement.down(".replace_find_button"); this.replaceAllButton = dialogElement.down(".replace_all_button"); this.regexpBox = dialogElement.down(".regular_expression_box"); this.statusOutput = dialogElement.down(".status_output"); this.lastNodeFound = undefined; }, _createDomElement: function() { if ($('ct_search_replace_dialog')) return $('ct_search_replace_dialog'); Element.insert($$('body').first(), { bottom: "" }); return $('ct_search_replace_dialog'); }, _buttonPressed: function(element) { var featureDesc = this.propertyInput.value.strip(); if (/^(\w*|\*|\w+#\*|\w+#\w+)$/.match(featureDesc)) { if (featureDesc.include("#")) { var className = featureDesc.split("#")[0]; var featureName = featureDesc.split("#")[1]; } else { var featureName = featureDesc; } } else { this._setStatus("Invalid feature description: specify , #, #*, * or leave empty"); return; } var searchPattern = this.searchInput.value; if (this.regexpBox.checked) { try { searchPattern = new RegExp(searchPattern); } catch(e) { this._setStatus("Invalid regular expression"); return; } } var replaceText = this.replaceInput.value; if (element == this.findButton) { this._findCommand(className, featureName, searchPattern); } else if (element == this.replaceButton) { this._replaceCommand(searchPattern, replaceText); } else if (element == this.replaceFindButton) { this._replaceCommand(searchPattern, replaceText); this._findCommand(className, featureName, searchPattern); } else if (element == this.replaceAllButton) { this._replaceAllCommand(className, featureName, searchPattern, replaceText); } }, _findCommand: function(className, featureName, searchPattern) { this._setStatus("Searching..."); this._defer(function() { if (this._selectNextMatch(className, featureName, searchPattern)) { this._setStatus("Found next occurance"); } else { this._setStatus("Pattern not found"); } }); }, _replaceCommand: function(searchPattern, replaceText) { if (this.lastNodeFound && this.lastSearchPattern.toString() == searchPattern.toString() && this.editor.selector.selected == this.lastNodeFound) { this._setStatus("Replacing..."); this._defer(function() { if (this._replaceMatch(this.lastNodeFound, searchPattern, replaceText)) { this._setStatus("Replaced 1 occurance"); } else { this._setStatus("Nothing replaced"); } }); } else { this._setStatus("Use find before replace"); } }, _replaceAllCommand: function(className, featureName, searchPattern, replaceText) { this._setStatus("Replacing..."); this._defer(function() { var numReplaced = this._replaceAll(className, featureName, searchPattern, replaceText); this._setStatus("Replaced "+numReplaced+" occurances"); }); }, // this is a special defer which is necessary, since Prototype's defer doesn't seem to yield // the current process in a way that UI redrawing can take place _defer: function(func) { func = func.bind(this); window.setTimeout(function() { window.setTimeout(func, 0); }, 0); }, _selectNextMatch: function(className, featureName, searchPattern) { var startLoc = this._findLocation(this.editor.selector.selected); if (!startLoc) return false; var nextLoc = this.findNext(startLoc[0], startLoc[1], startLoc[2], className, featureName, searchPattern); if (nextLoc) { var node = nextLoc[0].features[nextLoc[1]].slot.childElements()[nextLoc[2]]; this.editor.expandParentElements(node); if (node.ancestors().any(function(a) {return !a.visible();})) { this.editor.showHiddenFeatures(node.up(".ct_element")); } this.editor.selector.selectDirect(node); this.lastNodeFound = node; this.lastSearchPattern = searchPattern; } return (nextLoc != false); }, _replaceAll: function(className, featureName, searchPattern, replaceText) { var startLoc = this._findLocation(this.editor.selector.selected); if (!startLoc) return 0; var firstMatch = this.findNext(startLoc[0], startLoc[1], startLoc[2], className, featureName, searchPattern); var nextMatch = firstMatch; var matches = []; while (nextMatch) { matches.push(nextMatch); nextMatch = this.findNext(nextMatch[0], nextMatch[1], nextMatch[2], className, featureName, searchPattern); if (nextMatch[0] == firstMatch[0] && nextMatch[1] == firstMatch[1] && nextMatch[2] == firstMatch[2]) { break; } } var numReplaced = 0; matches.each(function(match) { var node = match[0].features[match[1]].slot.childElements()[match[2]]; if (this._replaceMatch(node, searchPattern, replaceText)) { numReplaced++; } }, this); return numReplaced; }, _replaceMatch: function(node, searchPattern, replaceText) { if (Object.isString(searchPattern) && (searchPattern.empty() || searchPattern.strip() == "*")) { if (replaceText.empty()) { //this.editor.modelInterface.removeValue(node); } else { var newValue = replaceText; } } else { var newValue = node.value.replace(searchPattern, replaceText); } if (newValue) { this.editor.modelInterface.changeValue(node, newValue); return true; } else { return false; } }, // finds the next value starting from +element+, feature index +fIndex+, value index +vIndex+ // when the last model element has been checked, the search will continue with the first element // the search will stop after the starting point has been reached and checked // findNext: function(element, fIndex, vIndex, className, featureName, searchPattern) { var startElement = element; var startFIndex = fIndex; var startVIndex = vIndex; var containmentStack = []; var wrapAround = 0; while (true) { var feature = element.features[fIndex]; var values = feature && element.featureValues(feature.mmFeature.name); if (feature && (vIndex < values.size()-1)) { // next value vIndex++; if (feature.mmFeature.isContainment()) { // go down to child element containmentStack.push(element); containmentStack.push(feature); element = values[vIndex]; fIndex = 0; vIndex = -1; } else { // found a value if ((!className || className == "*" || element.mmClass.name == className) && (!featureName || featureName == "*" || feature.mmFeature.name == featureName) && (!searchPattern || (Object.isString(searchPattern) && (searchPattern == "*" || (""+values[vIndex]).include(searchPattern))) || (!Object.isString(searchPattern) && searchPattern.match(""+values[vIndex])))) { return [element, fIndex, vIndex]; } } } else if (fIndex < element.features.size()-1) { // next feature fIndex++; vIndex = -1; } else { var parentFeature = containmentStack.pop() || element.up(".ct_containment"); if (parentFeature) { // go up to parent var parentElement = containmentStack.pop() || parentFeature.up(".ct_element"); fIndex = parentElement.features.indexOf(parentFeature); vIndex = parentFeature.slot.childElements().indexOf(element); element = parentElement; } else if (element.next()) { // next on root level element = element.next(); fIndex = 0; vIndex = -1; } else { // first on root level, wrap around element = element.up(".ct_root").childElements().first(); fIndex = 0; vIndex = -1; wrapAround++; } } if ((element == startElement && fIndex == startFIndex && vIndex == startVIndex) || wrapAround > 1) { // reached starting point, or wrapped around more than once (safety check to avoid endless loop) return false; } } }, _setStatus: function(text) { this.statusOutput.textContent = text; }, _findLocation: function(node) { if (node.mmClass) { return [node, 0, -1]; } else if (node.hasClassName("ct_empty")) { var feature = node.findAncestor(["ct_containment", "ct_reference", "ct_attribute"]); if (feature) { var element = feature.up(".ct_element"); return [element, element.features.indexOf(feature), -1]; } else { // empty element on root level return false; } } else { var feature = node.findAncestor(["ct_containment", "ct_reference", "ct_attribute"]); var element = feature.up(".ct_element"); return [element, element.features.indexOf(feature), feature.slot.childElements().indexOf(node)]; } }, open: function($super, editor) { $super(); this.editor = editor; this.propertyOptions.clear(); this.lastNodeFound = undefined; this.statusOutput.innerHTML = " "; var featureNames = []; this.options.metamodelProvider.metaclasses.each(function(c) { this.propertyOptions.push(c.name+"#*"); c.allFeatures().each(function(f) { featureNames.push(f.name); this.propertyOptions.push(c.name+"#"+f.name); }, this); }, this); featureNames.uniq().reverse().each(function(fn) { this.propertyOptions.unshift(fn); }, this); } });