From 8d3c4feeb5c798d4bbbb14f930c865f1543c7bab Mon Sep 17 00:00:00 2001 From: Vlad Zinculescu Date: Wed, 28 Nov 2012 16:01:31 +0100 Subject: [PATCH] searchbox wip --- demo/kitchen-sink/demo.js | 4 +- lib/ace/commands/default_commands.js | 12 +- lib/ace/css/editor.css | 130 ++++++++++++++ lib/ace/ext/searchbox.js | 259 +++++++++++++++++++++++++++ 4 files changed, 394 insertions(+), 11 deletions(-) create mode 100644 lib/ace/ext/searchbox.js diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 93859a61..183a872a 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -108,7 +108,7 @@ env.editor.commands.addCommands([{ editor.gotoLine(line); }, readOnly: true -}, { +}/*, { name: "find", bindKey: {win: "Ctrl-F", mac: "Command-F"}, exec: function(editor, needle) { @@ -121,7 +121,7 @@ env.editor.commands.addCommands([{ editor.find(needle); }, readOnly: true -}, { +}*/, { name: "focusCommandLine", bindKey: "shift-esc", exec: function(editor, needle) { editor.cmdLine.focus(); }, diff --git a/lib/ace/commands/default_commands.js b/lib/ace/commands/default_commands.js index 8c33fc42..7c112300 100644 --- a/lib/ace/commands/default_commands.js +++ b/lib/ace/commands/default_commands.js @@ -33,6 +33,7 @@ define(function(require, exports, module) { var lang = require("../lib/lang"); +var SearchBox = require("ace/ext/searchbox"); function bindKey(win, mac) { return { win: win, @@ -94,8 +95,7 @@ exports.commands = [{ name: "find", bindKey: bindKey("Ctrl-F", "Command-F"), exec: function(editor) { - var needle = prompt("Find:", editor.getCopyText()); - editor.find(needle); + SearchBox.SearchBox(editor, editor.getSelectionRange()); }, readOnly: true }, { @@ -345,13 +345,7 @@ exports.commands = [{ name: "replace", bindKey: bindKey("Ctrl-R", "Command-Option-F"), exec: function(editor) { - var needle = prompt("Find:", editor.getCopyText()); - if (!needle) - return; - var replacement = prompt("Replacement:"); - if (!replacement) - return; - editor.replace(replacement, {needle: needle}); + SearchBox.SearchBox(editor, editor.getSelectionRange(), true); } }, { name: "replaceall", diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index 73702bdf..5581e8df 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -366,3 +366,133 @@ .ace_italic { font-style: italic; } + + +/* ------------------------------------------------------------------------------------------ + * Editor Search Form + * --------------------------------------------------------------------------------------- */ + .ace_search { + background-color: #ddd; + border: 1px solid #cbcbcb; + border-top: 0 none; + max-width: 297px; + overflow: hidden; + padding: 4px; + padding-right: 6px; + padding-bottom: 0; + position: absolute; + top: 0px; + z-index: 99; +} +.ace_search.left { + border-left: 0 none; + border-radius: 0px 0px 5px 0px; + left: 0; +} +.ace_search.right { + border-radius: 0px 0px 0px 5px; + border-right: 0 none; + right: 0; +} + +.ace_search_form { + border-radius: 3px; + border: 1px solid #cbcbcb; + float: left; + margin-bottom: 4px; + overflow: hidden; +} +.ace_search label { + float: left; + color: #656565; +} +.ace_search input { + background: white; + border-right: 1px solid #cbcbcb; + border: 0 none; + box-sizing: border-box; + display: block; + float: left; + height: 22px; + outline: 0; + padding: 0 7px; + width: 214px; +} +.ace_searchbtn, +.ace_replacebtn { + background: #fff; + border: 0 none; + border-left: 1px solid #dcdcdc; + cursor: pointer; + display: block; + float: left; + height: 22px; + margin: 0; + padding: 0; + position: relative; +} +.ace_searchbtn:last-child, +.ace_replacebtn:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.ace_searchbtn { + width: 27px; +} +.ace_searchbtn:after { + border: 2px solid #656565; + content: ""; + display: block; + height: 4px; + position: absolute; + width: 4px; + -webkit-transform: rotateZ(45deg); +} +.ace_searchbtn.prev:after { + border-right: 0 none; + border-bottom: 0 none; + left: 10px; + top: 9px; +} +.ace_searchbtn.next:after { + border-left: 0 none; + border-top: 0 none; + left: 10px; + top: 7px; +} +.ace_searchbtn:disabled { + background: none; + cursor: default; +} +.ace_searchbtn:disabled:after { + border-color: #ccc; +} +.ace_searchbtn_close { + background: none; + border-radius: 50%; + border: 0 none; + color: #656565; + cursor: pointer; + display: block; + float: right; + font-family: Arial; + font-size: 16px; + height: 14px; + line-height: 16px; + margin: 5px 1px 9px 5px; + padding: 0; + text-align: center; + width: 14px; +} +.ace_searchbtn_close:hover { + background: #656565; + color: white; +} +.ace_replacebtn { +} +.ace_replacebtn.prev { + width: 54px +} +.ace_replacebtn.next { + width: 27px +} \ No newline at end of file diff --git a/lib/ace/ext/searchbox.js b/lib/ace/ext/searchbox.js new file mode 100644 index 00000000..cd27e808 --- /dev/null +++ b/lib/ace/ext/searchbox.js @@ -0,0 +1,259 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { +"use strict"; + +var dom = require("ace/lib/dom"); +var event = require("ace/lib/event"); +var Range = require("ace/range").Range; + +exports.SearchBox = function (editor, range, showReplaceForm) { + var _self = this; + var sb = null;//search box + var n = null;//needle + var r = null;//replace + var p = null;//parent DOM node + + var needle = editor.session.getTextRange(range); + + this.$init = function() { + + p = editor.container.parentNode; + sb = p.querySelector(".ace_search"); //the complete form + + if(!sb) { + + //Create the elements + sb = document.createElement("div"); + sb.setAttribute("class", "ace_search right"); + p.appendChild( sb ); + + var c = document.createElement("button"); //cancel btn + c.setAttribute("type","button"); + c.setAttribute("class","ace_searchbtn_close"); + c.innerHTML = "×"; + sb.appendChild( c ); + c.onclick = _self.hide; + + var sf = document.createElement("div"); //search form + sf.setAttribute("id", "ace_search_form"); + sf.setAttribute("class", "ace_search_form"); + sb.appendChild( sf ); + + n = document.createElement("input"); //needle + n.setAttribute("class", "ace_search_field"); + n.setAttribute("id", "ace_search_field"); + n.setAttribute("placeholder", "Search for"); + n.setAttribute("type", "text"); + sf.appendChild( n ); + + var sp = document.createElement("button"); + sp.setAttribute("type","button"); + sp.setAttribute("class","ace_searchbtn prev"); + sf.appendChild( sp ); + sp.onclick = _self.findPrev; + + var sn = document.createElement("button"); + sn.setAttribute("type","button"); + sn.setAttribute("class","ace_searchbtn next"); + sf.appendChild( sn ); + sn.onclick = _self.findNext; + + var rf = document.createElement("div"); //replace form + rf.setAttribute("id", "ace_replace_form"); + rf.setAttribute("class", "ace_search_form"); + rf.setAttribute("style", "display: none"); + sb.appendChild( rf); + + r = document.createElement("input"); //needle + r.setAttribute("class", "ace_search_field"); + r.setAttribute("id", "ace_replace_field"); + r.setAttribute("placeholder", "Replace with"); + r.setAttribute("type", "text"); + rf.appendChild( r ); + + var rr = document.createElement("button"); + rr.setAttribute("type","button"); + rr.setAttribute("class","ace_replacebtn prev"); + rr.innerHTML = "Replace"; + rf.appendChild( rr ); + rr.onclick = _self.replace; + + var ra = document.createElement("button"); + ra.setAttribute("type","button"); + ra.setAttribute("class","ace_replacebtn next"); + ra.innerHTML = "All"; + rf.appendChild( ra ); + ra.onclick = _self.replaceAll; + + + } else { + sb.removeAttribute("style"); + n = sb.querySelector("#ace_search_field"); + r = sb.querySelector("#ace_replace_field"); + } + + console.log( "showReplaceForm", showReplaceForm ); + if( showReplaceForm ) { + p.querySelector("#ace_replace_form").removeAttribute("style"); + } + + + if( needle ) + n.value = needle; + + //set initial focus and select text in input + n.focus(); + n.select(); + + //keybinging outsite of the searchbox + _self.$searchKeybingin = { + handleKeyboard: function(data, hashId, keyString, keyCode) { + console.log("MERGE"); + if (keyString == "esc") + return {command: this.command}; + }, + command: { + exec: function(editor) { + _self.hide(); + } + } + }; + editor.keyBinding.addKeyboardHandler(this.$searchKeybingin); + + n.onkeydown = searchonkeydown; + r.onkeydown = searchonkeydown; + function searchonkeydown(e) { + + var cmdKey = e.metaKey || e.ctrlKey; + + console.log(cmdKey); + + if( cmdKey ) { + if( e.which == 70 ) { //f key + var rf = p.querySelector("#ace_replace_form"); + if( e.altKey && rf.hasAttribute("style") ) { + p.querySelector("#ace_replace_form").removeAttribute("style"); + } else { + _self.hide(); + } + return false; + } + } + + if( e.which === 9 ) { //tab + if( e.target == n ) { + r.focus(); + r.select(); + } else { + n.focus(); + n.select(); + } + return false; + } + } + + n.onpaste = function(e) { + //I do this because the onpaste event is fired before the value of the input actually changes + setTimeout(function() { + e.target.onkeyup(); + }, 100); + } + n.onkeyup = function(e){ + console.log("KEYUP", e); + if(!e) { + e = {}; + e.which = 0; + } + + if( e.which === 27 ) { //esc key + _self.hide(); + } else if( e.which === 13 ) { //enter key + _self.findNext( ); + } else { + editor.moveCursorTo( range.start.row, range.start.column ); + _self.findNext(); + } + }; + r.onkeyup = function(e){ + if( e.which === 27 ) { //esc key + _self.hide(); + } else if( e.which === 13 ) { //enter key + editor.moveCursorTo( range.start.row, range.start.column ); + _self.replace(); + } + }; + }; + + this.findPrev = function() { + editor.find(n.value, { + backwards: true, + wrap: true + }); + }; + this.findNext = function() { + editor.find(n.value, { + backwards: false, + wrap: true + }); + }; + this.replace = function() { + editor.replace( r.value ); + _self.findNext(); + }; + this.replaceAll = function() { + editor.replaceAll( r.value ); + }; + + this.hide = function () { + n.value = ""; + r.value = ""; + sb.setAttribute("style", "display: none"); + sb.querySelector("#ace_replace_form").setAttribute("style", "display: none"); + editor.keyBinding.removeKeyboardHandler(this.$searchKeybingin); + editor.focus(); + }; + + _self.$init(); +}; + +}); + + +/* ------------------------------------------------------------------------------------------ + * TODO + * --------------------------------------------------------------------------------------- */ +/* +- move search form to the left if it masks current word +- includ all options that search has. ex: regex +- searchbox.searchbox is not that pretty. we should have just searchbox +- disable prev button if it makes sence +*/ \ No newline at end of file