├── .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 | '
' + 8 | '' + 9 | '' + 10 | '' + 11 | '
' +                        
12 |     '
'); 13 | }]); 14 | 15 | })(window.angular); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-prettyjson", 3 | "description": "AngularJS directive for json pretty output (colors and indent)", 4 | "version": "0.2.0", 5 | "license": "MIT", 6 | "author": { 7 | "name": "Julien Valéry", 8 | "email": "darul75@gmail.com" 9 | }, 10 | "main": [ 11 | "./src/ng-prettyjson.js", 12 | "./src/ng-prettyjson-tmpl.js", 13 | "./src/ng-prettyjson.css" 14 | ], 15 | "homepage": "https://github.com/darul75/ng-prettyjson", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/darul75/ng-prettyjson.git" 19 | }, 20 | "keywords": [ 21 | "angular", 22 | "json", 23 | "pretty", 24 | "color", 25 | "format" 26 | ], 27 | "dependencies": { 28 | "angular": "1.2.16" 29 | }, 30 | "devDependencies": { 31 | "angular-mocks": "1.2.16" 32 | }, 33 | "ignore": [ 34 | "test", 35 | "node_modules", 36 | "demo", 37 | "**/.*" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2013 Julien Valéry 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-prettyjson", 3 | "description": "AngularJS directive for json pretty output (colors / indent / editor)", 4 | "version": "0.2.1", 5 | "license": "MIT", 6 | "author": { 7 | "name": "Julien Valéry", 8 | "email": "darul75@gmail.com" 9 | }, 10 | "filename": "./src/ng-prettyjson.js", 11 | "homepage": "https://github.com/darul75/ng-prettyjson", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/darul75/ng-prettyjson.git" 15 | }, 16 | "keywords": [ 17 | "ace", 18 | "angular", 19 | "color", 20 | "editor", 21 | "format", 22 | "json", 23 | "pretty" 24 | ], 25 | "scripts": { 26 | "test": "grunt test-continuous" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/darul75/ng-prettyjson/issues" 30 | }, 31 | "dependencies": {}, 32 | "devDependencies": { 33 | "grunt": "1.0.1", 34 | "grunt-bower-task": "0.4.0", 35 | "grunt-karma": "2.0.0", 36 | "grunt-contrib-jshint": "1.1.0", 37 | "grunt-contrib-uglify": "2.2.0", 38 | "grunt-contrib-cssmin": "2.0.0", 39 | "karma": "^0.13.0", 40 | "karma-chrome-launcher": "^0.1.3", 41 | "karma-firefox-launcher": "~0.1.3", 42 | "karma-jasmine": "^0.1.5", 43 | "karma-phantomjs-launcher": "^0.1.4" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /demo/ng-prettyjson.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 |
25 | 26 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /test/my.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Wed Nov 06 2013 00:03:21 GMT+0100 (CET) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path, that will be used to resolve files and exclude 8 | basePath: '../', 9 | 10 | 11 | // frameworks to use 12 | frameworks: ['jasmine'], 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'lib/angular/angular.min.js', 18 | 'lib/angular-mocks/angular-mocks.js', 19 | 'src/ng-prettyjson.js', 20 | 'src/ng-prettyjson-tmpl.js', 21 | 'test/*Spec.js' 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | 28 | ], 29 | 30 | 31 | // test results reporter to use 32 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 33 | reporters: ['progress'], 34 | 35 | 36 | // web server port 37 | port: 9876, 38 | 39 | 40 | // enable / disable colors in the output (reporters and logs) 41 | colors: true, 42 | 43 | 44 | // level of logging 45 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 46 | logLevel: config.LOG_INFO, 47 | 48 | 49 | // enable / disable watching file and executing tests whenever any file changes 50 | autoWatch: true, 51 | 52 | 53 | // Start these browsers, currently available: 54 | // - Chrome 55 | // - ChromeCanary 56 | // - Firefox 57 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 58 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 59 | // - PhantomJS 60 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 61 | // browsers: ['Chrome', 'PhantomJS'], 62 | browsers: ['Firefox'], 63 | 64 | 65 | // If browser does not capture in given timeout [ms], kill it 66 | captureTimeout: 60000, 67 | 68 | 69 | // Continuous Integration mode 70 | // if true, it capture browsers, run tests and exit 71 | singleRun: false 72 | }); 73 | }; 74 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 'use strict'; 3 | 4 | // Project configuration. 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | bower: { 8 | install: { 9 | //just run 'grunt bower:install' and you'll see files from your Bower packages in lib directory 10 | } 11 | }, 12 | jshint: { 13 | files: ['src/ng-prettyjson.js', 'src/ng-prettyjson-tmpl.js', 'test/**/*.js'] 14 | }, 15 | // KARMA TASK CONFIG 16 | karma: { 17 | options: { 18 | basePath: './', 19 | frameworks: ['jasmine'], 20 | files: [ 21 | 'lib/angular/angular.js', 22 | 'lib/angular-mocks/angular-mocks.js', 23 | 'src/ng-prettyjson.js', 24 | 'src/ng-prettyjson-tmpl.js', 25 | 'test/**/*Spec.js' 26 | ], 27 | autoWatch: true, 28 | singleRun: true 29 | }, 30 | unit: { 31 | options: { 32 | browsers: ['Firefox'] 33 | } 34 | }, 35 | continuous: { 36 | options: { 37 | browsers: ['PhantomJS'] 38 | } 39 | } 40 | }, 41 | // UGLIFY TASK 42 | uglify: { 43 | task1: { 44 | options: { 45 | preserveComments: 'all', 46 | report: 'min', 47 | banner: '/** \n* @license <%= pkg.name %> - v<%= pkg.version %>\n' + 48 | '* (c) 2013 Julien VALERY https://github.com/darul75/ng-prettyjson\n' + 49 | '* License: MIT \n**/\n' 50 | }, 51 | files: { 52 | 'dist/ng-prettyjson.min.js': ['src/ng-prettyjson.js', 'src/ng-prettyjson-tmpl.js'] 53 | } 54 | } 55 | }, 56 | // MINIFY CSS 57 | cssmin: { 58 | options: { 59 | keepSpecialComments: false, 60 | banner: '/** \n* @license <%= pkg.name %> - v<%= pkg.version %>\n' + 61 | '* (c) 2013 Julien VALERY https://github.com/darul75/ng-prettyjson\n' + 62 | '* License: MIT \n**/\n' 63 | }, 64 | compress: { 65 | files: { 66 | 'dist/ng-prettyjson.min.css': ['src/ng-prettyjson.css'] 67 | } 68 | } 69 | } 70 | }); 71 | 72 | // LOAD PLUGINS 73 | grunt.loadNpmTasks('grunt-bower-task'); 74 | grunt.loadNpmTasks('grunt-contrib-jshint'); 75 | grunt.loadNpmTasks('grunt-contrib-uglify'); 76 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 77 | grunt.loadNpmTasks('grunt-karma'); 78 | 79 | // TASK REGISTER 80 | grunt.registerTask('test', ['jshint', 'bower', 'karma:unit']); 81 | grunt.registerTask('test-continuous', ['jshint', 'bower', 'karma:unit']); 82 | grunt.registerTask('build', ['cssmin', 'uglify']); 83 | grunt.registerTask('default', ['build', 'test']); 84 | }; 85 | -------------------------------------------------------------------------------- /dist/ng-prettyjson.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license ng-prettyjson - v0.2.1 3 | * (c) 2013 Julien VALERY https://github.com/darul75/ng-prettyjson 4 | * License: MIT 5 | **/ 6 | !function(a){"use strict";a.module("ngPrettyJson",[]).directive("prettyJson",["$compile","$templateCache","ngPrettyJsonFunctions",function(b,c,d){var e=a.isDefined;return{restrict:"AE",scope:{json:"=",prettyJson:"=",onEdit:"&"},replace:!0,link:function(f,g,h){var i={},j=null;f.id=h.id||"jsonEditor"+function(){return Math.floor(9999*Math.random())+1}(),f.editActivated=!1,f.edition=h.edition,f.aceEditor=void 0!==window.ace; 7 | // compile template 8 | var k=b(c.get("ng-prettyjson/ng-prettyjson-panel.tmpl.html"))(f,function(a,b){b.tmplElt=a});g.removeAttr("id"),g.append(k); 9 | // prefer the "json" attribute over the "prettyJson" one. 10 | // the value on the scope might not be defined yet, so look at the markup. 11 | var l,m=e(h.json)?"json":"prettyJson",n=function(a){var b=d.syntaxHighlight(a)||"";return b=b.replace(/\{/g,"{").replace(/\}/g,"}").replace(/\[/g,"[").replace(/\]/g,"]").replace(/\,/g,","),e(a)?f.tmplElt.find("pre").html(b):f.tmplElt.find("pre").empty()};l=f.$watch(m,function(b){ 12 | // BACKWARDS COMPATIBILITY: 13 | // if newValue is an object, and we find a `json` property, 14 | // then stop watching on `exp`. 15 | a.isObject(b)&&e(b.json)?(l(),f.$watch(m+".json",function(a){f.editActivated||n(a),i=a},!0)):(f.editActivated||n(b),i=b),j&&(j.removeListener("change",o),j.setValue(JSON.stringify(b,null,"\t")),j.on("change",o),j.resize())},!0);var o=function(a){try{i=JSON.parse(j.getValue()),f.parsable=!0}catch(a){f.parsable=!1} 16 | // trigger update 17 | f.$apply(function(){})},p=i;f.edit=function(){if(!f.aceEditor)return void(console&&console.error("'ace lib is missing'"));f.editActivated?(j&&(document.getElementById(f.id).env=null),n(p),i=p,f.parsable=!1):(p=i,j=ace.edit(f.id),j.setAutoScrollEditorIntoView(!0),j.setOptions({maxLines:1/0}),j.on("change",o),j.getSession().setMode("ace/mode/json")),f.editActivated=!f.editActivated},f.update=function(){f[m]=i,f.$emit("json-updated",i),f.onEdit&&f.onEdit({newJson:i}),this.edit()}}}}]).factory("ngPrettyJsonFunctions",function(){ 18 | // cache some regular expressions 19 | var b={entities:/((&)|(<)|(>))/g,json:/"(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|(null))\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g},c=["&","<",">"],d=["number","string","key","boolean","null"],e=function(){var a=arguments.length-2;do{a--}while(!arguments[a]);return a},f=function(a){var b; 20 | // the final two arguments are the length, and the entire string itself; 21 | // we don't care about those. 22 | if(arguments.length<7)throw new Error("markup() must be called from String.prototype.replace()");return b=e.apply(null,arguments),''+a+""},g=function(){var a;if(arguments.length<5)throw new Error("makeEntities() must be called from String.prototype.replace()");return a=e.apply(null,arguments),c[a-2]};return{syntaxHighlight:function(c){if(a.isString(c)||(c=JSON.stringify(a.copy(c),null,2)),a.isDefined(c))return c.replace(b.entities,g).replace(b.json,f)},makeEntities:g,markup:f,rx:b}})}(window.angular),function(a){"use strict";a.module("ngPrettyJson").run(["$templateCache",function(a){a.put("ng-prettyjson/ng-prettyjson-panel.tmpl.html",'
')}])}(window.angular); -------------------------------------------------------------------------------- /test/prettySpec.js: -------------------------------------------------------------------------------- 1 | describe('ngPrettyJson', function () { 2 | var testDirective, 3 | noop = angular.noop, 4 | scope, 5 | fixture = function fixture($compile, $rootScope, $timeout) { 6 | scope = $rootScope.$new(); 7 | scope.obj = obj; 8 | scope.badjson = noop; 9 | scope.json = { 10 | json: obj 11 | }; 12 | 13 | /** 14 | * Compiles markup and compares resulting output to expected.s 15 | * @param {string} markup Markup to compile 16 | * @param {number} [nodeCount] Number of nodes expected; defaults to 0 17 | */ 18 | return function tester(markup, nodeCount) { 19 | var elt = angular.element(markup); 20 | elt = $compile(elt)(scope); // Compile the directive 21 | scope.$digest(); // Update the HTML 22 | 23 | // $timeout.flush(2000); 24 | 25 | angular.element(document.body).append(elt); 26 | 27 | var selCount = elt.find('pre').children('span').length; 28 | // console.log(selCount); 29 | 30 | expect(selCount).toBe(nodeCount || 0); 31 | //expect(element[0].tagName).toBe('PRE'); 32 | return elt; 33 | }; 34 | }, 35 | 36 | obj; 37 | 38 | beforeEach(module('ngPrettyJson')); 39 | 40 | beforeEach(inject(function ($injector) { 41 | obj = {a: 1, 'b': 'foo', c: [noop, false, null, {d: {e: 1.3e5}}]}; 42 | testDirective = $injector.invoke(fixture); 43 | })); 44 | 45 | describe('prettyJson directive', function () { 46 | it('ignores empty (undefined) JSON', function () { 47 | testDirective('
');
48 |         });
49 | 
50 |         it('creates an instance with default values', function () {
51 |             testDirective('
', 24);
52 |         });
53 | 
54 |         it('uses prettyJson attribute', function () {
55 |             testDirective('
', 24);
56 |         });
57 | 
58 |         it('creates an instance with default values w/o presence of "json" key in obj', function () {
59 |             testDirective('
', 24);
60 |         });
61 | 
62 |         it('creates an instance with default values, using element syntax', function () {
63 |             testDirective('', 24);
64 |         });
65 | 
66 |         it('ignores updates to the root object if it has a json property', function () {
67 |             var element = testDirective('
', 24);
68 |             scope.$apply("json.f = 'bar'");
69 |             expect(element.find('pre').children('span').length).toBe(24);
70 |         });
71 | 
72 |         it('zaps markup if empty', function () {
73 |             var element = testDirective('
', 24);
74 |             delete scope.json.json;
75 |             scope.$apply();
76 |             expect(element.find('pre').children('span').length).toBe(0);
77 |         });
78 |     });
79 | 
80 |     describe('makeEntities function', function () {
81 |         it('makes entities', inject(function (ngPrettyJsonFunctions) {
82 |             var foo = '&';
83 |             expect(foo.replace(ngPrettyJsonFunctions.rx.entities,
84 |                 ngPrettyJsonFunctions.makeEntities)).toBe('<g>&</g>');
85 |         }));
86 |     });
87 | 
88 |     describe('markup function', function () {
89 |         it('marks up properly', inject(function (ngPrettyJsonFunctions) {
90 |             var foo = JSON.stringify(obj);
91 |             expect(foo.replace(ngPrettyJsonFunctions.rx.json, ngPrettyJsonFunctions.markup)).toBe('{"a":1,"b":"foo","c":[null,false,null,{"d":{"e":130000}}]}');
92 |         }));
93 |     });
94 | 
95 | });
96 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | ng-prettyjson [![NPM version](https://badge.fury.io/js/ng-prettyjson.png)](http://badge.fury.io/js/ng-prettyjson) [![Build Status](https://travis-ci.org/darul75/ng-prettyjson.png?branch=master)](https://travis-ci.org/darul75/ng-prettyjson) [![CDNJS](https://img.shields.io/cdnjs/v/ng-prettyjson.svg)](https://cdnjs.com/libraries/ng-prettyjson)
  2 | =====================
  3 | 
  4 | Angular directive for JSON pretty display output, indent and colorized.
  5 | 
  6 | Idea was given by the need to display some configuration JSON format files in a back office.
  7 | 
  8 | Inspired by this from stackoverflow
  9 | [pretty json javascript](http://stackoverflow.com/questions/4810841/json-pretty-print-using-javascript)
 10 | 
 11 | Edition is now available with awesome Ace editor:
 12 | [ace editor](http://ace.c9.io/)
 13 | 
 14 | Demo
 15 | ------------
 16 | http://darul75.github.io/ng-prettyjson/
 17 | 
 18 | 
 19 | Screenshot
 20 | ------------
 21 | ![pretty json screenshot](http://darul75.github.io/ng-prettyjson/images/capture.png "pretty json screenshot")
 22 | 
 23 | Installation
 24 | ------------
 25 | 
 26 | Using [Bower](http://bower.io):
 27 | 
 28 | ```
 29 | bower install ng-prettyjson
 30 | ```
 31 | 
 32 | How to use it
 33 | -------------
 34 | 
 35 | You should already have script required for Angular
 36 | 
 37 | ```html
 38 | 
 39 | ```
 40 | 
 41 | to the list above, you should add:
 42 | 
 43 | ```html
 44 | 
 45 | ```
 46 | 
 47 | ```html
 48 | 
 49 | ```
 50 | 
 51 | Then, require `ngPrettyJson` in your application module:
 52 | 
 53 | ```javascript
 54 | angular.module('myApp', ['ngPrettyJson']);
 55 | ```
 56 | 
 57 | and then just add a `pre` with `pretty-json` directive:
 58 | 
 59 | ```html
 60 | 
 61 | 
 62 | 
 63 | 
 64 | 
 65 | ```
 66 | 
 67 | * `jsonObj` is a variable on the scope to be output as JSON:
 68 | 
 69 | ```javascript
 70 | $scope.jsonObj = {a:1, 'b':'foo', c:[false,null, {d:{e:1.3e5}}]};
 71 | ```
 72 | 
 73 | * `edition` activate edition buttons, Ace library has to be loaded, see ace documentation or example [here](https://github.com/darul75/ng-prettyjson/blob/master/demo/ng-prettyjson.html).
 74 | 
 75 | * `on-edit` parent scope function : parameter name has to be 'newJson'.
 76 | 
 77 | By default whether no edition callback has been set, an event is fired from directive. Here is how to catch it:
 78 | 
 79 | ```javascript
 80 | $scope.$on('json-updated', function(msg, value) {
 81 | 	
 82 | });
 83 | ```
 84 | 
 85 | ### Tag Usage
 86 | 
 87 | Alternatively, you can use a `` tag.  This tag will be replaced with a `
`:
 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 | [![NPM](https://nodei.co/npm/ng-prettyjson.png?downloads=true&downloadRank=true&stars=true)](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 | 


--------------------------------------------------------------------------------