├── .gitignore ├── .bowerrc ├── .travis.yml ├── dist ├── ng-prettyjson.min.css └── ng-prettyjson.min.js ├── src ├── ng-prettyjson.css ├── ng-prettyjson-tmpl.js ├── ng-prettyjson.js └── mode-json.js ├── bower.json ├── LICENSE ├── package.json ├── demo └── ng-prettyjson.html ├── test ├── my.conf.js └── prettySpec.js ├── Gruntfile.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "lib" 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.12 4 | before_install: 5 | - "export DISPLAY=:99.0" 6 | - "sh -e /etc/init.d/xvfb start" 7 | - npm install -g grunt-cli 8 | install: npm install 9 | before_script: grunt build 10 | -------------------------------------------------------------------------------- /dist/ng-prettyjson.min.css: -------------------------------------------------------------------------------- 1 | pre.pretty-json{outline:1px solid #e9e9e9;padding:5px;margin:5px}pre.pretty-json span.string{color:green}pre.pretty-json span.number{color:#ff8c00}pre.pretty-json span.boolean{color:#00f}pre.pretty-json span.$1 $2]{color:#ff00ff}pre.pretty-json span.key{color:red}pre.pretty-json span.sep{color:#000} -------------------------------------------------------------------------------- /src/ng-prettyjson.css: -------------------------------------------------------------------------------- 1 | pre.pretty-json {outline: 1px solid #e9e9e9; padding: 5px; margin: 5px; } 2 | pre.pretty-json span.string { color: green; } 3 | pre.pretty-json span.number { color: darkorange; } 4 | pre.pretty-json span.boolean { color: blue; } 5 | pre.pretty-json span.null { color: magenta; } 6 | pre.pretty-json span.key { color: red; } 7 | pre.pretty-json span.sep { color: black; } -------------------------------------------------------------------------------- /src/ng-prettyjson-tmpl.js: -------------------------------------------------------------------------------- 1 | (function(angular) { 2 | 'use strict'; 3 | 4 | angular.module('ngPrettyJson') 5 | .run(['$templateCache', function ($templateCache) { 6 | $templateCache.put('ng-prettyjson/ng-prettyjson-panel.tmpl.html', 7 | '
`: 88 | 89 | ```html 90 |91 | ``` 92 | 93 | RELEASE 94 | ------------- 95 | 96 | * 0.2.0: fix behavior when clicking on cancel button 97 | * 0.1.8: fix ace editor resize and json $watch change binding 98 | * 0.1.7: fix several "id" and compliance with ace editor 99 | 100 | ### Build 101 | 102 | You can run the tests by running 103 | 104 | ``` 105 | npm install 106 | ``` 107 | and 108 | ``` 109 | npm test 110 | ``` 111 | 112 | assuming you already have `grunt` installed, otherwise you also need to do: 113 | 114 | ``` 115 | npm install -g grunt-cli 116 | ``` 117 | 118 | ## Metrics 119 | 120 | [](https://nodei.co/npm/ng-prettyjson/) 121 | 122 | ## License 123 | 124 | The MIT License (MIT) 125 | 126 | Copyright (c) 2013 Julien Valéry 127 | 128 | Permission is hereby granted, free of charge, to any person obtaining a copy 129 | of this software and associated documentation files (the "Software"), to deal 130 | in the Software without restriction, including without limitation the rights 131 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 132 | copies of the Software, and to permit persons to whom the Software is 133 | furnished to do so, subject to the following conditions: 134 | 135 | The above copyright notice and this permission notice shall be included in 136 | all copies or substantial portions of the Software. 137 | 138 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 139 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 140 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 141 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 142 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 143 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 144 | THE SOFTWARE. 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/ng-prettyjson.js: -------------------------------------------------------------------------------- 1 | (function(angular) { 2 | 'use strict'; 3 | 4 | angular.module('ngPrettyJson', []) 5 | .directive('prettyJson', ['$compile', '$templateCache', 'ngPrettyJsonFunctions', 6 | function (compile, templateCache, ngPrettyJsonFunctions) { 7 | 8 | var isDefined = angular.isDefined; 9 | 10 | return { 11 | restrict: 'AE', 12 | scope: { 13 | json: '=', 14 | prettyJson: '=', 15 | edition: '=', 16 | onEdit: '&' 17 | }, 18 | replace: true, 19 | link: function (scope, element, attrs) { 20 | var currentValue = {}, editor = null, clonedElement = null; 21 | 22 | var getRandomId = function() { 23 | var min = 1; 24 | var max = 10000; 25 | var randomId = Math.floor(Math.random() * (max - min)) + min; 26 | return randomId; 27 | }; 28 | 29 | scope.id = attrs.id || 'jsonEditor' + getRandomId(); 30 | 31 | scope.editActivated = false; 32 | scope.aceEditor = window.ace !== undefined; 33 | 34 | // compile template 35 | var e = compile(templateCache.get('ng-prettyjson/ng-prettyjson-panel.tmpl.html'))(scope, function(clonedElement, scope) { 36 | scope.tmplElt = clonedElement; 37 | }); 38 | 39 | element.removeAttr("id"); 40 | element.append(e); 41 | 42 | // prefer the "json" attribute over the "prettyJson" one. 43 | // the value on the scope might not be defined yet, so look at the markup. 44 | var exp = isDefined(attrs.json) ? 'json' : 'prettyJson', 45 | highlight = function highlight(value) { 46 | var html = ngPrettyJsonFunctions.syntaxHighlight(value) || ""; 47 | html = html 48 | .replace(/\{/g, "{") 49 | .replace(/\}/g, "}") 50 | .replace(/\[/g, "[") 51 | .replace(/\]/g, "]") 52 | .replace(/\,/g, ","); 53 | return isDefined(value) ? scope.tmplElt.find('pre').html(html) : scope.tmplElt.find('pre').empty(); 54 | }, 55 | objWatch; 56 | 57 | objWatch = scope.$watch(exp, function (newValue) { 58 | // BACKWARDS COMPATIBILITY: 59 | // if newValue is an object, and we find a `json` property, 60 | // then stop watching on `exp`. 61 | if (angular.isObject(newValue) && isDefined(newValue.json)) { 62 | objWatch(); 63 | scope.$watch(exp + '.json', function (newValue) { 64 | if (!scope.editActivated) highlight(newValue); 65 | currentValue = newValue; 66 | }, true); 67 | } 68 | else { 69 | if (!scope.editActivated) highlight(newValue); 70 | currentValue = newValue; 71 | } 72 | if (editor) { 73 | editor.removeListener('change', editChanges); 74 | editor.setValue(JSON.stringify(newValue, null, '\t')); 75 | editor.on('change', editChanges); 76 | editor.resize(); 77 | } 78 | }, true); 79 | 80 | var editChanges = function(e) { 81 | try { 82 | currentValue = JSON.parse(editor.getValue()); 83 | scope.parsable = true; 84 | } 85 | catch (error) {scope.parsable = false;} 86 | 87 | // trigger update 88 | scope.$apply(function () {}); 89 | }; 90 | var valueBeforeEdition = currentValue; 91 | scope.edit = function() { 92 | if (!scope.aceEditor) { 93 | if (console) 94 | console.error('\'ace lib is missing\''); 95 | return; 96 | } 97 | 98 | if (!scope.editActivated) { 99 | valueBeforeEdition = currentValue; 100 | editor = ace.edit(scope.id); 101 | editor.setAutoScrollEditorIntoView(true); 102 | editor.setOptions({maxLines: Infinity}); 103 | editor.on('change', editChanges); 104 | editor.getSession().setMode("ace/mode/json"); 105 | } 106 | else { 107 | if (editor) { document.getElementById(scope.id).env = null; } 108 | highlight(valueBeforeEdition); 109 | currentValue = valueBeforeEdition; 110 | scope.parsable = false; 111 | } 112 | scope.editActivated = !scope.editActivated; 113 | }; 114 | 115 | scope.update = function() { 116 | scope[exp] = currentValue; 117 | scope.$emit('json-updated', currentValue); 118 | if (scope.onEdit) 119 | scope.onEdit({newJson: currentValue}); 120 | this.edit(); 121 | }; 122 | 123 | 124 | } 125 | }; 126 | }]) 127 | // mostly we want to just expose this stuff for unit testing; if it's within the directive's 128 | // function scope, we can't get to it. 129 | .factory('ngPrettyJsonFunctions', function ngPrettyJsonFunctions() { 130 | 131 | // cache some regular expressions 132 | var rx = { 133 | entities: /((&)|(<)|(>))/g, 134 | json: /"(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|(null))\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g 135 | }; 136 | 137 | // mapping of chars to entities 138 | var entities = ['&','<','>']; 139 | 140 | // lookup of positional regex matches in rx.json to CSS classes 141 | var classes = ['number','string','key','boolean','null']; 142 | 143 | /** 144 | * @description Used by {@link makeEntities} and {@link markup}. Expects all arguments 145 | * to be nonempty strings. 146 | * @private 147 | * @returns {number} Index of last nonempty string argument in list of arguments. 148 | */ 149 | var reverseCoalesce = function reverseCoalesce() { 150 | var i = arguments.length - 2; 151 | do { 152 | i--; 153 | } while (!arguments[i]); 154 | return i; 155 | }; 156 | 157 | /** 158 | * @description Callback to String.prototype.replace(); marks up JSON string 159 | * @param {string|number} match Any one of the below, or if none of the below, it's a number 160 | * @returns {string} Marked-up JSON string 161 | */ 162 | var markup = function markup(match) { 163 | var idx; 164 | // the final two arguments are the length, and the entire string itself; 165 | // we don't care about those. 166 | if (arguments.length < 7) { 167 | throw new Error('markup() must be called from String.prototype.replace()'); 168 | } 169 | idx = reverseCoalesce.apply(null, arguments); 170 | return '' + match + ''; 171 | }; 172 | 173 | /** 174 | * @description Finds chars in string to turn into entities for HTML output. 175 | * @returns {string} Entity-ized string 176 | */ 177 | var makeEntities = function makeEntities() { 178 | var idx; 179 | if (arguments.length < 5) { 180 | throw new Error('makeEntities() must be called from String.prototype.replace()'); 181 | } 182 | idx = reverseCoalesce.apply(null, arguments); 183 | return entities[idx - 2]; 184 | }; 185 | 186 | /** 187 | * @description Does some regex matching to sanitize for HTML and finally returns a bunch of 188 | * syntax-highlighted markup. 189 | * @param {*} json Something to be output as pretty-printed JSON 190 | * @returns {string|undefined} If we could convert to JSON, you get markup as a string, otherwise 191 | * no return value for you. 192 | */ 193 | var syntaxHighlight = function syntaxHighlight(json) { 194 | if (!angular.isString(json)) 195 | json = JSON.stringify(angular.copy(json), null, 2); 196 | if (angular.isDefined(json)) { 197 | return json.replace(rx.entities, makeEntities) 198 | .replace(rx.json, markup); 199 | } 200 | }; 201 | 202 | return { 203 | syntaxHighlight: syntaxHighlight, 204 | makeEntities: makeEntities, 205 | markup: markup, 206 | rx: rx 207 | }; 208 | }); 209 | 210 | })(window.angular); 211 | -------------------------------------------------------------------------------- /src/mode-json.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Distributed under the BSD license: 3 | * 4 | * Copyright (c) 2010, Ajax.org B.V. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Ajax.org B.V. nor the 15 | * names of its contributors may be used to endorse or promote products 16 | * derived from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | * ***** END LICENSE BLOCK ***** */ 30 | 31 | define('ace/mode/json', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/mode/json_highlight_rules', 'ace/mode/matching_brace_outdent', 'ace/mode/behaviour/cstyle', 'ace/mode/folding/cstyle', 'ace/worker/worker_client'], function(require, exports, module) { 32 | 33 | 34 | var oop = require("../lib/oop"); 35 | var TextMode = require("./text").Mode; 36 | var HighlightRules = require("./json_highlight_rules").JsonHighlightRules; 37 | var MatchingBraceOutdent = require("./matching_brace_outdent").MatchingBraceOutdent; 38 | var CstyleBehaviour = require("./behaviour/cstyle").CstyleBehaviour; 39 | var CStyleFoldMode = require("./folding/cstyle").FoldMode; 40 | var WorkerClient = require("../worker/worker_client").WorkerClient; 41 | 42 | var Mode = function() { 43 | this.HighlightRules = HighlightRules; 44 | this.$outdent = new MatchingBraceOutdent(); 45 | this.$behaviour = new CstyleBehaviour(); 46 | this.foldingRules = new CStyleFoldMode(); 47 | }; 48 | oop.inherits(Mode, TextMode); 49 | 50 | (function() { 51 | 52 | this.getNextLineIndent = function(state, line, tab) { 53 | var indent = this.$getIndent(line); 54 | 55 | if (state == "start") { 56 | var match = line.match(/^.*[\{\(\[]\s*$/); 57 | if (match) { 58 | indent += tab; 59 | } 60 | } 61 | 62 | return indent; 63 | }; 64 | 65 | this.checkOutdent = function(state, line, input) { 66 | return this.$outdent.checkOutdent(line, input); 67 | }; 68 | 69 | this.autoOutdent = function(state, doc, row) { 70 | this.$outdent.autoOutdent(doc, row); 71 | }; 72 | 73 | this.createWorker = function(session) { 74 | var worker = new WorkerClient(["ace"], "ace/mode/json_worker", "JsonWorker"); 75 | worker.attachToDocument(session.getDocument()); 76 | 77 | worker.on("error", function(e) { 78 | session.setAnnotations([e.data]); 79 | }); 80 | 81 | worker.on("ok", function() { 82 | session.clearAnnotations(); 83 | }); 84 | 85 | return worker; 86 | }; 87 | 88 | 89 | this.$id = "ace/mode/json"; 90 | }).call(Mode.prototype); 91 | 92 | exports.Mode = Mode; 93 | }); 94 | 95 | define('ace/mode/json_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text_highlight_rules'], function(require, exports, module) { 96 | 97 | 98 | var oop = require("../lib/oop"); 99 | var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; 100 | 101 | var JsonHighlightRules = function() { 102 | this.$rules = { 103 | "start" : [ 104 | { 105 | token : "variable", // single line 106 | regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]\\s*(?=:)' 107 | }, { 108 | token : "string", // single line 109 | regex : '"', 110 | next : "string" 111 | }, { 112 | token : "constant.numeric", // hex 113 | regex : "0[xX][0-9a-fA-F]+\\b" 114 | }, { 115 | token : "constant.numeric", // float 116 | regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b" 117 | }, { 118 | token : "constant.language.boolean", 119 | regex : "(?:true|false)\\b" 120 | }, { 121 | token : "invalid.illegal", // single quoted strings are not allowed 122 | regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']" 123 | }, { 124 | token : "invalid.illegal", // comments are not allowed 125 | regex : "\\/\\/.*$" 126 | }, { 127 | token : "paren.lparen", 128 | regex : "[[({]" 129 | }, { 130 | token : "paren.rparen", 131 | regex : "[\\])}]" 132 | }, { 133 | token : "text", 134 | regex : "\\s+" 135 | } 136 | ], 137 | "string" : [ 138 | { 139 | token : "constant.language.escape", 140 | regex : /\\(?:x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|["\\\/bfnrt])/ 141 | }, { 142 | token : "string", 143 | regex : '[^"\\\\]+' 144 | }, { 145 | token : "string", 146 | regex : '"', 147 | next : "start" 148 | }, { 149 | token : "string", 150 | regex : "", 151 | next : "start" 152 | } 153 | ] 154 | }; 155 | 156 | }; 157 | 158 | oop.inherits(JsonHighlightRules, TextHighlightRules); 159 | 160 | exports.JsonHighlightRules = JsonHighlightRules; 161 | }); 162 | 163 | define('ace/mode/matching_brace_outdent', ['require', 'exports', 'module' , 'ace/range'], function(require, exports, module) { 164 | 165 | 166 | var Range = require("../range").Range; 167 | 168 | var MatchingBraceOutdent = function() {}; 169 | 170 | (function() { 171 | 172 | this.checkOutdent = function(line, input) { 173 | if (! /^\s+$/.test(line)) 174 | return false; 175 | 176 | return /^\s*\}/.test(input); 177 | }; 178 | 179 | this.autoOutdent = function(doc, row) { 180 | var line = doc.getLine(row); 181 | var match = line.match(/^(\s*\})/); 182 | 183 | if (!match) return 0; 184 | 185 | var column = match[1].length; 186 | var openBracePos = doc.findMatchingBracket({row: row, column: column}); 187 | 188 | if (!openBracePos || openBracePos.row == row) return 0; 189 | 190 | var indent = this.$getIndent(doc.getLine(openBracePos.row)); 191 | doc.replace(new Range(row, 0, row, column-1), indent); 192 | }; 193 | 194 | this.$getIndent = function(line) { 195 | return line.match(/^\s*/)[0]; 196 | }; 197 | 198 | }).call(MatchingBraceOutdent.prototype); 199 | 200 | exports.MatchingBraceOutdent = MatchingBraceOutdent; 201 | }); 202 | 203 | define('ace/mode/behaviour/cstyle', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/behaviour', 'ace/token_iterator', 'ace/lib/lang'], function(require, exports, module) { 204 | 205 | 206 | var oop = require("../../lib/oop"); 207 | var Behaviour = require("../behaviour").Behaviour; 208 | var TokenIterator = require("../../token_iterator").TokenIterator; 209 | var lang = require("../../lib/lang"); 210 | 211 | var SAFE_INSERT_IN_TOKENS = 212 | ["text", "paren.rparen", "punctuation.operator"]; 213 | var SAFE_INSERT_BEFORE_TOKENS = 214 | ["text", "paren.rparen", "punctuation.operator", "comment"]; 215 | 216 | var context; 217 | var contextCache = {} 218 | var initContext = function(editor) { 219 | var id = -1; 220 | if (editor.multiSelect) { 221 | id = editor.selection.id; 222 | if (contextCache.rangeCount != editor.multiSelect.rangeCount) 223 | contextCache = {rangeCount: editor.multiSelect.rangeCount}; 224 | } 225 | if (contextCache[id]) 226 | return context = contextCache[id]; 227 | context = contextCache[id] = { 228 | autoInsertedBrackets: 0, 229 | autoInsertedRow: -1, 230 | autoInsertedLineEnd: "", 231 | maybeInsertedBrackets: 0, 232 | maybeInsertedRow: -1, 233 | maybeInsertedLineStart: "", 234 | maybeInsertedLineEnd: "" 235 | }; 236 | }; 237 | 238 | var CstyleBehaviour = function() { 239 | this.add("braces", "insertion", function(state, action, editor, session, text) { 240 | var cursor = editor.getCursorPosition(); 241 | var line = session.doc.getLine(cursor.row); 242 | if (text == '{') { 243 | initContext(editor); 244 | var selection = editor.getSelectionRange(); 245 | var selected = session.doc.getTextRange(selection); 246 | if (selected !== "" && selected !== "{" && editor.getWrapBehavioursEnabled()) { 247 | return { 248 | text: '{' + selected + '}', 249 | selection: false 250 | }; 251 | } else if (CstyleBehaviour.isSaneInsertion(editor, session)) { 252 | if (/[\]\}\)]/.test(line[cursor.column]) || editor.inMultiSelectMode) { 253 | CstyleBehaviour.recordAutoInsert(editor, session, "}"); 254 | return { 255 | text: '{}', 256 | selection: [1, 1] 257 | }; 258 | } else { 259 | CstyleBehaviour.recordMaybeInsert(editor, session, "{"); 260 | return { 261 | text: '{', 262 | selection: [1, 1] 263 | }; 264 | } 265 | } 266 | } else if (text == '}') { 267 | initContext(editor); 268 | var rightChar = line.substring(cursor.column, cursor.column + 1); 269 | if (rightChar == '}') { 270 | var matching = session.$findOpeningBracket('}', {column: cursor.column + 1, row: cursor.row}); 271 | if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) { 272 | CstyleBehaviour.popAutoInsertedClosing(); 273 | return { 274 | text: '', 275 | selection: [1, 1] 276 | }; 277 | } 278 | } 279 | } else if (text == "\n" || text == "\r\n") { 280 | initContext(editor); 281 | var closing = ""; 282 | if (CstyleBehaviour.isMaybeInsertedClosing(cursor, line)) { 283 | closing = lang.stringRepeat("}", context.maybeInsertedBrackets); 284 | CstyleBehaviour.clearMaybeInsertedClosing(); 285 | } 286 | var rightChar = line.substring(cursor.column, cursor.column + 1); 287 | if (rightChar === '}') { 288 | var openBracePos = session.findMatchingBracket({row: cursor.row, column: cursor.column+1}, '}'); 289 | if (!openBracePos) 290 | return null; 291 | var next_indent = this.$getIndent(session.getLine(openBracePos.row)); 292 | } else if (closing) { 293 | var next_indent = this.$getIndent(line); 294 | } else { 295 | CstyleBehaviour.clearMaybeInsertedClosing(); 296 | return; 297 | } 298 | var indent = next_indent + session.getTabString(); 299 | 300 | return { 301 | text: '\n' + indent + '\n' + next_indent + closing, 302 | selection: [1, indent.length, 1, indent.length] 303 | }; 304 | } else { 305 | CstyleBehaviour.clearMaybeInsertedClosing(); 306 | } 307 | }); 308 | 309 | this.add("braces", "deletion", function(state, action, editor, session, range) { 310 | var selected = session.doc.getTextRange(range); 311 | if (!range.isMultiLine() && selected == '{') { 312 | initContext(editor); 313 | var line = session.doc.getLine(range.start.row); 314 | var rightChar = line.substring(range.end.column, range.end.column + 1); 315 | if (rightChar == '}') { 316 | range.end.column++; 317 | return range; 318 | } else { 319 | context.maybeInsertedBrackets--; 320 | } 321 | } 322 | }); 323 | 324 | this.add("parens", "insertion", function(state, action, editor, session, text) { 325 | if (text == '(') { 326 | initContext(editor); 327 | var selection = editor.getSelectionRange(); 328 | var selected = session.doc.getTextRange(selection); 329 | if (selected !== "" && editor.getWrapBehavioursEnabled()) { 330 | return { 331 | text: '(' + selected + ')', 332 | selection: false 333 | }; 334 | } else if (CstyleBehaviour.isSaneInsertion(editor, session)) { 335 | CstyleBehaviour.recordAutoInsert(editor, session, ")"); 336 | return { 337 | text: '()', 338 | selection: [1, 1] 339 | }; 340 | } 341 | } else if (text == ')') { 342 | initContext(editor); 343 | var cursor = editor.getCursorPosition(); 344 | var line = session.doc.getLine(cursor.row); 345 | var rightChar = line.substring(cursor.column, cursor.column + 1); 346 | if (rightChar == ')') { 347 | var matching = session.$findOpeningBracket(')', {column: cursor.column + 1, row: cursor.row}); 348 | if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) { 349 | CstyleBehaviour.popAutoInsertedClosing(); 350 | return { 351 | text: '', 352 | selection: [1, 1] 353 | }; 354 | } 355 | } 356 | } 357 | }); 358 | 359 | this.add("parens", "deletion", function(state, action, editor, session, range) { 360 | var selected = session.doc.getTextRange(range); 361 | if (!range.isMultiLine() && selected == '(') { 362 | initContext(editor); 363 | var line = session.doc.getLine(range.start.row); 364 | var rightChar = line.substring(range.start.column + 1, range.start.column + 2); 365 | if (rightChar == ')') { 366 | range.end.column++; 367 | return range; 368 | } 369 | } 370 | }); 371 | 372 | this.add("brackets", "insertion", function(state, action, editor, session, text) { 373 | if (text == '[') { 374 | initContext(editor); 375 | var selection = editor.getSelectionRange(); 376 | var selected = session.doc.getTextRange(selection); 377 | if (selected !== "" && editor.getWrapBehavioursEnabled()) { 378 | return { 379 | text: '[' + selected + ']', 380 | selection: false 381 | }; 382 | } else if (CstyleBehaviour.isSaneInsertion(editor, session)) { 383 | CstyleBehaviour.recordAutoInsert(editor, session, "]"); 384 | return { 385 | text: '[]', 386 | selection: [1, 1] 387 | }; 388 | } 389 | } else if (text == ']') { 390 | initContext(editor); 391 | var cursor = editor.getCursorPosition(); 392 | var line = session.doc.getLine(cursor.row); 393 | var rightChar = line.substring(cursor.column, cursor.column + 1); 394 | if (rightChar == ']') { 395 | var matching = session.$findOpeningBracket(']', {column: cursor.column + 1, row: cursor.row}); 396 | if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) { 397 | CstyleBehaviour.popAutoInsertedClosing(); 398 | return { 399 | text: '', 400 | selection: [1, 1] 401 | }; 402 | } 403 | } 404 | } 405 | }); 406 | 407 | this.add("brackets", "deletion", function(state, action, editor, session, range) { 408 | var selected = session.doc.getTextRange(range); 409 | if (!range.isMultiLine() && selected == '[') { 410 | initContext(editor); 411 | var line = session.doc.getLine(range.start.row); 412 | var rightChar = line.substring(range.start.column + 1, range.start.column + 2); 413 | if (rightChar == ']') { 414 | range.end.column++; 415 | return range; 416 | } 417 | } 418 | }); 419 | 420 | this.add("string_dquotes", "insertion", function(state, action, editor, session, text) { 421 | if (text == '"' || text == "'") { 422 | initContext(editor); 423 | var quote = text; 424 | var selection = editor.getSelectionRange(); 425 | var selected = session.doc.getTextRange(selection); 426 | if (selected !== "" && selected !== "'" && selected != '"' && editor.getWrapBehavioursEnabled()) { 427 | return { 428 | text: quote + selected + quote, 429 | selection: false 430 | }; 431 | } else { 432 | var cursor = editor.getCursorPosition(); 433 | var line = session.doc.getLine(cursor.row); 434 | var leftChar = line.substring(cursor.column-1, cursor.column); 435 | if (leftChar == '\\') { 436 | return null; 437 | } 438 | var tokens = session.getTokens(selection.start.row); 439 | var col = 0, token; 440 | var quotepos = -1; // Track whether we're inside an open quote. 441 | 442 | for (var x = 0; x < tokens.length; x++) { 443 | token = tokens[x]; 444 | if (token.type == "string") { 445 | quotepos = -1; 446 | } else if (quotepos < 0) { 447 | quotepos = token.value.indexOf(quote); 448 | } 449 | if ((token.value.length + col) > selection.start.column) { 450 | break; 451 | } 452 | col += tokens[x].value.length; 453 | } 454 | if (!token || (quotepos < 0 && token.type !== "comment" && (token.type !== "string" || ((selection.start.column !== token.value.length+col-1) && token.value.lastIndexOf(quote) === token.value.length-1)))) { 455 | if (!CstyleBehaviour.isSaneInsertion(editor, session)) 456 | return; 457 | return { 458 | text: quote + quote, 459 | selection: [1,1] 460 | }; 461 | } else if (token && token.type === "string") { 462 | var rightChar = line.substring(cursor.column, cursor.column + 1); 463 | if (rightChar == quote) { 464 | return { 465 | text: '', 466 | selection: [1, 1] 467 | }; 468 | } 469 | } 470 | } 471 | } 472 | }); 473 | 474 | this.add("string_dquotes", "deletion", function(state, action, editor, session, range) { 475 | var selected = session.doc.getTextRange(range); 476 | if (!range.isMultiLine() && (selected == '"' || selected == "'")) { 477 | initContext(editor); 478 | var line = session.doc.getLine(range.start.row); 479 | var rightChar = line.substring(range.start.column + 1, range.start.column + 2); 480 | if (rightChar == selected) { 481 | range.end.column++; 482 | return range; 483 | } 484 | } 485 | }); 486 | 487 | }; 488 | 489 | 490 | CstyleBehaviour.isSaneInsertion = function(editor, session) { 491 | var cursor = editor.getCursorPosition(); 492 | var iterator = new TokenIterator(session, cursor.row, cursor.column); 493 | if (!this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) { 494 | var iterator2 = new TokenIterator(session, cursor.row, cursor.column + 1); 495 | if (!this.$matchTokenType(iterator2.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) 496 | return false; 497 | } 498 | iterator.stepForward(); 499 | return iterator.getCurrentTokenRow() !== cursor.row || 500 | this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_BEFORE_TOKENS); 501 | }; 502 | 503 | CstyleBehaviour.$matchTokenType = function(token, types) { 504 | return types.indexOf(token.type || token) > -1; 505 | }; 506 | 507 | CstyleBehaviour.recordAutoInsert = function(editor, session, bracket) { 508 | var cursor = editor.getCursorPosition(); 509 | var line = session.doc.getLine(cursor.row); 510 | if (!this.isAutoInsertedClosing(cursor, line, context.autoInsertedLineEnd[0])) 511 | context.autoInsertedBrackets = 0; 512 | context.autoInsertedRow = cursor.row; 513 | context.autoInsertedLineEnd = bracket + line.substr(cursor.column); 514 | context.autoInsertedBrackets++; 515 | }; 516 | 517 | CstyleBehaviour.recordMaybeInsert = function(editor, session, bracket) { 518 | var cursor = editor.getCursorPosition(); 519 | var line = session.doc.getLine(cursor.row); 520 | if (!this.isMaybeInsertedClosing(cursor, line)) 521 | context.maybeInsertedBrackets = 0; 522 | context.maybeInsertedRow = cursor.row; 523 | context.maybeInsertedLineStart = line.substr(0, cursor.column) + bracket; 524 | context.maybeInsertedLineEnd = line.substr(cursor.column); 525 | context.maybeInsertedBrackets++; 526 | }; 527 | 528 | CstyleBehaviour.isAutoInsertedClosing = function(cursor, line, bracket) { 529 | return context.autoInsertedBrackets > 0 && 530 | cursor.row === context.autoInsertedRow && 531 | bracket === context.autoInsertedLineEnd[0] && 532 | line.substr(cursor.column) === context.autoInsertedLineEnd; 533 | }; 534 | 535 | CstyleBehaviour.isMaybeInsertedClosing = function(cursor, line) { 536 | return context.maybeInsertedBrackets > 0 && 537 | cursor.row === context.maybeInsertedRow && 538 | line.substr(cursor.column) === context.maybeInsertedLineEnd && 539 | line.substr(0, cursor.column) == context.maybeInsertedLineStart; 540 | }; 541 | 542 | CstyleBehaviour.popAutoInsertedClosing = function() { 543 | context.autoInsertedLineEnd = context.autoInsertedLineEnd.substr(1); 544 | context.autoInsertedBrackets--; 545 | }; 546 | 547 | CstyleBehaviour.clearMaybeInsertedClosing = function() { 548 | if (context) { 549 | context.maybeInsertedBrackets = 0; 550 | context.maybeInsertedRow = -1; 551 | } 552 | }; 553 | 554 | 555 | 556 | oop.inherits(CstyleBehaviour, Behaviour); 557 | 558 | exports.CstyleBehaviour = CstyleBehaviour; 559 | }); 560 | 561 | define('ace/mode/folding/cstyle', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/range', 'ace/mode/folding/fold_mode'], function(require, exports, module) { 562 | 563 | 564 | var oop = require("../../lib/oop"); 565 | var Range = require("../../range").Range; 566 | var BaseFoldMode = require("./fold_mode").FoldMode; 567 | 568 | var FoldMode = exports.FoldMode = function(commentRegex) { 569 | if (commentRegex) { 570 | this.foldingStartMarker = new RegExp( 571 | this.foldingStartMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.start) 572 | ); 573 | this.foldingStopMarker = new RegExp( 574 | this.foldingStopMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.end) 575 | ); 576 | } 577 | }; 578 | oop.inherits(FoldMode, BaseFoldMode); 579 | 580 | (function() { 581 | 582 | this.foldingStartMarker = /(\{|\[)[^\}\]]*$|^\s*(\/\*)/; 583 | this.foldingStopMarker = /^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/; 584 | 585 | this.getFoldWidgetRange = function(session, foldStyle, row, forceMultiline) { 586 | var line = session.getLine(row); 587 | var match = line.match(this.foldingStartMarker); 588 | if (match) { 589 | var i = match.index; 590 | 591 | if (match[1]) 592 | return this.openingBracketBlock(session, match[1], row, i); 593 | 594 | var range = session.getCommentFoldRange(row, i + match[0].length, 1); 595 | 596 | if (range && !range.isMultiLine()) { 597 | if (forceMultiline) { 598 | range = this.getSectionRange(session, row); 599 | } else if (foldStyle != "all") 600 | range = null; 601 | } 602 | 603 | return range; 604 | } 605 | 606 | if (foldStyle === "markbegin") 607 | return; 608 | 609 | var match = line.match(this.foldingStopMarker); 610 | if (match) { 611 | var i = match.index + match[0].length; 612 | 613 | if (match[1]) 614 | return this.closingBracketBlock(session, match[1], row, i); 615 | 616 | return session.getCommentFoldRange(row, i, -1); 617 | } 618 | }; 619 | 620 | this.getSectionRange = function(session, row) { 621 | var line = session.getLine(row); 622 | var startIndent = line.search(/\S/); 623 | var startRow = row; 624 | var startColumn = line.length; 625 | row = row + 1; 626 | var endRow = row; 627 | var maxRow = session.getLength(); 628 | while (++row < maxRow) { 629 | line = session.getLine(row); 630 | var indent = line.search(/\S/); 631 | if (indent === -1) 632 | continue; 633 | if (startIndent > indent) 634 | break; 635 | var subRange = this.getFoldWidgetRange(session, "all", row); 636 | 637 | if (subRange) { 638 | if (subRange.start.row <= startRow) { 639 | break; 640 | } else if (subRange.isMultiLine()) { 641 | row = subRange.end.row; 642 | } else if (startIndent == indent) { 643 | break; 644 | } 645 | } 646 | endRow = row; 647 | } 648 | 649 | return new Range(startRow, startColumn, endRow, session.getLine(endRow).length); 650 | }; 651 | 652 | }).call(FoldMode.prototype); 653 | 654 | }); 655 | --------------------------------------------------------------------------------