Code Snippets - Version 1.8

Version Description

  • Allow no snippet name or code to be set
  • Prevented an error on fresh multisite installations
  • Refactored code to use best practices
  • Improved database table creation method: on a single-site install, the snippets table will always be created. On a multisite install, the network snippets table will always be created; the site-specific table will always be created for the main site; for sub-sites the snippets table will only be created on a visit to a snippets admin page.
  • Updated to CodeMirror 3.14
  • Changes to action and filter hook API
  • Added error message handling for import snippets page
  • Don't encode HTML entities in database
Download this release

Release Info

Developer bungeshea
Plugin Icon Code Snippets
Version 1.8
Comparing to
See all releases

Code changes from version 1.7.1.2 to 1.8

Files changed (48) hide show
  1. admin/help/import.php +41 -0
  2. admin/help/manage.php +42 -0
  3. admin/help/single.php +47 -0
  4. admin/messages/import.php +34 -0
  5. admin/messages/manage.php +42 -0
  6. admin/messages/single.php +32 -0
  7. admin/views/import.php +47 -0
  8. admin/views/manage.php +51 -0
  9. admin/views/single.php +78 -0
  10. assets/admin-single.css +7 -6
  11. assets/admin-single.js +17 -0
  12. assets/menu-icon.mp6.css +2 -2
  13. assets/screen-icon.css +1 -1
  14. assets/table.css +44 -44
  15. assets/table.mp6.css +1 -1
  16. code-snippets.php +326 -257
  17. includes/admin/import.php +0 -61
  18. includes/admin/manage.php +0 -56
  19. includes/admin/single.php +0 -99
  20. includes/class-admin.php +187 -126
  21. includes/class-list-table.php +142 -131
  22. includes/export.php +36 -28
  23. includes/functions.php +23 -0
  24. includes/help/import.php +0 -41
  25. includes/help/manage.php +0 -42
  26. includes/help/single.php +0 -47
  27. languages/code-snippets-de_DE.po +4 -4
  28. languages/code-snippets.po +2 -2
  29. languages/code-snippets.pot +2 -2
  30. license.txt +7 -7
  31. readme.txt +18 -5
  32. screenshot-1.jpg +0 -0
  33. screenshot-1.png +0 -0
  34. screenshot-2.jpg +0 -0
  35. screenshot-2.png +0 -0
  36. screenshot-3.jpg +0 -0
  37. screenshot-3.png +0 -0
  38. screenshot-4.jpg +0 -0
  39. screenshot-4.png +0 -0
  40. screenshot-5.jpg +0 -0
  41. screenshot-5.png +0 -0
  42. vendor/codemirror/addon/matchbrackets.js +86 -74
  43. vendor/codemirror/addon/search.js +131 -131
  44. vendor/codemirror/addon/searchcursor.js +143 -130
  45. vendor/codemirror/lib/codemirror.css +248 -246
  46. vendor/codemirror/lib/codemirror.js +612 -413
  47. vendor/codemirror/mode/clike.js +361 -302
  48. vendor/codemirror/mode/php.js +132 -129
admin/help/import.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register and handle the help tabs for the
5
+ * import snippets admin page
6
+ *
7
+ * @package Code_Snippets
8
+ * @subpackage Contextual_Help
9
+ */
10
+
11
+ global $code_snippets;
12
+ $screen = get_current_screen();
13
+
14
+ $screen->add_help_tab( array(
15
+ 'id' => 'overview',
16
+ 'title' => __( 'Overview', 'code-snippets' ),
17
+ 'content' =>
18
+ '<p>' . __( 'Snippets are similar to plugins - they both extend and expand the functionality of WordPress. Snippets are more light-weight, just a few lines of code, and do not put as much load on your server. Here you can load snippets from a Code Snippets (.xml) import file into the database with your existing snippets.', 'code-snippets' ) . '</p>'
19
+ ) );
20
+
21
+ $screen->add_help_tab( array(
22
+ 'id' => 'import',
23
+ 'title' => __( 'Importing', 'code-snippets' ),
24
+ 'content' =>
25
+ '<p>' . __( 'You can load your snippets from a code snippets (.xml) export file using this page.', 'code-snippets' ) .
26
+ sprintf( __( 'Snippets will be added to the database along with your existing snippets. Regardless of whether the snippets were active on the previous site, imported snippets are always inactive until activated using the <a href="%s">Manage Snippets</a> page.</p>', 'code-snippets' ), $code_snippets->admin->manage_url ) . '</p>'
27
+ ) );
28
+
29
+ $screen->add_help_tab( array(
30
+ 'id' => 'export',
31
+ 'title' => __( 'Exporting', 'code-snippets' ),
32
+ 'content' =>
33
+ '<p>' . sprintf( __( 'You can save your snippets to a Code Snippets (.xml) export file using the <a href="%s">Manage Snippets</a> page.', 'code-snippets' ), $code_snippets->admin->manage_url ) . '</p>'
34
+ ) );
35
+
36
+ $screen->set_help_sidebar(
37
+ '<p><strong>' . __( 'For more information:', 'code-snippets' ) . '</strong></p>' .
38
+ '<p>' . __( '<a href="http://wordpress.org/plugins/code-snippets" target="_blank">WordPress Extend</a>', 'code-snippets' ) . '</p>' .
39
+ '<p>' . __( '<a href="http://wordpress.org/support/plugin/code-snippets" target="_blank">Support Forums</a>', 'code-snippets' ) . '</p>' .
40
+ '<p>' . __( '<a href="http://code-snippets.bungeshea.com/" target="_blank">Project Website</a>', 'code-snippets' ) . '</p>'
41
+ );
admin/help/manage.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register and handle the help tabs for the
5
+ * manage snippets admin page
6
+ *
7
+ * @package Code_Snippets
8
+ * @subpackage Contextual_Help
9
+ */
10
+
11
+ global $code_snippets;
12
+ $screen = get_current_screen();
13
+
14
+ $screen->add_help_tab( array(
15
+ 'id' => 'overview',
16
+ 'title' => __( 'Overview', 'code-snippets' ),
17
+ 'content' =>
18
+ '<p>' . __( 'Snippets are similar to plugins - they both extend and expand the functionality of WordPress. Snippets are more light-weight, just a few lines of code, and do not put as much load on your server. Here you can manage your existing snippets and preform tasks on them such as activating, deactivating, deleting and exporting.', 'code-snippets' ) . '</p>'
19
+ ) );
20
+
21
+ $screen->add_help_tab( array(
22
+ 'id' => 'safe-mode',
23
+ 'title' => __( 'Safe Mode', 'code-snippets' ),
24
+ 'content' =>
25
+ '<p>' . __( 'Be sure to check your snippets for errors before you activate them, as a faulty snippet could bring your whole blog down. If your site starts doing strange things, deactivate all your snippets and activate them one at a time.', 'code-snippets' ) . '</p>' .
26
+ '<p>' . __("If something goes wrong with a snippet and you can't use WordPress, you can cause all snippets to stop executing by adding <code>define('CODE_SNIPPETS_SAFE_MODE', true);</code> to your <code>wp-config.php</code> file. After you have deactivated the offending snippet, you can turn off safe mode by removing this line or replacing <strong>true</strong> with <strong>false</strong>.", 'code-snippets' ) . '</p>'
27
+ ) );
28
+
29
+ $screen->add_help_tab( array(
30
+ 'id' => 'uninstall',
31
+ 'title' => __( 'Uninstall', 'code-snippets' ),
32
+ 'content' =>
33
+ '<p>' . sprintf( __( 'When you delete Code Snippets through the Plugins menu in WordPress it will clear up the <code>%1$s</code> table and a few other bits of data stored in the database. If you want to keep this data (ie: you are only temporally uninstalling Code Snippets) then remove the <code>%2$s</code> folder using FTP.', 'code-snippets' ), $code_snippets->get_table_name(), $code_snippets->plugin_dir ) .
34
+ '<p>' . __("Even if you're sure that you don't want to use Code Snippets ever again on this WordPress installation, you may want to use the export feature to back up your snippets.", 'code-snippets' ) . '</p>'
35
+ ) );
36
+
37
+ $screen->set_help_sidebar(
38
+ '<p><strong>' . __( 'For more information:', 'code-snippets' ) . '</strong></p>' .
39
+ '<p>' . __( '<a href="http://wordpress.org/plugins/code-snippets" target="_blank">WordPress Extend</a></p>', 'code-snippets' ) . '</p>' .
40
+ '<p>' . __( '<a href="http://wordpress.org/support/plugin/code-snippets" target="_blank">Support Forums</a>', 'code-snippets' ) . '</p>' .
41
+ '<p>' . __( '<a href="http://code-snippets.bungeshea.com/" target="_blank">Project Website</a>', 'code-snippets' ) . '</p>'
42
+ );
admin/help/single.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register and handle the help tabs for the
5
+ * single snippet admin page
6
+ *
7
+ * @package Code_Snippets
8
+ * @subpackage Contextual_Help
9
+ */
10
+
11
+ $screen = get_current_screen();
12
+
13
+ $screen->add_help_tab( array(
14
+ 'id' => 'overview',
15
+ 'title' => __( 'Overview', 'code-snippets' ),
16
+ 'content' =>
17
+ '<p>' . __( 'Snippets are similar to plugins - they both extend and expand the functionality of WordPress. Snippets are more light-weight, just a few lines of code, and do not put as much load on your server. Here you can add a new snippet, or edit an existing one.', 'code-snippets' ) . '</p>',
18
+ ) );
19
+
20
+ $screen->add_help_tab( array(
21
+ 'id' => 'finding',
22
+ 'title' => __( 'Finding Snippets', 'code-snippets' ),
23
+ 'content' =>
24
+ '<p>' . __( 'Here are some links to websites which host a large number of snippets that you can add to your site.
25
+ <ul>
26
+ <li><a href="http://wp-snippets.com" title="WordPress Snippets">WP-Snippets</a></li>
27
+ <li><a href="http://wpsnipp.com" title="WP Snipp">WP Snipp</a></li>
28
+ <li><a href="http://www.catswhocode.com/blog/snippets" title="Cats Who Code Snippet Library">Cats Who Code</a></li>
29
+ <li><a href="http://www.wpfunction.me">WP Function Me</a></li>
30
+ </ul>', 'code-snippets' ) .
31
+ __( 'More places to find snippets, as well as a selection of example snippets, can be found in the <a href="http://code-snippets.bungeshea.com/docs/finding-snippets/">plugin documentation</a>', 'code-snippets' ) . '</p>'
32
+ ) );
33
+
34
+ $screen->add_help_tab( array(
35
+ 'id' => 'adding',
36
+ 'title' => __( 'Adding Snippets', 'code-snippets' ),
37
+ 'content' =>
38
+ '<p>' . __( 'You need to fill out the name and code fields for your snippet to be added. While the description field will add more information about how your snippet works, what is does and where you found it, it is completely optional.', 'code-snippets' ) . '</p>' .
39
+ '<p>' . __( 'Please be sure to check that your snippet is valid PHP code and will not produce errors before adding it through this page. While doing so will not become active straight away, it will help to minimise the chance of a faulty snippet becoming active on your site.', 'code-snippets' ) . '</p>'
40
+ ) );
41
+
42
+ $screen->set_help_sidebar(
43
+ '<p><strong>' . __( 'For more information:', 'code-snippets' ) . '</strong></p>' .
44
+ '<p>' . __( '<a href="http://wordpress.org/plugins/code-snippets" target="_blank">WordPress Extend</a>', 'code-snippets' ) . '</p>' .
45
+ '<p>' . __( '<a href="http://wordpress.org/support/plugin/code-snippets" target="_blank">Support Forums</a>', 'code-snippets' ) . '</p>' .
46
+ '<p>' . __( '<a href="http://code-snippets.bungeshea.com/" target="_blank">Project Website</a>', 'code-snippets' ) . '</p>'
47
+ );
admin/messages/import.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Status and error messages for the import snippets page
5
+ *
6
+ * @package Code_Snippets
7
+ * @subpackage Admin_Messages
8
+ */
9
+
10
+ global $code_snippets;
11
+
12
+ if ( isset( $_REQUEST['imported'] ) && 0 !== intval( $_REQUEST['imported'] ) ) {
13
+
14
+ echo '<div id="message" class="updated fade"><p>';
15
+
16
+ printf(
17
+ _n(
18
+ 'Successfully imported <strong>%d</strong> snippet. <a href="%s">Have fun!</a>',
19
+ 'Successfully imported <strong>%d</strong> snippets. <a href="%s">Have fun!</a>',
20
+ $_REQUEST['imported'],
21
+ 'code-snippets'
22
+ ),
23
+ $_REQUEST['imported'],
24
+ $code_snippets->admin->manage_url
25
+ );
26
+
27
+ echo '</p></div>';
28
+ }
29
+ elseif ( isset( $_REQUEST['error'] ) && $_REQUEST['error'] ) {
30
+ printf (
31
+ '<div id="message" class="error fade"><p>%s</p></div>',
32
+ __( 'An error occurred when processing the import file.', 'code-snippets' )
33
+ );
34
+ }
admin/messages/manage.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Status and error messages for the manage snippets page
5
+ *
6
+ * @package Code_Snippets
7
+ * @subpackage Admin_Messages
8
+ */
9
+
10
+ $_f = '<div id="message" class="%2$s fade"><p>%1$s</p></div>';
11
+
12
+ if ( defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) :
13
+
14
+ printf ( $_f, __( '<strong>Warning:</strong> Safe mode is active and snippets will not execute! Remove the <code>CODE_SNIPPETS_SAFE_MODE</code> constant from <code>wp-config.php</code> to turn off safe mode. <a href="http://code-snippets.bungeshea.com/docs/safe-mode/" target="_blank">Help</a>', 'code-snippets' ), 'error' );
15
+
16
+ endif;
17
+
18
+ if ( isset( $_REQUEST['activate'] ) ) :
19
+
20
+ printf ( $_f, __( 'Snippet <strong>activated</strong>.', 'code-snippets' ), 'updated' );
21
+
22
+ elseif ( isset( $_REQUEST['activate-multi'] ) ) :
23
+
24
+ printf ( $_f, __( 'Selected snippets <strong>activated</strong>.', 'code-snippets' ), 'updated' );
25
+
26
+ elseif ( isset( $_REQUEST['deactivate'] ) ) :
27
+
28
+ printf ( $_f, __( 'Snippet <strong>deactivated</strong>.', 'code-snippets' ), 'updated' );
29
+
30
+ elseif ( isset( $_REQUEST['deactivate-multi'] ) ) :
31
+
32
+ printf ( $_f, __( 'Selected snippets <strong>deactivated</strong>.', 'code-snippets' ), 'updated' );
33
+
34
+ elseif ( isset( $_REQUEST['delete'] ) ) :
35
+
36
+ printf ( $_f, __( 'Snippet <strong>deleted</strong>.', 'code-snippets' ), 'updated' );
37
+
38
+ elseif ( isset( $_REQUEST['delete-multi'] ) ) :
39
+
40
+ printf ( $_f, __( 'Selected snippets <strong>deleted</strong>.', 'code-snippets' ), 'updated' );
41
+
42
+ endif;
admin/messages/single.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Status and error messages for the single snippet page
5
+ *
6
+ * @package Code_Snippets
7
+ * @subpackage Admin_Messages
8
+ */
9
+
10
+ $_f = '<div id="message" class="%2$s fade"><p>%1$s</p></div>';
11
+
12
+ if ( isset( $_REQUEST['invalid'] ) && $_REQUEST['invalid'] ) :
13
+
14
+ printf ( $_f, __( 'An error occurred when saving the snippet.', 'code-snippets' ), 'error' );
15
+
16
+ elseif ( isset( $_REQUEST['activated'], $_REQUEST['updated'] ) && $_REQUEST['activated'] && $_REQUEST['updated'] ) :
17
+
18
+ printf ( $_f, __( 'Snippet <strong>updated</strong> and <strong>activated</strong>.', 'code-snippets' ), 'updated' );
19
+
20
+ elseif ( isset( $_REQUEST['activated'], $_REQUEST['added'] ) && $_REQUEST['activated'] && $_REQUEST['added'] ) :
21
+
22
+ printf ( $_f, __( 'Snippet <strong>added</strong> and <strong>activated</strong>.', 'code-snippets' ), 'updated' );
23
+
24
+ elseif ( isset( $_REQUEST['updated'] ) && $_REQUEST['updated'] ) :
25
+
26
+ printf ( $_f, __( 'Snippet <strong>updated</strong>.', 'code-snippets' ), 'updated' );
27
+
28
+ elseif ( isset( $_REQUEST['added'] ) && $_REQUEST['added'] ) :
29
+
30
+ printf ( $_f, __( 'Snippet <strong>added</strong>.', 'code-snippets' ), 'updated' );
31
+
32
+ endif;
admin/views/import.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * HTML code for the Import Snippets page
5
+ *
6
+ * @package Code_Snippets
7
+ * @subpackage Admin_Views
8
+ */
9
+
10
+ if ( ! class_exists( 'Code_Snippets' ) )
11
+ exit;
12
+
13
+ global $code_snippets;
14
+
15
+ $code_snippets->admin->get_messages( 'import' );
16
+
17
+ ?>
18
+ <div class="wrap">
19
+ <?php screen_icon(); ?>
20
+ <h2><?php _e( 'Import Snippets', 'code-snippets' ); ?></h2>
21
+
22
+ <div class="narrow">
23
+
24
+ <p><?php _e( 'Howdy! Upload your Code Snippets export file and we&#8217;ll import the snippets to this site.', 'code-snippets' ); ?></p>
25
+
26
+ <p><?php printf( __( 'You will need to go to the <a href="%s">Manage Snippets</a> page to activate the imported snippets.', 'code-snippets' ), $code_snippets->admin->manage_url ); ?></p>
27
+
28
+ <p><?php _e( 'Choose a Code Snippets (.xml) file to upload, then click Upload file and import.', 'code-snippets' ); ?></p>
29
+
30
+ <form enctype="multipart/form-data" method="post" action="" id="import-upload-form" name="code_snippets_import">
31
+ <p>
32
+ <input type="hidden" name="action" value="save" />
33
+ <input type="hidden" name="max_file_size" value="8388608" />
34
+
35
+ <label for="upload"><?php _e( 'Choose a file from your computer:', 'code-snippets' ); ?></label>
36
+ <?php _e( '(Maximum size: 8MB)', 'code-snippets' ); ?>
37
+ <input type="file" id="upload" name="code_snippets_import_file" size="25" accept="text/xml" />
38
+ </p>
39
+
40
+ <?php
41
+ do_action( 'code_snippets/admin/import_form' );
42
+ submit_button( __( 'Upload file and import', 'code-snippets' ) );
43
+ ?>
44
+
45
+ </form>
46
+ </div>
47
+ </div>
admin/views/manage.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * HTML code for the Manage Snippets page
5
+ *
6
+ * @package Code_Snippets
7
+ * @subpackage Admin_Views
8
+ */
9
+
10
+ if ( ! class_exists( 'Code_Snippets' ) )
11
+ exit;
12
+
13
+ global $code_snippets;
14
+ $screen = get_current_screen();
15
+ $code_snippets->admin->get_messages( 'manage' );
16
+ ?>
17
+
18
+ <div class="wrap">
19
+ <?php screen_icon(); ?>
20
+ <h2><?php
21
+ esc_html_e( 'Snippets', 'code-snippets' );
22
+
23
+ if ( $code_snippets->user_can( 'install' ) ) {
24
+
25
+ printf ( '<a href="%2$s" class="add-new-h2">%1$s</a>',
26
+ esc_html_x( 'Add New', 'snippet', 'code-snippets' ),
27
+ $code_snippets->admin->single_url
28
+ );
29
+ }
30
+
31
+ $code_snippets->list_table->search_notice();
32
+ ?></h2>
33
+
34
+ <?php $code_snippets->list_table->views(); ?>
35
+
36
+ <form method="get" action="">
37
+ <?php
38
+ $code_snippets->list_table->required_form_fields( 'search_box' );
39
+ $code_snippets->list_table->search_box( __( 'Search Installed Snippets', 'code-snippets' ), 'search_id' );
40
+ ?>
41
+ </form>
42
+ <form method="post" action="">
43
+ <?php
44
+ $code_snippets->list_table->required_form_fields();
45
+ $code_snippets->list_table->display();
46
+ ?>
47
+ </form>
48
+
49
+ <?php do_action( 'code_snippets/admin/manage' ); ?>
50
+
51
+ </div>
admin/views/single.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * HTML code for the Add New/Edit Snippet page
5
+ *
6
+ * @package Code_Snippets
7
+ * @subpackage Admin_Views
8
+ */
9
+
10
+ if ( ! class_exists( 'Code_Snippets' ) )
11
+ exit;
12
+
13
+ global $code_snippets;
14
+
15
+ $table = $code_snippets->get_table_name();
16
+ $screen = get_current_screen();
17
+
18
+ $edit_id = ( isset( $_REQUEST['edit'] ) ? absint( $_REQUEST['edit'] ) : 0 );
19
+ $snippet = $code_snippets->get_snippet( $edit_id );
20
+
21
+ $code_snippets->admin->get_messages( 'single' );
22
+
23
+ ?>
24
+
25
+ <div class="wrap">
26
+ <?php screen_icon(); ?>
27
+ <h2><?php
28
+ if ( $edit_id ) {
29
+ esc_html_e( 'Edit Snippet', 'code-snippets' );
30
+
31
+ if ( $code_snippets->user_can( 'install' ) )
32
+ printf( ' <a href="%1$s" class="add-new-h2">%2$s</a>',
33
+ $code_snippets->admin->single_url,
34
+ esc_html_x( 'Add New', 'snippet', 'code-snippets' )
35
+ );
36
+ } else {
37
+ esc_html_e( 'Add New Snippet', 'code-snippets' );
38
+ }
39
+ ?></h2>
40
+
41
+ <form method="post" action="" style="margin-top: 10px;">
42
+ <?php
43
+
44
+ /* Output the hidden fields */
45
+
46
+ if ( 0 !== $snippet->id )
47
+ printf ( '<input type="hidden" name="snippet_id" value="%d" />', $snippet->id );
48
+
49
+ printf ( '<input type="hidden" name="snippet_active" value="%d" />', $snippet->active );
50
+ ?>
51
+ <div id="titlediv">
52
+ <div id="titlewrap">
53
+ <label for="title" style="display: none;"><?php _e( 'Name (short title)', 'code-snippets' ); ?></label>
54
+ <input id="title" type="text" autocomplete="off" name="snippet_name" value="<?php echo $snippet->name; ?>" placeholder="<?php _e( 'Name (short title)', 'code-snippets' ); ?>" />
55
+ </div>
56
+ </div>
57
+
58
+ <label for="snippet_code">
59
+ <h3><?php _e( 'Code', 'code-snippets' ); ?></h3>
60
+ </label>
61
+
62
+ <textarea id="snippet_code" name="snippet_code" rows="20" spellcheck="false" style="font-family: monospace; width: 100%;"><?php echo esc_textarea( $snippet->code ); ?></textarea>
63
+
64
+ <?php do_action( 'code_snippets/admin/single', $snippet ); ?>
65
+
66
+ <p class="submit">
67
+ <?php
68
+ submit_button( null, 'primary', 'save_snippet', false );
69
+
70
+ if ( ! $snippet->active ) {
71
+ echo '&nbsp;&nbsp;&nbsp;';
72
+ submit_button( __( 'Save Changes &amp; Activate', 'code-snippets' ), 'secondary', 'save_snippet_activate', false );
73
+ }
74
+ ?>
75
+ </p>
76
+
77
+ </form>
78
+ </div>
assets/admin-single.css CHANGED
@@ -1,7 +1,7 @@
1
  /**
2
  * Custom styling for the single snippet admin page
3
  *
4
- * @package Code Snippets
5
  * @subpackage Assets
6
  */
7
 
@@ -22,8 +22,11 @@ label {
22
  .CodeMirror {
23
  min-height: 300px;
24
  height: auto;
25
- border: 1px solid #eee; -webkit-border-radius: 3px;
26
- -moz-border-radius: 3px; border-radius: 3px; -o-border-radius: 3px;
 
 
 
27
  }
28
 
29
  .CodeMirror-scroll {
@@ -35,9 +38,7 @@ label {
35
  min-height: 300px !important;
36
  }
37
 
38
- /**
39
- * Tweak things for the MP6 interface
40
- */
41
  .admin-color-mp6 .CodeMirror {
42
  width: 100%;
43
  border-color: #dfdfdf;
1
  /**
2
  * Custom styling for the single snippet admin page
3
  *
4
+ * @package Code_Snippets
5
  * @subpackage Assets
6
  */
7
 
22
  .CodeMirror {
23
  min-height: 300px;
24
  height: auto;
25
+ border: 1px solid #eee;
26
+ -webkit-border-radius: 3px;
27
+ -moz-border-radius: 3px;
28
+ border-radius: 3px;
29
+ -o-border-radius: 3px;
30
  }
31
 
32
  .CodeMirror-scroll {
38
  min-height: 300px !important;
39
  }
40
 
41
+ /** Tweak things for the MP6 interface */
 
 
42
  .admin-color-mp6 .CodeMirror {
43
  width: 100%;
44
  border-color: #dfdfdf;
assets/admin-single.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Loads CodeMirror on the snippet editor
3
+ *
4
+ * @package Code_Snippets
5
+ * @subpackage Assets
6
+ */
7
+
8
+ var editor = CodeMirror.fromTextArea(document.getElementById("snippet_code"), {
9
+ lineNumbers: true,
10
+ matchBrackets: true,
11
+ lineWrapping: true,
12
+ mode: "text/x-php",
13
+ indentUnit: 4,
14
+ indentWithTabs: true,
15
+ enterMode: "keep",
16
+ tabMode: "shift"
17
+ });
assets/menu-icon.mp6.css CHANGED
@@ -2,7 +2,7 @@
2
  * Use our awesome new SVG icon on
3
  * MP6 admin pages
4
  *
5
- * @package Code Snippets
6
  * @subpackage Assets
7
  */
8
 
@@ -20,4 +20,4 @@
20
 
21
  #toplevel_page_snippets:hover .wp-menu-image {
22
  background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cgo8c3ZnIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjQ0cHQiIHdpZHRoPSI0OHB0IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA0OCA0NCI+CgoJPHBhdGggY2xhc3M9InNuaXBwZXRzLWljb24tc3ZnIiBkPSJNMzIuNzIsMS44MWMzLjE3LTIuMjcsOS43MS0xLjE2LDEwLjMyLDMuMTMtMi44LDMuNzItNi40OSw2LjY1LTkuNjQsMTAuMDYsNC44MiwwLjUzLDEwLjE2LTEuMjIsMTQuNiwxLjE2djEuNTVjLTAuMzcsMy4xMS0yLjM1LDYuMTQtNS40OSw2Ljk3LTYuNzksMS4yOS0xNC4wNC0xLjAyLTIwLjYyLDEuMjUsMi42NiwxLjY3LDUuNTYsMy43NCw2LjA3LDcuMSwxLjI0LDUuMTItMi45MSwxMC40My04LjA4LDEwLjgtNS4yNywwLjUtMTAuMzctNC40NS05LjY2LTkuNzksMC4zNi00LjY2LDQuNzMtNy4yMiw3LjQ2LTEwLjQ5LDEuMTUtMC45OCwxLjAyLTMuMTYtMC43My0zLjM3LTMuNzgtMC41Ny03Ljc1LDAuNDMtMTEuNDItMC44OS0zLjA2LTEuMi00Ljk5LTQuMTktNS41My03LjM1di0yLjE1YzAuODMtNS44Nyw3Ljc4LTkuNzQsMTMuMDgtNi43OSw0LjU5LDIuMTcsNS43Nyw3LjgzLDQuMjEsMTIuMzIsMC41OC0wLjIxLDEuNzUtMC42MSwyLjM0LTAuODIsNC41Ny00LDguMzgtOC44NCwxMy4wOS0xMi42OXogTTcuMzMsNy40YzIuNDgtMS4yNyw1Ljc5LDAuODEsNS42OSwzLjYxLDAuMTMsMy4wNi0zLjg4LDUuMTctNi4yOCwzLjIzLTIuNTgtMS40OS0yLjE1LTUuNzcsMC41OS02Ljg0eiBNMTcuMzYsMzEuMzhjMi44MS0xLjc4LDYuNzQsMS40NSw1LjY2LDQuNTYtMC42OCwyLjgzLTQuNjgsMy45LTYuNjUsMS43NC0xLjkzLTEuNzctMS40LTUuMjEsMC45OS02LjN6IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9IiMyZWEyY2MiLz4KCjwvc3ZnPg==) !important;
23
- }
2
  * Use our awesome new SVG icon on
3
  * MP6 admin pages
4
  *
5
+ * @package Code_Snippets
6
  * @subpackage Assets
7
  */
8
 
20
 
21
  #toplevel_page_snippets:hover .wp-menu-image {
22
  background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cgo8c3ZnIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjQ0cHQiIHdpZHRoPSI0OHB0IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA0OCA0NCI+CgoJPHBhdGggY2xhc3M9InNuaXBwZXRzLWljb24tc3ZnIiBkPSJNMzIuNzIsMS44MWMzLjE3LTIuMjcsOS43MS0xLjE2LDEwLjMyLDMuMTMtMi44LDMuNzItNi40OSw2LjY1LTkuNjQsMTAuMDYsNC44MiwwLjUzLDEwLjE2LTEuMjIsMTQuNiwxLjE2djEuNTVjLTAuMzcsMy4xMS0yLjM1LDYuMTQtNS40OSw2Ljk3LTYuNzksMS4yOS0xNC4wNC0xLjAyLTIwLjYyLDEuMjUsMi42NiwxLjY3LDUuNTYsMy43NCw2LjA3LDcuMSwxLjI0LDUuMTItMi45MSwxMC40My04LjA4LDEwLjgtNS4yNywwLjUtMTAuMzctNC40NS05LjY2LTkuNzksMC4zNi00LjY2LDQuNzMtNy4yMiw3LjQ2LTEwLjQ5LDEuMTUtMC45OCwxLjAyLTMuMTYtMC43My0zLjM3LTMuNzgtMC41Ny03Ljc1LDAuNDMtMTEuNDItMC44OS0zLjA2LTEuMi00Ljk5LTQuMTktNS41My03LjM1di0yLjE1YzAuODMtNS44Nyw3Ljc4LTkuNzQsMTMuMDgtNi43OSw0LjU5LDIuMTcsNS43Nyw3LjgzLDQuMjEsMTIuMzIsMC41OC0wLjIxLDEuNzUtMC42MSwyLjM0LTAuODIsNC41Ny00LDguMzgtOC44NCwxMy4wOS0xMi42OXogTTcuMzMsNy40YzIuNDgtMS4yNyw1Ljc5LDAuODEsNS42OSwzLjYxLDAuMTMsMy4wNi0zLjg4LDUuMTctNi4yOCwzLjIzLTIuNTgtMS40OS0yLjE1LTUuNzcsMC41OS02Ljg0eiBNMTcuMzYsMzEuMzhjMi44MS0xLjc4LDYuNzQsMS40NSw1LjY2LDQuNTYtMC42OCwyLjgzLTQuNjgsMy45LTYuNjUsMS43NC0xLjkzLTEuNzctMS40LTUuMjEsMC45OS02LjN6IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9IiMyZWEyY2MiLz4KCjwvc3ZnPg==) !important;
23
+ }
assets/screen-icon.css CHANGED
@@ -1,7 +1,7 @@
1
  /**
2
  * Add the snippet screen icon to admin pages
3
  *
4
- * @package Code Snippets
5
  * @subpackage Assets
6
  */
7
 
1
  /**
2
  * Add the snippet screen icon to admin pages
3
  *
4
+ * @package Code_Snippets
5
  * @subpackage Assets
6
  */
7
 
assets/table.css CHANGED
@@ -1,44 +1,44 @@
1
- /**
2
- * Custom styling for the snippets table
3
- *
4
- * @package Code Snippets
5
- * @subpackage Assets
6
- */
7
-
8
- .snippets .inactive a {
9
- color: #557799;
10
- }
11
-
12
- .snippets .inactive a:hover,
13
- #all-snippets-table .snippets .inactive a:hover,
14
- #search-snippets-table .snippets .inactive a:hover {
15
- color: #d54e21;
16
- }
17
-
18
- .snippets .delete a {
19
- color: #21759b;
20
- }
21
-
22
- #wpbody-content .snippets tr {
23
- background-color: #fcfcfc;
24
- }
25
-
26
- .snippets .inactive,
27
- .snippets .inactive th,
28
- .snippets .inactive td {
29
- background-color: #f4f4f4;
30
- }
31
-
32
- .snippets .active,
33
- .snippets .active th,
34
- .snippets .active td {
35
- color: #000;
36
- }
37
-
38
- #wpbody-content .snippets .column-name {
39
- white-space: nowrap; /* prevents wrapping of snippet title */
40
- }
41
-
42
- #wpbody-content .snippets .manage-column.column-id.sortable {
43
- min-width: 44px;
44
- }
1
+ /**
2
+ * Custom styling for the snippets table
3
+ *
4
+ * @package Code_Snippets
5
+ * @subpackage Assets
6
+ */
7
+
8
+ .snippets .inactive a {
9
+ color: #557799;
10
+ }
11
+
12
+ .snippets .inactive a:hover,
13
+ #all-snippets-table .snippets .inactive a:hover,
14
+ #search-snippets-table .snippets .inactive a:hover {
15
+ color: #d54e21;
16
+ }
17
+
18
+ .snippets .delete a {
19
+ color: #21759b;
20
+ }
21
+
22
+ #wpbody-content .snippets tr {
23
+ background-color: #fcfcfc;
24
+ }
25
+
26
+ .snippets .inactive,
27
+ .snippets .inactive th,
28
+ .snippets .inactive td {
29
+ background-color: #f4f4f4;
30
+ }
31
+
32
+ .snippets .active,
33
+ .snippets .active th,
34
+ .snippets .active td {
35
+ color: #000;
36
+ }
37
+
38
+ #wpbody-content .snippets .column-name {
39
+ white-space: nowrap; /* prevents wrapping of snippet title */
40
+ }
41
+
42
+ #wpbody-content .snippets .manage-column.column-id.sortable {
43
+ min-width: 44px;
44
+ }
assets/table.mp6.css CHANGED
@@ -2,7 +2,7 @@
2
  * Custom styling for the snippets table
3
  * and the MP6 interface
4
  *
5
- * @package Code Snippets
6
  * @subpackage Assets
7
  */
8
 
2
  * Custom styling for the snippets table
3
  * and the MP6 interface
4
  *
5
+ * @package Code_Snippets
6
  * @subpackage Assets
7
  */
8
 
code-snippets.php CHANGED
@@ -6,8 +6,12 @@
6
  * If you're interested in helping to develop Code Snippets, or perhaps
7
  * contribute to the localization, please see http://code-snippets.bungeshea.com
8
  *
9
- * @package Code Snippets
10
- * @subpackage Main
 
 
 
 
11
  */
12
 
13
  /*
@@ -16,15 +20,16 @@
16
  * Description: An easy, clean and simple way to add code snippets to your site. No need to edit to your theme's functions.php file again!
17
  * Author: Shea Bunge
18
  * Author URI: http://bungeshea.com
19
- * Version: 1.7.1.2
20
  * License: MIT
21
  * License URI: license.txt
22
  * Text Domain: code-snippets
23
  * Domain Path: /languages/
24
  */
25
 
26
- // Exit if accessed directly
27
- if ( ! defined( 'ABSPATH' ) ) exit;
 
28
 
29
  if ( ! class_exists( 'Code_Snippets' ) ) :
30
 
@@ -36,9 +41,9 @@ if ( ! class_exists( 'Code_Snippets' ) ) :
36
  * the methods or variables in this class. Anything you need
37
  * to access should be publicly available there
38
  *
39
- * @since 1.0
40
- * @package Code Snippets
41
- * @access private
42
  */
43
  final class Code_Snippets {
44
 
@@ -49,42 +54,46 @@ final class Code_Snippets {
49
  * This should be set to the 'Plugin Version' value,
50
  * as defined above in the plugin header
51
  *
52
- * @since 1.0
53
  * @access public
54
- * @var string A PHP-standardized version number string
55
  */
56
- public $version = '1.7.1.2';
57
 
58
  /**
59
  * Variables to hold plugin paths
60
  *
61
- * @since 1.0
62
  * @access public
63
- * @var string
64
  */
65
- public $file, $basename, $plugin_dir, $plugin_url;
66
 
67
  /**
68
  * Stores an instance of the list table class
69
  *
70
- * @since 1.5
 
71
  * @access public
72
- * @see Code_Snippets_List_Table
73
  */
74
  public $list_table;
75
 
76
  /**
77
  * Stores an instance of the administration class
78
  *
79
- * @since Code_Snippets 1.7.1
 
80
  * @access public
81
- * @see Code_Snippets_Admin
82
  */
83
  public $admin;
84
 
85
  /**
86
  * Used by maybe_create_tables() for bailing early
87
- * @var boolean
 
 
88
  */
89
  static $tables_created = false;
90
 
@@ -95,21 +104,23 @@ final class Code_Snippets {
95
  * $wpdb->ms_snippets, but these are maintained
96
  * as references for backwards-compatibility
97
  *
98
- * @var string
 
99
  */
100
- public $table, $ms_table;
101
 
102
  /**
103
  * These are now deprecated in favor of those in
104
  * the Code_Snippets_Admin class, but maintained as
105
  * references so we don't break existing code
106
  *
107
- * @since 1.0
108
  * @deprecated Moved to the Code_Snippets_Admin class in 1.7.1
109
- * @access public
110
- * @var string
111
  */
112
- public $admin_manage, $admin_single, $admin_import, $admin_manage_url, $admin_single_url, $admin_import_url;
 
113
 
114
  /**
115
  * The constructor function for our class
@@ -118,9 +129,8 @@ final class Code_Snippets {
118
  * so other plugins may not have loaded yet. Only do stuff
119
  * here that really can't wait
120
  *
121
- * @since 1.0
122
  * @access private
123
- *
124
  * @return void
125
  */
126
  function __construct() {
@@ -138,6 +148,7 @@ final class Code_Snippets {
138
 
139
  /* Hook our initialize function to the plugins_loaded action */
140
  add_action( 'plugins_loaded', array( $this, 'init' ) );
 
141
  }
142
 
143
  /**
@@ -146,9 +157,8 @@ final class Code_Snippets {
146
  * This method is called *after* other plugins
147
  * have been run
148
  *
149
- * @since 1.7
150
  * @access public
151
- *
152
  * @return void
153
  */
154
  public function init() {
@@ -168,41 +178,39 @@ final class Code_Snippets {
168
  */
169
  load_plugin_textdomain( 'code-snippets', false, dirname( $this->basename ) . '/languages/' );
170
 
171
- /*
172
- * Cleanup the plugin data on uninstall
173
- */
174
  register_uninstall_hook( $this->file, array( __CLASS__, 'uninstall' ) );
175
 
176
- /**
177
- * Let extension plugins know that it's okay to load
178
- */
 
179
  do_action( 'code_snippets_init' );
180
  }
181
 
182
  /**
183
  * Initialize variables
184
  *
185
- * @since 1.2
186
  * @access private
187
- *
188
  * @return void
189
  */
190
  function setup_vars() {
191
 
192
  /* Plugin directory variables */
193
  $this->file = __FILE__;
194
- $this->basename = plugin_basename( $this->file );
195
  $this->plugin_dir = plugin_dir_path( $this->file );
196
  $this->plugin_url = plugin_dir_url ( $this->file );
197
 
198
  if ( is_admin() ) {
199
 
200
  /* Load our administration class */
201
- require_once $this->plugin_dir . 'includes/class-admin.php';
202
  $this->admin = new Code_Snippets_Admin;
203
 
204
  /* Remap deprecated variables */
205
- $this->admin_manage_url = &$this->admin->manage_url;
206
  $this->admin_single_url = &$this->admin->single_url;
207
  $this->admin_import_url = &$this->admin->import_url;
208
 
@@ -212,121 +220,122 @@ final class Code_Snippets {
212
  }
213
  }
214
 
 
 
 
 
 
 
 
 
 
 
215
  /**
216
  * Register the snippet table names with WordPress
217
  *
218
- * @since 1.7
219
  * @access public
220
- *
221
- * @uses $wpdb
222
- *
223
  * @return void
224
  */
225
  public function set_table_vars() {
226
  global $wpdb;
227
 
228
  /* Register the snippet table names with WordPress */
229
- $wpdb->tables[] = 'snippets';
230
  $wpdb->ms_global_tables[] = 'ms_snippets';
231
 
232
  /* Setup initial table variables */
233
- $wpdb->snippets = $wpdb->prefix . 'snippets';
234
- $wpdb->ms_snippets = $wpdb->base_prefix . 'ms_snippets';
235
 
236
  /* Add a pointer to the old variables */
237
- $this->table = &$wpdb->snippets;
238
- $this->ms_table = &$wpdb->ms_snippets;
239
  }
240
 
241
  /**
242
  * Return the appropriate snippet table name
243
  *
244
- * @since 1.6
245
  * @access public
246
- *
247
- * @param string $scope Retrieve the multisite table name or the site table name?
248
- * @param bool $check_screen Query the current screen if no scope passed?
249
- * @return string $table The snippet table name
250
  */
251
  public function get_table_name( $scope = '', $check_screen = true ) {
252
-
253
  global $wpdb;
254
 
255
- $this->maybe_create_tables(); // create the snippet tables if they do not exist
256
-
257
  if ( ! is_multisite() ) {
258
  $network = false;
259
  }
 
 
260
  elseif ( in_array( $scope, array( 'multisite', 'network' ) ) ) {
261
  $network = true;
262
  }
 
 
263
  elseif ( empty( $scope ) && $check_screen && function_exists( 'get_current_screen' ) ) {
264
- /* if no scope is set, query the current screen to see if in network admin */
265
  $network = get_current_screen()->is_network;
266
  }
 
 
267
  else {
268
  $network = false;
269
  }
270
 
271
- $table = ( $network ? $wpdb->ms_snippets : $wpdb->snippets );
272
-
273
- return $table;
274
  }
275
 
276
  /**
277
  * Create the snippet tables if they do not already exist
278
  *
279
- * @since 1.7.1
280
  * @access public
281
  *
282
- * @uses $this->create_table() To create a single snippet table
283
- * @uses $wpdb->get_var() To test of the table exists
284
- * @uses self::$tables_created To check if we've already done this or not
285
- *
286
- * @param bool $force Force table creation/upgrade
287
- *
288
- * @return void
289
  */
290
- public function maybe_create_tables( $force = false ) {
291
 
292
  /* Bail early if we've done this already */
293
- if ( self::$tables_created && ! $force ) return;
 
294
 
295
  global $wpdb;
296
 
297
- if ( ! isset( $wpdb->snippets, $wpdb->ms_snippets ) )
 
298
  $this->set_table_vars();
299
-
300
- $table_exists = $force || $wpdb->get_var( "SHOW TABLES LIKE '$wpdb->snippets'" ) === $wpdb->snippets;
301
-
302
- if ( ! is_multisite() && ! $table_exists ) {
303
- $table = $wpdb->snippets;
304
  }
305
- elseif ( is_multisite() ) {
306
- $ms_table_exists = $force || $wpdb->get_var( "SHOW TABLES LIKE '$wpdb->ms_snippets'" ) === $wpdb->ms_snippets;
307
-
308
- if ( ! $ms_table_exists && ! $table_exists )
309
- $table = $wpdb->snippets . ',' . $wpdb->ms_snippets;
310
- elseif ( ! $table_exists && $ms_table_exists )
311
- $table = $wpdb->snippets;
312
- elseif ( $table_exists && ! $ms_table_exists )
313
- $table = $wpdb->ms_snippets;
314
  }
315
 
316
- if ( isset( $table ) )
317
- $this->create_table( $table );
 
 
318
 
 
319
  self::$tables_created = true;
320
  }
321
 
322
  /**
323
  * Create the snippet tables if they do not already exist
324
  *
325
- * @since Code Snippets 1.2
326
  * @deprecated Code Snippets 1.7.1
327
- * @access public
328
- *
329
- * @return void
330
  */
331
  public function create_tables() {
332
  _deprecated_function(
@@ -341,19 +350,26 @@ final class Code_Snippets {
341
  * Create a single snippet table
342
  * if one of the same name does not already exist
343
  *
344
- * @since 1.6
345
  * @access private
346
  *
347
- * @uses dbDelta() To add the table to the database
348
  *
349
- * @param string $table_name The name of the table to create
 
350
  * @return void
351
  */
352
- function create_table( $table_name ) {
353
- require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
354
 
355
  global $wpdb;
356
 
 
 
 
 
 
 
357
  $charset_collate = '';
358
 
359
  if ( ! empty( $wpdb->charset ) ) {
@@ -364,40 +380,43 @@ final class Code_Snippets {
364
  $charset_collate .= " COLLATE $wpdb->collate";
365
  }
366
 
367
- $table_columns = apply_filters( 'code_snippets_database_table_columns', array(
368
- 'name tinytext not null',
369
- 'description text',
370
- 'code longtext not null',
 
 
371
  ) );
372
 
373
- $table_columns_sql = implode( ",\n", $table_columns );
 
 
374
 
375
  $sql = "CREATE TABLE $table_name (
376
- id bigint(20) unsigned not null auto_increment,
377
  {$table_columns_sql},
378
- active tinyint(1) not null default 0,
379
  PRIMARY KEY (id),
380
  KEY id (id)
381
 
382
  ) {$charset_collate};";
383
 
384
- dbDelta( apply_filters( 'code_snippets_table_sql', $sql ) );
385
 
386
- do_action( 'code_snippets_create_table', $table_name );
387
  }
388
 
389
  /**
390
  * Preform upgrade tasks such as deleting and updating options
391
  *
392
- * @since 1.2
393
  * @access private
394
- *
395
  * @return void
396
  */
397
  function upgrade() {
398
  global $wpdb;
399
 
400
- // get the current plugin version from the database
401
 
402
  $current_version = get_option( 'code_snippets_version' );
403
 
@@ -409,20 +428,57 @@ final class Code_Snippets {
409
 
410
  $previous_version = ( $current_version ? $current_version : $this->version );
411
 
412
- /* skip this if we're on the latest version */
413
  if ( version_compare( $current_version, $this->version, '<' ) ) {
414
 
415
- // Register the capabilities once only
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  if ( version_compare( $current_version, '1.5', '<' ) ) {
417
  $this->setup_roles( true );
418
  }
419
 
420
  if ( version_compare( $previous_version, '1.2', '<' ) ) {
421
- // The 'Complete Uninstall' option was removed in version 1.2
422
  delete_option( 'cs_complete_uninstall' );
423
  }
424
 
425
- // Update the current version stored in the database
426
  update_option( 'code_snippets_version', $this->version );
427
  }
428
 
@@ -438,7 +494,7 @@ final class Code_Snippets {
438
  $this->setup_ms_roles( true );
439
  }
440
 
441
- // migrate the recently_network_activated_snippets to the site options
442
  if ( get_option( 'recently_network_activated_snippets' ) ) {
443
  add_site_option( 'recently_activated_snippets', get_option( 'recently_network_activated_snippets', array() ) );
444
  delete_option( 'recently_network_activated_snippets' );
@@ -454,23 +510,22 @@ final class Code_Snippets {
454
  /**
455
  * Register the user roles and capabilities
456
  *
457
- * @since 1.5
458
  * @access private
459
- *
460
- * @param bool $install true to add the capabilities, false to remove
461
  * @return void
462
  */
463
  function setup_roles( $install = true ) {
464
 
465
  $this->caps = apply_filters( 'code_snippets_caps', array(
466
- 'manage_snippets',
467
- 'install_snippets',
468
- 'edit_snippets'
469
- ) );
470
 
471
  $this->role = get_role( apply_filters( 'code_snippets_role', 'administrator' ) );
472
 
473
- foreach( $this->caps as $cap ) {
474
  if ( $install )
475
  $this->role->add_cap( $cap );
476
  else
@@ -481,26 +536,26 @@ final class Code_Snippets {
481
  /**
482
  * Register the multisite user roles and capabilities
483
  *
484
- * @since 1.5
485
  * @access private
486
- *
487
- * @param bool $install true to add the capabilities, false to remove
488
  * @return void
489
  */
490
  function setup_ms_roles( $install = true ) {
491
 
492
- if ( ! is_multisite() ) return;
 
493
 
494
  $this->network_caps = apply_filters( 'code_snippets_network_caps', array(
495
- 'manage_network_snippets',
496
- 'install_network_snippets',
497
- 'edit_network_snippets'
498
- ) );
499
 
500
  $supers = get_super_admins();
501
- foreach( $supers as $admin ) {
502
  $user = new WP_User( 0, $admin );
503
- foreach( $this->network_caps as $cap ) {
504
  if ( $install )
505
  $user->add_cap( $cap );
506
  else
@@ -513,12 +568,12 @@ final class Code_Snippets {
513
  * Check if the current user can perform some action on snippets or not
514
  *
515
  * @uses current_user_can() To check if the current user can perform a task
516
- * @uses $this->get_cap() To get the required capability
517
  *
518
- * @param string $do_what The task to check against.
519
- * @return bool Whether the current user can perform this task or not
520
  *
521
- * @since 1.7.1.1 Moved logic to $this->get_cap() method
522
  * @since 1.7.1
523
  * @access public
524
  */
@@ -535,12 +590,14 @@ final class Code_Snippets {
535
  *
536
  * @since 1.7.1.1
537
  * @access public
 
 
538
  */
539
  public function get_cap( $do_what ) {
540
 
541
  if ( is_multisite() ) {
542
 
543
- if ( in_array( 'snippets', get_site_option( 'menu_items' ) ) )
544
  return "{$do_what}_snippets";
545
  else
546
  return "{$do_what}_network_snippets";
@@ -553,23 +610,23 @@ final class Code_Snippets {
553
  /**
554
  * Converts an array of snippet data into a snippet object
555
  *
556
- * @since 1.7
557
  * @access public
558
  *
559
- * @param mixed $data The snippet data to convert
560
- * @return object The resulting snippet object
561
  */
562
  public function build_snippet_object( $data = null ) {
563
 
564
  $snippet = new stdClass;
565
 
566
- // define an empty snippet object (or one with default values )
567
- $snippet->id = 0;
568
- $snippet->name = '';
569
  $snippet->description = '';
570
- $snippet->code = '';
571
- $snippet->active = 0;
572
- $snippet = apply_filters( 'code_snippets_build_default_snippet', $snippet );
573
 
574
  if ( ! isset( $data ) ) {
575
  return $snippet;
@@ -606,7 +663,7 @@ final class Code_Snippets {
606
  elseif ( isset( $data['snippet_active'] ) )
607
  $snippet->active = $data['snippet_active'];
608
 
609
- return apply_filters( 'code_snippets_build_snippet_object', $snippet, $data );
610
  }
611
 
612
  return $snippet;
@@ -615,75 +672,75 @@ final class Code_Snippets {
615
  /**
616
  * Retrieve a list of snippets from the database
617
  *
618
- * @since 1.7
619
- * @access public
620
  *
621
- * @uses $wpdb To query the database for snippets
622
- * @uses $this->get_table_name() To dynamically retrieve the snippet table name
623
  *
624
- * @param string $scope Retrieve multisite-wide or site-wide snippets?
625
- * @return array An array of snippet objects
626
  */
627
- public function get_snippets( $scope = '' ) {
628
  global $wpdb;
629
 
630
- $table = $this->get_table_name( $scope );
631
  $snippets = $wpdb->get_results( "SELECT * FROM $table", ARRAY_A );
632
 
633
- foreach( $snippets as $index => $snippet ) {
634
  $snippets[ $index ] = $this->unescape_snippet_data( $snippet );
635
  }
636
 
637
- return apply_filters( 'code_snippets_get_snippets', $snippets, $scope );
638
  }
639
 
640
  /**
641
  * Escape snippet data for inserting into the database
642
  *
643
- * @since 1.7
644
  * @access public
645
  *
646
- * @param mixed $snippet An object or array containing the data to escape
647
- * @return object The resulting snippet object, with data escaped
648
  */
649
  public function escape_snippet_data( $snippet ) {
650
 
651
  $snippet = $this->build_snippet_object( $snippet );
652
 
653
  /* remove the <?php and ?> tags from the snippet */
654
- $snippet->code = trim ( $snippet->code );
655
  $snippet->code = ltrim( $snippet->code, '<?php' );
656
  $snippet->code = ltrim( $snippet->code, '<?' );
657
  $snippet->code = rtrim( $snippet->code, '?>' );
658
-
659
  /* escape the data */
660
- $snippet->name = mysql_real_escape_string( htmlspecialchars( $snippet->name ) );
661
- $snippet->description = mysql_real_escape_string( htmlspecialchars( $snippet->description ) );
662
- $snippet->code = mysql_real_escape_string( htmlspecialchars( $snippet->code ) );
663
- $snippet->id = intval( $snippet->id );
664
 
665
- return apply_filters( 'code_snippets_escape_snippet_data', $snippet );
666
  }
667
 
668
  /**
669
  * Unescape snippet data after retrieval from the database
670
  * ready for use
671
  *
672
- * @since 1.7
673
  * @access public
674
  *
675
- * @param mixed $snippet An object or array containing the data to unescape
676
- * @return object The resulting snippet object, with data unescaped
677
  */
678
  public function unescape_snippet_data( $snippet ) {
679
 
680
  $snippet = $this->build_snippet_object( $snippet );
681
 
682
- $snippet->name = htmlspecialchars_decode( stripslashes( $snippet->name ) );
683
- $snippet->code = htmlspecialchars_decode( stripslashes( $snippet->code ) );
684
- $snippet->description = htmlspecialchars_decode( stripslashes( $snippet->description ) );
685
 
686
- return apply_filters( 'code_snippets_unescape_snippet_data', $snippet );
687
  }
688
 
689
  /**
@@ -691,21 +748,22 @@ final class Code_Snippets {
691
  * Will return empty snippet object if no snippet
692
  * ID is specified
693
  *
694
- * @since 1.7
695
  * @access public
696
  *
697
- * @uses $wpdb To query the database for snippets
698
- * @uses $this->get_table_name() To dynamically retrieve the snippet table name
699
  *
700
- * @param int $id The ID of the snippet to retrieve. 0 to build a new snippet
701
- * @param string $scope Retrieve a multisite-wide or site-wide snippet?
702
- * @return object A single snippet object
703
  */
704
- public function get_snippet( $id = 0, $scope = '' ) {
705
  global $wpdb;
706
  $table = $this->get_table_name( $scope );
 
707
 
708
- if ( intval( $id ) > 0 ) {
709
  /* Retrieve the snippet from the database */
710
  $snippet = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE id = %d", $id ) );
711
  /* Unescape the snippet data, ready for use */
@@ -714,19 +772,19 @@ final class Code_Snippets {
714
  /* Get an empty snippet object */
715
  $snippet = $this->build_snippet_object();
716
  }
717
- return apply_filters( 'code_snippets_get_snippet', $snippet, $id, $scope );
718
  }
719
 
720
  /**
721
  * Activates a snippet
722
  *
723
- * @since 1.5
724
  * @access public
725
  *
726
- * @uses $wpdb To set the snippets' active status
727
  *
728
- * @param array $ids The ids of the snippets to activate
729
- * @param string $scope Are the snippets multisite-wide or site-wide?
730
  * @return void
731
  */
732
  public function activate( $ids, $scope = '' ) {
@@ -736,7 +794,7 @@ final class Code_Snippets {
736
 
737
  $table = $this->get_table_name( $scope );
738
 
739
- foreach( $ids as $id ) {
740
  $wpdb->update(
741
  $table,
742
  array( 'active' => '1' ),
@@ -745,22 +803,22 @@ final class Code_Snippets {
745
  array( '%d' )
746
  );
747
 
748
- do_action( 'code_snippets_activate_snippet', $id, $scope );
749
  }
750
 
751
- do_action( 'code_snippets_activate', $ids, $scope );
752
  }
753
 
754
  /**
755
  * Deactivates selected snippets
756
  *
757
- * @since 1.5
758
  * @access public
759
  *
760
- * @uses $wpdb To set the snippets' active status
761
  *
762
- * @param array $ids The IDs of the snippets to deactivate
763
- * @param string $scope Are the snippets multisite-wide or site-wide?
764
  * @return void
765
  */
766
  public function deactivate( $ids, $scope = '' ) {
@@ -771,7 +829,7 @@ final class Code_Snippets {
771
 
772
  $table = $this->get_table_name( $scope );
773
 
774
- foreach( $ids as $id ) {
775
  $wpdb->update(
776
  $table,
777
  array( 'active' => '0' ),
@@ -781,7 +839,7 @@ final class Code_Snippets {
781
  );
782
  $recently_active = array( $id => time() ) + (array) $recently_active;
783
 
784
- do_action( 'code_snippets_deactivate_snippet', $id, $scope );
785
  }
786
 
787
  if ( $table === $wpdb->ms_snippets )
@@ -795,59 +853,60 @@ final class Code_Snippets {
795
  $recently_active + (array) get_option( 'recently_activated_snippets' )
796
  );
797
 
798
- do_action( 'code_snippets_deactivate', $ids, $scope );
799
  }
800
 
801
  /**
802
  * Deletes a snippet from the database
803
  *
804
- * @since 1.5
805
  * @access public
806
  *
807
- * @uses $wpdb To access the database
808
- * @uses $this->get_table_name() To dynamically retrieve the name of the snippet table
809
  *
810
- * @param int $id The ID of the snippet to delete
811
- * @param string $scope Delete from site-wide or network-wide table?
812
  */
813
  public function delete_snippet( $id, $scope = '' ) {
814
  global $wpdb;
815
 
816
  $table = $this->get_table_name( $scope );
 
817
 
818
- $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE id='%d' LIMIT 1", intval( $id ) ) );
819
 
820
- do_action( 'code_snippets_delete_snippet', $id, $scope );
821
  }
822
 
823
  /**
824
  * Saves a snippet to the database.
825
  *
826
- * @since 1.5
827
  * @access public
828
  *
829
- * @uses $wpdb To update/add the snippet to the database
830
- * @uses $this->get_table_name() To dynamically retrieve the name of the snippet table
831
  *
832
- * @param object $snippet The snippet to add/update to the database
833
- * @param string $scope Save the snippet to the site-wide or network-wide table?
834
- * @return int|bool The ID of the snippet on success, false on failure
835
  */
836
  public function save_snippet( $snippet, $scope = '' ) {
837
  global $wpdb;
838
 
839
  $snippet = $this->escape_snippet_data( $snippet );
840
-
841
- if ( empty( $snippet->name ) or empty( $snippet->code ) )
842
- return false;
843
-
844
- $table = $this->get_table_name( $scope );
845
 
846
  $fields = '';
847
  foreach ( get_object_vars( $snippet ) as $field => $value ) {
848
- if ( 'id' !== $field ) {
849
- $fields .= "{$field}='{$value}',";
850
- }
 
 
 
 
851
  }
852
  $fields = rtrim( $fields, ',' );
853
 
@@ -857,7 +916,7 @@ final class Code_Snippets {
857
  $wpdb->query( $wpdb->prepare( "UPDATE $table SET $fields WHERE id='%d' LIMIT 1", $snippet->id ) );
858
  }
859
 
860
- do_action( 'code_snippets_update_snippet', $snippet, $table );
861
  return $snippet->id;
862
 
863
  } else {
@@ -866,7 +925,7 @@ final class Code_Snippets {
866
  $wpdb->query( "INSERT INTO $table SET $fields" );
867
  }
868
 
869
- do_action( 'code_snippets_create_snippet', $snippet, $table );
870
  return $wpdb->insert_id;
871
  }
872
  }
@@ -874,14 +933,14 @@ final class Code_Snippets {
874
  /**
875
  * Imports snippets from an XML file
876
  *
877
- * @since 1.5
878
  * @access public
879
  *
880
- * @uses $this->save_snippet() To add the snippets to the database
881
  *
882
- * @param file $file The path to the XML file to import
883
- * @param string $scope Import into network-wide table or site-wide table?
884
- * @return mixed The number of snippets imported on success, false on failure
885
  */
886
  public function import( $file, $scope = '' ) {
887
 
@@ -890,13 +949,16 @@ final class Code_Snippets {
890
 
891
  $xml = simplexml_load_file( $file );
892
 
 
 
 
893
  foreach ( $xml->children() as $snippet ) {
894
  /* force manual build of object to strip out unsupported fields
895
- by converting snippet object into array */
896
  $this->save_snippet( get_object_vars( $snippet ), $scope );
897
  }
898
 
899
- do_action( 'code_snippets_import', $xml, $scope );
900
 
901
  return $xml->count();
902
  }
@@ -904,14 +966,14 @@ final class Code_Snippets {
904
  /**
905
  * Exports snippets as an XML file
906
  *
907
- * @since 1.5
908
  * @access public
909
  *
910
- * @uses code_snippets_export() To export selected snippets
911
- * @uses $this->get_table_name() To dynamically retrieve the name of the snippet table
912
  *
913
- * @param array $ids The IDs of the snippets to export
914
- * @param string $scope Is the snippet a network-wide or site-wide snippet?
915
  * @return void
916
  */
917
  public function export( $ids, $scope = '' ) {
@@ -919,7 +981,7 @@ final class Code_Snippets {
919
  $table = $this->get_table_name( $scope );
920
 
921
  if ( ! function_exists( 'code_snippets_export' ) )
922
- require_once $this->plugin_dir . 'includes/export.php';
923
 
924
  code_snippets_export( $ids, 'xml', $table );
925
  }
@@ -927,14 +989,14 @@ final class Code_Snippets {
927
  /**
928
  * Exports snippets as a PHP file
929
  *
930
- * @since 1.5
931
  * @access public
932
  *
933
- * @uses code_snippets_export() To export selected snippets
934
- * @uses $this->get_table_name() To dynamically retrieve the name of the snippet table
935
  *
936
- * @param array $ids The IDs of the snippets to export
937
- * @param string $scope Is the snippet a network-wide or site-wide snippet?
938
  * @return void
939
  */
940
  public function export_php( $ids, $scope = '' ) {
@@ -942,7 +1004,7 @@ final class Code_Snippets {
942
  $table = $this->get_table_name( $scope );
943
 
944
  if ( ! function_exists( 'code_snippets_export' ) )
945
- require_once $this->plugin_dir . 'includes/export.php';
946
 
947
  code_snippets_export( $ids, 'php', $table );
948
  }
@@ -953,31 +1015,36 @@ final class Code_Snippets {
953
  * Code must NOT be escaped, as
954
  * it will be executed directly
955
  *
956
- * @since 1.5
957
  * @access public
958
  *
959
- * @param string $code The snippet code to execute
960
- * @return $result The result of the code execution
961
  */
962
  public function execute_snippet( $code ) {
 
 
 
 
963
  ob_start();
964
  $result = eval( $code );
965
  $output = ob_get_contents();
966
  ob_end_clean();
967
- do_action( 'code_snippets_execute_snippet', $code );
 
968
  return $result;
969
  }
970
 
971
  /**
972
  * Run the active snippets
973
  *
974
- * @since 1.0
975
  * @access public
976
  *
977
- * @uses $wpdb To grab the active snippets from the database
978
- * @uses $this->execute_snippet() To execute a snippet
979
  *
980
- * @return bool true on success, false on failure
981
  */
982
  public function run_snippets() {
983
 
@@ -1006,7 +1073,7 @@ final class Code_Snippets {
1006
  $active_snippets = $wpdb->get_col( $sql );
1007
 
1008
  if ( count( $active_snippets ) ) {
1009
- foreach( $active_snippets as $snippet ) {
1010
  /* Execute the PHP code */
1011
  $this->execute_snippet( htmlspecialchars_decode( stripslashes( $snippet ) ) );
1012
  }
@@ -1021,15 +1088,15 @@ final class Code_Snippets {
1021
  /**
1022
  * Cleans up data created by the Code_Snippets class
1023
  *
1024
- * @since 1.2
1025
  * @access private
1026
  *
1027
- * @uses $wpdb To remove tables from the database
1028
- * @uses $code_snippets->get_table_name() To find out which table to drop
1029
- * @uses is_multisite() To check the type of installation
1030
- * @uses switch_to_blog() To switch between blogs
1031
- * @uses restore_current_blog() To switch between blogs
1032
- * @uses delete_option() To remove site options
1033
  *
1034
  * @return void
1035
  */
@@ -1065,13 +1132,15 @@ final class Code_Snippets {
1065
  /**
1066
  * The global variable in which the Code_Snippets class is stored
1067
  *
1068
- * @since 1.0
 
1069
  * @access public
 
1070
  */
1071
  global $code_snippets;
1072
  $code_snippets = new Code_Snippets;
1073
 
1074
- /* set up a pointer in the old variable (for backwards-compatibility) */
1075
  global $cs;
1076
  $cs = &$code_snippets;
1077
 
6
  * If you're interested in helping to develop Code Snippets, or perhaps
7
  * contribute to the localization, please see http://code-snippets.bungeshea.com
8
  *
9
+ * @package Code_Snippets
10
+ * @version 1.8
11
+ * @author Shea Bunge <http://bungeshea.com/>
12
+ * @copyright Copyright (c) 2012-2013, Shea Bunge
13
+ * @link http://code-snippets.bungeshea.com
14
+ * @license http://opensource.org/licenses/MIT
15
  */
16
 
17
  /*
20
  * Description: An easy, clean and simple way to add code snippets to your site. No need to edit to your theme's functions.php file again!
21
  * Author: Shea Bunge
22
  * Author URI: http://bungeshea.com
23
+ * Version: 1.8
24
  * License: MIT
25
  * License URI: license.txt
26
  * Text Domain: code-snippets
27
  * Domain Path: /languages/
28
  */
29
 
30
+ /* Exit if accessed directly */
31
+ if ( ! defined( 'ABSPATH' ) )
32
+ exit;
33
 
34
  if ( ! class_exists( 'Code_Snippets' ) ) :
35
 
41
  * the methods or variables in this class. Anything you need
42
  * to access should be publicly available there
43
  *
44
+ * @since 1.0
45
+ * @package Code_Snippets
46
+ * @access private
47
  */
48
  final class Code_Snippets {
49
 
54
  * This should be set to the 'Plugin Version' value,
55
  * as defined above in the plugin header
56
  *
57
+ * @since 1.0
58
  * @access public
59
+ * @var string A PHP-standardized version number string
60
  */
61
+ public $version = '1.8';
62
 
63
  /**
64
  * Variables to hold plugin paths
65
  *
66
+ * @since 1.0
67
  * @access public
68
+ * @var string
69
  */
70
+ public $file, $basename, $plugin_dir, $plugin_url = '';
71
 
72
  /**
73
  * Stores an instance of the list table class
74
  *
75
+ * @var object
76
+ * @since 1.5
77
  * @access public
78
+ * @see Code_Snippets_List_Table
79
  */
80
  public $list_table;
81
 
82
  /**
83
  * Stores an instance of the administration class
84
  *
85
+ * @var object
86
+ * @since Code_Snippets 1.7.1
87
  * @access public
88
+ * @see Code_Snippets_Admin
89
  */
90
  public $admin;
91
 
92
  /**
93
  * Used by maybe_create_tables() for bailing early
94
+ *
95
+ * @var boolean
96
+ * @access protected
97
  */
98
  static $tables_created = false;
99
 
104
  * $wpdb->ms_snippets, but these are maintained
105
  * as references for backwards-compatibility
106
  *
107
+ * @var string
108
+ * @access public
109
  */
110
+ public $table, $ms_table = '';
111
 
112
  /**
113
  * These are now deprecated in favor of those in
114
  * the Code_Snippets_Admin class, but maintained as
115
  * references so we don't break existing code
116
  *
117
+ * @since 1.0
118
  * @deprecated Moved to the Code_Snippets_Admin class in 1.7.1
119
+ * @access public
120
+ * @var string
121
  */
122
+ public $admin_manage, $admin_single, $admin_import,
123
+ $admin_manage_url, $admin_single_url, $admin_import_url = '';
124
 
125
  /**
126
  * The constructor function for our class
129
  * so other plugins may not have loaded yet. Only do stuff
130
  * here that really can't wait
131
  *
132
+ * @since 1.0
133
  * @access private
 
134
  * @return void
135
  */
136
  function __construct() {
148
 
149
  /* Hook our initialize function to the plugins_loaded action */
150
  add_action( 'plugins_loaded', array( $this, 'init' ) );
151
+
152
  }
153
 
154
  /**
157
  * This method is called *after* other plugins
158
  * have been run
159
  *
160
+ * @since 1.7
161
  * @access public
 
162
  * @return void
163
  */
164
  public function init() {
178
  */
179
  load_plugin_textdomain( 'code-snippets', false, dirname( $this->basename ) . '/languages/' );
180
 
181
+ /* Cleanup the plugin data on uninstall */
 
 
182
  register_uninstall_hook( $this->file, array( __CLASS__, 'uninstall' ) );
183
 
184
+ /* Load the global functions file */
185
+ $this->get_include( 'functions' );
186
+
187
+ /* Let extension plugins know that it's okay to load */
188
  do_action( 'code_snippets_init' );
189
  }
190
 
191
  /**
192
  * Initialize variables
193
  *
194
+ * @since 1.2
195
  * @access private
 
196
  * @return void
197
  */
198
  function setup_vars() {
199
 
200
  /* Plugin directory variables */
201
  $this->file = __FILE__;
202
+ $this->basename = plugin_basename( $this->file );
203
  $this->plugin_dir = plugin_dir_path( $this->file );
204
  $this->plugin_url = plugin_dir_url ( $this->file );
205
 
206
  if ( is_admin() ) {
207
 
208
  /* Load our administration class */
209
+ $this->get_include( 'class-admin' );
210
  $this->admin = new Code_Snippets_Admin;
211
 
212
  /* Remap deprecated variables */
213
+ $this->admin_manage_url = &$this->admin->manage_url;
214
  $this->admin_single_url = &$this->admin->single_url;
215
  $this->admin_import_url = &$this->admin->import_url;
216
 
220
  }
221
  }
222
 
223
+ /**
224
+ * Require a PHP file from the includes directory
225
+ * @since 1.8
226
+ * @param string $slug The file slug (filename with no path or extension) to load
227
+ * @return void
228
+ */
229
+ public function get_include( $slug ) {
230
+ require_once $this->plugin_dir . "includes/{$slug}.php";
231
+ }
232
+
233
  /**
234
  * Register the snippet table names with WordPress
235
  *
236
+ * @since 1.7
237
  * @access public
238
+ * @uses $wpdb
 
 
239
  * @return void
240
  */
241
  public function set_table_vars() {
242
  global $wpdb;
243
 
244
  /* Register the snippet table names with WordPress */
245
+ $wpdb->tables[] = 'snippets';
246
  $wpdb->ms_global_tables[] = 'ms_snippets';
247
 
248
  /* Setup initial table variables */
249
+ $wpdb->snippets = $wpdb->prefix . 'snippets';
250
+ $wpdb->ms_snippets = $wpdb->base_prefix . 'ms_snippets';
251
 
252
  /* Add a pointer to the old variables */
253
+ $this->table = &$wpdb->snippets;
254
+ $this->ms_table = &$wpdb->ms_snippets;
255
  }
256
 
257
  /**
258
  * Return the appropriate snippet table name
259
  *
260
+ * @since 1.6
261
  * @access public
262
+ * @param string $scope Retrieve the multisite table name or the site table name?
263
+ * @param boolean $check_screen Query the current screen if no scope passed?
264
+ * @return string The snippet table name
 
265
  */
266
  public function get_table_name( $scope = '', $check_screen = true ) {
 
267
  global $wpdb;
268
 
269
+ /* If multisite is not active, always return the site-wide table name */
 
270
  if ( ! is_multisite() ) {
271
  $network = false;
272
  }
273
+
274
+ /* If the scope is 'multisite' or 'network', return the network-wide table name */
275
  elseif ( in_array( $scope, array( 'multisite', 'network' ) ) ) {
276
  $network = true;
277
  }
278
+
279
+ /* If no scope is set, query the current screen to see if in network admin */
280
  elseif ( empty( $scope ) && $check_screen && function_exists( 'get_current_screen' ) ) {
 
281
  $network = get_current_screen()->is_network;
282
  }
283
+
284
+ /* If none of the above conditions match, just use the site-wide table name */
285
  else {
286
  $network = false;
287
  }
288
 
289
+ /* Retrieve the table name from $wpdb depending on the above conditionals */
290
+ return ( $network ? $wpdb->ms_snippets : $wpdb->snippets );
 
291
  }
292
 
293
  /**
294
  * Create the snippet tables if they do not already exist
295
  *
296
+ * @since 1.7.1
297
  * @access public
298
  *
299
+ * @uses $this->create_table() To create a single snippet table
300
+ * @staticvar boolean $tables_created Used to check if we've already done this or not
301
+ * @param boolean $redo Skip the already-done-this check
302
+ * @param boolean $always_create_table Always create the site-wide table if it doesn't exist
303
+ * @return void
 
 
304
  */
305
+ public function maybe_create_tables( $redo = false, $always_create_table = false ) {
306
 
307
  /* Bail early if we've done this already */
308
+ if ( ! $redo && true === self::$tables_created )
309
+ return;
310
 
311
  global $wpdb;
312
 
313
+ /* Set the table name variables if not yet defined */
314
+ if ( ! isset( $wpdb->snippets, $wpdb->ms_snippets ) ) {
315
  $this->set_table_vars();
 
 
 
 
 
316
  }
317
+
318
+ /* Always create the network-wide snippet table */
319
+ if ( is_multisite() ) {
320
+ $this->create_table( $wpdb->ms_snippets );
 
 
 
 
 
321
  }
322
 
323
+ /* Create the site-specific table if we're on the main site */
324
+ if ( $always_create_table || is_main_site() ) {
325
+ $this->create_table( $wpdb->snippets );
326
+ }
327
 
328
+ /* Set the flag so we don't have to do this again */
329
  self::$tables_created = true;
330
  }
331
 
332
  /**
333
  * Create the snippet tables if they do not already exist
334
  *
335
+ * @since Code Snippets 1.2
336
  * @deprecated Code Snippets 1.7.1
337
+ * @access public
338
+ * @return void
 
339
  */
340
  public function create_tables() {
341
  _deprecated_function(
350
  * Create a single snippet table
351
  * if one of the same name does not already exist
352
  *
353
+ * @since 1.6
354
  * @access private
355
  *
356
+ * @uses dbDelta() To add the table to the database
357
  *
358
+ * @param string $table_name The name of the table to create
359
+ * @param boolean $force_creation Skip the table exists check
360
  * @return void
361
  */
362
+ function create_table( $table_name, $force_creation = false ) {
363
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
364
 
365
  global $wpdb;
366
 
367
+ if ( ! $force_creation && $wpdb->get_var( "SHOW TABLES LIKE '$wpdb->snippets'" ) === $wpdb->snippets ) {
368
+ return; // bail if the table already exists
369
+ }
370
+
371
+ /* Set the database charset */
372
+
373
  $charset_collate = '';
374
 
375
  if ( ! empty( $wpdb->charset ) ) {
380
  $charset_collate .= " COLLATE $wpdb->collate";
381
  }
382
 
383
+ /* Set the snippet data columns */
384
+
385
+ $table_columns = apply_filters( 'code_snippets/database_table_columns', array(
386
+ 'name tinytext not null',
387
+ 'description text',
388
+ 'code longtext not null',
389
  ) );
390
 
391
+ $table_columns_sql = implode( ",\n", $table_columns ); // convert the array into SQL code
392
+
393
+ /* Create the database table */
394
 
395
  $sql = "CREATE TABLE $table_name (
396
+ id bigint(20) unsigned not null auto_increment,
397
  {$table_columns_sql},
398
+ active tinyint(1) not null default 0,
399
  PRIMARY KEY (id),
400
  KEY id (id)
401
 
402
  ) {$charset_collate};";
403
 
404
+ dbDelta( apply_filters( 'code_snippets/table_sql', $sql ) );
405
 
406
+ do_action( 'code_snippets/create_table', $table_name );
407
  }
408
 
409
  /**
410
  * Preform upgrade tasks such as deleting and updating options
411
  *
412
+ * @since 1.2
413
  * @access private
 
414
  * @return void
415
  */
416
  function upgrade() {
417
  global $wpdb;
418
 
419
+ /* Get the current plugin version from the database */
420
 
421
  $current_version = get_option( 'code_snippets_version' );
422
 
428
 
429
  $previous_version = ( $current_version ? $current_version : $this->version );
430
 
431
+ /* Skip this if we're on the latest version */
432
  if ( version_compare( $current_version, $this->version, '<' ) ) {
433
 
434
+ /* Data in database is unsecapped in 1.8 */
435
+ if ( version_compare( $current_version, '1.8', '<' ) ) {
436
+
437
+ $tables = array();
438
+
439
+ if ( $wpdb->get_var( "SHOW TABLES LIKE '$wpdb->snippets'" ) === $wpdb->snippets ) {
440
+ $tables[] = $wpdb->snippets;
441
+ }
442
+
443
+ if ( is_multisite() && is_main_site() && $wpdb->get_var( "SHOW TABLES LIKE '$wpdb->ms_snippets'" ) === $wpdb->ms_snippets ) {
444
+ $tables[] = $wpdb->ms_snippets;
445
+ }
446
+
447
+ foreach ( $tables as $table ) {
448
+ $snippets = $wpdb->get_results( "SELECT * FROM $table" );
449
+
450
+ foreach ( $snippets as $snippet ) {
451
+
452
+ $snippet->name = esc_sql( htmlspecialchars_decode( stripslashes( $snippet->name ) ) );
453
+ $snippet->code = esc_sql( htmlspecialchars_decode( stripslashes( $snippet->code ) ) );
454
+ $snippet->description = esc_sql( htmlspecialchars_decode( stripslashes( $snippet->description ) ) );
455
+
456
+ $wpdb->update( $table,
457
+ array(
458
+ 'name' => $snippet->name,
459
+ 'code' => $snippet->code,
460
+ 'description' => $snippet->description
461
+ ),
462
+ array( 'id' => $snippet->id ),
463
+ array( '%s' ),
464
+ array( '%d' )
465
+ );
466
+ }
467
+ } // end $table foreach
468
+
469
+ } // end < 1.8 version check
470
+
471
+ /* Register the capabilities once only */
472
  if ( version_compare( $current_version, '1.5', '<' ) ) {
473
  $this->setup_roles( true );
474
  }
475
 
476
  if ( version_compare( $previous_version, '1.2', '<' ) ) {
477
+ /* The 'Complete Uninstall' option was removed in version 1.2 */
478
  delete_option( 'cs_complete_uninstall' );
479
  }
480
 
481
+ /* Update the current version stored in the database */
482
  update_option( 'code_snippets_version', $this->version );
483
  }
484
 
494
  $this->setup_ms_roles( true );
495
  }
496
 
497
+ /* Migrate recently_network_activated_snippets to the site options */
498
  if ( get_option( 'recently_network_activated_snippets' ) ) {
499
  add_site_option( 'recently_activated_snippets', get_option( 'recently_network_activated_snippets', array() ) );
500
  delete_option( 'recently_network_activated_snippets' );
510
  /**
511
  * Register the user roles and capabilities
512
  *
513
+ * @since 1.5
514
  * @access private
515
+ * @param boolean $install true to add the capabilities, false to remove
 
516
  * @return void
517
  */
518
  function setup_roles( $install = true ) {
519
 
520
  $this->caps = apply_filters( 'code_snippets_caps', array(
521
+ 'manage_snippets',
522
+ 'install_snippets',
523
+ 'edit_snippets'
524
+ ) );
525
 
526
  $this->role = get_role( apply_filters( 'code_snippets_role', 'administrator' ) );
527
 
528
+ foreach ( $this->caps as $cap ) {
529
  if ( $install )
530
  $this->role->add_cap( $cap );
531
  else
536
  /**
537
  * Register the multisite user roles and capabilities
538
  *
539
+ * @since 1.5
540
  * @access private
541
+ * @param boolean $install true to add the capabilities, false to remove
 
542
  * @return void
543
  */
544
  function setup_ms_roles( $install = true ) {
545
 
546
+ if ( ! is_multisite() )
547
+ return;
548
 
549
  $this->network_caps = apply_filters( 'code_snippets_network_caps', array(
550
+ 'manage_network_snippets',
551
+ 'install_network_snippets',
552
+ 'edit_network_snippets'
553
+ ) );
554
 
555
  $supers = get_super_admins();
556
+ foreach ( $supers as $admin ) {
557
  $user = new WP_User( 0, $admin );
558
+ foreach ( $this->network_caps as $cap ) {
559
  if ( $install )
560
  $user->add_cap( $cap );
561
  else
568
  * Check if the current user can perform some action on snippets or not
569
  *
570
  * @uses current_user_can() To check if the current user can perform a task
571
+ * @uses $this->get_cap() To get the required capability
572
  *
573
+ * @param string $do_what The task to check against.
574
+ * @return boolean Whether the current user can perform this task or not
575
  *
576
+ * @since 1.7.1.1 Moved logic to $this->get_cap() method
577
  * @since 1.7.1
578
  * @access public
579
  */
590
  *
591
  * @since 1.7.1.1
592
  * @access public
593
+ * @param string $do_what The action to retrieve the capability for
594
+ * @return void
595
  */
596
  public function get_cap( $do_what ) {
597
 
598
  if ( is_multisite() ) {
599
 
600
+ if ( in_array( 'snippets', get_site_option( 'menu_items', array() ) ) )
601
  return "{$do_what}_snippets";
602
  else
603
  return "{$do_what}_network_snippets";
610
  /**
611
  * Converts an array of snippet data into a snippet object
612
  *
613
+ * @since 1.7
614
  * @access public
615
  *
616
+ * @param mixed $data The snippet data to convert
617
+ * @return object The resulting snippet object
618
  */
619
  public function build_snippet_object( $data = null ) {
620
 
621
  $snippet = new stdClass;
622
 
623
+ /* Define an empty snippet object (or one with default values ) */
624
+ $snippet->id = 0;
625
+ $snippet->name = '';
626
  $snippet->description = '';
627
+ $snippet->code = '';
628
+ $snippet->active = 0;
629
+ $snippet = apply_filters( 'code_snippets/build_default_snippet', $snippet );
630
 
631
  if ( ! isset( $data ) ) {
632
  return $snippet;
663
  elseif ( isset( $data['snippet_active'] ) )
664
  $snippet->active = $data['snippet_active'];
665
 
666
+ return apply_filters( 'code_snippets/build_snippet_object', $snippet, $data );
667
  }
668
 
669
  return $snippet;
672
  /**
673
  * Retrieve a list of snippets from the database
674
  *
675
+ * @since 1.7
676
+ * @access public
677
  *
678
+ * @uses $wpdb To query the database for snippets
679
+ * @uses $this->get_table_name() To dynamically retrieve the snippet table name
680
  *
681
+ * @param string $scope Retrieve multisite-wide or site-wide snippets?
682
+ * @return array An array of snippet objects
683
  */
684
+ public function get_snippets( $scope = '' ) {
685
  global $wpdb;
686
 
687
+ $table = $this->get_table_name( $scope );
688
  $snippets = $wpdb->get_results( "SELECT * FROM $table", ARRAY_A );
689
 
690
+ foreach ( $snippets as $index => $snippet ) {
691
  $snippets[ $index ] = $this->unescape_snippet_data( $snippet );
692
  }
693
 
694
+ return apply_filters( 'code_snippets/get_snippets', $snippets, $scope );
695
  }
696
 
697
  /**
698
  * Escape snippet data for inserting into the database
699
  *
700
+ * @since 1.7
701
  * @access public
702
  *
703
+ * @param mixed $snippet An object or array containing the data to escape
704
+ * @return object The resulting snippet object, with data escaped
705
  */
706
  public function escape_snippet_data( $snippet ) {
707
 
708
  $snippet = $this->build_snippet_object( $snippet );
709
 
710
  /* remove the <?php and ?> tags from the snippet */
711
+ $snippet->code = trim( $snippet->code );
712
  $snippet->code = ltrim( $snippet->code, '<?php' );
713
  $snippet->code = ltrim( $snippet->code, '<?' );
714
  $snippet->code = rtrim( $snippet->code, '?>' );
715
+
716
  /* escape the data */
717
+ $snippet->name = esc_sql( $snippet->name );
718
+ $snippet->description = esc_sql( $snippet->description );
719
+ $snippet->code = esc_sql( $snippet->code );
720
+ $snippet->id = absint ( $snippet->id );
721
 
722
+ return apply_filters( 'code_snippets/escape_snippet_data', $snippet );
723
  }
724
 
725
  /**
726
  * Unescape snippet data after retrieval from the database
727
  * ready for use
728
  *
729
+ * @since 1.7
730
  * @access public
731
  *
732
+ * @param mixed $snippet An object or array containing the data to unescape
733
+ * @return object The resulting snippet object, with data unescaped
734
  */
735
  public function unescape_snippet_data( $snippet ) {
736
 
737
  $snippet = $this->build_snippet_object( $snippet );
738
 
739
+ $snippet->name = stripslashes( $snippet->name );
740
+ $snippet->code = stripslashes( $snippet->code );
741
+ $snippet->description = stripslashes( $snippet->description );
742
 
743
+ return apply_filters( 'code_snippets/unescape_snippet_data', $snippet );
744
  }
745
 
746
  /**
748
  * Will return empty snippet object if no snippet
749
  * ID is specified
750
  *
751
+ * @since 1.7
752
  * @access public
753
  *
754
+ * @uses $wpdb To query the database for snippets
755
+ * @uses $this->get_table_name() To dynamically retrieve the snippet table name
756
  *
757
+ * @param int $id The ID of the snippet to retrieve. 0 to build a new snippet
758
+ * @param string $scope Retrieve a multisite-wide or site-wide snippet?
759
+ * @return object A single snippet object
760
  */
761
+ public function get_snippet( $id = 0, $scope = '' ) {
762
  global $wpdb;
763
  $table = $this->get_table_name( $scope );
764
+ $id = absint( $id );
765
 
766
+ if ( 0 !== $id ) {
767
  /* Retrieve the snippet from the database */
768
  $snippet = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE id = %d", $id ) );
769
  /* Unescape the snippet data, ready for use */
772
  /* Get an empty snippet object */
773
  $snippet = $this->build_snippet_object();
774
  }
775
+ return apply_filters( 'code_snippets/get_snippet', $snippet, $id, $scope );
776
  }
777
 
778
  /**
779
  * Activates a snippet
780
  *
781
+ * @since 1.5
782
  * @access public
783
  *
784
+ * @uses $wpdb To set the snippets' active status
785
  *
786
+ * @param array $ids The ids of the snippets to activate
787
+ * @param string $scope Are the snippets multisite-wide or site-wide?
788
  * @return void
789
  */
790
  public function activate( $ids, $scope = '' ) {
794
 
795
  $table = $this->get_table_name( $scope );
796
 
797
+ foreach ( $ids as $id ) {
798
  $wpdb->update(
799
  $table,
800
  array( 'active' => '1' ),
803
  array( '%d' )
804
  );
805
 
806
+ do_action( 'code_snippets/activate_snippet', $id, $scope );
807
  }
808
 
809
+ do_action( 'code_snippets/activate', $ids, $scope );
810
  }
811
 
812
  /**
813
  * Deactivates selected snippets
814
  *
815
+ * @since 1.5
816
  * @access public
817
  *
818
+ * @uses $wpdb To set the snippets' active status
819
  *
820
+ * @param array $ids The IDs of the snippets to deactivate
821
+ * @param string $scope Are the snippets multisite-wide or site-wide?
822
  * @return void
823
  */
824
  public function deactivate( $ids, $scope = '' ) {
829
 
830
  $table = $this->get_table_name( $scope );
831
 
832
+ foreach ( $ids as $id ) {
833
  $wpdb->update(
834
  $table,
835
  array( 'active' => '0' ),
839
  );
840
  $recently_active = array( $id => time() ) + (array) $recently_active;
841
 
842
+ do_action( 'code_snippets/deactivate_snippet', $id, $scope );
843
  }
844
 
845
  if ( $table === $wpdb->ms_snippets )
853
  $recently_active + (array) get_option( 'recently_activated_snippets' )
854
  );
855
 
856
+ do_action( 'code_snippets/deactivate', $ids, $scope );
857
  }
858
 
859
  /**
860
  * Deletes a snippet from the database
861
  *
862
+ * @since 1.5
863
  * @access public
864
  *
865
+ * @uses $wpdb To access the database
866
+ * @uses $this->get_table_name() To dynamically retrieve the name of the snippet table
867
  *
868
+ * @param int $id The ID of the snippet to delete
869
+ * @param string $scope Delete from site-wide or network-wide table?
870
  */
871
  public function delete_snippet( $id, $scope = '' ) {
872
  global $wpdb;
873
 
874
  $table = $this->get_table_name( $scope );
875
+ $id = absint( $id );
876
 
877
+ $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE id='%d' LIMIT 1", $id ) );
878
 
879
+ do_action( 'code_snippets/delete_snippet', $id, $scope );
880
  }
881
 
882
  /**
883
  * Saves a snippet to the database.
884
  *
885
+ * @since 1.5
886
  * @access public
887
  *
888
+ * @uses $wpdb To update/add the snippet to the database
889
+ * @uses $this->get_table_name() To dynamically retrieve the name of the snippet table
890
  *
891
+ * @param object $snippet The snippet to add/update to the database
892
+ * @param string $scope Save the snippet to the site-wide or network-wide table?
893
+ * @return int|boolean The ID of the snippet on success, false on failure
894
  */
895
  public function save_snippet( $snippet, $scope = '' ) {
896
  global $wpdb;
897
 
898
  $snippet = $this->escape_snippet_data( $snippet );
899
+ $table = $this->get_table_name( $scope );
 
 
 
 
900
 
901
  $fields = '';
902
  foreach ( get_object_vars( $snippet ) as $field => $value ) {
903
+ if ( 'id' === $field )
904
+ continue;
905
+
906
+ if ( is_array( $value ) )
907
+ $value = maybe_serialize( $value );
908
+
909
+ $fields .= "{$field}='{$value}',";
910
  }
911
  $fields = rtrim( $fields, ',' );
912
 
916
  $wpdb->query( $wpdb->prepare( "UPDATE $table SET $fields WHERE id='%d' LIMIT 1", $snippet->id ) );
917
  }
918
 
919
+ do_action( 'code_snippets/update_snippet', $snippet, $table );
920
  return $snippet->id;
921
 
922
  } else {
925
  $wpdb->query( "INSERT INTO $table SET $fields" );
926
  }
927
 
928
+ do_action( 'code_snippets/create_snippet', $snippet, $table );
929
  return $wpdb->insert_id;
930
  }
931
  }
933
  /**
934
  * Imports snippets from an XML file
935
  *
936
+ * @since 1.5
937
  * @access public
938
  *
939
+ * @uses $this->save_snippet() To add the snippets to the database
940
  *
941
+ * @param string $file The path to the XML file to import
942
+ * @param string $scope Import into network-wide table or site-wide table?
943
+ * @return integer|boolean The number of snippets imported on success, false on failure
944
  */
945
  public function import( $file, $scope = '' ) {
946
 
949
 
950
  $xml = simplexml_load_file( $file );
951
 
952
+ if ( ! is_object( $xml ) || ! method_exists( $xml, 'children' ) )
953
+ return false;
954
+
955
  foreach ( $xml->children() as $snippet ) {
956
  /* force manual build of object to strip out unsupported fields
957
+ by converting snippet object into an array */
958
  $this->save_snippet( get_object_vars( $snippet ), $scope );
959
  }
960
 
961
+ do_action( 'code_snippets/import', $xml, $scope );
962
 
963
  return $xml->count();
964
  }
966
  /**
967
  * Exports snippets as an XML file
968
  *
969
+ * @since 1.5
970
  * @access public
971
  *
972
+ * @uses code_snippets_export() To export selected snippets
973
+ * @uses $this->get_table_name() To dynamically retrieve the name of the snippet table
974
  *
975
+ * @param array $ids The IDs of the snippets to export
976
+ * @param string $scope Is the snippet a network-wide or site-wide snippet?
977
  * @return void
978
  */
979
  public function export( $ids, $scope = '' ) {
981
  $table = $this->get_table_name( $scope );
982
 
983
  if ( ! function_exists( 'code_snippets_export' ) )
984
+ $this->get_include( 'export' );
985
 
986
  code_snippets_export( $ids, 'xml', $table );
987
  }
989
  /**
990
  * Exports snippets as a PHP file
991
  *
992
+ * @since 1.5
993
  * @access public
994
  *
995
+ * @uses code_snippets_export() To export selected snippets
996
+ * @uses $this->get_table_name() To dynamically retrieve the name of the snippet table
997
  *
998
+ * @param array $ids The IDs of the snippets to export
999
+ * @param string $scope Is the snippet a network-wide or site-wide snippet?
1000
  * @return void
1001
  */
1002
  public function export_php( $ids, $scope = '' ) {
1004
  $table = $this->get_table_name( $scope );
1005
 
1006
  if ( ! function_exists( 'code_snippets_export' ) )
1007
+ $this->get_include( 'export' );
1008
 
1009
  code_snippets_export( $ids, 'php', $table );
1010
  }
1015
  * Code must NOT be escaped, as
1016
  * it will be executed directly
1017
  *
1018
+ * @since 1.5
1019
  * @access public
1020
  *
1021
+ * @param string $code The snippet code to execute
1022
+ * @return mixed The result of the code execution
1023
  */
1024
  public function execute_snippet( $code ) {
1025
+
1026
+ if ( empty( $code ) )
1027
+ return;
1028
+
1029
  ob_start();
1030
  $result = eval( $code );
1031
  $output = ob_get_contents();
1032
  ob_end_clean();
1033
+
1034
+ do_action( 'code_snippets/execute_snippet', $code );
1035
  return $result;
1036
  }
1037
 
1038
  /**
1039
  * Run the active snippets
1040
  *
1041
+ * @since 1.0
1042
  * @access public
1043
  *
1044
+ * @uses $wpdb To grab the active snippets from the database
1045
+ * @uses $this->execute_snippet() To execute a snippet
1046
  *
1047
+ * @return boolean true on success, false on failure
1048
  */
1049
  public function run_snippets() {
1050
 
1073
  $active_snippets = $wpdb->get_col( $sql );
1074
 
1075
  if ( count( $active_snippets ) ) {
1076
+ foreach ( $active_snippets as $snippet ) {
1077
  /* Execute the PHP code */
1078
  $this->execute_snippet( htmlspecialchars_decode( stripslashes( $snippet ) ) );
1079
  }
1088
  /**
1089
  * Cleans up data created by the Code_Snippets class
1090
  *
1091
+ * @since 1.2
1092
  * @access private
1093
  *
1094
+ * @uses $wpdb To remove tables from the database
1095
+ * @uses $code_snippets->get_table_name() To find out which table to drop
1096
+ * @uses is_multisite() To check the type of installation
1097
+ * @uses switch_to_blog() To switch between blogs
1098
+ * @uses restore_current_blog() To switch between blogs
1099
+ * @uses delete_option() To remove site options
1100
  *
1101
  * @return void
1102
  */
1132
  /**
1133
  * The global variable in which the Code_Snippets class is stored
1134
  *
1135
+ * @var object
1136
+ * @since 1.0
1137
  * @access public
1138
+ * @see Code_Snippets
1139
  */
1140
  global $code_snippets;
1141
  $code_snippets = new Code_Snippets;
1142
 
1143
+ /* Set up a pointer in the old variable (for backwards-compatibility) */
1144
  global $cs;
1145
  $cs = &$code_snippets;
1146
 
includes/admin/import.php DELETED
@@ -1,61 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * HTML code for the Import Snippets page
5
- *
6
- * @package Code Snippets
7
- * @subpackage Administration
8
- */
9
-
10
- if ( ! class_exists( 'Code_Snippets' ) ) exit;
11
-
12
- global $code_snippets;
13
-
14
- /* Display the admin notice */
15
-
16
- if ( isset( $_REQUEST['imported'] ) && 0 !== intval( $_REQUEST['imported'] ) ) {
17
-
18
- echo '<div id="message" class="updated fade"><p>';
19
-
20
- printf(
21
- _n(
22
- 'Imported <strong>%d</strong> snippet.',
23
- 'Imported <strong>%d</strong> snippets.',
24
- $_REQUEST['imported'],
25
- 'code-snippets'
26
- ),
27
- $_REQUEST['imported']
28
- );
29
-
30
- echo '</p></div>';
31
- }
32
-
33
- ?>
34
- <div class="wrap">
35
- <?php screen_icon(); ?>
36
- <h2><?php _e('Import Snippets', 'code-snippets'); ?></h2>
37
-
38
- <div class="narrow">
39
-
40
- <p><?php _e('Howdy! Upload your Code Snippets export file and we&#8217;ll import the snippets to this site.', 'code-snippets' ); ?></p>
41
-
42
- <p><?php printf( __('You will need to go to the <a href="%s">Manage Snippets</a> page to activate the imported snippets.', 'code-snippets'), $code_snippets->admin->manage_url ); ?></p>
43
-
44
- <p><?php _e('Choose a Code Snippets (.xml) file to upload, then click Upload file and import.', 'code-snippets'); ?></p>
45
-
46
- <form enctype="multipart/form-data" id="import-upload-form" method="post" action="" name="code_snippets_import">
47
- <p>
48
- <label for="upload"><?php _e('Choose a file from your computer:', 'code-snippets' ); ?></label> <?php _e('(Maximum size: 8MB)', 'code-snippets'); ?>
49
- <input type="file" id="upload" name="code_snippets_import_file" size="25" accept="text/xml" />
50
- <input type="hidden" name="action" value="save" />
51
- <input type="hidden" name="max_file_size" value="8388608" />
52
- </p>
53
-
54
- <?php do_action( 'code_snippets_admin_import_form' ); ?>
55
-
56
- <p class="submit">
57
- <input type="submit" name="submit" id="submit" class="button" value="<?php _e('Upload file and import', 'code-snippets'); ?>" />
58
- </p>
59
- </form>
60
- </div>
61
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/admin/manage.php DELETED
@@ -1,56 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * HTML code for the Manage Snippets page
5
- *
6
- * @package Code Snippets
7
- * @subpackage Administration
8
- */
9
-
10
- if ( ! class_exists( 'Code_Snippets' ) ) exit;
11
-
12
- global $code_snippets;
13
- $screen = get_current_screen();
14
- ?>
15
- <?php if ( defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) : ?>
16
- <div class="error"><p><strong>Warning:</strong> Safe mode is active and snippets will not execute! Remove the <code>CODE_SNIPPETS_SAFE_MODE</code> constant from <code>wp-config.php</code> to turn off safe mode. <a href="http://code-snippets.bungeshea.com/docs/safe-mode/" target="_blank">Help</a></p></div>
17
- <?php endif; ?>
18
-
19
- <?php if ( isset($_GET['activate']) ) : ?>
20
- <div id="message" class="updated"><p><?php _e('Snippet <strong>activated</strong>.', 'code-snippets') ?></p></div>
21
- <?php elseif (isset($_GET['activate-multi'])) : ?>
22
- <div id="message" class="updated"><p><?php _e('Selected snippets <strong>activated</strong>.', 'code-snippets'); ?></p></div>
23
- <?php elseif ( isset($_GET['deactivate']) ) : ?>
24
- <div id="message" class="updated"><p><?php _e('Snippet <strong>deactivated</strong>.', 'code-snippets') ?></p></div>
25
- <?php elseif (isset($_GET['deactivate-multi'])) : ?>
26
- <div id="message" class="updated"><p><?php _e('Selected snippets <strong>deactivated</strong>.', 'code-snippets'); ?></p></div>
27
- <?php elseif ( isset($_GET['delete']) ) : ?>
28
- <div id="message" class="updated"><p><?php _e('Snippet <strong>deleted</strong>.', 'code-snippets') ?></p></div>
29
- <?php elseif (isset($_GET['delete-multi'])) : ?>
30
- <div id="message" class="updated"><p><?php _e('Selected snippets <strong>deleted</strong>.', 'code-snippets'); ?></p></div>
31
- <?php endif; ?>
32
-
33
- <div class="wrap">
34
- <?php screen_icon(); ?>
35
- <h2><?php esc_html_e('Snippets', 'code-snippets'); ?>
36
- <?php if ( $code_snippets->user_can( 'install' ) ) { ?>
37
- <a href="<?php echo $code_snippets->admin->single_url; ?>" class="add-new-h2"><?php echo esc_html_x('Add New', 'snippet', 'code-snippets'); ?></a>
38
- <?php }
39
- $code_snippets->list_table->search_notice(); ?></h2>
40
-
41
- <?php $code_snippets->list_table->views(); ?>
42
-
43
- <form method="get" action="">
44
- <?php
45
- $code_snippets->list_table->required_form_fields( 'search_box' );
46
- $code_snippets->list_table->search_box( __( 'Search Installed Snippets', 'code-snippets' ), 'search_id' );
47
- ?>
48
- </form>
49
- <form method="post" action="">
50
- <?php $code_snippets->list_table->required_form_fields(); ?>
51
- <?php $code_snippets->list_table->display(); ?>
52
- </form>
53
-
54
- <?php do_action( 'code_snippets_admin_manage' ); ?>
55
-
56
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/admin/single.php DELETED
@@ -1,99 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * HTML code for the Add New/Edit Snippet page
5
- *
6
- * @package Code Snippets
7
- * @subpackage Administration
8
- */
9
-
10
- if ( ! class_exists( 'Code_Snippets' ) ) exit;
11
-
12
- global $code_snippets;
13
-
14
- $table = $code_snippets->get_table_name();
15
- $screen = get_current_screen();
16
-
17
- $edit_id = ( isset( $_REQUEST['edit'] ) ? intval( $_REQUEST['edit'] ) : 0 );
18
- $snippet = $code_snippets->get_snippet( $edit_id );
19
-
20
- ?>
21
-
22
- <?php if ( isset( $_REQUEST['invalid'] ) && $_REQUEST['invalid'] ) : ?>
23
- <div id="message" class="error fade"><p><?php _e('Please provide a name for the snippet and its code.', 'code-snippets'); ?></p></div>
24
- <?php elseif ( isset( $_REQUEST['activated'], $_REQUEST['updated'] ) && $_REQUEST['activated'] && $_REQUEST['updated'] ) : ?>
25
- <div id="message" class="updated fade"><p><?php _e('Snippet <strong>updated</strong> and <strong>activated</strong>.', 'code-snippets'); ?></p></div>
26
- <?php elseif ( isset( $_REQUEST['activated'], $_REQUEST['added'] ) && $_REQUEST['activated'] && $_REQUEST['added'] ) : ?>
27
- <div id="message" class="updated fade"><p><?php _e('Snippet <strong>added</strong> and <strong>activated</strong>.', 'code-snippets'); ?></p></div>
28
- <?php elseif ( isset( $_REQUEST['updated'] ) && $_REQUEST['updated'] ) : ?>
29
- <div id="message" class="updated fade"><p><?php _e('Snippet <strong>updated</strong>.', 'code-snippets'); ?></p></div>
30
- <?php elseif ( isset( $_REQUEST['added'] ) && $_REQUEST['added'] ) : ?>
31
- <div id="message" class="updated fade"><p><?php _e('Snippet <strong>added</strong>.', 'code-snippets'); ?></p></div>
32
- <?php endif; ?>
33
-
34
- <div class="wrap">
35
- <?php screen_icon(); ?>
36
- <h2><?php
37
- if ( $edit_id ) {
38
- esc_html_e('Edit Snippet', 'code-snippets');
39
-
40
- if ( $code_snippets->user_can( 'install' ) )
41
- printf( ' <a href="%1$s" class="add-new-h2">%2$s</a>',
42
- $code_snippets->admin->single_url,
43
- esc_html_x('Add New', 'snippet', 'code-snippets')
44
- );
45
- } else {
46
- esc_html_e('Add New Snippet', 'code-snippets');
47
- }
48
- ?></h2>
49
-
50
- <form method="post" action="" style="margin-top: 10px;">
51
- <?php
52
-
53
- /* Output the hidden fields */
54
-
55
- if ( intval( $snippet->id ) > 0 )
56
- printf ( '<input type="hidden" name="snippet_id" value="%d" />', $snippet->id );
57
-
58
- printf ( '<input type="hidden" name="snippet_active" value="%d" />', $snippet->active );
59
- ?>
60
- <div id="titlediv">
61
- <div id="titlewrap">
62
- <label for="title" style="display: none;"><?php _e('Name (short title)', 'code-snippets'); ?></label>
63
- <input id="title" type="text" autocomplete="off" name="snippet_name" value="<?php echo esc_html( $snippet->name ); ?>" placeholder="<?php _e('Name (short title)', 'code-snippets'); ?>" required="required" />
64
- </div>
65
- </div>
66
-
67
- <label for="snippet_code">
68
- <h3><?php _e('Code', 'code-snippets'); ?></h3>
69
- </label>
70
-
71
- <textarea id="snippet_code" name="snippet_code" rows="20" spellcheck="false" style="font-family: monospace; width:100%;"><?php echo $snippet->code; ?></textarea>
72
-
73
- <?php do_action( 'code_snippets_admin_single', $snippet ); ?>
74
-
75
- <p class="submit">
76
- <?php
77
- submit_button( null, 'primary', 'save_snippet', false );
78
-
79
- if ( ! $snippet->active ) {
80
- echo '&nbsp;&nbsp;&nbsp;';
81
- submit_button( __( 'Save Changes &amp; Activate', 'code-snippets' ), 'secondary', 'save_snippet_activate', false );
82
- }
83
- ?>
84
- </p>
85
-
86
- </form>
87
- </div>
88
- <script type="text/javascript">
89
- var editor = CodeMirror.fromTextArea(document.getElementById("snippet_code"), {
90
- lineNumbers: true,
91
- matchBrackets: true,
92
- lineWrapping: true,
93
- mode: "text/x-php",
94
- indentUnit: 4,
95
- indentWithTabs: true,
96
- enterMode: "keep",
97
- tabMode: "shift"
98
- });
99
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/class-admin.php CHANGED
@@ -3,7 +3,7 @@
3
  /**
4
  * Contains the class for handling the administration interface
5
  *
6
- * @package Code Snippets
7
  * @subpackage Administration
8
  */
9
 
@@ -13,27 +13,30 @@
13
  * Don't directly access the methods in this class or attempt to
14
  * re-initialize it. Instead, use the instance in $code_snippets->admin
15
  *
16
- * @since 1.7.1
17
- * @package Code Snippets
 
18
  */
19
  class Code_Snippets_Admin {
20
 
21
  /**
22
  * The full URLs to the admin pages
23
  *
24
- * @since 1.7.1
 
25
  * @access public
26
  */
27
- public $manage_url, $single_url, $import_url;
28
 
29
  /**
30
  * The hooks for the admin pages
31
  * Used primarily for enqueueing scripts and styles
32
  *
33
- * @since 1.7.1
 
34
  * @access public
35
  */
36
- public $manage_page, $single_page, $import_page;
37
 
38
  /**
39
  * Initializes the variables and
@@ -44,10 +47,8 @@ class Code_Snippets_Admin {
44
  function __construct() {
45
  global $code_snippets;
46
 
47
- $this->include_dir = trailingslashit( $code_snippets->plugin_dir . 'includes' );
48
-
49
- $this->manage_slug = apply_filters( 'code_snippets_admin_manage', 'snippets' );
50
- $this->single_slug = apply_filters( 'code_snippets_admin_single', 'snippet' );
51
 
52
  $this->manage_url = self_admin_url( 'admin.php?page=' . $this->manage_slug );
53
  $this->single_url = self_admin_url( 'admin.php?page=' . $this->single_slug );
@@ -58,9 +59,8 @@ class Code_Snippets_Admin {
58
  /**
59
  * Register action and filter hooks
60
  *
61
- * @since 1.7.1
62
  * @access private
63
- *
64
  * @return void
65
  */
66
  function setup_hooks() {
@@ -82,7 +82,7 @@ class Code_Snippets_Admin {
82
  add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_icon_style' ) );
83
 
84
  /* Add the description editor to the Snippets > Add New page */
85
- add_action( 'code_snippets_admin_single', array( $this, 'description_editor_box' ), 5 );
86
 
87
  /* Handle saving the user's screen option preferences */
88
  add_filter( 'set-screen-option', array( $this, 'set_screen_option' ), 10, 3 );
@@ -93,13 +93,15 @@ class Code_Snippets_Admin {
93
 
94
  /**
95
  * Handles saving the user's snippets per page preference
96
- * @param unknown $status
97
- * @param string $option
98
- * @param unknown $value
 
99
  * @return unknown
100
  */
101
  function set_screen_option( $status, $option, $value ) {
102
- if ( 'snippets_per_page' === $option ) return $value;
 
103
  }
104
 
105
  /**
@@ -109,14 +111,14 @@ class Code_Snippets_Admin {
109
  * Adds a checkbox to the *Settings > Network Settings*
110
  * network admin menu
111
  *
112
- * @since 1.7.1
113
  * @access private
114
  *
115
- * @param array $menu_items The current mu menu items
116
- * @return array The modified mu menu items
117
  */
118
  function mu_menu_items( $menu_items ) {
119
- $menu_items['snippets'] = __('Snippets', 'code-snippets');
120
  return $menu_items;
121
  }
122
 
@@ -126,16 +128,15 @@ class Code_Snippets_Admin {
126
  * Add both an importer to the Tools menu
127
  * and an Import Snippets page to the network admin menu
128
  *
129
- * @since 1.6
130
  * @access private
131
- *
132
  * @return void
133
  */
134
  function load_importer() {
135
 
136
  if ( defined( 'WP_LOAD_IMPORTERS' ) ) {
137
 
138
- // Load Importer API
139
  require_once ABSPATH . 'wp-admin/includes/import.php';
140
 
141
  if ( ! class_exists( 'WP_Importer' ) ) {
@@ -144,43 +145,86 @@ class Code_Snippets_Admin {
144
  require_once $class_wp_importer;
145
  }
146
 
 
147
  register_importer(
148
  'code-snippets',
149
- __('Code Snippets', 'code-snippets'),
150
- __('Import snippets from a <strong>Code Snippets</strong> export file', 'code-snippets'),
151
  array( $this, 'display_import_menu' )
152
  );
153
  }
154
 
155
- $this->import_url = self_admin_url( 'admin.php?import=code-snippets' );
156
  add_action( 'load-importer-code-snippets', array( $this, 'load_import_menu' ) );
157
  }
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  /**
160
  * Add the dashboard admin menu and subpages
161
  *
162
- * @since 1.0
163
  * @access private
164
  *
165
- * @uses add_menu_page() To register a top-level menu
166
- * @uses add_submenu_page() To register a submenu page
167
- * @uses apply_filters() To retrieve the current menu slug
168
- * @uses plugins_url() To retrieve the URL to a resource
169
  * @return void
170
  */
171
  function add_admin_menus() {
172
  global $code_snippets;
173
 
174
  /* Use a different screen icon for the MP6 interface */
175
- if ( get_user_option( 'admin_color' ) !== 'mp6' )
176
- $menu_icon = apply_filters( 'code_snippets_menu_icon', plugins_url( 'assets/menu-icon.png', $code_snippets->file ) );
177
- else
 
 
178
  $menu_icon = 'div';
 
179
 
180
- /* Add the top-level menu and relevant subpage */
181
  $this->manage_page = add_menu_page(
182
- __('Snippets', 'code-snippets'),
183
- __('Snippets', 'code-snippets'),
184
  $code_snippets->get_cap( 'manage' ),
185
  $this->manage_slug,
186
  array( $this, 'display_manage_menu' ),
@@ -190,8 +234,8 @@ class Code_Snippets_Admin {
190
 
191
  add_submenu_page(
192
  $this->manage_slug,
193
- __('Snippets', 'code-snippets'),
194
- __('Manage', 'code-snippets'),
195
  $code_snippets->get_cap( 'manage' ),
196
  $this->manage_slug,
197
  array( $this, 'display_manage_menu')
@@ -202,8 +246,8 @@ class Code_Snippets_Admin {
202
 
203
  $this->single_page = add_submenu_page(
204
  $this->manage_slug,
205
- $editing ? __('Edit Snippet', 'code-snippets') : __('Add New Snippet', 'code-snippets'),
206
- $editing ? __('Edit', 'code-snippets') : __('Add New', 'code-snippets'),
207
  $code_snippets->get_cap( 'install' ),
208
  $this->single_slug,
209
  array( $this, 'display_single_menu' )
@@ -211,9 +255,6 @@ class Code_Snippets_Admin {
211
 
212
  add_action( "load-$this->manage_page", array( $this, 'load_manage_menu' ) );
213
  add_action( "load-$this->single_page", array( $this, 'load_single_menu' ) );
214
-
215
- add_action( "load-$this->manage_page", array( $code_snippets, 'maybe_create_tables' ) );
216
- add_action( "load-$this->single_page", array( $code_snippets, 'maybe_create_tables' ) );
217
  }
218
 
219
  /**
@@ -221,12 +262,11 @@ class Code_Snippets_Admin {
221
  * We need to do this as there is no Tools menu in the network
222
  * admin, and so we cannot register an importer
223
  *
224
- * @since 1.6
225
  * @access private
226
- *
227
- * @uses add_submenu_page() To register the menu page
228
- * @uses apply_filters() To retrieve the current menu slug
229
- * @uses add_action() To enqueue scripts and styles
230
  * @return void
231
  */
232
  function add_import_admin_menu() {
@@ -234,8 +274,8 @@ class Code_Snippets_Admin {
234
 
235
  $this->import_page = add_submenu_page(
236
  $this->manage_slug,
237
- __('Import Snippets', 'code-snippets'),
238
- __('Import', 'code-snippets'),
239
  $code_snippets->get_cap( 'import' ),
240
  'import-code-snippets',
241
  array( $this, 'display_import_menu' )
@@ -243,29 +283,28 @@ class Code_Snippets_Admin {
243
 
244
  $this->import_url = self_admin_url( 'admin.php?page=import-code-snippets' );
245
  add_action( "load-$this->import_page", array( $this, 'load_import_menu' ) );
246
- add_action( "load-$this->import_page", array( $code_snippets, 'maybe_create_tables' ) );
247
  }
248
 
249
  /**
250
  * Enqueue the icon stylesheet
251
  *
252
- * @since 1.0
253
  * @access private
254
- *
255
- * @uses wp_enqueue_style() To add the stylesheet to the queue
256
- *
257
  * @return void
258
  */
259
  function load_admin_icon_style() {
260
  global $code_snippets;
261
 
262
- $stylesheet = ( 'mp6' === get_user_option( 'admin_color' ) ? 'menu-icon.mp6' : 'screen-icon');
263
 
264
  wp_enqueue_style(
265
- 'icon-snippets',
266
- plugins_url( "assets/{$stylesheet}.css", $code_snippets->file ),
267
- false,
268
- $code_snippets->version
269
  );
270
  }
271
 
@@ -273,19 +312,21 @@ class Code_Snippets_Admin {
273
  * Initializes the list table class and loads the help tabs
274
  * for the Manage Snippets page
275
  *
276
- * @since 1.0
277
  * @access private
278
- *
279
  * @return void
280
  */
281
  function load_manage_menu() {
282
  global $code_snippets;
283
 
 
 
 
284
  /* Load the screen help tabs */
285
- include $this->include_dir . 'help/manage.php';
286
 
287
  /* Initialize the snippet table class */
288
- require_once $this->include_dir . 'class-list-table.php';
289
  $code_snippets->list_table = new Code_Snippets_List_Table();
290
  $code_snippets->list_table->prepare_items();
291
  }
@@ -293,22 +334,23 @@ class Code_Snippets_Admin {
293
  /**
294
  * Loads the help tabs for the Edit Snippets page
295
  *
296
- * @since 1.0
297
  * @access private
298
- *
299
- * @uses $wpdb To save the posted snippet to the database
300
- * @uses wp_redirect To pass the results to the page
301
- *
302
  * @return void
 
 
 
303
  */
304
  function load_single_menu() {
305
  global $code_snippets;
306
-
307
  $screen = get_current_screen();
308
 
 
 
 
309
  /* Don't let the user pass if they can't edit (install check is done by WP) */
310
  if ( isset( $_REQUEST['edit'] ) && ! $code_snippets->user_can( 'edit' ) )
311
- wp_die( __("Sorry, you're not allowed to edit snippets", 'code-snippets') );
312
 
313
  /* Save the snippet if one has been submitted */
314
  if ( isset( $_REQUEST['save_snippet'] ) || isset( $_REQUEST['save_snippet_activate'] ) ) {
@@ -345,7 +387,7 @@ class Code_Snippets_Admin {
345
  }
346
 
347
  /* Load the screen help tabs */
348
- include $this->include_dir . 'help/single.php';
349
 
350
  /* Enqueue the code editor and other scripts and styles */
351
  add_filter( 'admin_enqueue_scripts', array( $this, 'single_menu_enqueue_scripts' ) );
@@ -354,16 +396,15 @@ class Code_Snippets_Admin {
354
  /**
355
  * Registers and loads the code editor's scripts
356
  *
357
- * @since 1.7
358
  * @access private
359
  *
360
- * @uses wp_register_script()
361
- * @uses wp_register_style()
362
- * @uses wp_enqueue_script() To add the scripts to the queue
363
- * @uses wp_enqueue_style() To add the stylesheets to the queue
364
- *
365
- * @param string $hook The current page hook, to be compared with the single snippet page hook
366
  *
 
367
  * @return void
368
  */
369
  function single_menu_enqueue_scripts( $hook ) {
@@ -374,7 +415,7 @@ class Code_Snippets_Admin {
374
  return;
375
 
376
  /* CodeMirror package version */
377
- $codemirror_version = '3.11';
378
 
379
  /* CodeMirror base framework */
380
 
@@ -448,84 +489,105 @@ class Code_Snippets_Admin {
448
  false,
449
  $code_snippets->version
450
  );
 
 
 
 
 
 
 
 
 
451
  }
452
 
453
  /**
454
  * Processes import files and loads the help tabs for the Import Snippets page
455
  *
456
- * @since 1.3
457
  *
458
- * @uses $code_snippets->import() To process the import file
459
- * @uses wp_redirect() To pass the import results to the page
460
- * @uses add_query_arg() To append the results to the current URI
 
461
  *
 
462
  * @return void
463
  */
464
  function load_import_menu() {
465
  global $code_snippets;
466
 
 
 
 
467
  /* Process import files */
 
468
  if ( isset( $_FILES['code_snippets_import_file']['tmp_name'] ) ) {
469
- $count = $code_snippets->import( $_FILES['code_snippets_import_file']['tmp_name'] );
470
- if ( $count ) {
471
- wp_redirect( add_query_arg( 'imported', $count ) );
 
 
 
 
 
 
472
  }
473
  }
474
 
475
  /* Load the screen help tabs */
476
- require_once $this->include_dir . 'help/import.php';
477
  }
478
 
479
  /**
480
  * Displays the manage snippets page
481
  *
482
- * @since 1.0
483
  * @access private
484
- *
485
  * @return void
486
  */
487
  function display_manage_menu() {
488
- require_once $this->include_dir . 'admin/manage.php';
489
  }
490
 
491
  /**
492
  * Displays the single snippet page
493
  *
494
- * @since 1.0
495
  * @access private
496
- *
497
  * @return void
498
  */
499
  function display_single_menu() {
500
- require_once $this->include_dir . 'admin/single.php';
501
  }
502
 
503
  /**
504
  * Displays the import snippets page
505
  *
506
- * @since 1.3
507
  * @access private
508
- *
509
  * @return void
510
  */
511
  function display_import_menu() {
512
- require_once $this->include_dir . 'admin/import.php';
513
  }
514
 
515
  /**
516
  * Add a description editor to the single snippet page
517
  *
518
- * @since 1.7
519
  * @access private
520
- *
521
- * @param object $snippet The snippet being used for this page
522
  */
523
  function description_editor_box( $snippet ) {
 
524
  ?>
525
 
526
  <label for="snippet_description">
527
- <h3><div style="position: absolute;"><?php _e('Description', 'code-snippets');
528
- ?> <span style="font-weight: normal;"><?php esc_html_e('(Optional)', 'code-snippets'); ?></span></div></h3>
529
  </label>
530
 
531
  <?php
@@ -535,7 +597,7 @@ class Code_Snippets_Admin {
535
  wp_editor(
536
  $snippet->description,
537
  'description',
538
- apply_filters( 'code_snippets_description_editor_settings', array(
539
  'textarea_name' => 'snippet_description',
540
  'textarea_rows' => 10,
541
  'teeny' => true,
@@ -547,18 +609,17 @@ class Code_Snippets_Admin {
547
  /**
548
  * Adds a link pointing to the Manage Snippets page
549
  *
550
- * @since 1.0
551
  * @access private
552
- *
553
- * @param array $links The existing plugin action links
554
- * @return array The modified plugin action links
555
  */
556
  function settings_link( $links ) {
557
  array_unshift( $links, sprintf(
558
  '<a href="%1$s" title="%2$s">%3$s</a>',
559
  $this->manage_url,
560
- __('Manage your existing snippets', 'code-snippets'),
561
- __('Manage', 'code-snippets')
562
  ) );
563
  return $links;
564
  }
@@ -566,39 +627,39 @@ class Code_Snippets_Admin {
566
  /**
567
  * Adds extra links related to the plugin
568
  *
569
- * @since 1.2
570
  * @access private
571
- *
572
- * @param array $links The existing plugin info links
573
- * @param string $file The plugin the links are for
574
- * @return array The modified plugin info links
575
  */
576
  function plugin_meta( $links, $file ) {
577
  global $code_snippets;
578
 
579
  /* We only want to affect the Code Snippets plugin listing */
580
- if ( $file !== $code_snippets->basename ) return $links;
 
581
 
582
  $format = '<a href="%1$s" title="%2$s">%3$s</a>';
583
 
584
  /* array_merge appends the links to the end */
585
  return array_merge( $links, array(
586
  sprintf( $format,
587
- 'http://wordpress.org/extend/plugins/code-snippets/',
588
- __('Visit the WordPress.org plugin page', 'code-snippets'),
589
- __('About', 'code-snippets')
590
  ),
591
  sprintf( $format,
592
  'http://wordpress.org/support/plugin/code-snippets/',
593
- __('Visit the support forums', 'code-snippets'),
594
- __('Support', 'code-snippets')
595
  ),
596
  sprintf( $format,
597
  'http://code-snippets.bungeshea.com/donate/',
598
- __("Support this plugin's development", 'code-snippets'),
599
- __('Donate', 'code-snippets')
600
  )
601
  ) );
602
  }
603
 
604
- }
3
  /**
4
  * Contains the class for handling the administration interface
5
  *
6
+ * @package Code_Snippets
7
  * @subpackage Administration
8
  */
9
 
13
  * Don't directly access the methods in this class or attempt to
14
  * re-initialize it. Instead, use the instance in $code_snippets->admin
15
  *
16
+ * @since 1.7.1
17
+ * @package Code_Snippets
18
+ * @subpackage Administration
19
  */
20
  class Code_Snippets_Admin {
21
 
22
  /**
23
  * The full URLs to the admin pages
24
  *
25
+ * @var string
26
+ * @since 1.7.1
27
  * @access public
28
  */
29
+ public $manage_url, $single_url, $import_url = '';
30
 
31
  /**
32
  * The hooks for the admin pages
33
  * Used primarily for enqueueing scripts and styles
34
  *
35
+ * @var string
36
+ * @since 1.7.1
37
  * @access public
38
  */
39
+ public $manage_page, $single_page, $import_page = '';
40
 
41
  /**
42
  * Initializes the variables and
47
  function __construct() {
48
  global $code_snippets;
49
 
50
+ $this->manage_slug = apply_filters( 'code_snippets/admin/manage_slug', 'snippets' );
51
+ $this->single_slug = apply_filters( 'code_snippets/admin/single_slug', 'snippet' );
 
 
52
 
53
  $this->manage_url = self_admin_url( 'admin.php?page=' . $this->manage_slug );
54
  $this->single_url = self_admin_url( 'admin.php?page=' . $this->single_slug );
59
  /**
60
  * Register action and filter hooks
61
  *
62
+ * @since 1.7.1
63
  * @access private
 
64
  * @return void
65
  */
66
  function setup_hooks() {
82
  add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_icon_style' ) );
83
 
84
  /* Add the description editor to the Snippets > Add New page */
85
+ add_action( 'code_snippets/admin/single', array( $this, 'description_editor_box' ), 5 );
86
 
87
  /* Handle saving the user's screen option preferences */
88
  add_filter( 'set-screen-option', array( $this, 'set_screen_option' ), 10, 3 );
93
 
94
  /**
95
  * Handles saving the user's snippets per page preference
96
+ *
97
+ * @param unknown $status
98
+ * @param string $option
99
+ * @param unknown $value
100
  * @return unknown
101
  */
102
  function set_screen_option( $status, $option, $value ) {
103
+ if ( 'snippets_per_page' === $option )
104
+ return $value;
105
  }
106
 
107
  /**
111
  * Adds a checkbox to the *Settings > Network Settings*
112
  * network admin menu
113
  *
114
+ * @since 1.7.1
115
  * @access private
116
  *
117
+ * @param array $menu_items The current mu menu items
118
+ * @return array The modified mu menu items
119
  */
120
  function mu_menu_items( $menu_items ) {
121
+ $menu_items['snippets'] = __( 'Snippets', 'code-snippets' );
122
  return $menu_items;
123
  }
124
 
128
  * Add both an importer to the Tools menu
129
  * and an Import Snippets page to the network admin menu
130
  *
131
+ * @since 1.6
132
  * @access private
 
133
  * @return void
134
  */
135
  function load_importer() {
136
 
137
  if ( defined( 'WP_LOAD_IMPORTERS' ) ) {
138
 
139
+ /* Load Importer API */
140
  require_once ABSPATH . 'wp-admin/includes/import.php';
141
 
142
  if ( ! class_exists( 'WP_Importer' ) ) {
145
  require_once $class_wp_importer;
146
  }
147
 
148
+ /* Register the Code Snippets importer with WordPress */
149
  register_importer(
150
  'code-snippets',
151
+ __( 'Code Snippets', 'code-snippets' ),
152
+ __( 'Import snippets from a Code Snippets export file', 'code-snippets' ),
153
  array( $this, 'display_import_menu' )
154
  );
155
  }
156
 
157
+ $this->import_url = self_admin_url( 'admin.php?import=code-snippets' );
158
  add_action( 'load-importer-code-snippets', array( $this, 'load_import_menu' ) );
159
  }
160
 
161
+ /**
162
+ * Load contextual help tabs for an admin screen.
163
+ *
164
+ * @since 1.8
165
+ * @access public
166
+ * @param string $slug The file handle (filename with no path or extension) to load
167
+ * @return void
168
+ */
169
+ public function load_help_tabs( $slug ) {
170
+ global $code_snippets;
171
+ include $code_snippets->plugin_dir . "admin/help/{$slug}.php";
172
+ }
173
+
174
+ /**
175
+ * Load an admin view template
176
+ *
177
+ * @since 1.8
178
+ * @access public
179
+ * @param string $slug The file handle (filename with no path or extension) to load
180
+ * @return void
181
+ */
182
+ public function get_view( $slug ) {
183
+ global $code_snippets;
184
+ require $code_snippets->plugin_dir . "admin/views/{$slug}.php";
185
+ }
186
+
187
+ /**
188
+ * Display the admin status and error messages
189
+ *
190
+ * @since 1.8
191
+ * @access public
192
+ * @param string $slug The file handle (filename with no path or extension) to load
193
+ * @return void
194
+ */
195
+ public function get_messages( $slug ) {
196
+ global $code_snippets;
197
+ require $code_snippets->plugin_dir . "admin/messages/{$slug}.php";
198
+ }
199
+
200
  /**
201
  * Add the dashboard admin menu and subpages
202
  *
203
+ * @since 1.0
204
  * @access private
205
  *
206
+ * @uses add_menu_page() To register a top-level menu
207
+ * @uses add_submenu_page() To register a submenu page
208
+ * @uses apply_filters() To retrieve the current menu slug
209
+ * @uses plugins_url() To retrieve the URL to a resource
210
  * @return void
211
  */
212
  function add_admin_menus() {
213
  global $code_snippets;
214
 
215
  /* Use a different screen icon for the MP6 interface */
216
+ if ( get_user_option( 'admin_color' ) !== 'mp6' ) {
217
+ $menu_icon = apply_filters( 'code_snippets/admin/menu_icon_url',
218
+ plugins_url( 'assets/menu-icon.png', $code_snippets->file )
219
+ );
220
+ } else {
221
  $menu_icon = 'div';
222
+ }
223
 
224
+ /* Add the top-level menu and associated subpage */
225
  $this->manage_page = add_menu_page(
226
+ __( 'Snippets', 'code-snippets' ),
227
+ __( 'Snippets', 'code-snippets' ),
228
  $code_snippets->get_cap( 'manage' ),
229
  $this->manage_slug,
230
  array( $this, 'display_manage_menu' ),
234
 
235
  add_submenu_page(
236
  $this->manage_slug,
237
+ __( 'Snippets', 'code-snippets' ),
238
+ __( 'Manage', 'code-snippets' ),
239
  $code_snippets->get_cap( 'manage' ),
240
  $this->manage_slug,
241
  array( $this, 'display_manage_menu')
246
 
247
  $this->single_page = add_submenu_page(
248
  $this->manage_slug,
249
+ $editing ? __( 'Edit Snippet', 'code-snippets' ) : __( 'Add New Snippet', 'code-snippets' ),
250
+ $editing ? __( 'Edit', 'code-snippets' ) : __( 'Add New', 'code-snippets' ),
251
  $code_snippets->get_cap( 'install' ),
252
  $this->single_slug,
253
  array( $this, 'display_single_menu' )
255
 
256
  add_action( "load-$this->manage_page", array( $this, 'load_manage_menu' ) );
257
  add_action( "load-$this->single_page", array( $this, 'load_single_menu' ) );
 
 
 
258
  }
259
 
260
  /**
262
  * We need to do this as there is no Tools menu in the network
263
  * admin, and so we cannot register an importer
264
  *
265
+ * @since 1.6
266
  * @access private
267
+ * @uses add_submenu_page() To register the menu page
268
+ * @uses apply_filters() To retrieve the current menu slug
269
+ * @uses add_action() To enqueue scripts and styles
 
270
  * @return void
271
  */
272
  function add_import_admin_menu() {
274
 
275
  $this->import_page = add_submenu_page(
276
  $this->manage_slug,
277
+ __( 'Import Snippets', 'code-snippets' ),
278
+ __( 'Import', 'code-snippets' ),
279
  $code_snippets->get_cap( 'import' ),
280
  'import-code-snippets',
281
  array( $this, 'display_import_menu' )
283
 
284
  $this->import_url = self_admin_url( 'admin.php?page=import-code-snippets' );
285
  add_action( "load-$this->import_page", array( $this, 'load_import_menu' ) );
 
286
  }
287
 
288
  /**
289
  * Enqueue the icon stylesheet
290
  *
291
+ * @since 1.0
292
  * @access private
293
+ * @uses wp_enqueue_style() To add the stylesheet to the queue
294
+ * @uses get_user_option() To check if MP6 mode is active
295
+ * @uses plugins_url To retrieve a URL to assets
296
  * @return void
297
  */
298
  function load_admin_icon_style() {
299
  global $code_snippets;
300
 
301
+ $stylesheet = ( 'mp6' === get_user_option( 'admin_color' ) ? 'menu-icon.mp6' : 'screen-icon' );
302
 
303
  wp_enqueue_style(
304
+ 'icon-snippets',
305
+ plugins_url( "assets/{$stylesheet}.css", $code_snippets->file ),
306
+ false,
307
+ $code_snippets->version
308
  );
309
  }
310
 
312
  * Initializes the list table class and loads the help tabs
313
  * for the Manage Snippets page
314
  *
315
+ * @since 1.0
316
  * @access private
 
317
  * @return void
318
  */
319
  function load_manage_menu() {
320
  global $code_snippets;
321
 
322
+ /* Create the snippet tables if they don't exist */
323
+ $code_snippets->maybe_create_tables( true, true );
324
+
325
  /* Load the screen help tabs */
326
+ $this->load_help_tabs( 'manage' );
327
 
328
  /* Initialize the snippet table class */
329
+ $code_snippets->get_include( 'class-list-table' );
330
  $code_snippets->list_table = new Code_Snippets_List_Table();
331
  $code_snippets->list_table->prepare_items();
332
  }
334
  /**
335
  * Loads the help tabs for the Edit Snippets page
336
  *
337
+ * @since 1.0
338
  * @access private
 
 
 
 
339
  * @return void
340
+ *
341
+ * @uses $wpdb To save the posted snippet to the database
342
+ * @uses wp_redirect To pass the results to the page
343
  */
344
  function load_single_menu() {
345
  global $code_snippets;
 
346
  $screen = get_current_screen();
347
 
348
+ /* Create the snippet tables if they don't exist */
349
+ $code_snippets->maybe_create_tables( true, true );
350
+
351
  /* Don't let the user pass if they can't edit (install check is done by WP) */
352
  if ( isset( $_REQUEST['edit'] ) && ! $code_snippets->user_can( 'edit' ) )
353
+ wp_die( __("Sorry, you're not allowed to edit snippets", 'code-snippets' ) );
354
 
355
  /* Save the snippet if one has been submitted */
356
  if ( isset( $_REQUEST['save_snippet'] ) || isset( $_REQUEST['save_snippet_activate'] ) ) {
387
  }
388
 
389
  /* Load the screen help tabs */
390
+ $this->load_help_tabs( 'single' );
391
 
392
  /* Enqueue the code editor and other scripts and styles */
393
  add_filter( 'admin_enqueue_scripts', array( $this, 'single_menu_enqueue_scripts' ) );
396
  /**
397
  * Registers and loads the code editor's scripts
398
  *
399
+ * @since 1.7
400
  * @access private
401
  *
402
+ * @uses wp_register_script()
403
+ * @uses wp_register_style()
404
+ * @uses wp_enqueue_script() To add the scripts to the queue
405
+ * @uses wp_enqueue_style() To add the stylesheets to the queue
 
 
406
  *
407
+ * @param string $hook The current page hook, to be compared with the single snippet page hook
408
  * @return void
409
  */
410
  function single_menu_enqueue_scripts( $hook ) {
415
  return;
416
 
417
  /* CodeMirror package version */
418
+ $codemirror_version = '3.14';
419
 
420
  /* CodeMirror base framework */
421
 
489
  false,
490
  $code_snippets->version
491
  );
492
+
493
+ /* Enqueue custom scripts */
494
+ wp_enqueue_script(
495
+ 'code-snippets-admin-single',
496
+ plugins_url( 'assets/admin-single.js', $code_snippets->file ),
497
+ false,
498
+ $code_snippets->version,
499
+ true // Load in footer
500
+ );
501
  }
502
 
503
  /**
504
  * Processes import files and loads the help tabs for the Import Snippets page
505
  *
506
+ * @since 1.3
507
  *
508
+ * @uses $code_snippets->import() To process the import file
509
+ * @uses wp_redirect() To pass the import results to the page
510
+ * @uses add_query_arg() To append the results to the current URI
511
+ * @uses $this->load_help_tabs() To load the screen contextual help tabs
512
  *
513
+ * @param string $file A filesystem path to the import file
514
  * @return void
515
  */
516
  function load_import_menu() {
517
  global $code_snippets;
518
 
519
+ /* Create the snippet tables if they don't exist */
520
+ $code_snippets->maybe_create_tables( true, true );
521
+
522
  /* Process import files */
523
+
524
  if ( isset( $_FILES['code_snippets_import_file']['tmp_name'] ) ) {
525
+
526
+ /* Import the snippets. The result is the number of snippets that were imported */
527
+ $result = $code_snippets->import( $_FILES['code_snippets_import_file']['tmp_name'] );
528
+
529
+ /* Send the amount of imported snippets to the page */
530
+ if ( false === $result ) {
531
+ wp_redirect( add_query_arg( 'error', true ) );
532
+ } else {
533
+ wp_redirect( add_query_arg( 'imported', $result ) );
534
  }
535
  }
536
 
537
  /* Load the screen help tabs */
538
+ $this->load_help_tabs( 'import' );
539
  }
540
 
541
  /**
542
  * Displays the manage snippets page
543
  *
544
+ * @since 1.0
545
  * @access private
546
+ * @uses $this->get_view() To load an admin view template
547
  * @return void
548
  */
549
  function display_manage_menu() {
550
+ $this->get_view( 'manage' );
551
  }
552
 
553
  /**
554
  * Displays the single snippet page
555
  *
556
+ * @since 1.0
557
  * @access private
558
+ * @uses $this->get_view() To load an admin view template
559
  * @return void
560
  */
561
  function display_single_menu() {
562
+ $this->get_view( 'single' );
563
  }
564
 
565
  /**
566
  * Displays the import snippets page
567
  *
568
+ * @since 1.3
569
  * @access private
570
+ * @uses $this->get_view() To load an admin view template
571
  * @return void
572
  */
573
  function display_import_menu() {
574
+ $this->get_view( 'import' );
575
  }
576
 
577
  /**
578
  * Add a description editor to the single snippet page
579
  *
580
+ * @since 1.7
581
  * @access private
582
+ * @param object $snippet The snippet being used for this page
583
+ * @return void
584
  */
585
  function description_editor_box( $snippet ) {
586
+
587
  ?>
588
 
589
  <label for="snippet_description">
590
+ <h3><div style="position: absolute;"><?php _e( 'Description', 'code-snippets' ); ?></div></h3>
 
591
  </label>
592
 
593
  <?php
597
  wp_editor(
598
  $snippet->description,
599
  'description',
600
+ apply_filters( 'code_snippets/admin/description_editor_settings', array(
601
  'textarea_name' => 'snippet_description',
602
  'textarea_rows' => 10,
603
  'teeny' => true,
609
  /**
610
  * Adds a link pointing to the Manage Snippets page
611
  *
612
+ * @since 1.0
613
  * @access private
614
+ * @param array $links The existing plugin action links
615
+ * @return array The modified plugin action links
 
616
  */
617
  function settings_link( $links ) {
618
  array_unshift( $links, sprintf(
619
  '<a href="%1$s" title="%2$s">%3$s</a>',
620
  $this->manage_url,
621
+ __( 'Manage your existing snippets', 'code-snippets' ),
622
+ __( 'Manage', 'code-snippets' )
623
  ) );
624
  return $links;
625
  }
627
  /**
628
  * Adds extra links related to the plugin
629
  *
630
+ * @since 1.2
631
  * @access private
632
+ * @param array $links The existing plugin info links
633
+ * @param string $file The plugin the links are for
634
+ * @return array The modified plugin info links
 
635
  */
636
  function plugin_meta( $links, $file ) {
637
  global $code_snippets;
638
 
639
  /* We only want to affect the Code Snippets plugin listing */
640
+ if ( $file !== $code_snippets->basename )
641
+ return $links;
642
 
643
  $format = '<a href="%1$s" title="%2$s">%3$s</a>';
644
 
645
  /* array_merge appends the links to the end */
646
  return array_merge( $links, array(
647
  sprintf( $format,
648
+ 'http://wordpress.org/plugins/code-snippets/',
649
+ __( 'Visit the WordPress.org plugin page', 'code-snippets' ),
650
+ __( 'About', 'code-snippets' )
651
  ),
652
  sprintf( $format,
653
  'http://wordpress.org/support/plugin/code-snippets/',
654
+ __( 'Visit the support forums', 'code-snippets' ),
655
+ __( 'Support', 'code-snippets' )
656
  ),
657
  sprintf( $format,
658
  'http://code-snippets.bungeshea.com/donate/',
659
+ __("Support this plugin's development", 'code-snippets' ),
660
+ __( 'Donate', 'code-snippets' )
661
  )
662
  ) );
663
  }
664
 
665
+ } // end of class
includes/class-list-table.php CHANGED
@@ -3,32 +3,32 @@
3
  /**
4
  * Contains the class for handling the administration interface
5
  *
6
- * @package Code Snippets
7
  * @subpackage Administration
8
  */
9
 
 
10
  if ( ! class_exists( 'WP_List_Table' ) ) {
11
- require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
12
  }
13
 
14
  /**
15
  * This class handles the table for the manage snippets menu
16
  *
17
- * @since 1.5
18
- * @package Code Snippets
19
- * @access private
20
  */
21
  class Code_Snippets_List_Table extends WP_List_Table {
22
 
23
  /**#@+
24
- * @since 1.5
25
  * @access private
26
  */
27
 
28
  /**
29
- * The constructor function for our class
30
- *
31
- * Adds hooks, initializes variables, setups class
32
  */
33
  function __construct() {
34
  global $status, $page, $code_snippets;
@@ -42,19 +42,32 @@ class Code_Snippets_List_Table extends WP_List_Table {
42
  if ( isset( $_REQUEST['s'] ) )
43
  $_SERVER['REQUEST_URI'] = add_query_arg( 's', stripslashes($_REQUEST['s'] ) );
44
 
 
45
  $page = $this->get_pagenum();
46
 
47
  add_screen_option( 'per_page', array(
48
- 'label' => __('Snippets per page', 'code-snippets'),
49
  'default' => 10,
50
  'option' => 'snippets_per_page'
51
  ) );
52
 
 
53
  add_filter( "get_user_option_manage{$screen->id}columnshidden", array( $this, 'get_default_hidden_columns' ), 15 );
 
 
54
  add_action( 'admin_enqueue_scripts', array( $this, 'load_table_style' ) );
55
 
 
56
  $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'activate', 'activate-multi', 'deactivate', 'deactivate-multi', 'delete', 'delete-multi' ) );
57
 
 
 
 
 
 
 
 
 
58
  parent::__construct( array(
59
  'singular' => 'snippet',
60
  'plural' => 'snippets',
@@ -65,18 +78,19 @@ class Code_Snippets_List_Table extends WP_List_Table {
65
  /**
66
  * Enqueue the table stylesheet
67
  *
68
- * @since 1.6
69
- *
70
- * @uses wp_enqueue_style() To add the stylesheet to the queue
71
- *
72
- * @param string $hook The current page hook, to be compared with the manage snippets page hook
73
  */
74
  function load_table_style( $hook ) {
75
  global $code_snippets;
76
 
 
77
  if ( $hook !== $code_snippets->admin->manage_page )
78
  return;
79
 
 
80
  if ( 'mp6' === get_user_option( 'admin_color' ) ) {
81
 
82
  wp_enqueue_style(
@@ -98,28 +112,7 @@ class Code_Snippets_List_Table extends WP_List_Table {
98
  }
99
 
100
  /**
101
- * Formats the snippet description
102
- * in the same way the post content is formatted
103
- *
104
- * @since 1.7
105
- * @access public
106
- *
107
- * @param string $desc The snippet description to format
108
- * @return string The formatted snippet description
109
- */
110
- public function format_description( $desc ) {
111
- $desc = wptexturize( $desc );
112
- $desc = convert_smilies( $desc );
113
- $desc = convert_chars( $desc );
114
- $desc = wpautop( $desc );
115
- $desc = shortcode_unautop( $desc );
116
- $desc = capital_P_dangit( $desc );
117
- return $desc;
118
- }
119
-
120
- /**
121
- * Define the output of all columns
122
- * that have no callback function
123
  * @param object $snippet The snippet object used for the current row
124
  * @param string $column_name The name of the column being printed
125
  * @return string The content of the column to output
@@ -131,11 +124,11 @@ class Code_Snippets_List_Table extends WP_List_Table {
131
  return $snippet->id;
132
  case 'description':
133
  if ( ! empty( $snippet->description ) )
134
- return $this->format_description( $snippet->description );
135
  else
136
  return '&#8212;';
137
  default:
138
- return do_action( "code_snippets_list_table_column_{$column_name}", $snippet );
139
  }
140
  }
141
 
@@ -155,7 +148,7 @@ class Code_Snippets_List_Table extends WP_List_Table {
155
  if ( $snippet->active ) {
156
  $actions['deactivate'] = sprintf(
157
  '<a href="%2$s">%1$s</a>',
158
- $screen->is_network ? __('Network Deactivate', 'code-snippets') : __('Deactivate', 'code-snippets'),
159
  add_query_arg( array(
160
  'action' => 'deactivate',
161
  'id' => $snippet->id
@@ -164,7 +157,7 @@ class Code_Snippets_List_Table extends WP_List_Table {
164
  } else {
165
  $actions['activate'] = sprintf(
166
  '<a href="%2$s">%1$s</a>',
167
- $screen->is_network ? __('Network Activate', 'code-snippets') : __('Activate', 'code-snippets'),
168
  add_query_arg( array(
169
  'action' => 'activate',
170
  'id' => $snippet->id
@@ -197,14 +190,19 @@ class Code_Snippets_List_Table extends WP_List_Table {
197
  esc_js( sprintf(
198
  'return confirm("%s");',
199
  __("You are about to permanently delete the selected item.
200
- 'Cancel' to stop, 'OK' to delete.", 'code-snippets')
201
  ) )
202
  );
203
 
204
- // Return the name contents
 
 
 
 
 
205
  return apply_filters(
206
- 'code_snippets_list_table_column_name',
207
- '<strong>' . stripslashes( $snippet->name ) . '</strong>' . $this->row_actions( $actions, true ),
208
  $snippet
209
  );
210
  }
@@ -216,7 +214,7 @@ class Code_Snippets_List_Table extends WP_List_Table {
216
  */
217
  function column_cb( $snippet ) {
218
  return apply_filters(
219
- 'code_snippets_list_table_column_cb',
220
  sprintf( '<input type="checkbox" name="ids[]" value="%s" />', $snippet->id ),
221
  $snippet
222
  );
@@ -228,12 +226,12 @@ class Code_Snippets_List_Table extends WP_List_Table {
228
  */
229
  function get_columns() {
230
  $columns = array(
231
- 'cb' => '<input type="checkbox" />',
232
- 'name' => __('Name', 'code-snippets'),
233
- 'id' => __('ID', 'code-snippets'),
234
- 'description' => __('Description', 'code-snippets'),
235
  );
236
- return apply_filters( 'code_snippets_list_table_columns', $columns );
237
  }
238
 
239
  /**
@@ -242,10 +240,10 @@ class Code_Snippets_List_Table extends WP_List_Table {
242
  */
243
  function get_sortable_columns() {
244
  $sortable_columns = array(
245
- 'id' => array( 'id', true ),
246
  'name' => array( 'name', false ),
247
  );
248
- return apply_filters( 'code_snippets_list_table_sortable_columns', $sortable_columns );
249
  }
250
 
251
  /**
@@ -267,13 +265,13 @@ class Code_Snippets_List_Table extends WP_List_Table {
267
  function get_bulk_actions() {
268
  $screen = get_current_screen();
269
  $actions = array(
270
- 'activate-selected' => $screen->is_network ? __('Network Activate', 'code-snippets') : __('Activate', 'code-snippets'),
271
- 'deactivate-selected' => $screen->is_network ? __('Network Deactivate', 'code-snippets') : __('Deactivate', 'code-snippets'),
272
- 'export-selected' => __('Export', 'code-snippets'),
273
- 'delete-selected' => __('Delete', 'code-snippets'),
274
- 'export-php-selected' => __('Export to PHP', 'code-snippets'),
275
  );
276
- return apply_filters( 'code_snippets_bulk_actions', $actions );
277
  }
278
 
279
  /**
@@ -286,7 +284,7 @@ class Code_Snippets_List_Table extends WP_List_Table {
286
  */
287
  function get_table_classes() {
288
  $classes = array( 'widefat', $this->_args['plural'] );
289
- return apply_filters( 'code_snippets_table_classes', $classes );
290
  }
291
 
292
  /**
@@ -301,21 +299,22 @@ class Code_Snippets_List_Table extends WP_List_Table {
301
 
302
  $status_links = array();
303
  foreach ( $totals as $type => $count ) {
304
- if ( !$count )
 
305
  continue;
306
 
307
  switch ( $type ) {
308
  case 'all':
309
- $text = _n( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $count, 'code-snippets');
310
  break;
311
  case 'active':
312
- $text = _n( 'Active <span class="count">(%s)</span>', 'Active <span class="count">(%s)</span>', $count, 'code-snippets');
313
  break;
314
  case 'recently_activated':
315
- $text = _n( 'Recently Active <span class="count">(%s)</span>', 'Recently Active <span class="count">(%s)</span>', $count, 'code-snippets');
316
  break;
317
  case 'inactive':
318
- $text = _n( 'Inactive <span class="count">(%s)</span>', 'Inactive <span class="count">(%s)</span>', $count, 'code-snippets');
319
  break;
320
  }
321
 
@@ -327,12 +326,12 @@ class Code_Snippets_List_Table extends WP_List_Table {
327
 
328
  }
329
 
330
- return apply_filters( 'code_snippets_list_table_views', $status_links );
331
  }
332
 
333
  /**
334
  * Add filters and extra actions above and below the table
335
- * @param string $which Are the actions displayed on the table top or bottom
336
  */
337
  function extra_tablenav( $which ) {
338
  global $status, $code_snippets;
@@ -340,22 +339,21 @@ class Code_Snippets_List_Table extends WP_List_Table {
340
  $screen = get_current_screen();
341
 
342
  if ( 'top' === $which && has_action( 'code_snippets_list_table_filter_controls' ) ) {
343
- ?>
344
- <div class="alignleft actions">
345
- <?php
346
- do_action( 'code_snippets_list_table_filter_controls' );
347
- submit_button( __('Filter', 'code-snippets'), 'button', false, false );
348
- ?>
349
- </div>
350
- <?php
351
  }
352
 
353
  echo '<div class="alignleft actions">';
354
 
355
  if ( 'recently_activated' === $status )
356
- submit_button( __('Clear List', 'code-snippets'), 'secondary', 'clear-recent-list', false );
357
 
358
- do_action( 'code_snippets_list_table_actions', $which );
359
 
360
  echo '</div>';
361
  }
@@ -364,14 +362,18 @@ class Code_Snippets_List_Table extends WP_List_Table {
364
  * Output form fields needed to preserve important
365
  * query vars over form submissions
366
  *
367
- * @param string $context In what context are the fields being outputted?
368
  */
369
  function required_form_fields( $context = 'main' ) {
370
 
371
- $vars = apply_filters( 'code_snippets_list_table_required_form_fields', array( 'page', 's', 'status', 'paged' ), $context );
 
 
 
 
372
 
373
  if ( 'search_box' === $context ) {
374
- // remove the 's' var if we're doing this for the search box
375
  $vars = array_diff( $vars, array( 's' ) );
376
  }
377
 
@@ -382,7 +384,7 @@ class Code_Snippets_List_Table extends WP_List_Table {
382
  }
383
  }
384
 
385
- do_action( 'code_snippets_list_table_print_required_form_fields', $context );
386
  }
387
 
388
 
@@ -395,30 +397,28 @@ class Code_Snippets_List_Table extends WP_List_Table {
395
  $action = 'clear-recent-list';
396
  else
397
  $action = parent::current_action();
398
- return apply_filters( 'code_snippets_list_table_current_action', $action );
399
  }
400
 
401
  /**
402
  * Processes a bulk action
403
  *
404
- * @uses $code_snippets->activate() To activate snippets
405
- * @uses $code_snippets->deactivate() To deactivate snippets
406
  * @uses $code_snippets->delete_snippet() To delete snippets
407
- * @uses $code_snippets->export() To export selected snippets
408
- * @uses wp_redirect To pass the results to the current page
409
- * @uses add_query_arg() To append the results to the current URI
410
  */
411
  function process_bulk_actions() {
412
  global $code_snippets;
413
 
414
  if ( isset( $_GET['action'], $_GET['id'] ) ) :
415
 
416
- $id = intval( $_GET['id'] );
417
-
418
  $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'action', 'id' ) );
419
 
420
- $action = sanitize_key( $_GET['action'] );
421
-
422
  if ( 'activate' === $action ) {
423
  $code_snippets->activate( $id );
424
  }
@@ -435,16 +435,17 @@ class Code_Snippets_List_Table extends WP_List_Table {
435
  $code_snippets->export_php( $id );
436
  }
437
 
438
- if ( 'export' !== $action || 'export-php' !== $action ) {
439
  wp_redirect( apply_filters(
440
- "code_snippets_{$action}_redirect",
441
  add_query_arg( $action, true )
442
  ) );
443
  }
444
 
445
  endif;
446
 
447
- if ( ! isset( $_POST['ids'] ) ) return;
 
448
 
449
  $ids = $_POST['ids'];
450
 
@@ -492,16 +493,14 @@ class Code_Snippets_List_Table extends WP_List_Table {
492
  */
493
  function no_items() {
494
  global $code_snippets;
495
- printf( __('You do not appear to have any snippets available at this time. <a href="%s">Add New&rarr;</a>', 'code-snippets'), $code_snippets->admin_single_url );
496
  }
497
 
498
  /**
499
- * Prepares the items to later display in the table
500
- *
501
- * Should run before any headers are sent
502
  */
503
  function prepare_items() {
504
-
505
  global $code_snippets, $status, $snippets, $totals, $page, $orderby, $order, $s;
506
 
507
  wp_reset_vars( array( 'orderby', 'order', 's' ) );
@@ -509,17 +508,17 @@ class Code_Snippets_List_Table extends WP_List_Table {
509
  $screen = get_current_screen();
510
  $user = get_current_user_id();
511
 
512
- // first, lets process the bulk actions
513
  $this->process_bulk_actions();
514
 
515
  $snippets = array(
516
- 'all' => apply_filters( 'code_snippets_list_table_get_snippets', $code_snippets->get_snippets() ),
517
  'active' => array(),
518
  'inactive' => array(),
519
  'recently_activated' => array(),
520
  );
521
 
522
- // filter snippets based on search query
523
  if ( $s ) {
524
  $snippets['all'] = array_filter( $snippets[ 'all' ], array( &$this, '_search_callback' ) );
525
  }
@@ -540,7 +539,7 @@ class Code_Snippets_List_Table extends WP_List_Table {
540
  update_option( 'recently_activated_snippets', $recently_activated );
541
 
542
  foreach ( (array) $snippets['all'] as $snippet ) {
543
- // Filter into individual sections
544
  if ( $snippet->active ) {
545
  $snippets['active'][] = $snippet;
546
  } else {
@@ -564,10 +563,9 @@ class Code_Snippets_List_Table extends WP_List_Table {
564
  * by getting the user's setting in the Screen Options
565
  * panel.
566
  */
567
- $sort_by = $screen->get_option( 'per_page', 'option' );
568
  $screen_option = $screen->get_option( 'per_page', 'option' );
569
-
570
- $per_page = get_user_meta( $user, $screen_option, true );
571
 
572
  if ( empty ( $per_page ) || $per_page < 1 ) {
573
  $per_page = $screen->get_option( 'per_page', 'default' );
@@ -584,51 +582,59 @@ class Code_Snippets_List_Table extends WP_List_Table {
584
  */
585
  function usort_reorder( $a, $b ) {
586
 
587
- // If no sort, default to id
588
- $orderby = ( ! empty($_REQUEST['orderby'] ) ) ? $_REQUEST['orderby'] : apply_filters( 'code_snippets_default_orderby', 'id' );
589
-
590
- // If no order, default to asc
591
- $order = ( ! empty( $_REQUEST['order'] ) ) ? $_REQUEST['order'] : 'asc';
592
-
593
- // Determine sort order
 
 
 
 
 
 
 
 
594
  if ( 'id' === $orderby )
595
  $result = $a->$orderby - $b->$orderby; // get the result for numerical data
596
  else
597
  $result = strcmp( $a->$orderby, $b->$orderby ); // get the result for string data
598
 
599
- // Send final sort direction to usort
600
  return ( 'asc' === $order ) ? $result : -$result;
601
  }
602
 
603
- usort($data, 'usort_reorder');
604
 
605
 
606
- /**
607
  * Let's figure out what page the user is currently
608
  * looking at.
609
  */
610
  $current_page = $this->get_pagenum();
611
 
612
- /**
613
  * Let's check how many items are in our data array.
614
  */
615
- $total_items = count($data);
616
 
617
 
618
- /**
619
  * The WP_List_Table class does not handle pagination for us, so we need
620
  * to ensure that the data is trimmed to only the current page.
621
  */
622
  $data = array_slice( $data, ( ( $current_page - 1 ) * $per_page ), $per_page );
623
 
624
 
625
- /**
626
  * Now we can add our *sorted* data to the items property, where
627
  * it can be used by the rest of the class.
628
  */
629
  $this->items = $data;
630
 
631
- /**
632
  * We also have to register our pagination options & calculations.
633
  */
634
  $this->set_pagination_args( array(
@@ -665,23 +671,28 @@ class Code_Snippets_List_Table extends WP_List_Table {
665
  /**
666
  * Display a notice showing the current search terms
667
  *
668
- * @since 1.7
669
  * @access public
670
  */
671
  public function search_notice() {
672
- if ( ! empty( $_REQUEST['s'] ) || apply_filters( 'code_snippets_list_table_search_notice', '' ) ) {
673
 
674
- echo '<span class="subtitle">' . __('Search results', 'code-snippets');
675
 
676
  if ( ! empty ( $_REQUEST['s'] ) )
677
  echo sprintf ( __( ' for &#8220;%s&#8221;', 'code-snippets' ), esc_html( $_REQUEST['s'] ) );
678
 
679
- echo apply_filters( 'code_snippets_list_table_search_notice', '' );
680
  echo '</span>';
681
 
682
  printf (
683
- '&nbsp;<a class="button" href="%s">' . __('Clear Filters', 'code-snippets') . '</a>',
684
- remove_query_arg( apply_filters( 'code_snippets_list_table_required_form_fields', array( 's' ), 'clear_filters' ) )
 
 
 
 
 
685
  );
686
  }
687
  }
@@ -693,9 +704,9 @@ class Code_Snippets_List_Table extends WP_List_Table {
693
  function single_row( $snippet ) {
694
  static $row_class = '';
695
  $row_class = ( $snippet->active ? 'active' : 'inactive' );
696
-
697
- echo '<tr class="' . $row_class . '">';
698
- echo $this->single_row_columns( $snippet );
699
  echo '</tr>';
700
  }
701
- }
 
3
  /**
4
  * Contains the class for handling the administration interface
5
  *
6
+ * @package Code_Snippets
7
  * @subpackage Administration
8
  */
9
 
10
+ /* The WP_List_Table base class is not included by default, so we need to load it */
11
  if ( ! class_exists( 'WP_List_Table' ) ) {
12
+ require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
13
  }
14
 
15
  /**
16
  * This class handles the table for the manage snippets menu
17
  *
18
+ * @since 1.5
19
+ * @access private
20
+ * @package Code_Snippets
21
  */
22
  class Code_Snippets_List_Table extends WP_List_Table {
23
 
24
  /**#@+
25
+ * @since 1.5
26
  * @access private
27
  */
28
 
29
  /**
30
+ * The constructor function for our class.
31
+ * Adds hooks, initializes variables, setups class.
 
32
  */
33
  function __construct() {
34
  global $status, $page, $code_snippets;
42
  if ( isset( $_REQUEST['s'] ) )
43
  $_SERVER['REQUEST_URI'] = add_query_arg( 's', stripslashes($_REQUEST['s'] ) );
44
 
45
+
46
  $page = $this->get_pagenum();
47
 
48
  add_screen_option( 'per_page', array(
49
+ 'label' => __( 'Snippets per page', 'code-snippets' ),
50
  'default' => 10,
51
  'option' => 'snippets_per_page'
52
  ) );
53
 
54
+ /* Set the table columns hidden in Screen Options by default */
55
  add_filter( "get_user_option_manage{$screen->id}columnshidden", array( $this, 'get_default_hidden_columns' ), 15 );
56
+
57
+ /* Load custom stylesheets */
58
  add_action( 'admin_enqueue_scripts', array( $this, 'load_table_style' ) );
59
 
60
+ /* Strip once-off query args from the URL */
61
  $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'activate', 'activate-multi', 'deactivate', 'deactivate-multi', 'delete', 'delete-multi' ) );
62
 
63
+ /* Add filters to format the snippet description in the same way the post content is formatted */
64
+ $filters = array( 'wptexturize', 'convert_smilies', 'convert_chars', 'wpautop', 'shortcode_unautop', 'capital_P_dangit' );
65
+
66
+ foreach ( $filters as $filter ) {
67
+ add_filter( 'code_snippets/list_table/print_snippet_description', $filter );
68
+ }
69
+
70
+ /* Setup the class */
71
  parent::__construct( array(
72
  'singular' => 'snippet',
73
  'plural' => 'snippets',
78
  /**
79
  * Enqueue the table stylesheet
80
  *
81
+ * @since 1.6
82
+ * @uses wp_enqueue_style() To add the stylesheet to the queue
83
+ * @param string $hook The current page hook, to be compared with the manage snippets page hook
84
+ * @return void
 
85
  */
86
  function load_table_style( $hook ) {
87
  global $code_snippets;
88
 
89
+ /* Only load the stylesheet on the manage snippets page */
90
  if ( $hook !== $code_snippets->admin->manage_page )
91
  return;
92
 
93
+ /* Load a different stylesheet if MP6 is active */
94
  if ( 'mp6' === get_user_option( 'admin_color' ) ) {
95
 
96
  wp_enqueue_style(
112
  }
113
 
114
  /**
115
+ * Define the output of all columns that have no callback function
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  * @param object $snippet The snippet object used for the current row
117
  * @param string $column_name The name of the column being printed
118
  * @return string The content of the column to output
124
  return $snippet->id;
125
  case 'description':
126
  if ( ! empty( $snippet->description ) )
127
+ return apply_filters( 'code_snippets/list_table/print_snippet_description', $snippet->description );
128
  else
129
  return '&#8212;';
130
  default:
131
+ return do_action( "code_snippets/list_table/column_{$column_name}", $snippet );
132
  }
133
  }
134
 
148
  if ( $snippet->active ) {
149
  $actions['deactivate'] = sprintf(
150
  '<a href="%2$s">%1$s</a>',
151
+ $screen->is_network ? __( 'Network Deactivate', 'code-snippets' ) : __( 'Deactivate', 'code-snippets' ),
152
  add_query_arg( array(
153
  'action' => 'deactivate',
154
  'id' => $snippet->id
157
  } else {
158
  $actions['activate'] = sprintf(
159
  '<a href="%2$s">%1$s</a>',
160
+ $screen->is_network ? __( 'Network Activate', 'code-snippets' ) : __( 'Activate', 'code-snippets' ),
161
  add_query_arg( array(
162
  'action' => 'activate',
163
  'id' => $snippet->id
190
  esc_js( sprintf(
191
  'return confirm("%s");',
192
  __("You are about to permanently delete the selected item.
193
+ 'Cancel' to stop, 'OK' to delete.", 'code-snippets' )
194
  ) )
195
  );
196
 
197
+ if ( ! empty( $snippet->name ) )
198
+ $title = $snippet->name;
199
+ else
200
+ $title = sprintf ( __( 'Untitled #%d', 'code-snippets' ), $snippet->id );
201
+
202
+ /* Return the name contents */
203
  return apply_filters(
204
+ 'code_snippets/list_table/column_name',
205
+ sprintf ( '<strong>%s</strong>', $title ) . $this->row_actions( $actions, true ),
206
  $snippet
207
  );
208
  }
214
  */
215
  function column_cb( $snippet ) {
216
  return apply_filters(
217
+ 'code_snippets/list_table/column_cb',
218
  sprintf( '<input type="checkbox" name="ids[]" value="%s" />', $snippet->id ),
219
  $snippet
220
  );
226
  */
227
  function get_columns() {
228
  $columns = array(
229
+ 'cb' => '<input type="checkbox" />',
230
+ 'name' => __( 'Name', 'code-snippets' ),
231
+ 'id' => __( 'ID', 'code-snippets' ),
232
+ 'description' => __( 'Description', 'code-snippets' ),
233
  );
234
+ return apply_filters( 'code_snippets/list_table/columns', $columns );
235
  }
236
 
237
  /**
240
  */
241
  function get_sortable_columns() {
242
  $sortable_columns = array(
243
+ 'id' => array( 'id', true ),
244
  'name' => array( 'name', false ),
245
  );
246
+ return apply_filters( 'code_snippets/list_table/sortable_columns', $sortable_columns );
247
  }
248
 
249
  /**
265
  function get_bulk_actions() {
266
  $screen = get_current_screen();
267
  $actions = array(
268
+ 'activate-selected' => $screen->is_network ? __( 'Network Activate', 'code-snippets' ) : __( 'Activate', 'code-snippets' ),
269
+ 'deactivate-selected' => $screen->is_network ? __( 'Network Deactivate', 'code-snippets' ) : __( 'Deactivate', 'code-snippets' ),
270
+ 'export-selected' => __( 'Export', 'code-snippets' ),
271
+ 'delete-selected' => __( 'Delete', 'code-snippets' ),
272
+ 'export-php-selected' => __( 'Export to PHP', 'code-snippets' ),
273
  );
274
+ return apply_filters( 'code_snippets/list_table/bulk_actions', $actions );
275
  }
276
 
277
  /**
284
  */
285
  function get_table_classes() {
286
  $classes = array( 'widefat', $this->_args['plural'] );
287
+ return apply_filters( 'code_snippets/list_table/table_classes', $classes );
288
  }
289
 
290
  /**
299
 
300
  $status_links = array();
301
  foreach ( $totals as $type => $count ) {
302
+
303
+ if ( ! $count )
304
  continue;
305
 
306
  switch ( $type ) {
307
  case 'all':
308
+ $text = _n( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $count, 'code-snippets' );
309
  break;
310
  case 'active':
311
+ $text = _n( 'Active <span class="count">(%s)</span>', 'Active <span class="count">(%s)</span>', $count, 'code-snippets' );
312
  break;
313
  case 'recently_activated':
314
+ $text = _n( 'Recently Active <span class="count">(%s)</span>', 'Recently Active <span class="count">(%s)</span>', $count, 'code-snippets' );
315
  break;
316
  case 'inactive':
317
+ $text = _n( 'Inactive <span class="count">(%s)</span>', 'Inactive <span class="count">(%s)</span>', $count, 'code-snippets' );
318
  break;
319
  }
320
 
326
 
327
  }
328
 
329
+ return apply_filters( 'code_snippets/list_table/views', $status_links );
330
  }
331
 
332
  /**
333
  * Add filters and extra actions above and below the table
334
+ * @param string $which Are the actions displayed on the table top or bottom
335
  */
336
  function extra_tablenav( $which ) {
337
  global $status, $code_snippets;
339
  $screen = get_current_screen();
340
 
341
  if ( 'top' === $which && has_action( 'code_snippets_list_table_filter_controls' ) ) {
342
+
343
+ echo '<div class="alignleft actions">';
344
+
345
+ do_action( 'code_snippets/list_table/filter_controls' );
346
+ submit_button( __( 'Filter', 'code-snippets' ), 'button', false, false );
347
+
348
+ echo '</div>';
 
349
  }
350
 
351
  echo '<div class="alignleft actions">';
352
 
353
  if ( 'recently_activated' === $status )
354
+ submit_button( __( 'Clear List', 'code-snippets' ), 'secondary', 'clear-recent-list', false );
355
 
356
+ do_action( 'code_snippets/list_table/actions', $which );
357
 
358
  echo '</div>';
359
  }
362
  * Output form fields needed to preserve important
363
  * query vars over form submissions
364
  *
365
+ * @param string $context In what context are the fields being outputted?
366
  */
367
  function required_form_fields( $context = 'main' ) {
368
 
369
+ $vars = apply_filters(
370
+ 'code_snippets/list_table/required_form_fields',
371
+ array( 'page', 's', 'status', 'paged' ),
372
+ $context
373
+ );
374
 
375
  if ( 'search_box' === $context ) {
376
+ /* Remove the 's' var if we're doing this for the search box */
377
  $vars = array_diff( $vars, array( 's' ) );
378
  }
379
 
384
  }
385
  }
386
 
387
+ do_action( 'code_snippets/list_table/print_required_form_fields', $context );
388
  }
389
 
390
 
397
  $action = 'clear-recent-list';
398
  else
399
  $action = parent::current_action();
400
+ return apply_filters( 'code_snippets/list_table/current_action', $action );
401
  }
402
 
403
  /**
404
  * Processes a bulk action
405
  *
406
+ * @uses $code_snippets->activate() To activate snippets
407
+ * @uses $code_snippets->deactivate() To deactivate snippets
408
  * @uses $code_snippets->delete_snippet() To delete snippets
409
+ * @uses $code_snippets->export() To export selected snippets
410
+ * @uses wp_redirect() To pass the results to the current page
411
+ * @uses add_query_arg() To append the results to the current URI
412
  */
413
  function process_bulk_actions() {
414
  global $code_snippets;
415
 
416
  if ( isset( $_GET['action'], $_GET['id'] ) ) :
417
 
418
+ $id = absint( $_GET['id'] );
419
+ $action = sanitize_key( $_GET['action'] );
420
  $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'action', 'id' ) );
421
 
 
 
422
  if ( 'activate' === $action ) {
423
  $code_snippets->activate( $id );
424
  }
435
  $code_snippets->export_php( $id );
436
  }
437
 
438
+ if ( ! in_array( $action, array( 'export', 'export-php' ) ) ) {
439
  wp_redirect( apply_filters(
440
+ "code_snippets/{$action}_redirect",
441
  add_query_arg( $action, true )
442
  ) );
443
  }
444
 
445
  endif;
446
 
447
+ if ( ! isset( $_POST['ids'] ) )
448
+ return;
449
 
450
  $ids = $_POST['ids'];
451
 
493
  */
494
  function no_items() {
495
  global $code_snippets;
496
+ printf( __( 'You do not appear to have any snippets available at this time. <a href="%s">Add New&rarr;</a>', 'code-snippets' ), $code_snippets->admin->single_url );
497
  }
498
 
499
  /**
500
+ * Prepares the items to later display in the table.
501
+ * Should run before any headers are sent.
 
502
  */
503
  function prepare_items() {
 
504
  global $code_snippets, $status, $snippets, $totals, $page, $orderby, $order, $s;
505
 
506
  wp_reset_vars( array( 'orderby', 'order', 's' ) );
508
  $screen = get_current_screen();
509
  $user = get_current_user_id();
510
 
511
+ /* First, lets process the bulk actions */
512
  $this->process_bulk_actions();
513
 
514
  $snippets = array(
515
+ 'all' => apply_filters( 'code_snippets/list_table/get_snippets', $code_snippets->get_snippets() ),
516
  'active' => array(),
517
  'inactive' => array(),
518
  'recently_activated' => array(),
519
  );
520
 
521
+ /* Filter snippets based on search query */
522
  if ( $s ) {
523
  $snippets['all'] = array_filter( $snippets[ 'all' ], array( &$this, '_search_callback' ) );
524
  }
539
  update_option( 'recently_activated_snippets', $recently_activated );
540
 
541
  foreach ( (array) $snippets['all'] as $snippet ) {
542
+ /* Filter into individual sections */
543
  if ( $snippet->active ) {
544
  $snippets['active'][] = $snippet;
545
  } else {
563
  * by getting the user's setting in the Screen Options
564
  * panel.
565
  */
566
+ $sort_by = $screen->get_option( 'per_page', 'option' );
567
  $screen_option = $screen->get_option( 'per_page', 'option' );
568
+ $per_page = get_user_meta( $user, $screen_option, true );
 
569
 
570
  if ( empty ( $per_page ) || $per_page < 1 ) {
571
  $per_page = $screen->get_option( 'per_page', 'default' );
582
  */
583
  function usort_reorder( $a, $b ) {
584
 
585
+ /* If no sort, default to ID */
586
+ $orderby = (
587
+ ! empty( $_REQUEST['orderby'] )
588
+ ? $_REQUEST['orderby']
589
+ : apply_filters( 'code_snippets/list_table/default_orderby', 'id' )
590
+ );
591
+
592
+ /* If no order, default to ascending */
593
+ $order = (
594
+ ! empty( $_REQUEST['order'] )
595
+ ? $_REQUEST['order']
596
+ : apply_filters( 'code_snippets/list_table/default_order', 'asc' )
597
+ );
598
+
599
+ /* Determine sort order */
600
  if ( 'id' === $orderby )
601
  $result = $a->$orderby - $b->$orderby; // get the result for numerical data
602
  else
603
  $result = strcmp( $a->$orderby, $b->$orderby ); // get the result for string data
604
 
605
+ /* Send final sort direction to usort */
606
  return ( 'asc' === $order ) ? $result : -$result;
607
  }
608
 
609
+ usort( $data, 'usort_reorder' );
610
 
611
 
612
+ /*
613
  * Let's figure out what page the user is currently
614
  * looking at.
615
  */
616
  $current_page = $this->get_pagenum();
617
 
618
+ /*
619
  * Let's check how many items are in our data array.
620
  */
621
+ $total_items = count( $data );
622
 
623
 
624
+ /*
625
  * The WP_List_Table class does not handle pagination for us, so we need
626
  * to ensure that the data is trimmed to only the current page.
627
  */
628
  $data = array_slice( $data, ( ( $current_page - 1 ) * $per_page ), $per_page );
629
 
630
 
631
+ /*
632
  * Now we can add our *sorted* data to the items property, where
633
  * it can be used by the rest of the class.
634
  */
635
  $this->items = $data;
636
 
637
+ /*
638
  * We also have to register our pagination options & calculations.
639
  */
640
  $this->set_pagination_args( array(
671
  /**
672
  * Display a notice showing the current search terms
673
  *
674
+ * @since 1.7
675
  * @access public
676
  */
677
  public function search_notice() {
678
+ if ( ! empty( $_REQUEST['s'] ) || apply_filters( 'code_snippets/list_table/search_notice', '' ) ) {
679
 
680
+ echo '<span class="subtitle">' . __( 'Search results', 'code-snippets' );
681
 
682
  if ( ! empty ( $_REQUEST['s'] ) )
683
  echo sprintf ( __( ' for &#8220;%s&#8221;', 'code-snippets' ), esc_html( $_REQUEST['s'] ) );
684
 
685
+ echo apply_filters( 'code_snippets/list_table/search_notice', '' );
686
  echo '</span>';
687
 
688
  printf (
689
+ '&nbsp;<a class="button" href="%s">' . __( 'Clear Filters', 'code-snippets' ) . '</a>',
690
+ remove_query_arg(
691
+ apply_filters( 'code_snippets/list_table/required_form_fields',
692
+ array( 's' ),
693
+ 'clear_filters'
694
+ )
695
+ )
696
  );
697
  }
698
  }
704
  function single_row( $snippet ) {
705
  static $row_class = '';
706
  $row_class = ( $snippet->active ? 'active' : 'inactive' );
707
+ printf ( '<tr class="%s">', $row_class );
708
+ $this->single_row_columns( $snippet );
 
709
  echo '</tr>';
710
  }
711
+
712
+ } // end of class
includes/export.php CHANGED
@@ -7,8 +7,8 @@
7
  * and $code_snippets->export_php() methods then
8
  * directly use those in this file
9
  *
10
- * @package Code Snippets
11
- * @subpackage Main
12
  */
13
 
14
  if ( ! function_exists( 'code_snippets_export' ) ) :
@@ -16,10 +16,9 @@ if ( ! function_exists( 'code_snippets_export' ) ) :
16
  /**
17
  * Exports selected snippets to a XML or PHP file.
18
  *
19
- * @since 1.3
20
- *
21
- * @param array $ids The IDs of the snippets to export
22
- * @param string $format The format of the export file
23
  * @return void
24
  */
25
  function code_snippets_export( $ids, $format = 'xml' ) {
@@ -28,15 +27,19 @@ function code_snippets_export( $ids, $format = 'xml' ) {
28
  $ids = (array) $ids;
29
 
30
  if ( 1 === count( $ids ) ) {
31
- // If there is only snippet to export, use its name instead of the site name
32
- $entry = $code_snippets->get_snippet( $ids );
33
- $sitename = strtolower( $entry->name );
34
  } else {
35
- // Otherwise, use the site name as set in Settings > General
36
  $sitename = strtolower( get_bloginfo( 'name' ) );
37
  }
38
 
39
- $filename = sanitize_file_name( apply_filters( 'code_snippets_export_filename', "{$sitename}.code-snippets.{$format}", $format, $sitename ) );
 
 
 
 
40
 
41
  /* Apply the file headers */
42
 
@@ -49,7 +52,7 @@ function code_snippets_export( $ids, $format = 'xml' ) {
49
 
50
  ?>
51
  <!-- This is a code snippets export file generated by the Code Snippets WordPress plugin. -->
52
- <!-- http://wordpress.org/extend/plugins/code-snippets -->
53
 
54
  <!-- To import these snippets a WordPress site follow these steps: -->
55
  <!-- 1. Log in to that site as an administrator. -->
@@ -63,12 +66,16 @@ function code_snippets_export( $ids, $format = 'xml' ) {
63
 
64
  <?php
65
 
66
- // run the generator line through the standard WordPress filter
67
- $gen = '<!-- generator="Code Snippets/' . $code_snippets->version . '" created="' . date('Y-m-d H:i') . '" -->';
 
 
 
 
68
  $type = 'code_snippets_export';
69
  echo apply_filters( "get_the_generator_$type", $gen, $type );
70
 
71
- // start the XML section
72
  echo "\n<snippets>";
73
 
74
  } elseif ( 'php' === $format ) {
@@ -77,19 +84,19 @@ function code_snippets_export( $ids, $format = 'xml' ) {
77
 
78
  }
79
 
80
- do_action( 'code_snippets_export_file_header', $format, $ids, $filename );
81
 
82
  /* Loop through the snippets */
83
 
84
- $table = $code_snippets->get_table_name();
85
- $exclude = apply_filters( 'code_snippets_exclude_from_export', array( 'id', 'active' ) );
86
 
87
- foreach( $ids as $id ) {
88
 
89
- // grab the snippet from the database
90
  $snippet = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE id = %d", $id ), ARRAY_A );
91
 
92
- // remove slashes
93
  $snippet = stripslashes_deep( $snippet );
94
 
95
  if ( 'xml' === $format ) {
@@ -98,11 +105,12 @@ function code_snippets_export( $ids, $format = 'xml' ) {
98
 
99
  foreach ( $snippet as $field => $value ) {
100
 
101
- // don't export certain fields
102
- if ( in_array( $field, $exclude ) ) continue;
 
103
 
104
- // output the field and value as indented XML
105
- if ( $value = apply_filters( "code_snippets_export_$field", $value ) )
106
  echo "\n\t\t<$field>$value</$field>";
107
  }
108
  echo "\n\t" . '</snippet>';
@@ -113,7 +121,7 @@ function code_snippets_export( $ids, $format = 'xml' ) {
113
 
114
  if ( ! empty( $snippet['description'] ) ) {
115
 
116
- // convert description to PHP Doc
117
  $desc = strip_tags( str_replace( "\n", "\n * ", $snippet['description'] ) );
118
 
119
  echo " *\n * $desc\n";
@@ -123,7 +131,7 @@ function code_snippets_export( $ids, $format = 'xml' ) {
123
  }
124
  }
125
 
126
- do_action( 'code_snippets_export_file_snippet', $format, $id, $filename );
127
 
128
  /* Finish off the file */
129
 
@@ -137,7 +145,7 @@ function code_snippets_export( $ids, $format = 'xml' ) {
137
 
138
  }
139
 
140
- do_action( 'code_snippets_export_file_footer', $format, $ids, $filename );
141
 
142
  exit;
143
  }
7
  * and $code_snippets->export_php() methods then
8
  * directly use those in this file
9
  *
10
+ * @package Code_Snippets
11
+ * @subpackage Functions
12
  */
13
 
14
  if ( ! function_exists( 'code_snippets_export' ) ) :
16
  /**
17
  * Exports selected snippets to a XML or PHP file.
18
  *
19
+ * @since 1.3
20
+ * @param array $ids The IDs of the snippets to export
21
+ * @param string $format The format of the export file
 
22
  * @return void
23
  */
24
  function code_snippets_export( $ids, $format = 'xml' ) {
27
  $ids = (array) $ids;
28
 
29
  if ( 1 === count( $ids ) ) {
30
+ /* If there is only snippet to export, use its name instead of the site name */
31
+ $snippet = $code_snippets->get_snippet( $ids );
32
+ $sitename = strtolower( $snippet->name );
33
  } else {
34
+ /* Otherwise, use the site name as set in Settings > General */
35
  $sitename = strtolower( get_bloginfo( 'name' ) );
36
  }
37
 
38
+ $filename = sanitize_file_name( apply_filters(
39
+ 'code_snippets/export/filename',
40
+ "{$sitename}.code-snippets.{$format}",
41
+ $format, $sitename
42
+ ) );
43
 
44
  /* Apply the file headers */
45
 
52
 
53
  ?>
54
  <!-- This is a code snippets export file generated by the Code Snippets WordPress plugin. -->
55
+ <!-- http://wordpress.org/plugins/code-snippets -->
56
 
57
  <!-- To import these snippets a WordPress site follow these steps: -->
58
  <!-- 1. Log in to that site as an administrator. -->
66
 
67
  <?php
68
 
69
+ /* Run the generator line through the standard WordPress filter */
70
+ $gen = sprinf (
71
+ '<!-- generator="Code Snippets/%s" created="%s" -->',
72
+ $code_snippets->version,
73
+ date('Y-m-d H:i')
74
+ );
75
  $type = 'code_snippets_export';
76
  echo apply_filters( "get_the_generator_$type", $gen, $type );
77
 
78
+ /* Start the XML section */
79
  echo "\n<snippets>";
80
 
81
  } elseif ( 'php' === $format ) {
84
 
85
  }
86
 
87
+ do_action( 'code_snippets/export/after_header', $format, $ids, $filename );
88
 
89
  /* Loop through the snippets */
90
 
91
+ $table = $code_snippets->get_table_name();
92
+ $exclude = apply_filters( 'code_snippets/export/exclude_from_export', array( 'id', 'active' ) );
93
 
94
+ foreach ( $ids as $id ) {
95
 
96
+ /* Grab the snippet from the database */
97
  $snippet = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE id = %d", $id ), ARRAY_A );
98
 
99
+ /* Remove slashes */
100
  $snippet = stripslashes_deep( $snippet );
101
 
102
  if ( 'xml' === $format ) {
105
 
106
  foreach ( $snippet as $field => $value ) {
107
 
108
+ /* Don't export certain fields */
109
+ if ( in_array( $field, $exclude ) )
110
+ continue;
111
 
112
+ /* Output the field and value as indented XML */
113
+ if ( $value = apply_filters( "code_snippets/export/$field", $value ) )
114
  echo "\n\t\t<$field>$value</$field>";
115
  }
116
  echo "\n\t" . '</snippet>';
121
 
122
  if ( ! empty( $snippet['description'] ) ) {
123
 
124
+ /* Convert description to PhpDoc */
125
  $desc = strip_tags( str_replace( "\n", "\n * ", $snippet['description'] ) );
126
 
127
  echo " *\n * $desc\n";
131
  }
132
  }
133
 
134
+ do_action( 'code_snippets/export/after_snippets', $format, $id, $filename );
135
 
136
  /* Finish off the file */
137
 
145
 
146
  }
147
 
148
+ do_action( 'code_snippets/export/after_footer', $format, $ids, $filename );
149
 
150
  exit;
151
  }
includes/functions.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Global functions which interact with the code snippets plugin
5
+ *
6
+ * @package Code_Snippets
7
+ * @subpackage Functions
8
+ */
9
+
10
+ /**
11
+ * Add submenu page to the snippets main menu.
12
+ *
13
+ * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected
14
+ * @param string $menu_title The text to be used for the menu
15
+ * @param string $capability The capability required for this menu to be displayed to the user.
16
+ * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu)
17
+ * @param callback $function The function to be called to output the content for this page.
18
+ * @return string|bool The resulting page's hook_suffix, or false if the user does not have the capability required.
19
+ */
20
+ function add_snippets_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
21
+ global $code_snippets;
22
+ return add_submenu_page( $code_snippets->admin->manage_page, $page_title, $menu_title, $capability, $menu_slug, $function );
23
+ }
includes/help/import.php DELETED
@@ -1,41 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Register and handle the help tabs for the
5
- * import snippets admin page
6
- *
7
- * @package Code Snippets
8
- * @subpackage Help
9
- */
10
-
11
- global $code_snippets;
12
- $screen = get_current_screen();
13
-
14
- $screen->add_help_tab( array(
15
- 'id' => 'overview',
16
- 'title' => __('Overview', 'code-snippets'),
17
- 'content' =>
18
- '<p>' . __('Snippets are similar to plugins - they both extend and expand the functionality of WordPress. Snippets are more light-weight, just a few lines of code, and do not put as much load on your server. Here you can load snippets from a Code Snippets (.xml) import file into the database with your existing snippets.', 'code-snippets') . '</p>'
19
- ) );
20
-
21
- $screen->add_help_tab( array(
22
- 'id' => 'import',
23
- 'title' => __('Importing', 'code-snippets'),
24
- 'content' =>
25
- '<p>' . __('You can load your snippets from a code snippets (.xml) export file using this page.', 'code-snippets') .
26
- sprintf( __('Snippets will be added to the database along with your existing snippets. Regardless of whether the snippets were active on the previous site, imported snippets are always inactive until activated using the <a href="%s">Manage Snippets</a> page.</p>', 'code-snippets'), $code_snippets->admin->manage_url ) . '</p>'
27
- ) );
28
-
29
- $screen->add_help_tab( array(
30
- 'id' => 'export',
31
- 'title' => __( 'Exporting', 'code-snippets' ),
32
- 'content' =>
33
- '<p>' . sprintf( __('You can save your snippets to a Code Snippets (.xml) export file using the <a href="%s">Manage Snippets</a> page.', 'code-snippets'), $code_snippets->admin->manage_url ) . '</p>'
34
- ) );
35
-
36
- $screen->set_help_sidebar(
37
- '<p><strong>' . __('For more information:', 'code-snippets') . '</strong></p>' .
38
- '<p>' . __('<a href="http://wordpress.org/extend/plugins/code-snippets" target="_blank">WordPress Extend</a>', 'code-snippets') . '</p>' .
39
- '<p>' . __('<a href="http://wordpress.org/support/plugin/code-snippets" target="_blank">Support Forums</a>', 'code-snippets') . '</p>' .
40
- '<p>' . __('<a href="http://code-snippets.bungeshea.com/" target="_blank">Project Website</a>', 'code-snippets') . '</p>'
41
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/help/manage.php DELETED
@@ -1,42 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Register and handle the help tabs for the
5
- * manage snippets admin page
6
- *
7
- * @package Code Snippets
8
- * @subpackage Help
9
- */
10
-
11
- global $code_snippets;
12
- $screen = get_current_screen();
13
-
14
- $screen->add_help_tab( array(
15
- 'id' => 'overview',
16
- 'title' => __('Overview', 'code-snippets'),
17
- 'content' =>
18
- '<p>' . __('Snippets are similar to plugins - they both extend and expand the functionality of WordPress. Snippets are more light-weight, just a few lines of code, and do not put as much load on your server. Here you can manage your existing snippets and preform tasks on them such as activating, deactivating, deleting and exporting.', 'code-snippets') . '</p>'
19
- ) );
20
-
21
- $screen->add_help_tab( array(
22
- 'id' => 'safe-mode',
23
- 'title' => __('Safe Mode', 'code-snippets'),
24
- 'content' =>
25
- '<p>' . __('Be sure to check your snippets for errors before you activate them, as a faulty snippet could bring your whole blog down. If your site starts doing strange things, deactivate all your snippets and activate them one at a time.', 'code-snippets') . '</p>' .
26
- '<p>' . __("If something goes wrong with a snippet and you can't use WordPress, you can cause all snippets to stop executing by adding <code>define('CODE_SNIPPETS_SAFE_MODE', true);</code> to your <code>wp-config.php</code> file. After you have deactivated the offending snippet, you can turn off safe mode by removing this line or replacing <strong>true</strong> with <strong>false</strong>.", 'code-snippets') . '</p>'
27
- ) );
28
-
29
- $screen->add_help_tab( array(
30
- 'id' => 'uninstall',
31
- 'title' => __('Uninstall', 'code-snippets'),
32
- 'content' =>
33
- '<p>' . sprintf( __('When you delete Code Snippets through the Plugins menu in WordPress it will clear up the <code>%1$s</code> table and a few other bits of data stored in the database. If you want to keep this data (ie: you are only temporally uninstalling Code Snippets) then remove the <code>%2$s</code> folder using FTP.', 'code-snippets'), $code_snippets->get_table_name(), $code_snippets->plugin_dir ) .
34
- '<p>' . __("Even if you're sure that you don't want to use Code Snippets ever again on this WordPress installation, you may want to use the export feature to back up your snippets.", 'code-snippets') . '</p>'
35
- ) );
36
-
37
- $screen->set_help_sidebar(
38
- '<p><strong>' . __('For more information:', 'code-snippets') . '</strong></p>' .
39
- '<p>' . __('<a href="http://wordpress.org/extend/plugins/code-snippets" target="_blank">WordPress Extend</a></p>', 'code-snippets') . '</p>' .
40
- '<p>' . __('<a href="http://wordpress.org/support/plugin/code-snippets" target="_blank">Support Forums</a>', 'code-snippets') . '</p>' .
41
- '<p>' . __('<a href="http://code-snippets.bungeshea.com/" target="_blank">Project Website</a>', 'code-snippets') . '</p>'
42
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/help/single.php DELETED
@@ -1,47 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Register and handle the help tabs for the
5
- * single snippet admin page
6
- *
7
- * @package Code Snippets
8
- * @subpackage Help
9
- */
10
-
11
- $screen = get_current_screen();
12
-
13
- $screen->add_help_tab( array(
14
- 'id' => 'overview',
15
- 'title' => __('Overview', 'code-snippets'),
16
- 'content' =>
17
- '<p>' . __('Snippets are similar to plugins - they both extend and expand the functionality of WordPress. Snippets are more light-weight, just a few lines of code, and do not put as much load on your server. Here you can add a new snippet, or edit an existing one.', 'code-snippets') . '</p>',
18
- ) );
19
-
20
- $screen->add_help_tab( array(
21
- 'id' => 'finding',
22
- 'title' => __('Finding Snippets', 'code-snippets'),
23
- 'content' =>
24
- '<p>' . __('Here are some links to websites which host a large number of snippets that you can add to your site.
25
- <ul>
26
- <li><a href="http://wp-snippets.com" title="WordPress Snippets">WP-Snippets</a></li>
27
- <li><a href="http://wpsnipp.com" title="WP Snipp">WP Snipp</a></li>
28
- <li><a href="http://www.catswhocode.com/blog/snippets" title="Cats Who Code Snippet Library">Cats Who Code</a></li>
29
- <li><a href="http://www.wpfunction.me">WP Function Me</a></li>
30
- </ul>', 'code-snippets') .
31
- __('More places to find snippets, as well as a selection of example snippets, can be found in the <a href="http://code-snippets.bungeshea.com/docs/finding-snippets/">plugin documentation</a>', 'code-snippets') . '</p>'
32
- ) );
33
-
34
- $screen->add_help_tab( array(
35
- 'id' => 'adding',
36
- 'title' => __('Adding Snippets', 'code-snippets'),
37
- 'content' =>
38
- '<p>' . __('You need to fill out the name and code fields for your snippet to be added. While the description field will add more information about how your snippet works, what is does and where you found it, it is completely optional.', 'code-snippets') . '</p>' .
39
- '<p>' . __('Please be sure to check that your snippet is valid PHP code and will not produce errors before adding it through this page. While doing so will not become active straight away, it will help to minimise the chance of a faulty snippet becoming active on your site.', 'code-snippets') . '</p>'
40
- ) );
41
-
42
- $screen->set_help_sidebar(
43
- '<p><strong>' . __('For more information:', 'code-snippets') . '</strong></p>' .
44
- '<p>' . __('<a href="http://wordpress.org/extend/plugins/code-snippets" target="_blank">WordPress Extend</a>', 'code-snippets') . '</p>' .
45
- '<p>' . __('<a href="http://wordpress.org/support/plugin/code-snippets" target="_blank">Support Forums</a>', 'code-snippets') . '</p>' .
46
- '<p>' . __('<a href="http://code-snippets.bungeshea.com/" target="_blank">Project Website</a>', 'code-snippets') . '</p>'
47
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
languages/code-snippets-de_DE.po CHANGED
@@ -386,8 +386,8 @@ msgstr "Weitere Informationen:"
386
  #@ code-snippets
387
  #: includes/help/import.php:27
388
  #: includes/help/single.php:32
389
- msgid "<a href=\"http://wordpress.org/extend/plugins/code-snippets\" target=\"_blank\">WordPress Extend</a>"
390
- msgstr "<a href=\"http://wordpress.org/extend/plugins/code-snippets\" target=\"_blank\">Plugin-Seite bei WordPress.org</a>"
391
 
392
  #@ code-snippets
393
  #: includes/help/import.php:28
@@ -429,8 +429,8 @@ msgstr "Auch wenn Sie sicher sind, dass Sie das Codeschnipsel-Plugin niemals meh
429
 
430
  #@ code-snippets
431
  #: includes/help/manage.php:28
432
- msgid "<a href=\"http://wordpress.org/extend/plugins/code-snippets\" target=\"_blank\">WordPress Extend</a></p>"
433
- msgstr "<a href=\"http://wordpress.org/extend/plugins/code-snippets\" target=\"_blank\">Plugin-Seite bei WordPress.org</a></p>"
434
 
435
  #@ code-snippets
436
  #: includes/help/single.php:7
386
  #@ code-snippets
387
  #: includes/help/import.php:27
388
  #: includes/help/single.php:32
389
+ msgid "<a href=\"http://wordpress.org/plugins/code-snippets\" target=\"_blank\">WordPress Extend</a>"
390
+ msgstr "<a href=\"http://wordpress.org/plugins/code-snippets\" target=\"_blank\">Plugin-Seite bei WordPress.org</a>"
391
 
392
  #@ code-snippets
393
  #: includes/help/import.php:28
429
 
430
  #@ code-snippets
431
  #: includes/help/manage.php:28
432
+ msgid "<a href=\"http://wordpress.org/plugins/code-snippets\" target=\"_blank\">WordPress Extend</a></p>"
433
+ msgstr "<a href=\"http://wordpress.org/plugins/code-snippets\" target=\"_blank\">Plugin-Seite bei WordPress.org</a></p>"
434
 
435
  #@ code-snippets
436
  #: includes/help/single.php:7
languages/code-snippets.po CHANGED
@@ -274,7 +274,7 @@ msgid "For more information:"
274
  msgstr ""
275
 
276
  #: includes/help/import.php:27 includes/help/single.php:32
277
- msgid "<a href=\"http://wordpress.org/extend/plugins/code-snippets\" target=\"_blank\">WordPress Extend</a>"
278
  msgstr ""
279
 
280
  #: includes/help/import.php:28 includes/help/manage.php:29
@@ -307,7 +307,7 @@ msgid "Even if you're sure that you don't want to use Code Snippets ever again o
307
  msgstr ""
308
 
309
  #: includes/help/manage.php:28
310
- msgid "<a href=\"http://wordpress.org/extend/plugins/code-snippets\" target=\"_blank\">WordPress Extend</a></p>"
311
  msgstr ""
312
 
313
  #: includes/help/single.php:7
274
  msgstr ""
275
 
276
  #: includes/help/import.php:27 includes/help/single.php:32
277
+ msgid "<a href=\"http://wordpress.org/plugins/code-snippets\" target=\"_blank\">WordPress Extend</a>"
278
  msgstr ""
279
 
280
  #: includes/help/import.php:28 includes/help/manage.php:29
307
  msgstr ""
308
 
309
  #: includes/help/manage.php:28
310
+ msgid "<a href=\"http://wordpress.org/plugins/code-snippets\" target=\"_blank\">WordPress Extend</a></p>"
311
  msgstr ""
312
 
313
  #: includes/help/single.php:7
languages/code-snippets.pot CHANGED
@@ -274,7 +274,7 @@ msgid "For more information:"
274
  msgstr ""
275
 
276
  #: includes/help/import.php:27 includes/help/single.php:32
277
- msgid "<a href=\"http://wordpress.org/extend/plugins/code-snippets\" target=\"_blank\">WordPress Extend</a>"
278
  msgstr ""
279
 
280
  #: includes/help/import.php:28 includes/help/manage.php:29
@@ -307,7 +307,7 @@ msgid "Even if you're sure that you don't want to use Code Snippets ever again o
307
  msgstr ""
308
 
309
  #: includes/help/manage.php:28
310
- msgid "<a href=\"http://wordpress.org/extend/plugins/code-snippets\" target=\"_blank\">WordPress Extend</a></p>"
311
  msgstr ""
312
 
313
  #: includes/help/single.php:7
274
  msgstr ""
275
 
276
  #: includes/help/import.php:27 includes/help/single.php:32
277
+ msgid "<a href=\"http://wordpress.org/plugins/code-snippets\" target=\"_blank\">WordPress Extend</a>"
278
  msgstr ""
279
 
280
  #: includes/help/import.php:28 includes/help/manage.php:29
307
  msgstr ""
308
 
309
  #: includes/help/manage.php:28
310
+ msgid "<a href=\"http://wordpress.org/plugins/code-snippets\" target=\"_blank\">WordPress Extend</a></p>"
311
  msgstr ""
312
 
313
  #: includes/help/single.php:7
license.txt CHANGED
@@ -1,7 +1,7 @@
1
- Copyright (c) 2013 Shea Bunge
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
-
5
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
-
7
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ Copyright (c) 2012-2013 Shea Bunge
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: http://code-snippets.bungeshea.com/donate/
4
  Tags: code-snippets, snippets, code, php, network, multisite
5
  Requires at least: 3.3
6
  Tested up to: 3.6
7
- Stable tag: 1.7.1.2
8
  License: MIT
9
  License URI: license.txt
10
 
@@ -18,11 +18,11 @@ A snippet is a small chunk of PHP code that you can use to extend the functional
18
  Most snippet-hosting sites tell you to add snippet code to your active theme's `functions.php` file, which can get rather long and messy after a while.
19
  Code Snippets changes that by providing a GUI interface for adding snippets and **actually running them on your site** as if they were in your theme's `functions.php` file.
20
 
21
- You can use a graphical interface, similar to the Plugins menu, to manage, activate, deactivate, edit and delete your snippets. Easily organise your snippets by adding a name and description using the visual editor. Code Snippets includes built-in syntax highlighting and other features to help you write your code. Snippets can be exported for transfer to another side, either in XML for later importing by the Code Snippets plugin, or in PHP for creating your own plugin or theme.
22
 
23
  Although Code Snippets is designed to be easy-to-use and its interface looks, feels and acts as if it was a native part of WordPress, each screen includes a help tab, just in case you get stuck.
24
 
25
- An addon-plugin for Code Snippets is available: [Code Snippets Tags](http://wordpress.org/extend/plugins/code-snippets-tags) will allow you to assign tags to your snippets and organize them in the table.
26
 
27
  Further information, documentation and updates are available on the [plugin homepage](http://code-snippets.bungeshea.com). You can also contribute to the code at [GitHub](https://github.com/bungeshea/code-snippets).
28
 
@@ -90,7 +90,7 @@ Yes. Click the checkboxes next to the snippets you want to export, and then choo
90
  You can run snippets across an entire multisite network by **Network Activating** Code Snippets through the Network Dashboard. You can also activate Code Snippets just on the main site, and then individually on other sites of your choice.
91
 
92
  = Is there anyway to add categories to snippets? =
93
- Users of Code Snippets version 1.7 and later can install the [Code Snippets Tags](http://wordpress.org/extend/plugins/code-snippets-tags) plugin for the ability to add tags to snippets, and then later filter the snippets by tag for easier organization.
94
 
95
  = I need help with Code Snippets =
96
  You can get help with Code Snippets either on the [WordPress Support Forums](http://wordpress.org/support/plugin/code-snippets/), on [GithHub](https://github.com/bungeshea/code-snippets/issues), or on [WordPress Answers](http://wordpress.stackexchange.com).
@@ -111,6 +111,16 @@ That's fantastic! Join me on [GitHub](https://github.com/bungeshea/code-snippets
111
 
112
  == Changelog ==
113
 
 
 
 
 
 
 
 
 
 
 
114
  = 1.7.1.2 =
115
  * Correct path to admin menu icon ([#](http://wordpress.org/support/topic/icon-disappears-with-mp6?replies=6#post-4148319))
116
 
@@ -210,6 +220,9 @@ Plugin updates will be posted on the [plugin's homepage](http://code-snippets.bu
210
 
211
  == Upgrade Notice ==
212
 
 
 
 
213
  = 1.7.1.2 =
214
  Fixes the admin menu icon not loading
215
 
@@ -220,7 +233,7 @@ Fixes a minor bug with custom capabilities and admin menus
220
  Added German translation thanks to David Decker; bug fixes and improvements
221
 
222
  = 1.7 =
223
- Many improvments and optimization. Download "Code Snippets Tags" plugin to add tags to snippets
224
 
225
  = 1.6 =
226
  Improvements and optimization with WordPress 3.5
4
  Tags: code-snippets, snippets, code, php, network, multisite
5
  Requires at least: 3.3
6
  Tested up to: 3.6
7
+ Stable tag: 1.8
8
  License: MIT
9
  License URI: license.txt
10
 
18
  Most snippet-hosting sites tell you to add snippet code to your active theme's `functions.php` file, which can get rather long and messy after a while.
19
  Code Snippets changes that by providing a GUI interface for adding snippets and **actually running them on your site** as if they were in your theme's `functions.php` file.
20
 
21
+ You can use a graphical interface, similar to the Plugins menu, to manage, activate, deactivate, edit and delete your snippets. Easily organize your snippets by adding a name and description using the visual editor. Code Snippets includes built-in syntax highlighting and other features to help you write your code. Snippets can be exported for transfer to another side, either in XML for later importing by the Code Snippets plugin, or in PHP for creating your own plugin or theme.
22
 
23
  Although Code Snippets is designed to be easy-to-use and its interface looks, feels and acts as if it was a native part of WordPress, each screen includes a help tab, just in case you get stuck.
24
 
25
+ An addon-plugin for Code Snippets is available: [Code Snippets Tags](http://wordpress.org/plugins/code-snippets-tags) will allow you to assign tags to your snippets and organize them in the table.
26
 
27
  Further information, documentation and updates are available on the [plugin homepage](http://code-snippets.bungeshea.com). You can also contribute to the code at [GitHub](https://github.com/bungeshea/code-snippets).
28
 
90
  You can run snippets across an entire multisite network by **Network Activating** Code Snippets through the Network Dashboard. You can also activate Code Snippets just on the main site, and then individually on other sites of your choice.
91
 
92
  = Is there anyway to add categories to snippets? =
93
+ Users of Code Snippets version 1.7 and later can install the [Code Snippets Tags](http://wordpress.org/plugins/code-snippets-tags) plugin for the ability to add tags to snippets, and then later filter the snippets by tag for easier organization.
94
 
95
  = I need help with Code Snippets =
96
  You can get help with Code Snippets either on the [WordPress Support Forums](http://wordpress.org/support/plugin/code-snippets/), on [GithHub](https://github.com/bungeshea/code-snippets/issues), or on [WordPress Answers](http://wordpress.stackexchange.com).
111
 
112
  == Changelog ==
113
 
114
+ = 1.8 =
115
+ * Allow no snippet name or code to be set
116
+ * Prevented an error on fresh multisite installations
117
+ * Refactored code to use best practices
118
+ * Improved database table creation method: on a single-site install, the snippets table will always be created. On a multisite install, the network snippets table will always be created; the site-specific table will always be created for the main site; for sub-sites the snippets table will only be created on a visit to a snippets admin page.
119
+ * Updated to CodeMirror 3.14
120
+ * Changes to action and filter hook API
121
+ * Added error message handling for import snippets page
122
+ * Don't encode HTML entities in database
123
+
124
  = 1.7.1.2 =
125
  * Correct path to admin menu icon ([#](http://wordpress.org/support/topic/icon-disappears-with-mp6?replies=6#post-4148319))
126
 
220
 
221
  == Upgrade Notice ==
222
 
223
+ = 1.8 =
224
+ Setting a snippet name and code are now optional; better table creation method; changes to API; bug fixes
225
+
226
  = 1.7.1.2 =
227
  Fixes the admin menu icon not loading
228
 
233
  Added German translation thanks to David Decker; bug fixes and improvements
234
 
235
  = 1.7 =
236
+ Many improvements and optimization. Download "Code Snippets Tags" plugin to add tags to snippets
237
 
238
  = 1.6 =
239
  Improvements and optimization with WordPress 3.5
screenshot-1.jpg DELETED
Binary file
screenshot-1.png ADDED
Binary file
screenshot-2.jpg DELETED
Binary file
screenshot-2.png ADDED
Binary file
screenshot-3.jpg DELETED
Binary file
screenshot-3.png ADDED
Binary file
screenshot-4.jpg DELETED
Binary file
screenshot-4.png ADDED
Binary file
screenshot-5.jpg DELETED
Binary file
screenshot-5.png ADDED
Binary file
vendor/codemirror/addon/matchbrackets.js CHANGED
@@ -1,74 +1,86 @@
1
- (function() {
2
- var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
3
- (document.documentMode == null || document.documentMode < 8);
4
-
5
- var Pos = CodeMirror.Pos;
6
- // Disable brace matching in long lines, since it'll cause hugely slow updates
7
- var maxLineLen = 1000;
8
-
9
- var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
10
- function findMatchingBracket(cm) {
11
- var cur = cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1;
12
- var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
13
- if (!match) return null;
14
- var forward = match.charAt(1) == ">", d = forward ? 1 : -1;
15
- var style = cm.getTokenAt(Pos(cur.line, pos + 1)).type;
16
-
17
- var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
18
- function scan(line, lineNo, start) {
19
- if (!line.text) return;
20
- var pos = forward ? 0 : line.text.length - 1, end = forward ? line.text.length : -1;
21
- if (start != null) pos = start + d;
22
- for (; pos != end; pos += d) {
23
- var ch = line.text.charAt(pos);
24
- if (re.test(ch) && cm.getTokenAt(Pos(lineNo, pos + 1)).type == style) {
25
- var match = matching[ch];
26
- if (match.charAt(1) == ">" == forward) stack.push(ch);
27
- else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
28
- else if (!stack.length) return {pos: pos, match: true};
29
- }
30
- }
31
- }
32
- for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) {
33
- if (i == cur.line) found = scan(line, i, pos);
34
- else found = scan(cm.getLineHandle(i), i);
35
- if (found) break;
36
- }
37
- return {from: Pos(cur.line, pos), to: found && Pos(i, found.pos), match: found && found.match};
38
- }
39
-
40
- function matchBrackets(cm, autoclear) {
41
- var found = findMatchingBracket(cm);
42
- if (!found || cm.getLine(found.from.line).length > maxLineLen ||
43
- found.to && cm.getLine(found.to.line).length > maxLineLen)
44
- return;
45
-
46
- var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
47
- var one = cm.markText(found.from, Pos(found.from.line, found.from.ch + 1), {className: style});
48
- var two = found.to && cm.markText(found.to, Pos(found.to.line, found.to.ch + 1), {className: style});
49
- // Kludge to work around the IE bug from issue #1193, where text
50
- // input stops going to the textare whever this fires.
51
- if (ie_lt8 && cm.state.focused) cm.display.input.focus();
52
- var clear = function() {
53
- cm.operation(function() { one.clear(); two && two.clear(); });
54
- };
55
- if (autoclear) setTimeout(clear, 800);
56
- else return clear;
57
- }
58
-
59
- var currentlyHighlighted = null;
60
- function doMatchBrackets(cm) {
61
- cm.operation(function() {
62
- if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
63
- if (!cm.somethingSelected()) currentlyHighlighted = matchBrackets(cm, false);
64
- });
65
- }
66
-
67
- CodeMirror.defineOption("matchBrackets", false, function(cm, val) {
68
- if (val) cm.on("cursorActivity", doMatchBrackets);
69
- else cm.off("cursorActivity", doMatchBrackets);
70
- });
71
-
72
- CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
73
- CodeMirror.defineExtension("findMatchingBracket", function(){return findMatchingBracket(this);});
74
- })();
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
3
+ (document.documentMode == null || document.documentMode < 8);
4
+
5
+ var Pos = CodeMirror.Pos;
6
+
7
+ var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
8
+ function findMatchingBracket(cm, where, strict) {
9
+ var state = cm.state.matchBrackets;
10
+ var maxScanLen = (state && state.maxScanLineLength) || 10000;
11
+
12
+ var cur = where || cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1;
13
+ var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
14
+ if (!match) return null;
15
+ var forward = match.charAt(1) == ">", d = forward ? 1 : -1;
16
+ if (strict && forward != (pos == cur.ch)) return null;
17
+ var style = cm.getTokenTypeAt(Pos(cur.line, pos + 1));
18
+
19
+ var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
20
+ function scan(line, lineNo, start) {
21
+ if (!line.text) return;
22
+ var pos = forward ? 0 : line.text.length - 1, end = forward ? line.text.length : -1;
23
+ if (line.text.length > maxScanLen) return null;
24
+ if (start != null) pos = start + d;
25
+ for (; pos != end; pos += d) {
26
+ var ch = line.text.charAt(pos);
27
+ if (re.test(ch) && cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style) {
28
+ var match = matching[ch];
29
+ if (match.charAt(1) == ">" == forward) stack.push(ch);
30
+ else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
31
+ else if (!stack.length) return {pos: pos, match: true};
32
+ }
33
+ }
34
+ }
35
+ for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) {
36
+ if (i == cur.line) found = scan(line, i, pos);
37
+ else found = scan(cm.getLineHandle(i), i);
38
+ if (found) break;
39
+ }
40
+ return {from: Pos(cur.line, pos), to: found && Pos(i, found.pos),
41
+ match: found && found.match, forward: forward};
42
+ }
43
+
44
+ function matchBrackets(cm, autoclear) {
45
+ // Disable brace matching in long lines, since it'll cause hugely slow updates
46
+ var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
47
+ var found = findMatchingBracket(cm);
48
+ if (!found || cm.getLine(found.from.line).length > maxHighlightLen ||
49
+ found.to && cm.getLine(found.to.line).length > maxHighlightLen)
50
+ return;
51
+
52
+ var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
53
+ var one = cm.markText(found.from, Pos(found.from.line, found.from.ch + 1), {className: style});
54
+ var two = found.to && cm.markText(found.to, Pos(found.to.line, found.to.ch + 1), {className: style});
55
+ // Kludge to work around the IE bug from issue #1193, where text
56
+ // input stops going to the textare whever this fires.
57
+ if (ie_lt8 && cm.state.focused) cm.display.input.focus();
58
+ var clear = function() {
59
+ cm.operation(function() { one.clear(); two && two.clear(); });
60
+ };
61
+ if (autoclear) setTimeout(clear, 800);
62
+ else return clear;
63
+ }
64
+
65
+ var currentlyHighlighted = null;
66
+ function doMatchBrackets(cm) {
67
+ cm.operation(function() {
68
+ if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
69
+ if (!cm.somethingSelected()) currentlyHighlighted = matchBrackets(cm, false);
70
+ });
71
+ }
72
+
73
+ CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
74
+ if (old && old != CodeMirror.Init)
75
+ cm.off("cursorActivity", doMatchBrackets);
76
+ if (val) {
77
+ cm.state.matchBrackets = typeof val == "object" ? val : {};
78
+ cm.on("cursorActivity", doMatchBrackets);
79
+ }
80
+ });
81
+
82
+ CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
83
+ CodeMirror.defineExtension("findMatchingBracket", function(pos, strict){
84
+ return findMatchingBracket(this, pos, strict);
85
+ });
86
+ })();
vendor/codemirror/addon/search.js CHANGED
@@ -1,131 +1,131 @@
1
- // Define search commands. Depends on dialog.js or another
2
- // implementation of the openDialog method.
3
-
4
- // Replace works a little oddly -- it will do the replace on the next
5
- // Ctrl-G (or whatever is bound to findNext) press. You prevent a
6
- // replace by making sure the match is no longer selected when hitting
7
- // Ctrl-G.
8
-
9
- (function() {
10
- function searchOverlay(query) {
11
- if (typeof query == "string") return {token: function(stream) {
12
- if (stream.match(query)) return "searching";
13
- stream.next();
14
- stream.skipTo(query.charAt(0)) || stream.skipToEnd();
15
- }};
16
- return {token: function(stream) {
17
- if (stream.match(query)) return "searching";
18
- while (!stream.eol()) {
19
- stream.next();
20
- if (stream.match(query, false)) break;
21
- }
22
- }};
23
- }
24
-
25
- function SearchState() {
26
- this.posFrom = this.posTo = this.query = null;
27
- this.overlay = null;
28
- }
29
- function getSearchState(cm) {
30
- return cm._searchState || (cm._searchState = new SearchState());
31
- }
32
- function getSearchCursor(cm, query, pos) {
33
- // Heuristic: if the query string is all lowercase, do a case insensitive search.
34
- return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase());
35
- }
36
- function dialog(cm, text, shortText, f) {
37
- if (cm.openDialog) cm.openDialog(text, f);
38
- else f(prompt(shortText, ""));
39
- }
40
- function confirmDialog(cm, text, shortText, fs) {
41
- if (cm.openConfirm) cm.openConfirm(text, fs);
42
- else if (confirm(shortText)) fs[0]();
43
- }
44
- function parseQuery(query) {
45
- var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
46
- return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
47
- }
48
- var queryDialog =
49
- 'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
50
- function doSearch(cm, rev) {
51
- var state = getSearchState(cm);
52
- if (state.query) return findNext(cm, rev);
53
- dialog(cm, queryDialog, "Search for:", function(query) {
54
- cm.operation(function() {
55
- if (!query || state.query) return;
56
- state.query = parseQuery(query);
57
- cm.removeOverlay(state.overlay);
58
- state.overlay = searchOverlay(query);
59
- cm.addOverlay(state.overlay);
60
- state.posFrom = state.posTo = cm.getCursor();
61
- findNext(cm, rev);
62
- });
63
- });
64
- }
65
- function findNext(cm, rev) {cm.operation(function() {
66
- var state = getSearchState(cm);
67
- var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
68
- if (!cursor.find(rev)) {
69
- cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
70
- if (!cursor.find(rev)) return;
71
- }
72
- cm.setSelection(cursor.from(), cursor.to());
73
- state.posFrom = cursor.from(); state.posTo = cursor.to();
74
- });}
75
- function clearSearch(cm) {cm.operation(function() {
76
- var state = getSearchState(cm);
77
- if (!state.query) return;
78
- state.query = null;
79
- cm.removeOverlay(state.overlay);
80
- });}
81
-
82
- var replaceQueryDialog =
83
- 'Replace: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
84
- var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
85
- var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
86
- function replace(cm, all) {
87
- dialog(cm, replaceQueryDialog, "Replace:", function(query) {
88
- if (!query) return;
89
- query = parseQuery(query);
90
- dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
91
- if (all) {
92
- cm.operation(function() {
93
- for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
94
- if (typeof query != "string") {
95
- var match = cm.getRange(cursor.from(), cursor.to()).match(query);
96
- cursor.replace(text.replace(/\$(\d)/, function(_, i) {return match[i];}));
97
- } else cursor.replace(text);
98
- }
99
- });
100
- } else {
101
- clearSearch(cm);
102
- var cursor = getSearchCursor(cm, query, cm.getCursor());
103
- var advance = function() {
104
- var start = cursor.from(), match;
105
- if (!(match = cursor.findNext())) {
106
- cursor = getSearchCursor(cm, query);
107
- if (!(match = cursor.findNext()) ||
108
- (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
109
- }
110
- cm.setSelection(cursor.from(), cursor.to());
111
- confirmDialog(cm, doReplaceConfirm, "Replace?",
112
- [function() {doReplace(match);}, advance]);
113
- };
114
- var doReplace = function(match) {
115
- cursor.replace(typeof query == "string" ? text :
116
- text.replace(/\$(\d)/, function(_, i) {return match[i];}));
117
- advance();
118
- };
119
- advance();
120
- }
121
- });
122
- });
123
- }
124
-
125
- CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
126
- CodeMirror.commands.findNext = doSearch;
127
- CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
128
- CodeMirror.commands.clearSearch = clearSearch;
129
- CodeMirror.commands.replace = replace;
130
- CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
131
- })();
1
+ // Define search commands. Depends on dialog.js or another
2
+ // implementation of the openDialog method.
3
+
4
+ // Replace works a little oddly -- it will do the replace on the next
5
+ // Ctrl-G (or whatever is bound to findNext) press. You prevent a
6
+ // replace by making sure the match is no longer selected when hitting
7
+ // Ctrl-G.
8
+
9
+ (function() {
10
+ function searchOverlay(query) {
11
+ if (typeof query == "string") return {token: function(stream) {
12
+ if (stream.match(query)) return "searching";
13
+ stream.next();
14
+ stream.skipTo(query.charAt(0)) || stream.skipToEnd();
15
+ }};
16
+ return {token: function(stream) {
17
+ if (stream.match(query)) return "searching";
18
+ while (!stream.eol()) {
19
+ stream.next();
20
+ if (stream.match(query, false)) break;
21
+ }
22
+ }};
23
+ }
24
+
25
+ function SearchState() {
26
+ this.posFrom = this.posTo = this.query = null;
27
+ this.overlay = null;
28
+ }
29
+ function getSearchState(cm) {
30
+ return cm.state.search || (cm.state.search = new SearchState());
31
+ }
32
+ function getSearchCursor(cm, query, pos) {
33
+ // Heuristic: if the query string is all lowercase, do a case insensitive search.
34
+ return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase());
35
+ }
36
+ function dialog(cm, text, shortText, f) {
37
+ if (cm.openDialog) cm.openDialog(text, f);
38
+ else f(prompt(shortText, ""));
39
+ }
40
+ function confirmDialog(cm, text, shortText, fs) {
41
+ if (cm.openConfirm) cm.openConfirm(text, fs);
42
+ else if (confirm(shortText)) fs[0]();
43
+ }
44
+ function parseQuery(query) {
45
+ var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
46
+ return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
47
+ }
48
+ var queryDialog =
49
+ 'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
50
+ function doSearch(cm, rev) {
51
+ var state = getSearchState(cm);
52
+ if (state.query) return findNext(cm, rev);
53
+ dialog(cm, queryDialog, "Search for:", function(query) {
54
+ cm.operation(function() {
55
+ if (!query || state.query) return;
56
+ state.query = parseQuery(query);
57
+ cm.removeOverlay(state.overlay);
58
+ state.overlay = searchOverlay(state.query);
59
+ cm.addOverlay(state.overlay);
60
+ state.posFrom = state.posTo = cm.getCursor();
61
+ findNext(cm, rev);
62
+ });
63
+ });
64
+ }
65
+ function findNext(cm, rev) {cm.operation(function() {
66
+ var state = getSearchState(cm);
67
+ var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
68
+ if (!cursor.find(rev)) {
69
+ cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
70
+ if (!cursor.find(rev)) return;
71
+ }
72
+ cm.setSelection(cursor.from(), cursor.to());
73
+ state.posFrom = cursor.from(); state.posTo = cursor.to();
74
+ });}
75
+ function clearSearch(cm) {cm.operation(function() {
76
+ var state = getSearchState(cm);
77
+ if (!state.query) return;
78
+ state.query = null;
79
+ cm.removeOverlay(state.overlay);
80
+ });}
81
+
82
+ var replaceQueryDialog =
83
+ 'Replace: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
84
+ var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
85
+ var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
86
+ function replace(cm, all) {
87
+ dialog(cm, replaceQueryDialog, "Replace:", function(query) {
88
+ if (!query) return;
89
+ query = parseQuery(query);
90
+ dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
91
+ if (all) {
92
+ cm.operation(function() {
93
+ for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
94
+ if (typeof query != "string") {
95
+ var match = cm.getRange(cursor.from(), cursor.to()).match(query);
96
+ cursor.replace(text.replace(/\$(\d)/, function(_, i) {return match[i];}));
97
+ } else cursor.replace(text);
98
+ }
99
+ });
100
+ } else {
101
+ clearSearch(cm);
102
+ var cursor = getSearchCursor(cm, query, cm.getCursor());
103
+ var advance = function() {
104
+ var start = cursor.from(), match;
105
+ if (!(match = cursor.findNext())) {
106
+ cursor = getSearchCursor(cm, query);
107
+ if (!(match = cursor.findNext()) ||
108
+ (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
109
+ }
110
+ cm.setSelection(cursor.from(), cursor.to());
111
+ confirmDialog(cm, doReplaceConfirm, "Replace?",
112
+ [function() {doReplace(match);}, advance]);
113
+ };
114
+ var doReplace = function(match) {
115
+ cursor.replace(typeof query == "string" ? text :
116
+ text.replace(/\$(\d)/, function(_, i) {return match[i];}));
117
+ advance();
118
+ };
119
+ advance();
120
+ }
121
+ });
122
+ });
123
+ }
124
+
125
+ CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
126
+ CodeMirror.commands.findNext = doSearch;
127
+ CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
128
+ CodeMirror.commands.clearSearch = clearSearch;
129
+ CodeMirror.commands.replace = replace;
130
+ CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
131
+ })();
vendor/codemirror/addon/searchcursor.js CHANGED
@@ -1,130 +1,143 @@
1
- (function(){
2
- var Pos = CodeMirror.Pos;
3
-
4
- function SearchCursor(cm, query, pos, caseFold) {
5
- this.atOccurrence = false; this.cm = cm;
6
- if (caseFold == null && typeof query == "string") caseFold = false;
7
-
8
- pos = pos ? cm.clipPos(pos) : Pos(0, 0);
9
- this.pos = {from: pos, to: pos};
10
-
11
- // The matches method is filled in based on the type of query.
12
- // It takes a position and a direction, and returns an object
13
- // describing the next occurrence of the query, or null if no
14
- // more matches were found.
15
- if (typeof query != "string") { // Regexp match
16
- if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
17
- this.matches = function(reverse, pos) {
18
- if (reverse) {
19
- query.lastIndex = 0;
20
- var line = cm.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
21
- for (;;) {
22
- query.lastIndex = cutOff;
23
- var newMatch = query.exec(line);
24
- if (!newMatch) break;
25
- match = newMatch;
26
- start = match.index;
27
- cutOff = match.index + 1;
28
- }
29
- } else {
30
- query.lastIndex = pos.ch;
31
- var line = cm.getLine(pos.line), match = query.exec(line),
32
- start = match && match.index;
33
- }
34
- if (match && match[0])
35
- return {from: Pos(pos.line, start),
36
- to: Pos(pos.line, start + match[0].length),
37
- match: match};
38
- };
39
- } else { // String query
40
- if (caseFold) query = query.toLowerCase();
41
- var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
42
- var target = query.split("\n");
43
- // Different methods for single-line and multi-line queries
44
- if (target.length == 1) {
45
- if (!query.length) {
46
- // Empty string would match anything and never progress, so
47
- // we define it to match nothing instead.
48
- this.matches = function() {};
49
- } else {
50
- this.matches = function(reverse, pos) {
51
- var line = fold(cm.getLine(pos.line)), len = query.length, match;
52
- if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
53
- : (match = line.indexOf(query, pos.ch)) != -1)
54
- return {from: Pos(pos.line, match),
55
- to: Pos(pos.line, match + len)};
56
- };
57
- }
58
- } else {
59
- this.matches = function(reverse, pos) {
60
- var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(cm.getLine(ln));
61
- var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
62
- if (reverse ? offsetA >= pos.ch || offsetA != match.length
63
- : offsetA <= pos.ch || offsetA != line.length - match.length)
64
- return;
65
- for (;;) {
66
- if (reverse ? !ln : ln == cm.lineCount() - 1) return;
67
- line = fold(cm.getLine(ln += reverse ? -1 : 1));
68
- match = target[reverse ? --idx : ++idx];
69
- if (idx > 0 && idx < target.length - 1) {
70
- if (line != match) return;
71
- else continue;
72
- }
73
- var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
74
- if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
75
- return;
76
- var start = Pos(pos.line, offsetA), end = Pos(ln, offsetB);
77
- return {from: reverse ? end : start, to: reverse ? start : end};
78
- }
79
- };
80
- }
81
- }
82
- }
83
-
84
- SearchCursor.prototype = {
85
- findNext: function() {return this.find(false);},
86
- findPrevious: function() {return this.find(true);},
87
-
88
- find: function(reverse) {
89
- var self = this, pos = this.cm.clipPos(reverse ? this.pos.from : this.pos.to);
90
- function savePosAndFail(line) {
91
- var pos = Pos(line, 0);
92
- self.pos = {from: pos, to: pos};
93
- self.atOccurrence = false;
94
- return false;
95
- }
96
-
97
- for (;;) {
98
- if (this.pos = this.matches(reverse, pos)) {
99
- if (!this.pos.from || !this.pos.to) { console.log(this.matches, this.pos); }
100
- this.atOccurrence = true;
101
- return this.pos.match || true;
102
- }
103
- if (reverse) {
104
- if (!pos.line) return savePosAndFail(0);
105
- pos = Pos(pos.line-1, this.cm.getLine(pos.line-1).length);
106
- }
107
- else {
108
- var maxLine = this.cm.lineCount();
109
- if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
110
- pos = Pos(pos.line + 1, 0);
111
- }
112
- }
113
- },
114
-
115
- from: function() {if (this.atOccurrence) return this.pos.from;},
116
- to: function() {if (this.atOccurrence) return this.pos.to;},
117
-
118
- replace: function(newText) {
119
- if (!this.atOccurrence) return;
120
- var lines = CodeMirror.splitLines(newText);
121
- this.cm.replaceRange(lines, this.pos.from, this.pos.to);
122
- this.pos.to = Pos(this.pos.from.line + lines.length - 1,
123
- lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
124
- }
125
- };
126
-
127
- CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
128
- return new SearchCursor(this, query, pos, caseFold);
129
- });
130
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function(){
2
+ var Pos = CodeMirror.Pos;
3
+
4
+ function SearchCursor(doc, query, pos, caseFold) {
5
+ this.atOccurrence = false; this.doc = doc;
6
+ if (caseFold == null && typeof query == "string") caseFold = false;
7
+
8
+ pos = pos ? doc.clipPos(pos) : Pos(0, 0);
9
+ this.pos = {from: pos, to: pos};
10
+
11
+ // The matches method is filled in based on the type of query.
12
+ // It takes a position and a direction, and returns an object
13
+ // describing the next occurrence of the query, or null if no
14
+ // more matches were found.
15
+ if (typeof query != "string") { // Regexp match
16
+ if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
17
+ this.matches = function(reverse, pos) {
18
+ if (reverse) {
19
+ query.lastIndex = 0;
20
+ var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
21
+ for (;;) {
22
+ query.lastIndex = cutOff;
23
+ var newMatch = query.exec(line);
24
+ if (!newMatch) break;
25
+ match = newMatch;
26
+ start = match.index;
27
+ cutOff = match.index + (match[0].length || 1);
28
+ if (cutOff == line.length) break;
29
+ }
30
+ var matchLen = (match && match[0].length) || 0;
31
+ if (!matchLen) {
32
+ if (start == 0 && line.length == 0) {match = undefined;}
33
+ else if (start != doc.getLine(pos.line).length) {
34
+ matchLen++;
35
+ }
36
+ }
37
+ } else {
38
+ query.lastIndex = pos.ch;
39
+ var line = doc.getLine(pos.line), match = query.exec(line);
40
+ var matchLen = (match && match[0].length) || 0;
41
+ var start = match && match.index;
42
+ if (start + matchLen != line.length && !matchLen) matchLen = 1;
43
+ }
44
+ if (match && matchLen)
45
+ return {from: Pos(pos.line, start),
46
+ to: Pos(pos.line, start + matchLen),
47
+ match: match};
48
+ };
49
+ } else { // String query
50
+ if (caseFold) query = query.toLowerCase();
51
+ var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
52
+ var target = query.split("\n");
53
+ // Different methods for single-line and multi-line queries
54
+ if (target.length == 1) {
55
+ if (!query.length) {
56
+ // Empty string would match anything and never progress, so
57
+ // we define it to match nothing instead.
58
+ this.matches = function() {};
59
+ } else {
60
+ this.matches = function(reverse, pos) {
61
+ var line = fold(doc.getLine(pos.line)), len = query.length, match;
62
+ if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
63
+ : (match = line.indexOf(query, pos.ch)) != -1)
64
+ return {from: Pos(pos.line, match),
65
+ to: Pos(pos.line, match + len)};
66
+ };
67
+ }
68
+ } else {
69
+ this.matches = function(reverse, pos) {
70
+ var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(doc.getLine(ln));
71
+ var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
72
+ if (reverse ? offsetA >= pos.ch || offsetA != match.length
73
+ : offsetA <= pos.ch || offsetA != line.length - match.length)
74
+ return;
75
+ for (;;) {
76
+ if (reverse ? !ln : ln == doc.lineCount() - 1) return;
77
+ line = fold(doc.getLine(ln += reverse ? -1 : 1));
78
+ match = target[reverse ? --idx : ++idx];
79
+ if (idx > 0 && idx < target.length - 1) {
80
+ if (line != match) return;
81
+ else continue;
82
+ }
83
+ var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
84
+ if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
85
+ return;
86
+ var start = Pos(pos.line, offsetA), end = Pos(ln, offsetB);
87
+ return {from: reverse ? end : start, to: reverse ? start : end};
88
+ }
89
+ };
90
+ }
91
+ }
92
+ }
93
+
94
+ SearchCursor.prototype = {
95
+ findNext: function() {return this.find(false);},
96
+ findPrevious: function() {return this.find(true);},
97
+
98
+ find: function(reverse) {
99
+ var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
100
+ function savePosAndFail(line) {
101
+ var pos = Pos(line, 0);
102
+ self.pos = {from: pos, to: pos};
103
+ self.atOccurrence = false;
104
+ return false;
105
+ }
106
+
107
+ for (;;) {
108
+ if (this.pos = this.matches(reverse, pos)) {
109
+ if (!this.pos.from || !this.pos.to) { console.log(this.matches, this.pos); }
110
+ this.atOccurrence = true;
111
+ return this.pos.match || true;
112
+ }
113
+ if (reverse) {
114
+ if (!pos.line) return savePosAndFail(0);
115
+ pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
116
+ }
117
+ else {
118
+ var maxLine = this.doc.lineCount();
119
+ if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
120
+ pos = Pos(pos.line + 1, 0);
121
+ }
122
+ }
123
+ },
124
+
125
+ from: function() {if (this.atOccurrence) return this.pos.from;},
126
+ to: function() {if (this.atOccurrence) return this.pos.to;},
127
+
128
+ replace: function(newText) {
129
+ if (!this.atOccurrence) return;
130
+ var lines = CodeMirror.splitLines(newText);
131
+ this.doc.replaceRange(lines, this.pos.from, this.pos.to);
132
+ this.pos.to = Pos(this.pos.from.line + lines.length - 1,
133
+ lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
134
+ }
135
+ };
136
+
137
+ CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
138
+ return new SearchCursor(this.doc, query, pos, caseFold);
139
+ });
140
+ CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
141
+ return new SearchCursor(this, query, pos, caseFold);
142
+ });
143
+ })();
vendor/codemirror/lib/codemirror.css CHANGED
@@ -1,246 +1,248 @@
1
- /* BASICS */
2
-
3
- .CodeMirror {
4
- /* Set height, width, borders, and global font properties here */
5
- font-family: monospace;
6
- height: 300px;
7
- }
8
- .CodeMirror-scroll {
9
- /* Set scrolling behaviour here */
10
- overflow: auto;
11
- }
12
-
13
- /* PADDING */
14
-
15
- .CodeMirror-lines {
16
- padding: 4px 0; /* Vertical padding around content */
17
- }
18
- .CodeMirror pre {
19
- padding: 0 4px; /* Horizontal padding of content */
20
- }
21
-
22
- .CodeMirror-scrollbar-filler {
23
- background-color: white; /* The little square between H and V scrollbars */
24
- }
25
-
26
- /* GUTTER */
27
-
28
- .CodeMirror-gutters {
29
- border-right: 1px solid #ddd;
30
- background-color: #f7f7f7;
31
- }
32
- .CodeMirror-linenumbers {}
33
- .CodeMirror-linenumber {
34
- padding: 0 3px 0 5px;
35
- min-width: 20px;
36
- text-align: right;
37
- color: #999;
38
- }
39
-
40
- /* CURSOR */
41
-
42
- .CodeMirror div.CodeMirror-cursor {
43
- border-left: 1px solid black;
44
- z-index: 3;
45
- }
46
- /* Shown when moving in bi-directional text */
47
- .CodeMirror div.CodeMirror-secondarycursor {
48
- border-left: 1px solid silver;
49
- }
50
- .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
51
- width: auto;
52
- border: 0;
53
- background: #7e7;
54
- z-index: 1;
55
- }
56
- /* Can style cursor different in overwrite (non-insert) mode */
57
- .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
58
-
59
- .cm-tab { display: inline-block; }
60
-
61
- /* DEFAULT THEME */
62
-
63
- .cm-s-default .cm-keyword {color: #708;}
64
- .cm-s-default .cm-atom {color: #219;}
65
- .cm-s-default .cm-number {color: #164;}
66
- .cm-s-default .cm-def {color: #00f;}
67
- .cm-s-default .cm-variable {color: black;}
68
- .cm-s-default .cm-variable-2 {color: #05a;}
69
- .cm-s-default .cm-variable-3 {color: #085;}
70
- .cm-s-default .cm-property {color: black;}
71
- .cm-s-default .cm-operator {color: black;}
72
- .cm-s-default .cm-comment {color: #a50;}
73
- .cm-s-default .cm-string {color: #a11;}
74
- .cm-s-default .cm-string-2 {color: #f50;}
75
- .cm-s-default .cm-meta {color: #555;}
76
- .cm-s-default .cm-error {color: #f00;}
77
- .cm-s-default .cm-qualifier {color: #555;}
78
- .cm-s-default .cm-builtin {color: #30a;}
79
- .cm-s-default .cm-bracket {color: #997;}
80
- .cm-s-default .cm-tag {color: #170;}
81
- .cm-s-default .cm-attribute {color: #00c;}
82
- .cm-s-default .cm-header {color: blue;}
83
- .cm-s-default .cm-quote {color: #090;}
84
- .cm-s-default .cm-hr {color: #999;}
85
- .cm-s-default .cm-link {color: #00c;}
86
-
87
- .cm-negative {color: #d44;}
88
- .cm-positive {color: #292;}
89
- .cm-header, .cm-strong {font-weight: bold;}
90
- .cm-em {font-style: italic;}
91
- .cm-link {text-decoration: underline;}
92
-
93
- .cm-invalidchar {color: #f00;}
94
-
95
- div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
96
- div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
97
-
98
- /* STOP */
99
-
100
- /* The rest of this file contains styles related to the mechanics of
101
- the editor. You probably shouldn't touch them. */
102
-
103
- .CodeMirror {
104
- line-height: 1;
105
- position: relative;
106
- overflow: hidden;
107
- background: white;
108
- color: black;
109
- }
110
-
111
- .CodeMirror-scroll {
112
- /* 30px is the magic margin used to hide the element's real scrollbars */
113
- /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */
114
- margin-bottom: -30px; margin-right: -30px;
115
- padding-bottom: 30px; padding-right: 30px;
116
- height: 100%;
117
- outline: none; /* Prevent dragging from highlighting the element */
118
- position: relative;
119
- }
120
- .CodeMirror-sizer {
121
- position: relative;
122
- }
123
-
124
- /* The fake, visible scrollbars. Used to force redraw during scrolling
125
- before actuall scrolling happens, thus preventing shaking and
126
- flickering artifacts. */
127
- .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler {
128
- position: absolute;
129
- z-index: 6;
130
- display: none;
131
- }
132
- .CodeMirror-vscrollbar {
133
- right: 0; top: 0;
134
- overflow-x: hidden;
135
- overflow-y: scroll;
136
- }
137
- .CodeMirror-hscrollbar {
138
- bottom: 0; left: 0;
139
- overflow-y: hidden;
140
- overflow-x: scroll;
141
- }
142
- .CodeMirror-scrollbar-filler {
143
- right: 0; bottom: 0;
144
- z-index: 6;
145
- }
146
-
147
- .CodeMirror-gutters {
148
- position: absolute; left: 0; top: 0;
149
- height: 100%;
150
- padding-bottom: 30px;
151
- z-index: 3;
152
- }
153
- .CodeMirror-gutter {
154
- height: 100%;
155
- padding-bottom: 30px;
156
- margin-bottom: -32px;
157
- display: inline-block;
158
- /* Hack to make IE7 behave */
159
- *zoom:1;
160
- *display:inline;
161
- }
162
- .CodeMirror-gutter-elt {
163
- position: absolute;
164
- cursor: default;
165
- z-index: 4;
166
- }
167
-
168
- .CodeMirror-lines {
169
- cursor: text;
170
- }
171
- .CodeMirror pre {
172
- /* Reset some styles that the rest of the page might have set */
173
- -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
174
- border-width: 0;
175
- background: transparent;
176
- font-family: inherit;
177
- font-size: inherit;
178
- margin: 0;
179
- white-space: pre;
180
- word-wrap: normal;
181
- line-height: inherit;
182
- color: inherit;
183
- z-index: 2;
184
- position: relative;
185
- overflow: visible;
186
- }
187
- .CodeMirror-wrap pre {
188
- word-wrap: break-word;
189
- white-space: pre-wrap;
190
- word-break: normal;
191
- }
192
- .CodeMirror-linebackground {
193
- position: absolute;
194
- left: 0; right: 0; top: 0; bottom: 0;
195
- z-index: 0;
196
- }
197
-
198
- .CodeMirror-linewidget {
199
- position: relative;
200
- z-index: 2;
201
- overflow: auto;
202
- }
203
-
204
- .CodeMirror-widget {
205
- display: inline-block;
206
- }
207
-
208
- .CodeMirror-wrap .CodeMirror-scroll {
209
- overflow-x: hidden;
210
- }
211
-
212
- .CodeMirror-measure {
213
- position: absolute;
214
- width: 100%; height: 0px;
215
- overflow: hidden;
216
- visibility: hidden;
217
- }
218
- .CodeMirror-measure pre { position: static; }
219
-
220
- .CodeMirror div.CodeMirror-cursor {
221
- position: absolute;
222
- visibility: hidden;
223
- border-right: none;
224
- width: 0;
225
- }
226
- .CodeMirror-focused div.CodeMirror-cursor {
227
- visibility: visible;
228
- }
229
-
230
- .CodeMirror-selected { background: #d9d9d9; }
231
- .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
232
-
233
- .cm-searching {
234
- background: #ffa;
235
- background: rgba(255, 255, 0, .4);
236
- }
237
-
238
- /* IE7 hack to prevent it from returning funny offsetTops on the spans */
239
- .CodeMirror span { *vertical-align: text-bottom; }
240
-
241
- @media print {
242
- /* Hide the cursor when printing */
243
- .CodeMirror div.CodeMirror-cursor {
244
- visibility: hidden;
245
- }
246
- }
 
 
1
+ /* BASICS */
2
+
3
+ .CodeMirror {
4
+ /* Set height, width, borders, and global font properties here */
5
+ font-family: monospace;
6
+ height: 300px;
7
+ }
8
+ .CodeMirror-scroll {
9
+ /* Set scrolling behaviour here */
10
+ overflow: auto;
11
+ }
12
+
13
+ /* PADDING */
14
+
15
+ .CodeMirror-lines {
16
+ padding: 4px 0; /* Vertical padding around content */
17
+ }
18
+ .CodeMirror pre {
19
+ padding: 0 4px; /* Horizontal padding of content */
20
+ }
21
+
22
+ .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
23
+ background-color: white; /* The little square between H and V scrollbars */
24
+ }
25
+
26
+ /* GUTTER */
27
+
28
+ .CodeMirror-gutters {
29
+ border-right: 1px solid #ddd;
30
+ background-color: #f7f7f7;
31
+ white-space: nowrap;
32
+ }
33
+ .CodeMirror-linenumbers {}
34
+ .CodeMirror-linenumber {
35
+ padding: 0 3px 0 5px;
36
+ min-width: 20px;
37
+ text-align: right;
38
+ color: #999;
39
+ }
40
+
41
+ /* CURSOR */
42
+
43
+ .CodeMirror div.CodeMirror-cursor {
44
+ border-left: 1px solid black;
45
+ z-index: 3;
46
+ }
47
+ /* Shown when moving in bi-directional text */
48
+ .CodeMirror div.CodeMirror-secondarycursor {
49
+ border-left: 1px solid silver;
50
+ }
51
+ .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
52
+ width: auto;
53
+ border: 0;
54
+ background: #7e7;
55
+ z-index: 1;
56
+ }
57
+ /* Can style cursor different in overwrite (non-insert) mode */
58
+ .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
59
+
60
+ .cm-tab { display: inline-block; }
61
+
62
+ /* DEFAULT THEME */
63
+
64
+ .cm-s-default .cm-keyword {color: #708;}
65
+ .cm-s-default .cm-atom {color: #219;}
66
+ .cm-s-default .cm-number {color: #164;}
67
+ .cm-s-default .cm-def {color: #00f;}
68
+ .cm-s-default .cm-variable {color: black;}
69
+ .cm-s-default .cm-variable-2 {color: #05a;}
70
+ .cm-s-default .cm-variable-3 {color: #085;}
71
+ .cm-s-default .cm-property {color: black;}
72
+ .cm-s-default .cm-operator {color: black;}
73
+ .cm-s-default .cm-comment {color: #a50;}
74
+ .cm-s-default .cm-string {color: #a11;}
75
+ .cm-s-default .cm-string-2 {color: #f50;}
76
+ .cm-s-default .cm-meta {color: #555;}
77
+ .cm-s-default .cm-error {color: #f00;}
78
+ .cm-s-default .cm-qualifier {color: #555;}
79
+ .cm-s-default .cm-builtin {color: #30a;}
80
+ .cm-s-default .cm-bracket {color: #997;}
81
+ .cm-s-default .cm-tag {color: #170;}
82
+ .cm-s-default .cm-attribute {color: #00c;}
83
+ .cm-s-default .cm-header {color: blue;}
84
+ .cm-s-default .cm-quote {color: #090;}
85
+ .cm-s-default .cm-hr {color: #999;}
86
+ .cm-s-default .cm-link {color: #00c;}
87
+
88
+ .cm-negative {color: #d44;}
89
+ .cm-positive {color: #292;}
90
+ .cm-header, .cm-strong {font-weight: bold;}
91
+ .cm-em {font-style: italic;}
92
+ .cm-link {text-decoration: underline;}
93
+
94
+ .cm-invalidchar {color: #f00;}
95
+
96
+ div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
97
+ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
98
+
99
+ /* STOP */
100
+
101
+ /* The rest of this file contains styles related to the mechanics of
102
+ the editor. You probably shouldn't touch them. */
103
+
104
+ .CodeMirror {
105
+ line-height: 1;
106
+ position: relative;
107
+ overflow: hidden;
108
+ background: white;
109
+ color: black;
110
+ }
111
+
112
+ .CodeMirror-scroll {
113
+ /* 30px is the magic margin used to hide the element's real scrollbars */
114
+ /* See overflow: hidden in .CodeMirror */
115
+ margin-bottom: -30px; margin-right: -30px;
116
+ padding-bottom: 30px; padding-right: 30px;
117
+ height: 100%;
118
+ outline: none; /* Prevent dragging from highlighting the element */
119
+ position: relative;
120
+ }
121
+ .CodeMirror-sizer {
122
+ position: relative;
123
+ }
124
+
125
+ /* The fake, visible scrollbars. Used to force redraw during scrolling
126
+ before actuall scrolling happens, thus preventing shaking and
127
+ flickering artifacts. */
128
+ .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
129
+ position: absolute;
130
+ z-index: 6;
131
+ display: none;
132
+ }
133
+ .CodeMirror-vscrollbar {
134
+ right: 0; top: 0;
135
+ overflow-x: hidden;
136
+ overflow-y: scroll;
137
+ }
138
+ .CodeMirror-hscrollbar {
139
+ bottom: 0; left: 0;
140
+ overflow-y: hidden;
141
+ overflow-x: scroll;
142
+ }
143
+ .CodeMirror-scrollbar-filler {
144
+ right: 0; bottom: 0;
145
+ }
146
+ .CodeMirror-gutter-filler {
147
+ left: 0; bottom: 0;
148
+ }
149
+
150
+ .CodeMirror-gutters {
151
+ position: absolute; left: 0; top: 0;
152
+ padding-bottom: 30px;
153
+ z-index: 3;
154
+ }
155
+ .CodeMirror-gutter {
156
+ white-space: normal;
157
+ height: 100%;
158
+ padding-bottom: 30px;
159
+ margin-bottom: -32px;
160
+ display: inline-block;
161
+ /* Hack to make IE7 behave */
162
+ *zoom:1;
163
+ *display:inline;
164
+ }
165
+ .CodeMirror-gutter-elt {
166
+ position: absolute;
167
+ cursor: default;
168
+ z-index: 4;
169
+ }
170
+
171
+ .CodeMirror-lines {
172
+ cursor: text;
173
+ }
174
+ .CodeMirror pre {
175
+ /* Reset some styles that the rest of the page might have set */
176
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
177
+ border-width: 0;
178
+ background: transparent;
179
+ font-family: inherit;
180
+ font-size: inherit;
181
+ margin: 0;
182
+ white-space: pre;
183
+ word-wrap: normal;
184
+ line-height: inherit;
185
+ color: inherit;
186
+ z-index: 2;
187
+ position: relative;
188
+ overflow: visible;
189
+ }
190
+ .CodeMirror-wrap pre {
191
+ word-wrap: break-word;
192
+ white-space: pre-wrap;
193
+ word-break: normal;
194
+ }
195
+ .CodeMirror-linebackground {
196
+ position: absolute;
197
+ left: 0; right: 0; top: 0; bottom: 0;
198
+ z-index: 0;
199
+ }
200
+
201
+ .CodeMirror-linewidget {
202
+ position: relative;
203
+ z-index: 2;
204
+ overflow: auto;
205
+ }
206
+
207
+ .CodeMirror-widget {
208
+ }
209
+
210
+ .CodeMirror-wrap .CodeMirror-scroll {
211
+ overflow-x: hidden;
212
+ }
213
+
214
+ .CodeMirror-measure {
215
+ position: absolute;
216
+ width: 100%; height: 0px;
217
+ overflow: hidden;
218
+ visibility: hidden;
219
+ }
220
+ .CodeMirror-measure pre { position: static; }
221
+
222
+ .CodeMirror div.CodeMirror-cursor {
223
+ position: absolute;
224
+ visibility: hidden;
225
+ border-right: none;
226
+ width: 0;
227
+ }
228
+ .CodeMirror-focused div.CodeMirror-cursor {
229
+ visibility: visible;
230
+ }
231
+
232
+ .CodeMirror-selected { background: #d9d9d9; }
233
+ .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
234
+
235
+ .cm-searching {
236
+ background: #ffa;
237
+ background: rgba(255, 255, 0, .4);
238
+ }
239
+
240
+ /* IE7 hack to prevent it from returning funny offsetTops on the spans */
241
+ .CodeMirror span { *vertical-align: text-bottom; }
242
+
243
+ @media print {
244
+ /* Hide the cursor when printing */
245
+ .CodeMirror div.CodeMirror-cursor {
246
+ visibility: hidden;
247
+ }
248
+ }
vendor/codemirror/lib/codemirror.js CHANGED
@@ -1,4 +1,4 @@
1
- // CodeMirror version 3.11
2
  //
3
  // CodeMirror is the only global var we claim
4
  window.CodeMirror = (function() {
@@ -94,12 +94,12 @@ window.CodeMirror = (function() {
94
  function makeDisplay(place, docStart) {
95
  var d = {};
96
 
97
- var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;");
98
  if (webkit) input.style.width = "1000px";
99
  else input.setAttribute("wrap", "off");
100
  // if border: 0; -- iOS fails to open keyboard (issue #1287)
101
  if (ios) input.style.border = "1px solid black";
102
- input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off");
103
 
104
  // Wraps and hides input textarea
105
  d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
@@ -107,8 +107,9 @@ window.CodeMirror = (function() {
107
  d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
108
  d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
109
  d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
 
110
  // DIVs containing the selection and the actual code
111
- d.lineDiv = elt("div");
112
  d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
113
  // Blinky cursor, and element used to ensure cursor fits at the end of a line
114
  d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
@@ -128,14 +129,12 @@ window.CodeMirror = (function() {
128
  // Will contain the gutters, if any
129
  d.gutters = elt("div", null, "CodeMirror-gutters");
130
  d.lineGutter = null;
131
- // Helper element to properly size the gutter backgrounds
132
- var scrollerInner = elt("div", [d.sizer, d.heightForcer, d.gutters], null, "position: relative; min-height: 100%");
133
  // Provides scrolling
134
- d.scroller = elt("div", [scrollerInner], "CodeMirror-scroll");
135
  d.scroller.setAttribute("tabIndex", "-1");
136
  // The element in which the editor lives.
137
  d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
138
- d.scrollbarFiller, d.scroller], "CodeMirror");
139
  // Work around IE7 z-index bug
140
  if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
141
  if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
@@ -166,8 +165,6 @@ window.CodeMirror = (function() {
166
  d.pollingFast = false;
167
  // Self-resetting timeout for the poller
168
  d.poll = new Delayed();
169
- // True when a drag from the editor is active
170
- d.draggingText = false;
171
 
172
  d.cachedCharWidth = d.cachedTextHeight = null;
173
  d.measureLineCache = [];
@@ -216,7 +213,7 @@ window.CodeMirror = (function() {
216
  estimateLineHeights(cm);
217
  regChange(cm);
218
  clearCaches(cm);
219
- setTimeout(function(){updateScrollbars(cm.display, cm.doc.height);}, 100);
220
  }
221
 
222
  function estimateHeight(cm) {
@@ -241,9 +238,10 @@ window.CodeMirror = (function() {
241
  }
242
 
243
  function keyMapChanged(cm) {
244
- var style = keyMap[cm.options.keyMap].style;
245
  cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
246
  (style ? " cm-keymap-" + style : "");
 
247
  }
248
 
249
  function themeChanged(cm) {
@@ -255,6 +253,7 @@ window.CodeMirror = (function() {
255
  function guttersChanged(cm) {
256
  updateGutters(cm);
257
  regChange(cm);
 
258
  }
259
 
260
  function updateGutters(cm) {
@@ -321,12 +320,14 @@ window.CodeMirror = (function() {
321
 
322
  // Re-synchronize the fake scrollbars with the actual size of the
323
  // content. Optionally force a scrollTop.
324
- function updateScrollbars(d /* display */, docHeight) {
 
325
  var totalHeight = docHeight + paddingVert(d);
326
  d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
 
327
  var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
328
- var needsH = d.scroller.scrollWidth > d.scroller.clientWidth;
329
- var needsV = scrollHeight > d.scroller.clientHeight;
330
  if (needsV) {
331
  d.scrollbarV.style.display = "block";
332
  d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
@@ -343,6 +344,11 @@ window.CodeMirror = (function() {
343
  d.scrollbarFiller.style.display = "block";
344
  d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
345
  } else d.scrollbarFiller.style.display = "";
 
 
 
 
 
346
 
347
  if (mac_geLion && scrollbarWidth(d.measure) === 0)
348
  d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
@@ -398,23 +404,36 @@ window.CodeMirror = (function() {
398
  // DISPLAY DRAWING
399
 
400
  function updateDisplay(cm, changes, viewPort) {
401
- var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo;
402
- var updated = updateDisplayInner(cm, changes, viewPort);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  if (updated) {
404
  signalLater(cm, "update", cm);
405
  if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
406
  signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
407
  }
408
- updateSelection(cm);
409
- updateScrollbars(cm.display, cm.doc.height);
410
-
411
  return updated;
412
  }
413
 
414
  // Uses a set of changes plus the current scroll position to
415
  // determine which DOM updates have to be made, and makes the
416
  // updates.
417
- function updateDisplayInner(cm, changes, viewPort) {
418
  var display = cm.display, doc = cm.doc;
419
  if (!display.wrapper.clientWidth) {
420
  display.showingFrom = display.showingTo = doc.first;
@@ -422,10 +441,6 @@ window.CodeMirror = (function() {
422
  return;
423
  }
424
 
425
- // Compute the new visible window
426
- // If scrollTop is specified, use that to determine which lines
427
- // to render instead of the current scrollbar position.
428
- var visible = visibleLines(display, doc, viewPort);
429
  // Bail out if the visible area is already rendered and nothing changed.
430
  if (changes.length == 0 &&
431
  visible.from > display.showingFrom && visible.to < display.showingTo)
@@ -486,19 +501,24 @@ window.CodeMirror = (function() {
486
  }
487
  intact.sort(function(a, b) {return a.from - b.from;});
488
 
489
- var focused = document.activeElement;
 
 
 
490
  if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
491
  patchDisplay(cm, from, to, intact, positionsChangedFrom);
492
  display.lineDiv.style.display = "";
493
- if (document.activeElement != focused && focused.offsetHeight) focused.focus();
494
 
495
  var different = from != display.showingFrom || to != display.showingTo ||
496
  display.lastSizeC != display.wrapper.clientHeight;
497
  // This is just a bogus formula that detects when the editor is
498
  // resized or the font size changes.
499
- if (different) display.lastSizeC = display.wrapper.clientHeight;
 
 
 
500
  display.showingFrom = from; display.showingTo = to;
501
- startWorker(cm, 100);
502
 
503
  var prevBottom = display.lineDiv.offsetTop;
504
  for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
@@ -521,8 +541,6 @@ window.CodeMirror = (function() {
521
  }
522
  updateViewOffset(cm);
523
 
524
- if (visibleLines(display, doc, viewPort).to > to)
525
- updateDisplayInner(cm, [], viewPort);
526
  return true;
527
  }
528
 
@@ -589,8 +607,9 @@ window.CodeMirror = (function() {
589
  if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
590
  if (lineIsHidden(cm.doc, line)) {
591
  if (line.height != 0) updateLineHeight(line, 0);
592
- if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i)
593
- if (line.widgets[i].showIfHidden) {
 
594
  var prev = cur.previousSibling;
595
  if (/pre/i.test(prev.nodeName)) {
596
  var wrap = elt("div", null, null, "position: relative");
@@ -598,9 +617,11 @@ window.CodeMirror = (function() {
598
  wrap.appendChild(prev);
599
  prev = wrap;
600
  }
601
- var wnode = prev.appendChild(elt("div", [line.widgets[i].node], "CodeMirror-linewidget"));
602
- positionLineWidget(line.widgets[i], wnode, prev, dims);
 
603
  }
 
604
  } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
605
  // This line is intact. Skip to the actual node. Update its
606
  // line number if needed.
@@ -643,25 +664,25 @@ window.CodeMirror = (function() {
643
 
644
  if (reuse) {
645
  reuse.alignable = null;
646
- var isOk = true, widgetsSeen = 0;
647
  for (var n = reuse.firstChild, next; n; n = next) {
648
  next = n.nextSibling;
649
  if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
650
  reuse.removeChild(n);
651
  } else {
652
  for (var i = 0, first = true; i < line.widgets.length; ++i) {
653
- var widget = line.widgets[i], isFirst = false;
654
- if (!widget.above) { isFirst = first; first = false; }
655
  if (widget.node == n.firstChild) {
656
  positionLineWidget(widget, n, reuse, dims);
657
  ++widgetsSeen;
658
- if (isFirst) reuse.insertBefore(lineElement, n);
659
  break;
660
  }
661
  }
662
  if (i == line.widgets.length) { isOk = false; break; }
663
  }
664
  }
 
665
  if (isOk && widgetsSeen == line.widgets.length) {
666
  wrap = reuse;
667
  reuse.className = line.wrapClass || "";
@@ -696,6 +717,7 @@ window.CodeMirror = (function() {
696
  if (ie_lt8) wrap.style.zIndex = 2;
697
  if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
698
  var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
 
699
  positionLineWidget(widget, node, wrap, dims);
700
  if (widget.above)
701
  wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
@@ -739,12 +761,14 @@ window.CodeMirror = (function() {
739
  display.selectionDiv.style.display = "none";
740
 
741
  // Move the hidden textarea near the cursor to prevent scrolling artifacts
742
- var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
743
- var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);
744
- display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
745
- headPos.top + lineOff.top - wrapOff.top)) + "px";
746
- display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
747
- headPos.left + lineOff.left - wrapOff.left)) + "px";
 
 
748
  }
749
 
750
  // No selection, plain cursor
@@ -776,67 +800,59 @@ window.CodeMirror = (function() {
776
  "px; height: " + (bottom - top) + "px"));
777
  }
778
 
779
- function drawForLine(line, fromArg, toArg, retTop) {
780
  var lineObj = getLine(doc, line);
781
- var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity;
782
- function coords(ch) {
783
- return charCoords(cm, Pos(line, ch), "div", lineObj);
 
784
  }
785
 
786
  iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
787
- var leftPos = coords(dir == "rtl" ? to - 1 : from);
788
- var rightPos = coords(dir == "rtl" ? from : to - 1);
789
- var left = leftPos.left, right = rightPos.right;
 
 
 
 
 
 
 
 
790
  if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
791
  add(left, leftPos.top, null, leftPos.bottom);
792
  left = pl;
793
  if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
794
  }
795
  if (toArg == null && to == lineLen) right = clientWidth;
796
- if (fromArg == null && from == 0) left = pl;
797
- rVal = retTop ? Math.min(rightPos.top, rVal) : Math.max(rightPos.bottom, rVal);
 
 
798
  if (left < pl + 1) left = pl;
799
  add(left, rightPos.top, right - left, rightPos.bottom);
800
  });
801
- return rVal;
802
  }
803
 
804
  if (sel.from.line == sel.to.line) {
805
  drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
806
  } else {
807
- var fromObj = getLine(doc, sel.from.line);
808
- var cur = fromObj, merged, path = [sel.from.line, sel.from.ch], singleLine;
809
- while (merged = collapsedSpanAtEnd(cur)) {
810
- var found = merged.find();
811
- path.push(found.from.ch, found.to.line, found.to.ch);
812
- if (found.to.line == sel.to.line) {
813
- path.push(sel.to.ch);
814
- singleLine = true;
815
- break;
 
816
  }
817
- cur = getLine(doc, found.to.line);
818
- }
819
-
820
- // This is a single, merged line
821
- if (singleLine) {
822
- for (var i = 0; i < path.length; i += 3)
823
- drawForLine(path[i], path[i+1], path[i+2]);
824
- } else {
825
- var middleTop, middleBot, toObj = getLine(doc, sel.to.line);
826
- if (sel.from.ch)
827
- // Draw the first line of selection.
828
- middleTop = drawForLine(sel.from.line, sel.from.ch, null, false);
829
- else
830
- // Simply include it in the middle block.
831
- middleTop = heightAtLine(cm, fromObj) - display.viewOffset;
832
-
833
- if (!sel.to.ch)
834
- middleBot = heightAtLine(cm, toObj) - display.viewOffset;
835
- else
836
- middleBot = drawForLine(sel.to.line, collapsedSpanAtStart(toObj) ? null : 0, sel.to.ch, true);
837
-
838
- if (middleTop < middleBot) add(pl, middleTop, null, middleBot);
839
  }
 
 
840
  }
841
 
842
  removeChildrenAndAdd(display.selectionDiv, fragment);
@@ -845,12 +861,12 @@ window.CodeMirror = (function() {
845
 
846
  // Cursor-blinking
847
  function restartBlink(cm) {
 
848
  var display = cm.display;
849
  clearInterval(display.blinker);
850
  var on = true;
851
  display.cursor.style.visibility = display.otherCursor.style.visibility = "";
852
  display.blinker = setInterval(function() {
853
- if (!display.cursor.offsetHeight) return;
854
  display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
855
  }, cm.options.cursorBlinkRate);
856
  }
@@ -902,12 +918,12 @@ window.CodeMirror = (function() {
902
  // valid state. If that fails, it returns the line with the
903
  // smallest indentation, which tends to need the least context to
904
  // parse correctly.
905
- function findStartLine(cm, n) {
906
  var minindent, minline, doc = cm.doc;
907
  for (var search = n, lim = n - 100; search > lim; --search) {
908
  if (search <= doc.first) return doc.first;
909
  var line = getLine(doc, search - 1);
910
- if (line.stateAfter) return search;
911
  var indented = countColumn(line.text, null, cm.options.tabSize);
912
  if (minline == null || minindent > indented) {
913
  minline = search - 1;
@@ -917,10 +933,10 @@ window.CodeMirror = (function() {
917
  return minline;
918
  }
919
 
920
- function getStateBefore(cm, n) {
921
  var doc = cm.doc, display = cm.display;
922
  if (!doc.mode.startState) return true;
923
- var pos = findStartLine(cm, n), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
924
  if (!state) state = startState(doc.mode);
925
  else state = copyState(doc.mode, state);
926
  doc.iter(pos, n, function(line) {
@@ -941,7 +957,7 @@ window.CodeMirror = (function() {
941
  return e.offsetLeft;
942
  }
943
 
944
- function measureChar(cm, line, ch, data) {
945
  var dir = -1;
946
  data = data || measureLine(cm, line);
947
 
@@ -950,9 +966,11 @@ window.CodeMirror = (function() {
950
  if (r) break;
951
  if (dir < 0 && pos == 0) dir = 1;
952
  }
 
953
  return {left: pos < ch ? r.right : r.left,
954
  right: pos > ch ? r.left : r.right,
955
- top: r.top, bottom: r.bottom};
 
956
  }
957
 
958
  function findCachedMeasurement(cm, line) {
@@ -962,23 +980,28 @@ window.CodeMirror = (function() {
962
  if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
963
  cm.display.scroller.clientWidth == memo.width &&
964
  memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass)
965
- return memo.measure;
966
  }
967
  }
968
 
 
 
 
 
 
969
  function measureLine(cm, line) {
970
  // First look in the cache
971
- var measure = findCachedMeasurement(cm, line);
972
- if (!measure) {
973
- // Failing that, recompute and store result in cache
974
- measure = measureLineInner(cm, line);
975
- var cache = cm.display.measureLineCache;
976
- var memo = {text: line.text, width: cm.display.scroller.clientWidth,
977
- markedSpans: line.markedSpans, measure: measure,
978
- classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass};
979
- if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo;
980
- else cache.push(memo);
981
- }
982
  return measure;
983
  }
984
 
@@ -1020,9 +1043,9 @@ window.CodeMirror = (function() {
1020
  if (ie_lt9 && display.measure.first != pre)
1021
  removeChildrenAndAdd(display.measure, pre);
1022
 
1023
- for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
1024
- var size = getRect(cur);
1025
- var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot);
1026
  for (var j = 0; j < vranges.length; j += 2) {
1027
  var rtop = vranges[j], rbot = vranges[j+1];
1028
  if (rtop > bot || rbot < top) continue;
@@ -1031,19 +1054,38 @@ window.CodeMirror = (function() {
1031
  Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
1032
  vranges[j] = Math.min(top, rtop);
1033
  vranges[j+1] = Math.max(bot, rbot);
1034
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1035
  }
1036
  }
1037
- if (j == vranges.length) vranges.push(top, bot);
 
1038
  var right = size.right;
1039
  if (cur.measureRight) right = getRect(cur.measureRight).left;
1040
- data[i] = {left: size.left - outer.left, right: right - outer.left, top: j};
1041
  }
1042
  for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
1043
- var vr = cur.top;
1044
  cur.top = vranges[vr]; cur.bottom = vranges[vr+1];
 
1045
  }
1046
-
1047
  return data;
1048
  }
1049
 
@@ -1054,7 +1096,7 @@ window.CodeMirror = (function() {
1054
  if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;
1055
  }
1056
  var cached = !hasBadSpan && findCachedMeasurement(cm, line);
1057
- if (cached) return measureChar(cm, line, line.text.length, cached).right;
1058
 
1059
  var pre = lineContent(cm, line);
1060
  var end = pre.appendChild(zeroWidthElement(cm.display.measure));
@@ -1065,10 +1107,13 @@ window.CodeMirror = (function() {
1065
  function clearCaches(cm) {
1066
  cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
1067
  cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
1068
- cm.display.maxLineChanged = true;
1069
  cm.display.lineNumChars = null;
1070
  }
1071
 
 
 
 
1072
  // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
1073
  function intoCoordSystem(cm, lineObj, rect, context) {
1074
  if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
@@ -1078,11 +1123,12 @@ window.CodeMirror = (function() {
1078
  if (context == "line") return rect;
1079
  if (!context) context = "local";
1080
  var yOff = heightAtLine(cm, lineObj);
1081
- if (context != "local") yOff -= cm.display.viewOffset;
1082
- if (context == "page") {
 
1083
  var lOff = getRect(cm.display.lineSpace);
1084
- yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop);
1085
- var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft);
1086
  rect.left += xOff; rect.right += xOff;
1087
  }
1088
  rect.top += yOff; rect.bottom += yOff;
@@ -1090,68 +1136,62 @@ window.CodeMirror = (function() {
1090
  }
1091
 
1092
  // Context may be "window", "page", "div", or "local"/null
1093
- // Result is in local coords
1094
  function fromCoordSystem(cm, coords, context) {
1095
  if (context == "div") return coords;
1096
  var left = coords.left, top = coords.top;
 
1097
  if (context == "page") {
1098
- left -= window.pageXOffset || (document.documentElement || document.body).scrollLeft;
1099
- top -= window.pageYOffset || (document.documentElement || document.body).scrollTop;
 
 
 
 
1100
  }
 
1101
  var lineSpaceBox = getRect(cm.display.lineSpace);
1102
- left -= lineSpaceBox.left;
1103
- top -= lineSpaceBox.top;
1104
- if (context == "local" || !context) {
1105
- var editorBox = getRect(cm.display.wrapper);
1106
- left -= editorBox.left;
1107
- top -= editorBox.top;
1108
- }
1109
- return {left: left, top: top};
1110
  }
1111
 
1112
- function charCoords(cm, pos, context, lineObj) {
1113
  if (!lineObj) lineObj = getLine(cm.doc, pos.line);
1114
- return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context);
1115
  }
1116
 
1117
  function cursorCoords(cm, pos, context, lineObj, measurement) {
1118
  lineObj = lineObj || getLine(cm.doc, pos.line);
1119
  if (!measurement) measurement = measureLine(cm, lineObj);
1120
  function get(ch, right) {
1121
- var m = measureChar(cm, lineObj, ch, measurement);
1122
  if (right) m.left = m.right; else m.right = m.left;
1123
  return intoCoordSystem(cm, lineObj, m, context);
1124
  }
1125
- var order = getOrder(lineObj), ch = pos.ch;
1126
- if (!order) return get(ch);
1127
- var main, other, linedir = order[0].level;
1128
- for (var i = 0; i < order.length; ++i) {
1129
- var part = order[i], rtl = part.level % 2, nb, here;
1130
- if (part.from < ch && part.to > ch) return get(ch, rtl);
1131
- var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to;
1132
- if (left == ch) {
1133
- // IE returns bogus offsets and widths for edges where the
1134
- // direction flips, but only for the side with the lower
1135
- // level. So we try to use the side with the higher level.
1136
- if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true);
1137
- else here = get(rtl && part.from != part.to ? ch - 1 : ch);
1138
- if (rtl == linedir) main = here; else other = here;
1139
- } else if (right == ch) {
1140
- var nb = i < order.length - 1 && order[i+1];
1141
- if (!rtl && nb && nb.from == nb.to) continue;
1142
- if (nb && part.level < nb.level) here = get(nb.level % 2 ? nb.to - 1 : nb.from);
1143
- else here = get(rtl ? ch : ch - 1, true);
1144
- if (rtl == linedir) main = here; else other = here;
1145
  }
 
 
1146
  }
1147
- if (linedir && !ch) other = get(order[0].to - 1);
1148
- if (!main) return other;
1149
- if (other) main.other = other;
1150
- return main;
 
 
1151
  }
1152
 
1153
- function PosMaybeOutside(line, ch, outside) {
1154
  var pos = new Pos(line, ch);
 
1155
  if (outside) pos.outside = true;
1156
  return pos;
1157
  }
@@ -1160,10 +1200,10 @@ window.CodeMirror = (function() {
1160
  function coordsChar(cm, x, y) {
1161
  var doc = cm.doc;
1162
  y += cm.display.viewOffset;
1163
- if (y < 0) return PosMaybeOutside(doc.first, 0, true);
1164
  var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
1165
  if (lineNo > last)
1166
- return PosMaybeOutside(doc.first + doc.size - 1, getLine(doc, last).text.length, true);
1167
  if (x < 0) x = 0;
1168
 
1169
  for (;;) {
@@ -1171,7 +1211,7 @@ window.CodeMirror = (function() {
1171
  var found = coordsCharInner(cm, lineObj, lineNo, x, y);
1172
  var merged = collapsedSpanAtEnd(lineObj);
1173
  var mergedPos = merged && merged.find();
1174
- if (merged && found.ch >= mergedPos.from.ch)
1175
  lineNo = mergedPos.to.line;
1176
  else
1177
  return found;
@@ -1197,14 +1237,15 @@ window.CodeMirror = (function() {
1197
  var from = lineLeft(lineObj), to = lineRight(lineObj);
1198
  var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
1199
 
1200
- if (x > toX) return PosMaybeOutside(lineNo, to, toOutside);
1201
  // Do a binary search between these bounds.
1202
  for (;;) {
1203
  if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
1204
- var after = x - fromX < toX - x, ch = after ? from : to;
 
1205
  while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
1206
- var pos = PosMaybeOutside(lineNo, ch, after ? fromOutside : toOutside);
1207
- pos.after = after;
1208
  return pos;
1209
  }
1210
  var step = Math.ceil(dist / 2), middle = from + step;
@@ -1213,8 +1254,8 @@ window.CodeMirror = (function() {
1213
  for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
1214
  }
1215
  var middleX = getX(middle);
1216
- if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist -= step;}
1217
- else {from = middle; fromX = middleX; fromOutside = wrongLine; dist = step;}
1218
  }
1219
  }
1220
 
@@ -1265,6 +1306,7 @@ window.CodeMirror = (function() {
1265
  userSelChange: null,
1266
  textChanged: null,
1267
  selectionChanged: false,
 
1268
  updateMaxLine: false,
1269
  updateScrollPos: false,
1270
  id: ++nextOpId
@@ -1277,7 +1319,7 @@ window.CodeMirror = (function() {
1277
  cm.curOp = null;
1278
 
1279
  if (op.updateMaxLine) computeMaxLength(cm);
1280
- if (display.maxLineChanged && !cm.options.lineWrapping) {
1281
  var width = measureLineWidth(cm, display.maxLine);
1282
  display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
1283
  display.maxLineChanged = false;
@@ -1301,6 +1343,8 @@ window.CodeMirror = (function() {
1301
  display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop;
1302
  display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft;
1303
  alignHorizontally(cm);
 
 
1304
  } else if (newScrollPos) {
1305
  scrollCursorIntoView(cm);
1306
  }
@@ -1322,7 +1366,7 @@ window.CodeMirror = (function() {
1322
  }
1323
  if (op.textChanged)
1324
  signal(cm, "change", cm, op.textChanged);
1325
- if (op.selectionChanged) signal(cm, "cursorActivity", cm);
1326
  if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
1327
  }
1328
 
@@ -1387,31 +1431,32 @@ window.CodeMirror = (function() {
1387
  // supported or compatible enough yet to rely on.)
1388
  function readInput(cm) {
1389
  var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
1390
- if (!cm.state.focused || hasSelection(input) || isReadOnly(cm)) return false;
1391
  var text = input.value;
1392
  if (text == prevInput && posEq(sel.from, sel.to)) return false;
1393
- // IE enjoys randomly deselecting our input's text when
1394
- // re-focusing. If the selection is gone but the cursor is at the
1395
- // start of the input, that's probably what happened.
1396
- if (ie && text && input.selectionStart === 0) {
1397
  resetInput(cm, true);
1398
  return false;
1399
  }
 
1400
  var withOp = !cm.curOp;
1401
  if (withOp) startOperation(cm);
1402
  sel.shift = false;
1403
  var same = 0, l = Math.min(prevInput.length, text.length);
1404
- while (same < l && prevInput[same] == text[same]) ++same;
1405
  var from = sel.from, to = sel.to;
1406
  if (same < prevInput.length)
1407
  from = Pos(from.line, from.ch - (prevInput.length - same));
1408
  else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
1409
  to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
1410
- var updateInput = cm.curOp.updateInput;
1411
- makeChange(cm.doc, {from: from, to: to, text: splitLines(text.slice(same)),
1412
- origin: cm.state.pasteIncoming ? "paste" : "+input"}, "end");
1413
 
 
 
 
 
1414
  cm.curOp.updateInput = updateInput;
 
 
1415
  if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
1416
  else cm.display.prevInput = text;
1417
  if (withOp) endOperation(cm);
@@ -1425,10 +1470,14 @@ window.CodeMirror = (function() {
1425
  cm.display.prevInput = "";
1426
  minimal = hasCopyEvent &&
1427
  (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
1428
- if (minimal) cm.display.input.value = "-";
1429
- else cm.display.input.value = selected || cm.getSelection();
1430
  if (cm.state.focused) selectInput(cm.display.input);
1431
- } else if (user) cm.display.prevInput = cm.display.input.value = "";
 
 
 
 
1432
  cm.display.inaccurateSelection = minimal;
1433
  }
1434
 
@@ -1446,7 +1495,17 @@ window.CodeMirror = (function() {
1446
  function registerEventHandlers(cm) {
1447
  var d = cm.display;
1448
  on(d.scroller, "mousedown", operation(cm, onMouseDown));
1449
- on(d.scroller, "dblclick", operation(cm, e_preventDefault));
 
 
 
 
 
 
 
 
 
 
1450
  on(d.lineSpace, "selectstart", function(e) {
1451
  if (!eventInWidget(d, e)) e_preventDefault(e);
1452
  });
@@ -1478,11 +1537,15 @@ window.CodeMirror = (function() {
1478
  // Prevent wrapper from ever scrolling
1479
  on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
1480
 
 
1481
  function onResize() {
1482
- // Might be a text scaling operation, clear size caches.
1483
- d.cachedCharWidth = d.cachedTextHeight = null;
1484
- clearCaches(cm);
1485
- runInOp(cm, bind(regChange, cm));
 
 
 
1486
  }
1487
  on(window, "resize", onResize);
1488
  // Above handler holds on to the editor and its data structures.
@@ -1496,7 +1559,7 @@ window.CodeMirror = (function() {
1496
  setTimeout(unregister, 5000);
1497
 
1498
  on(d.input, "keyup", operation(cm, function(e) {
1499
- if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1500
  if (e.keyCode == 16) cm.doc.sel.shift = false;
1501
  }));
1502
  on(d.input, "input", bind(fastPoll, cm));
@@ -1506,7 +1569,7 @@ window.CodeMirror = (function() {
1506
  on(d.input, "blur", bind(onBlur, cm));
1507
 
1508
  function drag_(e) {
1509
- if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1510
  e_stop(e);
1511
  }
1512
  if (cm.options.dragDrop) {
@@ -1545,9 +1608,7 @@ window.CodeMirror = (function() {
1545
 
1546
  function eventInWidget(display, e) {
1547
  for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
1548
- if (!n) return true;
1549
- if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) ||
1550
- n.parentNode == display.sizer && n != display.mover) return true;
1551
  }
1552
  }
1553
 
@@ -1557,7 +1618,7 @@ window.CodeMirror = (function() {
1557
  var target = e_target(e);
1558
  if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
1559
  target == display.scrollbarV || target == display.scrollbarV.firstChild ||
1560
- target == display.scrollbarFiller) return null;
1561
  }
1562
  var x, y, space = getRect(display.lineSpace);
1563
  // Fails unpredictably on IE[67] when mouse is dragged around quickly.
@@ -1567,6 +1628,7 @@ window.CodeMirror = (function() {
1567
 
1568
  var lastClick, lastDoubleClick;
1569
  function onMouseDown(e) {
 
1570
  var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
1571
  sel.shift = e.shiftKey;
1572
 
@@ -1637,9 +1699,12 @@ window.CodeMirror = (function() {
1637
  e_preventDefault(e);
1638
  if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
1639
 
1640
- var startstart = sel.from, startend = sel.to;
1641
 
1642
  function doSelect(cur) {
 
 
 
1643
  if (type == "single") {
1644
  extendSelection(cm.doc, clipPos(doc, start), cur);
1645
  return;
@@ -1687,8 +1752,6 @@ window.CodeMirror = (function() {
1687
 
1688
  function done(e) {
1689
  counter = Infinity;
1690
- var cur = posFromMouse(cm, e);
1691
- if (cur) doSelect(cur);
1692
  e_preventDefault(e);
1693
  focusInput(cm);
1694
  off(document, "mousemove", move);
@@ -1704,11 +1767,41 @@ window.CodeMirror = (function() {
1704
  on(document, "mouseup", up);
1705
  }
1706
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1707
  function onDrop(e) {
1708
  var cm = this;
1709
- if (eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
1710
  return;
1711
  e_preventDefault(e);
 
1712
  var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
1713
  if (!pos || isReadOnly(cm)) return;
1714
  if (files && files.length && window.FileReader && window.File) {
@@ -1748,40 +1841,16 @@ window.CodeMirror = (function() {
1748
  }
1749
  }
1750
 
1751
- function clickInGutter(cm, e) {
1752
- var display = cm.display;
1753
- try { var mX = e.clientX, mY = e.clientY; }
1754
- catch(e) { return false; }
1755
-
1756
- if (mX >= Math.floor(getRect(display.gutters).right)) return false;
1757
- e_preventDefault(e);
1758
- if (!hasHandler(cm, "gutterClick")) return true;
1759
-
1760
- var lineBox = getRect(display.lineDiv);
1761
- if (mY > lineBox.bottom) return true;
1762
- mY -= lineBox.top - display.viewOffset;
1763
-
1764
- for (var i = 0; i < cm.options.gutters.length; ++i) {
1765
- var g = display.gutters.childNodes[i];
1766
- if (g && getRect(g).right >= mX) {
1767
- var line = lineAtHeight(cm.doc, mY);
1768
- var gutter = cm.options.gutters[i];
1769
- signalLater(cm, "gutterClick", cm, line, gutter, e);
1770
- break;
1771
- }
1772
- }
1773
- return true;
1774
- }
1775
-
1776
  function onDragStart(cm, e) {
1777
- if (eventInWidget(cm.display, e)) return;
 
1778
 
1779
  var txt = cm.getSelection();
1780
  e.dataTransfer.setData("Text", txt);
1781
 
1782
  // Use dummy image instead of default browsers image.
1783
  // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
1784
- if (e.dataTransfer.setDragImage) {
1785
  var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
1786
  if (opera) {
1787
  img.width = img.height = 1;
@@ -1789,15 +1858,6 @@ window.CodeMirror = (function() {
1789
  // Force a relayout, or Opera won't use our image for some obscure reason
1790
  img._top = img.offsetTop;
1791
  }
1792
- if (safari) {
1793
- if (cm.display.dragImg) {
1794
- img = cm.display.dragImg;
1795
- } else {
1796
- cm.display.dragImg = img;
1797
- img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
1798
- cm.display.wrapper.appendChild(img);
1799
- }
1800
- }
1801
  e.dataTransfer.setDragImage(img, 0, 0);
1802
  if (opera) img.parentNode.removeChild(img);
1803
  }
@@ -1810,6 +1870,7 @@ window.CodeMirror = (function() {
1810
  if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
1811
  if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
1812
  if (gecko) updateDisplay(cm, []);
 
1813
  }
1814
  function setScrollLeft(cm, val, isScroller) {
1815
  if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
@@ -1847,6 +1908,11 @@ window.CodeMirror = (function() {
1847
  if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
1848
  else if (dy == null) dy = e.wheelDelta;
1849
 
 
 
 
 
 
1850
  // Webkit browsers on OS X abort momentum scrolls when the target
1851
  // of the scroll event is removed from the scrollable element.
1852
  // This hack (see related code in patchDisplay) makes sure the
@@ -1860,7 +1926,6 @@ window.CodeMirror = (function() {
1860
  }
1861
  }
1862
 
1863
- var display = cm.display, scroll = display.scroller;
1864
  // On some browsers, horizontal scrolling will cause redraws to
1865
  // happen before the gutter has been realigned, causing it to
1866
  // wriggle around in a most unseemly way. When we have an
@@ -1938,8 +2003,10 @@ window.CodeMirror = (function() {
1938
  var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
1939
  clearTimeout(maybeTransition);
1940
  if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
1941
- if (getKeyMap(cm.options.keyMap) == startMap)
1942
  cm.options.keyMap = (next.call ? next.call(null, cm) : next);
 
 
1943
  }, 50);
1944
 
1945
  var name = keyName(e, true), handled = false;
@@ -1952,17 +2019,18 @@ window.CodeMirror = (function() {
1952
  // 'go') bound to the keyname without 'Shift-'.
1953
  handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
1954
  || lookupKey(name, keymaps, function(b) {
1955
- if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b);
 
1956
  });
1957
  } else {
1958
  handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
1959
  }
1960
- if (handled == "stop") handled = false;
1961
 
1962
  if (handled) {
1963
  e_preventDefault(e);
1964
  restartBlink(cm);
1965
  if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
 
1966
  }
1967
  return handled;
1968
  }
@@ -1973,6 +2041,7 @@ window.CodeMirror = (function() {
1973
  if (handled) {
1974
  e_preventDefault(e);
1975
  restartBlink(cm);
 
1976
  }
1977
  return handled;
1978
  }
@@ -1982,7 +2051,7 @@ window.CodeMirror = (function() {
1982
  var cm = this;
1983
  if (!cm.state.focused) onFocus(cm);
1984
  if (ie && e.keyCode == 27) { e.returnValue = false; }
1985
- if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1986
  var code = e.keyCode;
1987
  // IE does strange things with escape.
1988
  cm.doc.sel.shift = code == 16 || e.shiftKey;
@@ -1998,7 +2067,7 @@ window.CodeMirror = (function() {
1998
 
1999
  function onKeyPress(e) {
2000
  var cm = this;
2001
- if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
2002
  var keyCode = e.keyCode, charCode = e.charCode;
2003
  if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
2004
  if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
@@ -2008,6 +2077,7 @@ window.CodeMirror = (function() {
2008
  this.doc.mode.electricChars.indexOf(ch) > -1)
2009
  setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
2010
  if (handleCharBinding(cm, e, ch)) return;
 
2011
  fastPoll(cm);
2012
  }
2013
 
@@ -2053,6 +2123,13 @@ window.CodeMirror = (function() {
2053
  // Adds "Select all" to context menu in FF
2054
  if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
2055
 
 
 
 
 
 
 
 
2056
  function rehide() {
2057
  display.inputDiv.style.position = "relative";
2058
  display.input.style.cssText = oldCSS;
@@ -2060,12 +2137,10 @@ window.CodeMirror = (function() {
2060
  slowPoll(cm);
2061
 
2062
  // Try to detect the user choosing select-all
2063
- if (display.input.selectionStart != null && (!ie || ie_lt9)) {
 
2064
  clearTimeout(detectingSelectAll);
2065
- var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value), i = 0;
2066
- display.prevInput = " ";
2067
- display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
2068
- var poll = function(){
2069
  if (display.prevInput == " " && display.input.selectionStart == 0)
2070
  operation(cm, commands.selectAll)(cm);
2071
  else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
@@ -2075,6 +2150,7 @@ window.CodeMirror = (function() {
2075
  }
2076
  }
2077
 
 
2078
  if (captureMiddleClick) {
2079
  e_stop(e);
2080
  var mouseup = function() {
@@ -2089,10 +2165,11 @@ window.CodeMirror = (function() {
2089
 
2090
  // UPDATING
2091
 
2092
- function changeEnd(change) {
 
2093
  return Pos(change.from.line + change.text.length - 1,
2094
  lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
2095
- }
2096
 
2097
  // Make sure a position will be valid after the given change.
2098
  function clipPostChange(doc, change, pos) {
@@ -2134,21 +2211,21 @@ window.CodeMirror = (function() {
2134
  return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)};
2135
  }
2136
 
2137
- function filterChange(doc, change) {
2138
  var obj = {
2139
  canceled: false,
2140
  from: change.from,
2141
  to: change.to,
2142
  text: change.text,
2143
  origin: change.origin,
2144
- update: function(from, to, text, origin) {
2145
- if (from) this.from = clipPos(doc, from);
2146
- if (to) this.to = clipPos(doc, to);
2147
- if (text) this.text = text;
2148
- if (origin !== undefined) this.origin = origin;
2149
- },
2150
  cancel: function() { this.canceled = true; }
2151
  };
 
 
 
 
 
 
2152
  signal(doc, "beforeChange", doc, obj);
2153
  if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
2154
 
@@ -2165,7 +2242,7 @@ window.CodeMirror = (function() {
2165
  }
2166
 
2167
  if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
2168
- change = filterChange(doc, change);
2169
  if (!change) return;
2170
  }
2171
 
@@ -2204,15 +2281,23 @@ window.CodeMirror = (function() {
2204
  var hist = doc.history;
2205
  var event = (type == "undo" ? hist.done : hist.undone).pop();
2206
  if (!event) return;
2207
- hist.dirtyCounter += type == "undo" ? -1 : 1;
2208
 
2209
  var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,
2210
- anchorAfter: event.anchorBefore, headAfter: event.headBefore};
 
2211
  (type == "undo" ? hist.undone : hist.done).push(anti);
 
 
 
2212
 
2213
  for (var i = event.changes.length - 1; i >= 0; --i) {
2214
  var change = event.changes[i];
2215
  change.origin = type;
 
 
 
 
 
2216
  anti.changes.push(historyChangeFromChange(doc, change));
2217
 
2218
  var after = i ? computeSelAfterChange(doc, change, null)
@@ -2282,6 +2367,9 @@ window.CodeMirror = (function() {
2282
  });
2283
  }
2284
 
 
 
 
2285
  updateDoc(doc, change, spans, selAfter, estimateHeight(cm));
2286
 
2287
  if (!cm.options.lineWrapping) {
@@ -2408,7 +2496,8 @@ window.CodeMirror = (function() {
2408
  sel.to = inv ? anchor : head;
2409
 
2410
  if (doc.cm)
2411
- doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
 
2412
 
2413
  signalLater(doc, "cursorActivity", doc);
2414
  }
@@ -2470,11 +2559,11 @@ window.CodeMirror = (function() {
2470
  // SCROLLING
2471
 
2472
  function scrollCursorIntoView(cm) {
2473
- var coords = scrollPosIntoView(cm, cm.doc.sel.head);
2474
  if (!cm.state.focused) return;
2475
- var display = cm.display, box = getRect(display.sizer), doScroll = null, pTop = paddingTop(cm.display);
2476
- if (coords.top + pTop + box.top < 0) doScroll = true;
2477
- else if (coords.bottom + pTop + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
2478
  if (doScroll != null && !phantom) {
2479
  var hidden = display.cursor.style.display == "none";
2480
  if (hidden) {
@@ -2512,13 +2601,17 @@ window.CodeMirror = (function() {
2512
  }
2513
 
2514
  function calculateScrollPos(cm, x1, y1, x2, y2) {
2515
- var display = cm.display, pt = paddingTop(display);
2516
- y1 += pt; y2 += pt;
2517
  var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
2518
  var docBottom = cm.doc.height + paddingVert(display);
2519
- var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;
2520
- if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
2521
- else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
 
 
 
 
2522
 
2523
  var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
2524
  x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
@@ -2534,7 +2627,8 @@ window.CodeMirror = (function() {
2534
  }
2535
 
2536
  function updateScrollPos(cm, left, top) {
2537
- cm.curOp.updateScrollPos = {scrollLeft: left, scrollTop: top};
 
2538
  }
2539
 
2540
  function addToScrollPos(cm, left, top) {
@@ -2548,7 +2642,7 @@ window.CodeMirror = (function() {
2548
 
2549
  function indentLine(cm, n, how, aggressive) {
2550
  var doc = cm.doc;
2551
- if (!how) how = "add";
2552
  if (how == "smart") {
2553
  if (!cm.doc.mode.indent) how = "prev";
2554
  else var state = getStateBefore(cm, n);
@@ -2571,6 +2665,8 @@ window.CodeMirror = (function() {
2571
  indentation = curSpace + cm.options.indentUnit;
2572
  } else if (how == "subtract") {
2573
  indentation = curSpace - cm.options.indentUnit;
 
 
2574
  }
2575
  indentation = Math.max(0, indentation);
2576
 
@@ -2595,7 +2691,7 @@ window.CodeMirror = (function() {
2595
  }
2596
 
2597
  function findPosH(doc, pos, dir, unit, visually) {
2598
- var line = pos.line, ch = pos.ch;
2599
  var lineObj = getLine(doc, line);
2600
  var possible = true;
2601
  function findNextLine() {
@@ -2634,7 +2730,7 @@ window.CodeMirror = (function() {
2634
  if (dir > 0 && !moveOnce(!first)) break;
2635
  }
2636
  }
2637
- var result = skipAtomic(doc, Pos(line, ch), dir, true);
2638
  if (!possible) result.hitSide = true;
2639
  return result;
2640
  }
@@ -2659,7 +2755,7 @@ window.CodeMirror = (function() {
2659
  function findWordAt(line, pos) {
2660
  var start = pos.ch, end = pos.ch;
2661
  if (line) {
2662
- if (pos.after === false || end == line.length) --start; else ++end;
2663
  var startChar = line.charAt(start);
2664
  var check = isWordChar(startChar) ? isWordChar
2665
  : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
@@ -2680,6 +2776,7 @@ window.CodeMirror = (function() {
2680
  // 'wrap f in an operation, performed on its `this` parameter'
2681
 
2682
  CodeMirror.prototype = {
 
2683
  focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},
2684
 
2685
  setOption: function(option, value) {
@@ -2715,7 +2812,8 @@ window.CodeMirror = (function() {
2715
  removeOverlay: operation(null, function(spec) {
2716
  var overlays = this.state.overlays;
2717
  for (var i = 0; i < overlays.length; ++i) {
2718
- if (overlays[i].modeSpec == spec) {
 
2719
  overlays.splice(i, 1);
2720
  this.state.modeGen++;
2721
  regChange(this);
@@ -2725,7 +2823,7 @@ window.CodeMirror = (function() {
2725
  }),
2726
 
2727
  indentLine: operation(null, function(n, dir, aggressive) {
2728
- if (typeof dir != "string") {
2729
  if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
2730
  else dir = dir ? "add" : "subtract";
2731
  }
@@ -2740,10 +2838,10 @@ window.CodeMirror = (function() {
2740
 
2741
  // Fetch the parser token for a given character. Useful for hacks
2742
  // that want to inspect the mode state (say, for completion).
2743
- getTokenAt: function(pos) {
2744
  var doc = this.doc;
2745
  pos = clipPos(doc, pos);
2746
- var state = getStateBefore(this, pos.line), mode = this.doc.mode;
2747
  var line = getLine(doc, pos.line);
2748
  var stream = new StringStream(line.text, this.options.tabSize);
2749
  while (stream.pos < pos.ch && !stream.eol()) {
@@ -2758,10 +2856,22 @@ window.CodeMirror = (function() {
2758
  state: state};
2759
  },
2760
 
2761
- getStateAfter: function(line) {
 
 
 
 
 
 
 
 
 
 
 
 
2762
  var doc = this.doc;
2763
  line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
2764
- return getStateBefore(this, line + 1);
2765
  },
2766
 
2767
  cursorCoords: function(start, mode) {
@@ -2781,6 +2891,19 @@ window.CodeMirror = (function() {
2781
  return coordsChar(this, coords.left, coords.top);
2782
  },
2783
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2784
  defaultTextHeight: function() { return textHeight(this.display); },
2785
  defaultCharWidth: function() { return charWidth(this.display); },
2786
 
@@ -2809,7 +2932,7 @@ window.CodeMirror = (function() {
2809
  return changeLine(this, handle, function(line) {
2810
  var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
2811
  if (!line[prop]) line[prop] = cls;
2812
- else if (new RegExp("\\b" + cls + "\\b").test(line[prop])) return false;
2813
  else line[prop] += " " + cls;
2814
  return true;
2815
  });
@@ -2822,9 +2945,10 @@ window.CodeMirror = (function() {
2822
  if (!cur) return false;
2823
  else if (cls == null) line[prop] = null;
2824
  else {
2825
- var upd = cur.replace(new RegExp("^" + cls + "\\b\\s*|\\s*\\b" + cls + "\\b"), "");
2826
- if (upd == cur) return false;
2827
- line[prop] = upd || null;
 
2828
  }
2829
  return true;
2830
  });
@@ -2872,7 +2996,7 @@ window.CodeMirror = (function() {
2872
  if (left + node.offsetWidth > hspace)
2873
  left = hspace - node.offsetWidth;
2874
  }
2875
- node.style.top = (top + paddingTop(display)) + "px";
2876
  node.style.left = node.style.right = "";
2877
  if (horiz == "right") {
2878
  left = display.sizer.clientWidth - node.offsetWidth;
@@ -2940,7 +3064,8 @@ window.CodeMirror = (function() {
2940
  sel.goalColumn = pos.left;
2941
  }),
2942
 
2943
- toggleOverwrite: function() {
 
2944
  if (this.state.overwrite = !this.state.overwrite)
2945
  this.display.cursor.className += " CodeMirror-overwrite";
2946
  else
@@ -2958,15 +3083,19 @@ window.CodeMirror = (function() {
2958
  clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
2959
  },
2960
 
2961
- scrollIntoView: function(pos, margin) {
2962
  if (typeof pos == "number") pos = Pos(pos, 0);
 
 
 
2963
  if (!pos || pos.line != null) {
2964
- pos = pos ? clipPos(this.doc, pos) : this.doc.sel.head;
2965
- scrollPosIntoView(this, pos, margin);
2966
- } else {
2967
- scrollIntoView(this, pos.left, pos.top - margin, pos.right, pos.bottom + margin);
2968
  }
2969
- },
 
 
2970
 
2971
  setSize: function(width, height) {
2972
  function interpret(val) {
@@ -2993,6 +3122,7 @@ window.CodeMirror = (function() {
2993
  old.cm = null;
2994
  attachDoc(this, doc);
2995
  clearCaches(this);
 
2996
  updateScrollPos(this, doc.scrollLeft, doc.scrollTop);
2997
  return old;
2998
  }),
@@ -3058,6 +3188,7 @@ window.CodeMirror = (function() {
3058
  cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
3059
  cm.refresh();
3060
  }, true);
 
3061
  option("lineNumbers", false, function(cm) {
3062
  setGuttersForLineNumbers(cm.options);
3063
  guttersChanged(cm);
@@ -3073,13 +3204,19 @@ window.CodeMirror = (function() {
3073
  option("dragDrop", true);
3074
 
3075
  option("cursorBlinkRate", 530);
 
3076
  option("cursorHeight", 1);
3077
  option("workTime", 100);
3078
  option("workDelay", 100);
3079
  option("flattenSpans", true);
3080
  option("pollInterval", 100);
3081
  option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;});
 
3082
  option("viewportMargin", 10, function(cm){cm.refresh();}, true);
 
 
 
 
3083
 
3084
  option("tabindex", null, function(cm, val) {
3085
  cm.display.input.tabIndex = val || "";
@@ -3105,10 +3242,15 @@ window.CodeMirror = (function() {
3105
  };
3106
 
3107
  CodeMirror.resolveMode = function(spec) {
3108
- if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
3109
  spec = mimeModes[spec];
3110
- else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
 
 
 
 
3111
  return CodeMirror.resolveMode("application/xml");
 
3112
  if (typeof spec == "string") return {name: spec};
3113
  else return spec || {name: "null"};
3114
  };
@@ -3146,7 +3288,9 @@ window.CodeMirror = (function() {
3146
  CodeMirror.defineExtension = function(name, func) {
3147
  CodeMirror.prototype[name] = func;
3148
  };
3149
-
 
 
3150
  CodeMirror.defineOption = option;
3151
 
3152
  var initHooks = [];
@@ -3197,6 +3341,10 @@ window.CodeMirror = (function() {
3197
  var l = cm.getCursor().line;
3198
  cm.replaceRange("", Pos(l, 0), Pos(l), "+delete");
3199
  },
 
 
 
 
3200
  undo: function(cm) {cm.undo();},
3201
  redo: function(cm) {cm.redo();},
3202
  goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
@@ -3292,7 +3440,7 @@ window.CodeMirror = (function() {
3292
  "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore",
3293
  "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
3294
  "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
3295
- "Cmd-[": "indentLess", "Cmd-]": "indentMore",
3296
  fallthrough: ["basic", "emacsy"]
3297
  };
3298
  keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
@@ -3331,7 +3479,7 @@ window.CodeMirror = (function() {
3331
 
3332
  for (var i = 0; i < maps.length; ++i) {
3333
  var done = lookup(maps[i]);
3334
- if (done) return done;
3335
  }
3336
  }
3337
  function isModifierKey(event) {
@@ -3339,6 +3487,7 @@ window.CodeMirror = (function() {
3339
  return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
3340
  }
3341
  function keyName(event, noShift) {
 
3342
  var name = keyNames[event.keyCode];
3343
  if (name == null || event.altGraphKey) return false;
3344
  if (event.altKey) name = "Alt-" + name;
@@ -3512,7 +3661,7 @@ window.CodeMirror = (function() {
3512
  if (min != null && cm) regChange(cm, min, max + 1);
3513
  this.lines.length = 0;
3514
  this.explicitlyCleared = true;
3515
- if (this.collapsed && this.doc.cantEdit) {
3516
  this.doc.cantEdit = false;
3517
  if (cm) reCheckSelection(cm);
3518
  }
@@ -3535,15 +3684,18 @@ window.CodeMirror = (function() {
3535
  return from && {from: from, to: to};
3536
  };
3537
 
3538
- TextMarker.prototype.getOptions = function(copyWidget) {
3539
- var repl = this.replacedWith;
3540
- return {className: this.className,
3541
- inclusiveLeft: this.inclusiveLeft, inclusiveRight: this.inclusiveRight,
3542
- atomic: this.atomic,
3543
- collapsed: this.collapsed,
3544
- replacedWith: copyWidget ? repl && repl.cloneNode(true) : repl,
3545
- readOnly: this.readOnly,
3546
- startStyle: this.startStyle, endStyle: this.endStyle};
 
 
 
3547
  };
3548
 
3549
  TextMarker.prototype.attachLine = function(line) {
@@ -3572,9 +3724,14 @@ window.CodeMirror = (function() {
3572
  if (marker.replacedWith) {
3573
  marker.collapsed = true;
3574
  marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
 
3575
  }
3576
  if (marker.collapsed) sawCollapsedSpans = true;
3577
 
 
 
 
 
3578
  var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine;
3579
  doc.iter(curLine, to.line + 1, function(line) {
3580
  if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)
@@ -3639,11 +3796,6 @@ window.CodeMirror = (function() {
3639
  SharedTextMarker.prototype.find = function() {
3640
  return this.primary.find();
3641
  };
3642
- SharedTextMarker.prototype.getOptions = function(copyWidget) {
3643
- var inner = this.primary.getOptions(copyWidget);
3644
- inner.shared = true;
3645
- return inner;
3646
- };
3647
 
3648
  function markTextShared(doc, from, to, options, type) {
3649
  options = copyObj(options);
@@ -3746,6 +3898,13 @@ window.CodeMirror = (function() {
3746
  }
3747
  }
3748
  }
 
 
 
 
 
 
 
3749
 
3750
  var newMarkers = [first];
3751
  if (!sameLine) {
@@ -3840,6 +3999,7 @@ window.CodeMirror = (function() {
3840
  sp = sps[i];
3841
  if (!sp.marker.collapsed) continue;
3842
  if (sp.from == null) return true;
 
3843
  if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
3844
  return true;
3845
  }
@@ -3853,7 +4013,7 @@ window.CodeMirror = (function() {
3853
  return true;
3854
  for (var sp, i = 0; i < line.markedSpans.length; ++i) {
3855
  sp = line.markedSpans[i];
3856
- if (sp.marker.collapsed && sp.from == span.to &&
3857
  (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
3858
  lineIsHiddenInner(doc, line, sp)) return true;
3859
  }
@@ -3965,25 +4125,25 @@ window.CodeMirror = (function() {
3965
  function runMode(cm, text, mode, state, f) {
3966
  var flattenSpans = mode.flattenSpans;
3967
  if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
3968
- var curText = "", curStyle = null;
3969
- var stream = new StringStream(text, cm.options.tabSize);
3970
  if (text == "" && mode.blankLine) mode.blankLine(state);
3971
  while (!stream.eol()) {
3972
- var style = mode.token(stream, state);
3973
- if (stream.pos > 5000) {
3974
  flattenSpans = false;
3975
  // Webkit seems to refuse to render text nodes longer than 57444 characters
3976
  stream.pos = Math.min(text.length, stream.start + 50000);
3977
  style = null;
 
 
3978
  }
3979
- var substr = stream.current();
3980
- stream.start = stream.pos;
3981
  if (!flattenSpans || curStyle != style) {
3982
- if (curText) f(curText, curStyle);
3983
- curText = substr; curStyle = style;
3984
- } else curText = curText + substr;
 
3985
  }
3986
- if (curText) f(curText, curStyle);
3987
  }
3988
 
3989
  function highlightLine(cm, line, state) {
@@ -3991,27 +4151,24 @@ window.CodeMirror = (function() {
3991
  // mode/overlays that it is based on (for easy invalidation).
3992
  var st = [cm.state.modeGen];
3993
  // Compute the base array of styles
3994
- runMode(cm, line.text, cm.doc.mode, state, function(txt, style) {st.push(txt, style);});
3995
 
3996
  // Run overlays, adjust style array.
3997
  for (var o = 0; o < cm.state.overlays.length; ++o) {
3998
- var overlay = cm.state.overlays[o], i = 1;
3999
- runMode(cm, line.text, overlay.mode, true, function(txt, style) {
4000
- var start = i, len = txt.length;
4001
  // Ensure there's a token end at the current position, and that i points at it
4002
- while (len) {
4003
- var cur = st[i], len_ = cur.length;
4004
- if (len_ <= len) {
4005
- len -= len_;
4006
- } else {
4007
- st.splice(i, 1, cur.slice(0, len), st[i+1], cur.slice(len));
4008
- len = 0;
4009
- }
4010
  i += 2;
 
4011
  }
4012
  if (!style) return;
4013
  if (overlay.opaque) {
4014
- st.splice(start, i - start, txt, style);
4015
  i = start + 2;
4016
  } else {
4017
  for (; start < i; start += 2) {
@@ -4037,7 +4194,7 @@ window.CodeMirror = (function() {
4037
  var mode = cm.doc.mode;
4038
  var stream = new StringStream(line.text, cm.options.tabSize);
4039
  if (line.text == "" && mode.blankLine) mode.blankLine(state);
4040
- while (!stream.eol() && stream.pos <= 5000) {
4041
  mode.token(stream, state);
4042
  stream.start = stream.pos;
4043
  }
@@ -4051,37 +4208,31 @@ window.CodeMirror = (function() {
4051
  }
4052
 
4053
  function lineContent(cm, realLine, measure) {
4054
- var merged, line = realLine, lineBefore, sawBefore, simple = true;
4055
- while (merged = collapsedSpanAtStart(line)) {
4056
- simple = false;
4057
  line = getLine(cm.doc, merged.find().from.line);
4058
- if (!lineBefore) lineBefore = line;
4059
- }
4060
 
4061
  var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure,
4062
- measure: null, addedOne: false, cm: cm};
4063
  if (line.textClass) builder.pre.className = line.textClass;
4064
 
4065
  do {
 
4066
  builder.measure = line == realLine && measure;
4067
  builder.pos = 0;
4068
  builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
4069
  if ((ie || webkit) && cm.getOption("lineWrapping"))
4070
  builder.addToken = buildTokenSplitSpaces(builder.addToken);
4071
- if (measure && sawBefore && line != realLine && !builder.addedOne) {
4072
- measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
4073
- builder.addedOne = true;
4074
- }
4075
  var next = insertLineContent(line, builder, getLineStyles(cm, line));
4076
- sawBefore = line == lineBefore;
4077
- if (next) {
4078
- line = getLine(cm.doc, next.to.line);
4079
- simple = false;
4080
  }
 
4081
  } while (next);
4082
 
4083
- if (measure && !builder.addedOne)
4084
- measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
4085
  if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine))
4086
  builder.pre.appendChild(document.createTextNode("\u00a0"));
4087
 
@@ -4150,8 +4301,7 @@ window.CodeMirror = (function() {
4150
  if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) {
4151
  ch = text.slice(i, i + 2);
4152
  ++i;
4153
- } else if (i && wrapping &&
4154
- spanAffectsWrapping.test(text.slice(i - 1, i + 1))) {
4155
  builder.pre.appendChild(elt("wbr"));
4156
  }
4157
  var span = builder.measure[builder.pos] =
@@ -4165,7 +4315,7 @@ window.CodeMirror = (function() {
4165
  span.style.whiteSpace = "normal";
4166
  builder.pos += ch.length;
4167
  }
4168
- if (text.length) builder.addedOne = true;
4169
  }
4170
 
4171
  function buildTokenSplitSpaces(inner) {
@@ -4183,11 +4333,12 @@ window.CodeMirror = (function() {
4183
  function buildCollapsedSpan(builder, size, widget) {
4184
  if (widget) {
4185
  if (!builder.display) widget = widget.cloneNode(true);
4186
- builder.pre.appendChild(widget);
4187
- if (builder.measure && size) {
4188
- builder.measure[builder.pos] = widget;
4189
- builder.addedOne = true;
4190
  }
 
4191
  }
4192
  builder.pos += size;
4193
  }
@@ -4195,15 +4346,14 @@ window.CodeMirror = (function() {
4195
  // Outputs a number of spans to make up a line, taking highlighting
4196
  // and marked text into account.
4197
  function insertLineContent(line, builder, styles) {
4198
- var spans = line.markedSpans;
4199
  if (!spans) {
4200
  for (var i = 1; i < styles.length; i+=2)
4201
- builder.addToken(builder, styles[i], styleToClass(styles[i+1]));
4202
  return;
4203
  }
4204
 
4205
- var allText = line.text, len = allText.length;
4206
- var pos = 0, i = 1, text = "", style;
4207
  var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed;
4208
  for (;;) {
4209
  if (nextChange == pos) { // Update current marker set
@@ -4217,7 +4367,7 @@ window.CodeMirror = (function() {
4217
  if (m.className) spanStyle += " " + m.className;
4218
  if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
4219
  if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
4220
- if (m.collapsed && (!collapsed || collapsed.marker.width < m.width))
4221
  collapsed = sp;
4222
  } else if (sp.from > pos && nextChange > sp.from) {
4223
  nextChange = sp.from;
@@ -4247,7 +4397,8 @@ window.CodeMirror = (function() {
4247
  pos = end;
4248
  spanStartStyle = "";
4249
  }
4250
- text = styles[i++]; style = styleToClass(styles[i++]);
 
4251
  }
4252
  }
4253
  }
@@ -4439,6 +4590,7 @@ window.CodeMirror = (function() {
4439
  this.scrollTop = this.scrollLeft = 0;
4440
  this.cantEdit = false;
4441
  this.history = makeHistory();
 
4442
  this.frontier = firstLine;
4443
  var start = Pos(firstLine, 0);
4444
  this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null};
@@ -4450,6 +4602,7 @@ window.CodeMirror = (function() {
4450
  };
4451
 
4452
  Doc.prototype = createObj(BranchChunk.prototype, {
 
4453
  iter: function(from, to, op) {
4454
  if (op) this.iterN(from - this.first, to - from, op);
4455
  else this.iterN(this.first, this.first + this.size, from);
@@ -4490,8 +4643,8 @@ window.CodeMirror = (function() {
4490
  replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line)));
4491
  },
4492
  removeLine: function(line) {
4493
- if (isLine(this, line))
4494
- replaceRange(this, "", Pos(line, 0), clipPos(this, Pos(line + 1, 0)));
4495
  },
4496
 
4497
  getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
@@ -4538,20 +4691,25 @@ window.CodeMirror = (function() {
4538
  var hist = this.history;
4539
  return {undo: hist.done.length, redo: hist.undone.length};
4540
  },
4541
- clearHistory: function() {this.history = makeHistory();},
4542
 
4543
  markClean: function() {
4544
- this.history.dirtyCounter = 0;
 
 
4545
  this.history.lastOp = this.history.lastOrigin = null;
 
 
 
 
4546
  },
4547
- isClean: function () {return this.history.dirtyCounter == 0;},
4548
 
4549
  getHistory: function() {
4550
  return {done: copyHistoryArray(this.history.done),
4551
  undone: copyHistoryArray(this.history.undone)};
4552
  },
4553
  setHistory: function(histData) {
4554
- var hist = this.history = makeHistory();
4555
  hist.done = histData.done.slice(0);
4556
  hist.undone = histData.undone.slice(0);
4557
  },
@@ -4781,7 +4939,7 @@ window.CodeMirror = (function() {
4781
 
4782
  // HISTORY
4783
 
4784
- function makeHistory() {
4785
  return {
4786
  // Arrays of history events. Doing something adds an event to
4787
  // done and clears undo. Undoing moves events from done to
@@ -4791,7 +4949,7 @@ window.CodeMirror = (function() {
4791
  // event
4792
  lastTime: 0, lastOp: null, lastOrigin: null,
4793
  // Used by the isClean() method
4794
- dirtyCounter: 0
4795
  };
4796
  }
4797
 
@@ -4819,7 +4977,8 @@ window.CodeMirror = (function() {
4819
  if (cur &&
4820
  (hist.lastOp == opId ||
4821
  hist.lastOrigin == change.origin && change.origin &&
4822
- ((change.origin.charAt(0) == "+" && hist.lastTime > time - 600) || change.origin.charAt(0) == "*"))) {
 
4823
  // Merge this change into the last event
4824
  var last = lst(cur.changes);
4825
  if (posEq(change.from, change.to) && posEq(change.from, last.to)) {
@@ -4834,17 +4993,13 @@ window.CodeMirror = (function() {
4834
  } else {
4835
  // Can not be merged, start a new event.
4836
  cur = {changes: [historyChangeFromChange(doc, change)],
 
4837
  anchorBefore: doc.sel.anchor, headBefore: doc.sel.head,
4838
  anchorAfter: selAfter.anchor, headAfter: selAfter.head};
4839
  hist.done.push(cur);
 
4840
  while (hist.done.length > hist.undoDepth)
4841
  hist.done.shift();
4842
- if (hist.dirtyCounter < 0)
4843
- // The user has made a change after undoing past the last clean state.
4844
- // We can never get back to a clean state now until markClean() is called.
4845
- hist.dirtyCounter = NaN;
4846
- else
4847
- hist.dirtyCounter++;
4848
  }
4849
  hist.lastTime = time;
4850
  hist.lastOp = opId;
@@ -4959,6 +5114,9 @@ window.CodeMirror = (function() {
4959
  if (e.stopPropagation) e.stopPropagation();
4960
  else e.cancelBubble = true;
4961
  }
 
 
 
4962
  function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
4963
  CodeMirror.e_stop = e_stop;
4964
  CodeMirror.e_preventDefault = e_preventDefault;
@@ -5025,6 +5183,11 @@ window.CodeMirror = (function() {
5025
  delayedCallbacks.push(bnd(arr[i]));
5026
  }
5027
 
 
 
 
 
 
5028
  function fireDelayed() {
5029
  --delayedCallbackDepth;
5030
  var delayed = delayedCallbacks;
@@ -5079,7 +5242,11 @@ window.CodeMirror = (function() {
5079
  if (ios) { // Mobile Safari apparently has a bug where select() is broken.
5080
  node.selectionStart = 0;
5081
  node.selectionEnd = node.value.length;
5082
- } else node.select();
 
 
 
 
5083
  }
5084
 
5085
  function indexOf(collection, elt) {
@@ -5113,7 +5280,7 @@ window.CodeMirror = (function() {
5113
  return function(){return f.apply(null, args);};
5114
  }
5115
 
5116
- var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/;
5117
  function isWordChar(ch) {
5118
  return /\w/.test(ch) || ch > "\x80" &&
5119
  (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
@@ -5174,13 +5341,24 @@ window.CodeMirror = (function() {
5174
  // word wrapping between certain characters *only* if a new inline
5175
  // element is started between them. This makes it hard to reliably
5176
  // measure the position of things, since that requires inserting an
5177
- // extra span. This terribly fragile set of regexps matches the
5178
  // character combinations that suffer from this phenomenon on the
5179
  // various browsers.
5180
- var spanAffectsWrapping = /^$/; // Won't match any two-character string
5181
- if (gecko) spanAffectsWrapping = /$'/;
5182
- else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent)) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
5183
- else if (webkit) spanAffectsWrapping = /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.]|\?[\w~`@#$%\^&*(_=+{[|><]/;
 
 
 
 
 
 
 
 
 
 
 
5184
 
5185
  var knownScrollbarWidth;
5186
  function scrollbarWidth(measure) {
@@ -5299,6 +5477,40 @@ window.CodeMirror = (function() {
5299
  return Pos(lineN, ch);
5300
  }
5301
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5302
  // This is somewhat involved. It is needed in order to move
5303
  // 'visually' through bi-directional text -- i.e., pressing left
5304
  // should make the cursor go left, even when in RTL text. The
@@ -5308,37 +5520,24 @@ window.CodeMirror = (function() {
5308
  function moveVisually(line, start, dir, byUnit) {
5309
  var bidi = getOrder(line);
5310
  if (!bidi) return moveLogically(line, start, dir, byUnit);
5311
- var moveOneUnit = byUnit ? function(pos, dir) {
5312
- do pos += dir;
5313
- while (pos > 0 && isExtendingChar.test(line.text.charAt(pos)));
5314
- return pos;
5315
- } : function(pos, dir) { return pos + dir; };
5316
- var linedir = bidi[0].level;
5317
- for (var i = 0; i < bidi.length; ++i) {
5318
- var part = bidi[i], sticky = part.level % 2 == linedir;
5319
- if ((part.from < start && part.to > start) ||
5320
- (sticky && (part.from == start || part.to == start))) break;
5321
- }
5322
- var target = moveOneUnit(start, part.level % 2 ? -dir : dir);
5323
-
5324
- while (target != null) {
5325
- if (part.level % 2 == linedir) {
5326
- if (target < part.from || target > part.to) {
5327
- part = bidi[i += dir];
5328
- target = part && (dir > 0 == part.level % 2 ? moveOneUnit(part.to, -1) : moveOneUnit(part.from, 1));
5329
- } else break;
5330
  } else {
5331
- if (target == bidiLeft(part)) {
5332
- part = bidi[--i];
5333
- target = part && bidiRight(part);
5334
- } else if (target == bidiRight(part)) {
5335
- part = bidi[++i];
5336
- target = part && bidiLeft(part);
5337
- } else break;
5338
  }
5339
  }
5340
-
5341
- return target < 0 || target > line.text.length ? null : target;
5342
  }
5343
 
5344
  function moveLogically(line, start, dir, byUnit) {
@@ -5510,7 +5709,7 @@ window.CodeMirror = (function() {
5510
 
5511
  // THE END
5512
 
5513
- CodeMirror.version = "3.11";
5514
 
5515
  return CodeMirror;
5516
  })();
1
+ // CodeMirror version 3.14
2
  //
3
  // CodeMirror is the only global var we claim
4
  window.CodeMirror = (function() {
94
  function makeDisplay(place, docStart) {
95
  var d = {};
96
 
97
+ var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;");
98
  if (webkit) input.style.width = "1000px";
99
  else input.setAttribute("wrap", "off");
100
  // if border: 0; -- iOS fails to open keyboard (issue #1287)
101
  if (ios) input.style.border = "1px solid black";
102
+ input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
103
 
104
  // Wraps and hides input textarea
105
  d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
107
  d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
108
  d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
109
  d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
110
+ d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
111
  // DIVs containing the selection and the actual code
112
+ d.lineDiv = elt("div", null, "CodeMirror-code");
113
  d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
114
  // Blinky cursor, and element used to ensure cursor fits at the end of a line
115
  d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
129
  // Will contain the gutters, if any
130
  d.gutters = elt("div", null, "CodeMirror-gutters");
131
  d.lineGutter = null;
 
 
132
  // Provides scrolling
133
+ d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
134
  d.scroller.setAttribute("tabIndex", "-1");
135
  // The element in which the editor lives.
136
  d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
137
+ d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
138
  // Work around IE7 z-index bug
139
  if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
140
  if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
165
  d.pollingFast = false;
166
  // Self-resetting timeout for the poller
167
  d.poll = new Delayed();
 
 
168
 
169
  d.cachedCharWidth = d.cachedTextHeight = null;
170
  d.measureLineCache = [];
213
  estimateLineHeights(cm);
214
  regChange(cm);
215
  clearCaches(cm);
216
+ setTimeout(function(){updateScrollbars(cm);}, 100);
217
  }
218
 
219
  function estimateHeight(cm) {
238
  }
239
 
240
  function keyMapChanged(cm) {
241
+ var map = keyMap[cm.options.keyMap], style = map.style;
242
  cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
243
  (style ? " cm-keymap-" + style : "");
244
+ cm.state.disableInput = map.disableInput;
245
  }
246
 
247
  function themeChanged(cm) {
253
  function guttersChanged(cm) {
254
  updateGutters(cm);
255
  regChange(cm);
256
+ setTimeout(function(){alignHorizontally(cm);}, 20);
257
  }
258
 
259
  function updateGutters(cm) {
320
 
321
  // Re-synchronize the fake scrollbars with the actual size of the
322
  // content. Optionally force a scrollTop.
323
+ function updateScrollbars(cm) {
324
+ var d = cm.display, docHeight = cm.doc.height;
325
  var totalHeight = docHeight + paddingVert(d);
326
  d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
327
+ d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px";
328
  var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
329
+ var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1);
330
+ var needsV = scrollHeight > (d.scroller.clientHeight + 1);
331
  if (needsV) {
332
  d.scrollbarV.style.display = "block";
333
  d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
344
  d.scrollbarFiller.style.display = "block";
345
  d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
346
  } else d.scrollbarFiller.style.display = "";
347
+ if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
348
+ d.gutterFiller.style.display = "block";
349
+ d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px";
350
+ d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
351
+ } else d.gutterFiller.style.display = "";
352
 
353
  if (mac_geLion && scrollbarWidth(d.measure) === 0)
354
  d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
404
  // DISPLAY DRAWING
405
 
406
  function updateDisplay(cm, changes, viewPort) {
407
+ var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated;
408
+ var visible = visibleLines(cm.display, cm.doc, viewPort);
409
+ for (;;) {
410
+ if (!updateDisplayInner(cm, changes, visible)) break;
411
+ updated = true;
412
+ updateSelection(cm);
413
+ updateScrollbars(cm);
414
+
415
+ // Clip forced viewport to actual scrollable area
416
+ if (viewPort)
417
+ viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight,
418
+ typeof viewPort == "number" ? viewPort : viewPort.top);
419
+ visible = visibleLines(cm.display, cm.doc, viewPort);
420
+ if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo)
421
+ break;
422
+ changes = [];
423
+ }
424
+
425
  if (updated) {
426
  signalLater(cm, "update", cm);
427
  if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
428
  signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
429
  }
 
 
 
430
  return updated;
431
  }
432
 
433
  // Uses a set of changes plus the current scroll position to
434
  // determine which DOM updates have to be made, and makes the
435
  // updates.
436
+ function updateDisplayInner(cm, changes, visible) {
437
  var display = cm.display, doc = cm.doc;
438
  if (!display.wrapper.clientWidth) {
439
  display.showingFrom = display.showingTo = doc.first;
441
  return;
442
  }
443
 
 
 
 
 
444
  // Bail out if the visible area is already rendered and nothing changed.
445
  if (changes.length == 0 &&
446
  visible.from > display.showingFrom && visible.to < display.showingTo)
501
  }
502
  intact.sort(function(a, b) {return a.from - b.from;});
503
 
504
+ // Avoid crashing on IE's "unspecified error" when in iframes
505
+ try {
506
+ var focused = document.activeElement;
507
+ } catch(e) {}
508
  if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
509
  patchDisplay(cm, from, to, intact, positionsChangedFrom);
510
  display.lineDiv.style.display = "";
511
+ if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus();
512
 
513
  var different = from != display.showingFrom || to != display.showingTo ||
514
  display.lastSizeC != display.wrapper.clientHeight;
515
  // This is just a bogus formula that detects when the editor is
516
  // resized or the font size changes.
517
+ if (different) {
518
+ display.lastSizeC = display.wrapper.clientHeight;
519
+ startWorker(cm, 400);
520
+ }
521
  display.showingFrom = from; display.showingTo = to;
 
522
 
523
  var prevBottom = display.lineDiv.offsetTop;
524
  for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
541
  }
542
  updateViewOffset(cm);
543
 
 
 
544
  return true;
545
  }
546
 
607
  if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
608
  if (lineIsHidden(cm.doc, line)) {
609
  if (line.height != 0) updateLineHeight(line, 0);
610
+ if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) {
611
+ var w = line.widgets[i];
612
+ if (w.showIfHidden) {
613
  var prev = cur.previousSibling;
614
  if (/pre/i.test(prev.nodeName)) {
615
  var wrap = elt("div", null, null, "position: relative");
617
  wrap.appendChild(prev);
618
  prev = wrap;
619
  }
620
+ var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget"));
621
+ if (!w.handleMouseEvents) wnode.ignoreEvents = true;
622
+ positionLineWidget(w, wnode, prev, dims);
623
  }
624
+ }
625
  } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
626
  // This line is intact. Skip to the actual node. Update its
627
  // line number if needed.
664
 
665
  if (reuse) {
666
  reuse.alignable = null;
667
+ var isOk = true, widgetsSeen = 0, insertBefore = null;
668
  for (var n = reuse.firstChild, next; n; n = next) {
669
  next = n.nextSibling;
670
  if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
671
  reuse.removeChild(n);
672
  } else {
673
  for (var i = 0, first = true; i < line.widgets.length; ++i) {
674
+ var widget = line.widgets[i];
675
+ if (!widget.above) { insertBefore = n; first = false; }
676
  if (widget.node == n.firstChild) {
677
  positionLineWidget(widget, n, reuse, dims);
678
  ++widgetsSeen;
 
679
  break;
680
  }
681
  }
682
  if (i == line.widgets.length) { isOk = false; break; }
683
  }
684
  }
685
+ reuse.insertBefore(lineElement, insertBefore);
686
  if (isOk && widgetsSeen == line.widgets.length) {
687
  wrap = reuse;
688
  reuse.className = line.wrapClass || "";
717
  if (ie_lt8) wrap.style.zIndex = 2;
718
  if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
719
  var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
720
+ if (!widget.handleMouseEvents) node.ignoreEvents = true;
721
  positionLineWidget(widget, node, wrap, dims);
722
  if (widget.above)
723
  wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
761
  display.selectionDiv.style.display = "none";
762
 
763
  // Move the hidden textarea near the cursor to prevent scrolling artifacts
764
+ if (cm.options.moveInputWithCursor) {
765
+ var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
766
+ var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);
767
+ display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
768
+ headPos.top + lineOff.top - wrapOff.top)) + "px";
769
+ display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
770
+ headPos.left + lineOff.left - wrapOff.left)) + "px";
771
+ }
772
  }
773
 
774
  // No selection, plain cursor
800
  "px; height: " + (bottom - top) + "px"));
801
  }
802
 
803
+ function drawForLine(line, fromArg, toArg) {
804
  var lineObj = getLine(doc, line);
805
+ var lineLen = lineObj.text.length;
806
+ var start, end;
807
+ function coords(ch, bias) {
808
+ return charCoords(cm, Pos(line, ch), "div", lineObj, bias);
809
  }
810
 
811
  iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
812
+ var leftPos = coords(from, "left"), rightPos, left, right;
813
+ if (from == to) {
814
+ rightPos = leftPos;
815
+ left = right = leftPos.left;
816
+ } else {
817
+ rightPos = coords(to - 1, "right");
818
+ if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
819
+ left = leftPos.left;
820
+ right = rightPos.right;
821
+ }
822
+ if (fromArg == null && from == 0) left = pl;
823
  if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
824
  add(left, leftPos.top, null, leftPos.bottom);
825
  left = pl;
826
  if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
827
  }
828
  if (toArg == null && to == lineLen) right = clientWidth;
829
+ if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
830
+ start = leftPos;
831
+ if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
832
+ end = rightPos;
833
  if (left < pl + 1) left = pl;
834
  add(left, rightPos.top, right - left, rightPos.bottom);
835
  });
836
+ return {start: start, end: end};
837
  }
838
 
839
  if (sel.from.line == sel.to.line) {
840
  drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
841
  } else {
842
+ var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line);
843
+ var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine);
844
+ var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end;
845
+ var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start;
846
+ if (singleVLine) {
847
+ if (leftEnd.top < rightStart.top - 2) {
848
+ add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
849
+ add(pl, rightStart.top, rightStart.left, rightStart.bottom);
850
+ } else {
851
+ add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
852
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
853
  }
854
+ if (leftEnd.bottom < rightStart.top)
855
+ add(pl, leftEnd.bottom, null, rightStart.top);
856
  }
857
 
858
  removeChildrenAndAdd(display.selectionDiv, fragment);
861
 
862
  // Cursor-blinking
863
  function restartBlink(cm) {
864
+ if (!cm.state.focused) return;
865
  var display = cm.display;
866
  clearInterval(display.blinker);
867
  var on = true;
868
  display.cursor.style.visibility = display.otherCursor.style.visibility = "";
869
  display.blinker = setInterval(function() {
 
870
  display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
871
  }, cm.options.cursorBlinkRate);
872
  }
918
  // valid state. If that fails, it returns the line with the
919
  // smallest indentation, which tends to need the least context to
920
  // parse correctly.
921
+ function findStartLine(cm, n, precise) {
922
  var minindent, minline, doc = cm.doc;
923
  for (var search = n, lim = n - 100; search > lim; --search) {
924
  if (search <= doc.first) return doc.first;
925
  var line = getLine(doc, search - 1);
926
+ if (line.stateAfter && (!precise || search <= doc.frontier)) return search;
927
  var indented = countColumn(line.text, null, cm.options.tabSize);
928
  if (minline == null || minindent > indented) {
929
  minline = search - 1;
933
  return minline;
934
  }
935
 
936
+ function getStateBefore(cm, n, precise) {
937
  var doc = cm.doc, display = cm.display;
938
  if (!doc.mode.startState) return true;
939
+ var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
940
  if (!state) state = startState(doc.mode);
941
  else state = copyState(doc.mode, state);
942
  doc.iter(pos, n, function(line) {
957
  return e.offsetLeft;
958
  }
959
 
960
+ function measureChar(cm, line, ch, data, bias) {
961
  var dir = -1;
962
  data = data || measureLine(cm, line);
963
 
966
  if (r) break;
967
  if (dir < 0 && pos == 0) dir = 1;
968
  }
969
+ var rightV = (pos < ch || bias == "right") && r.topRight != null;
970
  return {left: pos < ch ? r.right : r.left,
971
  right: pos > ch ? r.left : r.right,
972
+ top: rightV ? r.topRight : r.top,
973
+ bottom: rightV ? r.bottomRight : r.bottom};
974
  }
975
 
976
  function findCachedMeasurement(cm, line) {
980
  if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
981
  cm.display.scroller.clientWidth == memo.width &&
982
  memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass)
983
+ return memo;
984
  }
985
  }
986
 
987
+ function clearCachedMeasurement(cm, line) {
988
+ var exists = findCachedMeasurement(cm, line);
989
+ if (exists) exists.text = exists.measure = exists.markedSpans = null;
990
+ }
991
+
992
  function measureLine(cm, line) {
993
  // First look in the cache
994
+ var cached = findCachedMeasurement(cm, line);
995
+ if (cached) return cached.measure;
996
+
997
+ // Failing that, recompute and store result in cache
998
+ var measure = measureLineInner(cm, line);
999
+ var cache = cm.display.measureLineCache;
1000
+ var memo = {text: line.text, width: cm.display.scroller.clientWidth,
1001
+ markedSpans: line.markedSpans, measure: measure,
1002
+ classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass};
1003
+ if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo;
1004
+ else cache.push(memo);
1005
  return measure;
1006
  }
1007
 
1043
  if (ie_lt9 && display.measure.first != pre)
1044
  removeChildrenAndAdd(display.measure, pre);
1045
 
1046
+ function categorizeVSpan(top, bot) {
1047
+ if (bot > maxBot) bot = maxBot;
1048
+ if (top < 0) top = 0;
1049
  for (var j = 0; j < vranges.length; j += 2) {
1050
  var rtop = vranges[j], rbot = vranges[j+1];
1051
  if (rtop > bot || rbot < top) continue;
1054
  Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
1055
  vranges[j] = Math.min(top, rtop);
1056
  vranges[j+1] = Math.max(bot, rbot);
1057
+ return j;
1058
+ }
1059
+ }
1060
+ vranges.push(top, bot);
1061
+ return j;
1062
+ }
1063
+
1064
+ for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
1065
+ var size, node = cur;
1066
+ // A widget might wrap, needs special care
1067
+ if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) {
1068
+ if (cur.firstChild.nodeType == 1) node = cur.firstChild;
1069
+ var rects = node.getClientRects(), rLeft = rects[0], rRight = rects[rects.length - 1];
1070
+ if (rects.length > 1) {
1071
+ var vCatLeft = categorizeVSpan(rLeft.top - outer.top, rLeft.bottom - outer.top);
1072
+ var vCatRight = categorizeVSpan(rRight.top - outer.top, rRight.bottom - outer.top);
1073
+ data[i] = {left: rLeft.left - outer.left, right: rRight.right - outer.left,
1074
+ top: vCatLeft, topRight: vCatRight};
1075
+ continue;
1076
  }
1077
  }
1078
+ size = getRect(node);
1079
+ var vCat = categorizeVSpan(size.top - outer.top, size.bottom - outer.top);
1080
  var right = size.right;
1081
  if (cur.measureRight) right = getRect(cur.measureRight).left;
1082
+ data[i] = {left: size.left - outer.left, right: right - outer.left, top: vCat};
1083
  }
1084
  for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
1085
+ var vr = cur.top, vrRight = cur.topRight;
1086
  cur.top = vranges[vr]; cur.bottom = vranges[vr+1];
1087
+ if (vrRight != null) { cur.topRight = vranges[vrRight]; cur.bottomRight = vranges[vrRight+1]; }
1088
  }
 
1089
  return data;
1090
  }
1091
 
1096
  if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;
1097
  }
1098
  var cached = !hasBadSpan && findCachedMeasurement(cm, line);
1099
+ if (cached) return measureChar(cm, line, line.text.length, cached.measure, "right").right;
1100
 
1101
  var pre = lineContent(cm, line);
1102
  var end = pre.appendChild(zeroWidthElement(cm.display.measure));
1107
  function clearCaches(cm) {
1108
  cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
1109
  cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
1110
+ if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
1111
  cm.display.lineNumChars = null;
1112
  }
1113
 
1114
+ function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
1115
+ function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
1116
+
1117
  // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
1118
  function intoCoordSystem(cm, lineObj, rect, context) {
1119
  if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
1123
  if (context == "line") return rect;
1124
  if (!context) context = "local";
1125
  var yOff = heightAtLine(cm, lineObj);
1126
+ if (context == "local") yOff += paddingTop(cm.display);
1127
+ else yOff -= cm.display.viewOffset;
1128
+ if (context == "page" || context == "window") {
1129
  var lOff = getRect(cm.display.lineSpace);
1130
+ yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
1131
+ var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
1132
  rect.left += xOff; rect.right += xOff;
1133
  }
1134
  rect.top += yOff; rect.bottom += yOff;
1136
  }
1137
 
1138
  // Context may be "window", "page", "div", or "local"/null
1139
+ // Result is in "div" coords
1140
  function fromCoordSystem(cm, coords, context) {
1141
  if (context == "div") return coords;
1142
  var left = coords.left, top = coords.top;
1143
+ // First move into "page" coordinate system
1144
  if (context == "page") {
1145
+ left -= pageScrollX();
1146
+ top -= pageScrollY();
1147
+ } else if (context == "local" || !context) {
1148
+ var localBox = getRect(cm.display.sizer);
1149
+ left += localBox.left;
1150
+ top += localBox.top;
1151
  }
1152
+
1153
  var lineSpaceBox = getRect(cm.display.lineSpace);
1154
+ return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
 
 
 
 
 
 
 
1155
  }
1156
 
1157
+ function charCoords(cm, pos, context, lineObj, bias) {
1158
  if (!lineObj) lineObj = getLine(cm.doc, pos.line);
1159
+ return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context);
1160
  }
1161
 
1162
  function cursorCoords(cm, pos, context, lineObj, measurement) {
1163
  lineObj = lineObj || getLine(cm.doc, pos.line);
1164
  if (!measurement) measurement = measureLine(cm, lineObj);
1165
  function get(ch, right) {
1166
+ var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left");
1167
  if (right) m.left = m.right; else m.right = m.left;
1168
  return intoCoordSystem(cm, lineObj, m, context);
1169
  }
1170
+ function getBidi(ch, partPos) {
1171
+ var part = order[partPos], right = part.level % 2;
1172
+ if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {
1173
+ part = order[--partPos];
1174
+ ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
1175
+ right = true;
1176
+ } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {
1177
+ part = order[++partPos];
1178
+ ch = bidiLeft(part) - part.level % 2;
1179
+ right = false;
 
 
 
 
 
 
 
 
 
 
1180
  }
1181
+ if (right && ch == part.to && ch > part.from) return get(ch - 1);
1182
+ return get(ch, right);
1183
  }
1184
+ var order = getOrder(lineObj), ch = pos.ch;
1185
+ if (!order) return get(ch);
1186
+ var partPos = getBidiPartAt(order, ch);
1187
+ var val = getBidi(ch, partPos);
1188
+ if (bidiOther != null) val.other = getBidi(ch, bidiOther);
1189
+ return val;
1190
  }
1191
 
1192
+ function PosWithInfo(line, ch, outside, xRel) {
1193
  var pos = new Pos(line, ch);
1194
+ pos.xRel = xRel;
1195
  if (outside) pos.outside = true;
1196
  return pos;
1197
  }
1200
  function coordsChar(cm, x, y) {
1201
  var doc = cm.doc;
1202
  y += cm.display.viewOffset;
1203
+ if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
1204
  var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
1205
  if (lineNo > last)
1206
+ return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
1207
  if (x < 0) x = 0;
1208
 
1209
  for (;;) {
1211
  var found = coordsCharInner(cm, lineObj, lineNo, x, y);
1212
  var merged = collapsedSpanAtEnd(lineObj);
1213
  var mergedPos = merged && merged.find();
1214
+ if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
1215
  lineNo = mergedPos.to.line;
1216
  else
1217
  return found;
1237
  var from = lineLeft(lineObj), to = lineRight(lineObj);
1238
  var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
1239
 
1240
+ if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
1241
  // Do a binary search between these bounds.
1242
  for (;;) {
1243
  if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
1244
+ var ch = x < fromX || x - fromX <= toX - x ? from : to;
1245
+ var xDiff = x - (ch == from ? fromX : toX);
1246
  while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
1247
+ var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
1248
+ xDiff < 0 ? -1 : xDiff ? 1 : 0);
1249
  return pos;
1250
  }
1251
  var step = Math.ceil(dist / 2), middle = from + step;
1254
  for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
1255
  }
1256
  var middleX = getX(middle);
1257
+ if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
1258
+ else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
1259
  }
1260
  }
1261
 
1306
  userSelChange: null,
1307
  textChanged: null,
1308
  selectionChanged: false,
1309
+ cursorActivity: false,
1310
  updateMaxLine: false,
1311
  updateScrollPos: false,
1312
  id: ++nextOpId
1319
  cm.curOp = null;
1320
 
1321
  if (op.updateMaxLine) computeMaxLength(cm);
1322
+ if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) {
1323
  var width = measureLineWidth(cm, display.maxLine);
1324
  display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
1325
  display.maxLineChanged = false;
1343
  display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop;
1344
  display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft;
1345
  alignHorizontally(cm);
1346
+ if (op.scrollToPos)
1347
+ scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin);
1348
  } else if (newScrollPos) {
1349
  scrollCursorIntoView(cm);
1350
  }
1366
  }
1367
  if (op.textChanged)
1368
  signal(cm, "change", cm, op.textChanged);
1369
+ if (op.cursorActivity) signal(cm, "cursorActivity", cm);
1370
  if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
1371
  }
1372
 
1431
  // supported or compatible enough yet to rely on.)
1432
  function readInput(cm) {
1433
  var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
1434
+ if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false;
1435
  var text = input.value;
1436
  if (text == prevInput && posEq(sel.from, sel.to)) return false;
1437
+ if (ie && !ie_lt9 && cm.display.inputHasSelection === text) {
 
 
 
1438
  resetInput(cm, true);
1439
  return false;
1440
  }
1441
+
1442
  var withOp = !cm.curOp;
1443
  if (withOp) startOperation(cm);
1444
  sel.shift = false;
1445
  var same = 0, l = Math.min(prevInput.length, text.length);
1446
+ while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
1447
  var from = sel.from, to = sel.to;
1448
  if (same < prevInput.length)
1449
  from = Pos(from.line, from.ch - (prevInput.length - same));
1450
  else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
1451
  to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
 
 
 
1452
 
1453
+ var updateInput = cm.curOp.updateInput;
1454
+ var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)),
1455
+ origin: cm.state.pasteIncoming ? "paste" : "+input"};
1456
+ makeChange(cm.doc, changeEvent, "end");
1457
  cm.curOp.updateInput = updateInput;
1458
+ signalLater(cm, "inputRead", cm, changeEvent);
1459
+
1460
  if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
1461
  else cm.display.prevInput = text;
1462
  if (withOp) endOperation(cm);
1470
  cm.display.prevInput = "";
1471
  minimal = hasCopyEvent &&
1472
  (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
1473
+ var content = minimal ? "-" : selected || cm.getSelection();
1474
+ cm.display.input.value = content;
1475
  if (cm.state.focused) selectInput(cm.display.input);
1476
+ if (ie && !ie_lt9) cm.display.inputHasSelection = content;
1477
+ } else if (user) {
1478
+ cm.display.prevInput = cm.display.input.value = "";
1479
+ if (ie && !ie_lt9) cm.display.inputHasSelection = null;
1480
+ }
1481
  cm.display.inaccurateSelection = minimal;
1482
  }
1483
 
1495
  function registerEventHandlers(cm) {
1496
  var d = cm.display;
1497
  on(d.scroller, "mousedown", operation(cm, onMouseDown));
1498
+ if (ie)
1499
+ on(d.scroller, "dblclick", operation(cm, function(e) {
1500
+ if (signalDOMEvent(cm, e)) return;
1501
+ var pos = posFromMouse(cm, e);
1502
+ if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
1503
+ e_preventDefault(e);
1504
+ var word = findWordAt(getLine(cm.doc, pos.line).text, pos);
1505
+ extendSelection(cm.doc, word.from, word.to);
1506
+ }));
1507
+ else
1508
+ on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
1509
  on(d.lineSpace, "selectstart", function(e) {
1510
  if (!eventInWidget(d, e)) e_preventDefault(e);
1511
  });
1537
  // Prevent wrapper from ever scrolling
1538
  on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
1539
 
1540
+ var resizeTimer;
1541
  function onResize() {
1542
+ if (resizeTimer == null) resizeTimer = setTimeout(function() {
1543
+ resizeTimer = null;
1544
+ // Might be a text scaling operation, clear size caches.
1545
+ d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null;
1546
+ clearCaches(cm);
1547
+ runInOp(cm, bind(regChange, cm));
1548
+ }, 100);
1549
  }
1550
  on(window, "resize", onResize);
1551
  // Above handler holds on to the editor and its data structures.
1559
  setTimeout(unregister, 5000);
1560
 
1561
  on(d.input, "keyup", operation(cm, function(e) {
1562
+ if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1563
  if (e.keyCode == 16) cm.doc.sel.shift = false;
1564
  }));
1565
  on(d.input, "input", bind(fastPoll, cm));
1569
  on(d.input, "blur", bind(onBlur, cm));
1570
 
1571
  function drag_(e) {
1572
+ if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1573
  e_stop(e);
1574
  }
1575
  if (cm.options.dragDrop) {
1608
 
1609
  function eventInWidget(display, e) {
1610
  for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
1611
+ if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
 
 
1612
  }
1613
  }
1614
 
1618
  var target = e_target(e);
1619
  if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
1620
  target == display.scrollbarV || target == display.scrollbarV.firstChild ||
1621
+ target == display.scrollbarFiller || target == display.gutterFiller) return null;
1622
  }
1623
  var x, y, space = getRect(display.lineSpace);
1624
  // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1628
 
1629
  var lastClick, lastDoubleClick;
1630
  function onMouseDown(e) {
1631
+ if (signalDOMEvent(this, e)) return;
1632
  var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
1633
  sel.shift = e.shiftKey;
1634
 
1699
  e_preventDefault(e);
1700
  if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
1701
 
1702
+ var startstart = sel.from, startend = sel.to, lastPos = start;
1703
 
1704
  function doSelect(cur) {
1705
+ if (posEq(lastPos, cur)) return;
1706
+ lastPos = cur;
1707
+
1708
  if (type == "single") {
1709
  extendSelection(cm.doc, clipPos(doc, start), cur);
1710
  return;
1752
 
1753
  function done(e) {
1754
  counter = Infinity;
 
 
1755
  e_preventDefault(e);
1756
  focusInput(cm);
1757
  off(document, "mousemove", move);
1767
  on(document, "mouseup", up);
1768
  }
1769
 
1770
+ function clickInGutter(cm, e) {
1771
+ var display = cm.display;
1772
+ try { var mX = e.clientX, mY = e.clientY; }
1773
+ catch(e) { return false; }
1774
+
1775
+ if (mX >= Math.floor(getRect(display.gutters).right)) return false;
1776
+ e_preventDefault(e);
1777
+ if (!hasHandler(cm, "gutterClick")) return true;
1778
+
1779
+ var lineBox = getRect(display.lineDiv);
1780
+ if (mY > lineBox.bottom) return true;
1781
+ mY -= lineBox.top - display.viewOffset;
1782
+
1783
+ for (var i = 0; i < cm.options.gutters.length; ++i) {
1784
+ var g = display.gutters.childNodes[i];
1785
+ if (g && getRect(g).right >= mX) {
1786
+ var line = lineAtHeight(cm.doc, mY);
1787
+ var gutter = cm.options.gutters[i];
1788
+ signalLater(cm, "gutterClick", cm, line, gutter, e);
1789
+ break;
1790
+ }
1791
+ }
1792
+ return true;
1793
+ }
1794
+
1795
+ // Kludge to work around strange IE behavior where it'll sometimes
1796
+ // re-fire a series of drag-related events right after the drop (#1551)
1797
+ var lastDrop = 0;
1798
+
1799
  function onDrop(e) {
1800
  var cm = this;
1801
+ if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
1802
  return;
1803
  e_preventDefault(e);
1804
+ if (ie) lastDrop = +new Date;
1805
  var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
1806
  if (!pos || isReadOnly(cm)) return;
1807
  if (files && files.length && window.FileReader && window.File) {
1841
  }
1842
  }
1843
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1844
  function onDragStart(cm, e) {
1845
+ if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
1846
+ if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
1847
 
1848
  var txt = cm.getSelection();
1849
  e.dataTransfer.setData("Text", txt);
1850
 
1851
  // Use dummy image instead of default browsers image.
1852
  // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
1853
+ if (e.dataTransfer.setDragImage && !safari) {
1854
  var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
1855
  if (opera) {
1856
  img.width = img.height = 1;
1858
  // Force a relayout, or Opera won't use our image for some obscure reason
1859
  img._top = img.offsetTop;
1860
  }
 
 
 
 
 
 
 
 
 
1861
  e.dataTransfer.setDragImage(img, 0, 0);
1862
  if (opera) img.parentNode.removeChild(img);
1863
  }
1870
  if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
1871
  if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
1872
  if (gecko) updateDisplay(cm, []);
1873
+ startWorker(cm, 100);
1874
  }
1875
  function setScrollLeft(cm, val, isScroller) {
1876
  if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
1908
  if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
1909
  else if (dy == null) dy = e.wheelDelta;
1910
 
1911
+ var display = cm.display, scroll = display.scroller;
1912
+ // Quit if there's nothing to scroll here
1913
+ if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
1914
+ dy && scroll.scrollHeight > scroll.clientHeight)) return;
1915
+
1916
  // Webkit browsers on OS X abort momentum scrolls when the target
1917
  // of the scroll event is removed from the scrollable element.
1918
  // This hack (see related code in patchDisplay) makes sure the
1926
  }
1927
  }
1928
 
 
1929
  // On some browsers, horizontal scrolling will cause redraws to
1930
  // happen before the gutter has been realigned, causing it to
1931
  // wriggle around in a most unseemly way. When we have an
2003
  var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
2004
  clearTimeout(maybeTransition);
2005
  if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
2006
+ if (getKeyMap(cm.options.keyMap) == startMap) {
2007
  cm.options.keyMap = (next.call ? next.call(null, cm) : next);
2008
+ keyMapChanged(cm);
2009
+ }
2010
  }, 50);
2011
 
2012
  var name = keyName(e, true), handled = false;
2019
  // 'go') bound to the keyname without 'Shift-'.
2020
  handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
2021
  || lookupKey(name, keymaps, function(b) {
2022
+ if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
2023
+ return doHandleBinding(cm, b);
2024
  });
2025
  } else {
2026
  handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
2027
  }
 
2028
 
2029
  if (handled) {
2030
  e_preventDefault(e);
2031
  restartBlink(cm);
2032
  if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
2033
+ signalLater(cm, "keyHandled", cm, name, e);
2034
  }
2035
  return handled;
2036
  }
2041
  if (handled) {
2042
  e_preventDefault(e);
2043
  restartBlink(cm);
2044
+ signalLater(cm, "keyHandled", cm, "'" + ch + "'", e);
2045
  }
2046
  return handled;
2047
  }
2051
  var cm = this;
2052
  if (!cm.state.focused) onFocus(cm);
2053
  if (ie && e.keyCode == 27) { e.returnValue = false; }
2054
+ if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
2055
  var code = e.keyCode;
2056
  // IE does strange things with escape.
2057
  cm.doc.sel.shift = code == 16 || e.shiftKey;
2067
 
2068
  function onKeyPress(e) {
2069
  var cm = this;
2070
+ if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
2071
  var keyCode = e.keyCode, charCode = e.charCode;
2072
  if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
2073
  if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
2077
  this.doc.mode.electricChars.indexOf(ch) > -1)
2078
  setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
2079
  if (handleCharBinding(cm, e, ch)) return;
2080
+ if (ie && !ie_lt9) cm.display.inputHasSelection = null;
2081
  fastPoll(cm);
2082
  }
2083
 
2123
  // Adds "Select all" to context menu in FF
2124
  if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
2125
 
2126
+ function prepareSelectAllHack() {
2127
+ if (display.input.selectionStart != null) {
2128
+ var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value);
2129
+ display.prevInput = " ";
2130
+ display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
2131
+ }
2132
+ }
2133
  function rehide() {
2134
  display.inputDiv.style.position = "relative";
2135
  display.input.style.cssText = oldCSS;
2137
  slowPoll(cm);
2138
 
2139
  // Try to detect the user choosing select-all
2140
+ if (display.input.selectionStart != null) {
2141
+ if (!ie || ie_lt9) prepareSelectAllHack();
2142
  clearTimeout(detectingSelectAll);
2143
+ var i = 0, poll = function(){
 
 
 
2144
  if (display.prevInput == " " && display.input.selectionStart == 0)
2145
  operation(cm, commands.selectAll)(cm);
2146
  else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
2150
  }
2151
  }
2152
 
2153
+ if (ie && !ie_lt9) prepareSelectAllHack();
2154
  if (captureMiddleClick) {
2155
  e_stop(e);
2156
  var mouseup = function() {
2165
 
2166
  // UPDATING
2167
 
2168
+ var changeEnd = CodeMirror.changeEnd = function(change) {
2169
+ if (!change.text) return change.to;
2170
  return Pos(change.from.line + change.text.length - 1,
2171
  lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
2172
+ };
2173
 
2174
  // Make sure a position will be valid after the given change.
2175
  function clipPostChange(doc, change, pos) {
2211
  return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)};
2212
  }
2213
 
2214
+ function filterChange(doc, change, update) {
2215
  var obj = {
2216
  canceled: false,
2217
  from: change.from,
2218
  to: change.to,
2219
  text: change.text,
2220
  origin: change.origin,
 
 
 
 
 
 
2221
  cancel: function() { this.canceled = true; }
2222
  };
2223
+ if (update) obj.update = function(from, to, text, origin) {
2224
+ if (from) this.from = clipPos(doc, from);
2225
+ if (to) this.to = clipPos(doc, to);
2226
+ if (text) this.text = text;
2227
+ if (origin !== undefined) this.origin = origin;
2228
+ };
2229
  signal(doc, "beforeChange", doc, obj);
2230
  if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
2231
 
2242
  }
2243
 
2244
  if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
2245
+ change = filterChange(doc, change, true);
2246
  if (!change) return;
2247
  }
2248
 
2281
  var hist = doc.history;
2282
  var event = (type == "undo" ? hist.done : hist.undone).pop();
2283
  if (!event) return;
 
2284
 
2285
  var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,
2286
+ anchorAfter: event.anchorBefore, headAfter: event.headBefore,
2287
+ generation: hist.generation};
2288
  (type == "undo" ? hist.undone : hist.done).push(anti);
2289
+ hist.generation = event.generation || ++hist.maxGeneration;
2290
+
2291
+ var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
2292
 
2293
  for (var i = event.changes.length - 1; i >= 0; --i) {
2294
  var change = event.changes[i];
2295
  change.origin = type;
2296
+ if (filter && !filterChange(doc, change, false)) {
2297
+ (type == "undo" ? hist.done : hist.undone).length = 0;
2298
+ return;
2299
+ }
2300
+
2301
  anti.changes.push(historyChangeFromChange(doc, change));
2302
 
2303
  var after = i ? computeSelAfterChange(doc, change, null)
2367
  });
2368
  }
2369
 
2370
+ if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head))
2371
+ cm.curOp.cursorActivity = true;
2372
+
2373
  updateDoc(doc, change, spans, selAfter, estimateHeight(cm));
2374
 
2375
  if (!cm.options.lineWrapping) {
2496
  sel.to = inv ? anchor : head;
2497
 
2498
  if (doc.cm)
2499
+ doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
2500
+ doc.cm.curOp.cursorActivity = true;
2501
 
2502
  signalLater(doc, "cursorActivity", doc);
2503
  }
2559
  // SCROLLING
2560
 
2561
  function scrollCursorIntoView(cm) {
2562
+ var coords = scrollPosIntoView(cm, cm.doc.sel.head, cm.options.cursorScrollMargin);
2563
  if (!cm.state.focused) return;
2564
+ var display = cm.display, box = getRect(display.sizer), doScroll = null;
2565
+ if (coords.top + box.top < 0) doScroll = true;
2566
+ else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
2567
  if (doScroll != null && !phantom) {
2568
  var hidden = display.cursor.style.display == "none";
2569
  if (hidden) {
2601
  }
2602
 
2603
  function calculateScrollPos(cm, x1, y1, x2, y2) {
2604
+ var display = cm.display, snapMargin = textHeight(cm.display);
2605
+ if (y1 < 0) y1 = 0;
2606
  var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
2607
  var docBottom = cm.doc.height + paddingVert(display);
2608
+ var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
2609
+ if (y1 < screentop) {
2610
+ result.scrollTop = atTop ? 0 : y1;
2611
+ } else if (y2 > screentop + screen) {
2612
+ var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);
2613
+ if (newTop != screentop) result.scrollTop = newTop;
2614
+ }
2615
 
2616
  var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
2617
  x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
2627
  }
2628
 
2629
  function updateScrollPos(cm, left, top) {
2630
+ cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left,
2631
+ scrollTop: top == null ? cm.doc.scrollTop : top};
2632
  }
2633
 
2634
  function addToScrollPos(cm, left, top) {
2642
 
2643
  function indentLine(cm, n, how, aggressive) {
2644
  var doc = cm.doc;
2645
+ if (how == null) how = "add";
2646
  if (how == "smart") {
2647
  if (!cm.doc.mode.indent) how = "prev";
2648
  else var state = getStateBefore(cm, n);
2665
  indentation = curSpace + cm.options.indentUnit;
2666
  } else if (how == "subtract") {
2667
  indentation = curSpace - cm.options.indentUnit;
2668
+ } else if (typeof how == "number") {
2669
+ indentation = curSpace + how;
2670
  }
2671
  indentation = Math.max(0, indentation);
2672
 
2691
  }
2692
 
2693
  function findPosH(doc, pos, dir, unit, visually) {
2694
+ var line = pos.line, ch = pos.ch, origDir = dir;
2695
  var lineObj = getLine(doc, line);
2696
  var possible = true;
2697
  function findNextLine() {
2730
  if (dir > 0 && !moveOnce(!first)) break;
2731
  }
2732
  }
2733
+ var result = skipAtomic(doc, Pos(line, ch), origDir, true);
2734
  if (!possible) result.hitSide = true;
2735
  return result;
2736
  }
2755
  function findWordAt(line, pos) {
2756
  var start = pos.ch, end = pos.ch;
2757
  if (line) {
2758
+ if (pos.xRel < 0 || end == line.length) --start; else ++end;
2759
  var startChar = line.charAt(start);
2760
  var check = isWordChar(startChar) ? isWordChar
2761
  : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
2776
  // 'wrap f in an operation, performed on its `this` parameter'
2777
 
2778
  CodeMirror.prototype = {
2779
+ constructor: CodeMirror,
2780
  focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},
2781
 
2782
  setOption: function(option, value) {
2812
  removeOverlay: operation(null, function(spec) {
2813
  var overlays = this.state.overlays;
2814
  for (var i = 0; i < overlays.length; ++i) {
2815
+ var cur = overlays[i].modeSpec;
2816
+ if (cur == spec || typeof spec == "string" && cur.name == spec) {
2817
  overlays.splice(i, 1);
2818
  this.state.modeGen++;
2819
  regChange(this);
2823
  }),
2824
 
2825
  indentLine: operation(null, function(n, dir, aggressive) {
2826
+ if (typeof dir != "string" && typeof dir != "number") {
2827
  if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
2828
  else dir = dir ? "add" : "subtract";
2829
  }
2838
 
2839
  // Fetch the parser token for a given character. Useful for hacks
2840
  // that want to inspect the mode state (say, for completion).
2841
+ getTokenAt: function(pos, precise) {
2842
  var doc = this.doc;
2843
  pos = clipPos(doc, pos);
2844
+ var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode;
2845
  var line = getLine(doc, pos.line);
2846
  var stream = new StringStream(line.text, this.options.tabSize);
2847
  while (stream.pos < pos.ch && !stream.eol()) {
2856
  state: state};
2857
  },
2858
 
2859
+ getTokenTypeAt: function(pos) {
2860
+ pos = clipPos(this.doc, pos);
2861
+ var styles = getLineStyles(this, getLine(this.doc, pos.line));
2862
+ var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
2863
+ for (;;) {
2864
+ var mid = (before + after) >> 1;
2865
+ if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
2866
+ else if (styles[mid * 2 + 1] < ch) before = mid + 1;
2867
+ else return styles[mid * 2 + 2];
2868
+ }
2869
+ },
2870
+
2871
+ getStateAfter: function(line, precise) {
2872
  var doc = this.doc;
2873
  line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
2874
+ return getStateBefore(this, line + 1, precise);
2875
  },
2876
 
2877
  cursorCoords: function(start, mode) {
2891
  return coordsChar(this, coords.left, coords.top);
2892
  },
2893
 
2894
+ lineAtHeight: function(height, mode) {
2895
+ height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top;
2896
+ return lineAtHeight(this.doc, height + this.display.viewOffset);
2897
+ },
2898
+ heightAtLine: function(line, mode) {
2899
+ var end = false, last = this.doc.first + this.doc.size - 1;
2900
+ if (line < this.doc.first) line = this.doc.first;
2901
+ else if (line > last) { line = last; end = true; }
2902
+ var lineObj = getLine(this.doc, line);
2903
+ return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top +
2904
+ (end ? lineObj.height : 0);
2905
+ },
2906
+
2907
  defaultTextHeight: function() { return textHeight(this.display); },
2908
  defaultCharWidth: function() { return charWidth(this.display); },
2909
 
2932
  return changeLine(this, handle, function(line) {
2933
  var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
2934
  if (!line[prop]) line[prop] = cls;
2935
+ else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false;
2936
  else line[prop] += " " + cls;
2937
  return true;
2938
  });
2945
  if (!cur) return false;
2946
  else if (cls == null) line[prop] = null;
2947
  else {
2948
+ var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)"));
2949
+ if (!found) return false;
2950
+ var end = found.index + found[0].length;
2951
+ line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
2952
  }
2953
  return true;
2954
  });
2996
  if (left + node.offsetWidth > hspace)
2997
  left = hspace - node.offsetWidth;
2998
  }
2999
+ node.style.top = top + "px";
3000
  node.style.left = node.style.right = "";
3001
  if (horiz == "right") {
3002
  left = display.sizer.clientWidth - node.offsetWidth;
3064
  sel.goalColumn = pos.left;
3065
  }),
3066
 
3067
+ toggleOverwrite: function(value) {
3068
+ if (value != null && value == this.state.overwrite) return;
3069
  if (this.state.overwrite = !this.state.overwrite)
3070
  this.display.cursor.className += " CodeMirror-overwrite";
3071
  else
3083
  clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
3084
  },
3085
 
3086
+ scrollIntoView: operation(null, function(pos, margin) {
3087
  if (typeof pos == "number") pos = Pos(pos, 0);
3088
+ if (!margin) margin = 0;
3089
+ var coords = pos;
3090
+
3091
  if (!pos || pos.line != null) {
3092
+ this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head;
3093
+ this.curOp.scrollToPosMargin = margin;
3094
+ coords = cursorCoords(this, this.curOp.scrollToPos);
 
3095
  }
3096
+ var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin);
3097
+ updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop);
3098
+ }),
3099
 
3100
  setSize: function(width, height) {
3101
  function interpret(val) {
3122
  old.cm = null;
3123
  attachDoc(this, doc);
3124
  clearCaches(this);
3125
+ resetInput(this, true);
3126
  updateScrollPos(this, doc.scrollLeft, doc.scrollTop);
3127
  return old;
3128
  }),
3188
  cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
3189
  cm.refresh();
3190
  }, true);
3191
+ option("coverGutterNextToScrollbar", false, updateScrollbars, true);
3192
  option("lineNumbers", false, function(cm) {
3193
  setGuttersForLineNumbers(cm.options);
3194
  guttersChanged(cm);
3204
  option("dragDrop", true);
3205
 
3206
  option("cursorBlinkRate", 530);
3207
+ option("cursorScrollMargin", 0);
3208
  option("cursorHeight", 1);
3209
  option("workTime", 100);
3210
  option("workDelay", 100);
3211
  option("flattenSpans", true);
3212
  option("pollInterval", 100);
3213
  option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;});
3214
+ option("historyEventDelay", 500);
3215
  option("viewportMargin", 10, function(cm){cm.refresh();}, true);
3216
+ option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true);
3217
+ option("moveInputWithCursor", true, function(cm, val) {
3218
+ if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
3219
+ });
3220
 
3221
  option("tabindex", null, function(cm, val) {
3222
  cm.display.input.tabIndex = val || "";
3242
  };
3243
 
3244
  CodeMirror.resolveMode = function(spec) {
3245
+ if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
3246
  spec = mimeModes[spec];
3247
+ } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
3248
+ var found = mimeModes[spec.name];
3249
+ spec = createObj(found, spec);
3250
+ spec.name = found.name;
3251
+ } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
3252
  return CodeMirror.resolveMode("application/xml");
3253
+ }
3254
  if (typeof spec == "string") return {name: spec};
3255
  else return spec || {name: "null"};
3256
  };
3288
  CodeMirror.defineExtension = function(name, func) {
3289
  CodeMirror.prototype[name] = func;
3290
  };
3291
+ CodeMirror.defineDocExtension = function(name, func) {
3292
+ Doc.prototype[name] = func;
3293
+ };
3294
  CodeMirror.defineOption = option;
3295
 
3296
  var initHooks = [];
3341
  var l = cm.getCursor().line;
3342
  cm.replaceRange("", Pos(l, 0), Pos(l), "+delete");
3343
  },
3344
+ delLineLeft: function(cm) {
3345
+ var cur = cm.getCursor();
3346
+ cm.replaceRange("", Pos(cur.line, 0), cur, "+delete");
3347
+ },
3348
  undo: function(cm) {cm.undo();},
3349
  redo: function(cm) {cm.redo();},
3350
  goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
3440
  "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore",
3441
  "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
3442
  "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
3443
+ "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft",
3444
  fallthrough: ["basic", "emacsy"]
3445
  };
3446
  keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
3479
 
3480
  for (var i = 0; i < maps.length; ++i) {
3481
  var done = lookup(maps[i]);
3482
+ if (done) return done != "stop";
3483
  }
3484
  }
3485
  function isModifierKey(event) {
3487
  return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
3488
  }
3489
  function keyName(event, noShift) {
3490
+ if (opera && event.keyCode == 34 && event["char"]) return false;
3491
  var name = keyNames[event.keyCode];
3492
  if (name == null || event.altGraphKey) return false;
3493
  if (event.altKey) name = "Alt-" + name;
3661
  if (min != null && cm) regChange(cm, min, max + 1);
3662
  this.lines.length = 0;
3663
  this.explicitlyCleared = true;
3664
+ if (this.atomic && this.doc.cantEdit) {
3665
  this.doc.cantEdit = false;
3666
  if (cm) reCheckSelection(cm);
3667
  }
3684
  return from && {from: from, to: to};
3685
  };
3686
 
3687
+ TextMarker.prototype.changed = function() {
3688
+ var pos = this.find(), cm = this.doc.cm;
3689
+ if (!pos || !cm) return;
3690
+ var line = getLine(this.doc, pos.from.line);
3691
+ clearCachedMeasurement(cm, line);
3692
+ if (pos.from.line >= cm.display.showingFrom && pos.from.line < cm.display.showingTo) {
3693
+ for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) {
3694
+ if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight);
3695
+ break;
3696
+ }
3697
+ runInOp(cm, function() { cm.curOp.selectionChanged = true; });
3698
+ }
3699
  };
3700
 
3701
  TextMarker.prototype.attachLine = function(line) {
3724
  if (marker.replacedWith) {
3725
  marker.collapsed = true;
3726
  marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
3727
+ if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true;
3728
  }
3729
  if (marker.collapsed) sawCollapsedSpans = true;
3730
 
3731
+ if (marker.addToHistory)
3732
+ addToHistory(doc, {from: from, to: to, origin: "markText"},
3733
+ {head: doc.sel.head, anchor: doc.sel.anchor}, NaN);
3734
+
3735
  var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine;
3736
  doc.iter(curLine, to.line + 1, function(line) {
3737
  if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)
3796
  SharedTextMarker.prototype.find = function() {
3797
  return this.primary.find();
3798
  };
 
 
 
 
 
3799
 
3800
  function markTextShared(doc, from, to, options, type) {
3801
  options = copyObj(options);
3898
  }
3899
  }
3900
  }
3901
+ if (sameLine && first) {
3902
+ // Make sure we didn't create any zero-length spans
3903
+ for (var i = 0; i < first.length; ++i)
3904
+ if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != "bookmark")
3905
+ first.splice(i--, 1);
3906
+ if (!first.length) first = null;
3907
+ }
3908
 
3909
  var newMarkers = [first];
3910
  if (!sameLine) {
3999
  sp = sps[i];
4000
  if (!sp.marker.collapsed) continue;
4001
  if (sp.from == null) return true;
4002
+ if (sp.marker.replacedWith) continue;
4003
  if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
4004
  return true;
4005
  }
4013
  return true;
4014
  for (var sp, i = 0; i < line.markedSpans.length; ++i) {
4015
  sp = line.markedSpans[i];
4016
+ if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to &&
4017
  (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
4018
  lineIsHiddenInner(doc, line, sp)) return true;
4019
  }
4125
  function runMode(cm, text, mode, state, f) {
4126
  var flattenSpans = mode.flattenSpans;
4127
  if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
4128
+ var curStart = 0, curStyle = null;
4129
+ var stream = new StringStream(text, cm.options.tabSize), style;
4130
  if (text == "" && mode.blankLine) mode.blankLine(state);
4131
  while (!stream.eol()) {
4132
+ if (stream.pos > cm.options.maxHighlightLength) {
 
4133
  flattenSpans = false;
4134
  // Webkit seems to refuse to render text nodes longer than 57444 characters
4135
  stream.pos = Math.min(text.length, stream.start + 50000);
4136
  style = null;
4137
+ } else {
4138
+ style = mode.token(stream, state);
4139
  }
 
 
4140
  if (!flattenSpans || curStyle != style) {
4141
+ if (curStart < stream.start) f(stream.start, curStyle);
4142
+ curStart = stream.start; curStyle = style;
4143
+ }
4144
+ stream.start = stream.pos;
4145
  }
4146
+ if (curStart < stream.pos) f(stream.pos, curStyle);
4147
  }
4148
 
4149
  function highlightLine(cm, line, state) {
4151
  // mode/overlays that it is based on (for easy invalidation).
4152
  var st = [cm.state.modeGen];
4153
  // Compute the base array of styles
4154
+ runMode(cm, line.text, cm.doc.mode, state, function(end, style) {st.push(end, style);});
4155
 
4156
  // Run overlays, adjust style array.
4157
  for (var o = 0; o < cm.state.overlays.length; ++o) {
4158
+ var overlay = cm.state.overlays[o], i = 1, at = 0;
4159
+ runMode(cm, line.text, overlay.mode, true, function(end, style) {
4160
+ var start = i;
4161
  // Ensure there's a token end at the current position, and that i points at it
4162
+ while (at < end) {
4163
+ var i_end = st[i];
4164
+ if (i_end > end)
4165
+ st.splice(i, 1, end, st[i+1], i_end);
 
 
 
 
4166
  i += 2;
4167
+ at = Math.min(end, i_end);
4168
  }
4169
  if (!style) return;
4170
  if (overlay.opaque) {
4171
+ st.splice(start, i - start, end, style);
4172
  i = start + 2;
4173
  } else {
4174
  for (; start < i; start += 2) {
4194
  var mode = cm.doc.mode;
4195
  var stream = new StringStream(line.text, cm.options.tabSize);
4196
  if (line.text == "" && mode.blankLine) mode.blankLine(state);
4197
+ while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
4198
  mode.token(stream, state);
4199
  stream.start = stream.pos;
4200
  }
4208
  }
4209
 
4210
  function lineContent(cm, realLine, measure) {
4211
+ var merged, line = realLine, empty = true;
4212
+ while (merged = collapsedSpanAtStart(line))
 
4213
  line = getLine(cm.doc, merged.find().from.line);
 
 
4214
 
4215
  var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure,
4216
+ measure: null, measuredSomething: false, cm: cm};
4217
  if (line.textClass) builder.pre.className = line.textClass;
4218
 
4219
  do {
4220
+ if (line.text) empty = false;
4221
  builder.measure = line == realLine && measure;
4222
  builder.pos = 0;
4223
  builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
4224
  if ((ie || webkit) && cm.getOption("lineWrapping"))
4225
  builder.addToken = buildTokenSplitSpaces(builder.addToken);
 
 
 
 
4226
  var next = insertLineContent(line, builder, getLineStyles(cm, line));
4227
+ if (measure && line == realLine && !builder.measuredSomething) {
4228
+ measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
4229
+ builder.measuredSomething = true;
 
4230
  }
4231
+ if (next) line = getLine(cm.doc, next.to.line);
4232
  } while (next);
4233
 
4234
+ if (measure && !builder.measuredSomething && !measure[0])
4235
+ measure[0] = builder.pre.appendChild(empty ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
4236
  if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine))
4237
  builder.pre.appendChild(document.createTextNode("\u00a0"));
4238
 
4301
  if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) {
4302
  ch = text.slice(i, i + 2);
4303
  ++i;
4304
+ } else if (i && wrapping && spanAffectsWrapping(text, i)) {
 
4305
  builder.pre.appendChild(elt("wbr"));
4306
  }
4307
  var span = builder.measure[builder.pos] =
4315
  span.style.whiteSpace = "normal";
4316
  builder.pos += ch.length;
4317
  }
4318
+ if (text.length) builder.measuredSomething = true;
4319
  }
4320
 
4321
  function buildTokenSplitSpaces(inner) {
4333
  function buildCollapsedSpan(builder, size, widget) {
4334
  if (widget) {
4335
  if (!builder.display) widget = widget.cloneNode(true);
4336
+ if (builder.measure) {
4337
+ builder.measure[builder.pos] = size ? widget
4338
+ : builder.pre.appendChild(zeroWidthElement(builder.cm.display.measure));
4339
+ builder.measuredSomething = true;
4340
  }
4341
+ builder.pre.appendChild(widget);
4342
  }
4343
  builder.pos += size;
4344
  }
4346
  // Outputs a number of spans to make up a line, taking highlighting
4347
  // and marked text into account.
4348
  function insertLineContent(line, builder, styles) {
4349
+ var spans = line.markedSpans, allText = line.text, at = 0;
4350
  if (!spans) {
4351
  for (var i = 1; i < styles.length; i+=2)
4352
+ builder.addToken(builder, allText.slice(at, at = styles[i]), styleToClass(styles[i+1]));
4353
  return;
4354
  }
4355
 
4356
+ var len = allText.length, pos = 0, i = 1, text = "", style;
 
4357
  var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed;
4358
  for (;;) {
4359
  if (nextChange == pos) { // Update current marker set
4367
  if (m.className) spanStyle += " " + m.className;
4368
  if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
4369
  if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
4370
+ if (m.collapsed && (!collapsed || collapsed.marker.size < m.size))
4371
  collapsed = sp;
4372
  } else if (sp.from > pos && nextChange > sp.from) {
4373
  nextChange = sp.from;
4397
  pos = end;
4398
  spanStartStyle = "";
4399
  }
4400
+ text = allText.slice(at, at = styles[i++]);
4401
+ style = styleToClass(styles[i++]);
4402
  }
4403
  }
4404
  }
4590
  this.scrollTop = this.scrollLeft = 0;
4591
  this.cantEdit = false;
4592
  this.history = makeHistory();
4593
+ this.cleanGeneration = 1;
4594
  this.frontier = firstLine;
4595
  var start = Pos(firstLine, 0);
4596
  this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null};
4602
  };
4603
 
4604
  Doc.prototype = createObj(BranchChunk.prototype, {
4605
+ constructor: Doc,
4606
  iter: function(from, to, op) {
4607
  if (op) this.iterN(from - this.first, to - from, op);
4608
  else this.iterN(this.first, this.first + this.size, from);
4643
  replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line)));
4644
  },
4645
  removeLine: function(line) {
4646
+ if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line)));
4647
+ else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0)));
4648
  },
4649
 
4650
  getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
4691
  var hist = this.history;
4692
  return {undo: hist.done.length, redo: hist.undone.length};
4693
  },
4694
+ clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);},
4695
 
4696
  markClean: function() {
4697
+ this.cleanGeneration = this.changeGeneration();
4698
+ },
4699
+ changeGeneration: function() {
4700
  this.history.lastOp = this.history.lastOrigin = null;
4701
+ return this.history.generation;
4702
+ },
4703
+ isClean: function (gen) {
4704
+ return this.history.generation == (gen || this.cleanGeneration);
4705
  },
 
4706
 
4707
  getHistory: function() {
4708
  return {done: copyHistoryArray(this.history.done),
4709
  undone: copyHistoryArray(this.history.undone)};
4710
  },
4711
  setHistory: function(histData) {
4712
+ var hist = this.history = makeHistory(this.history.maxGeneration);
4713
  hist.done = histData.done.slice(0);
4714
  hist.undone = histData.undone.slice(0);
4715
  },
4939
 
4940
  // HISTORY
4941
 
4942
+ function makeHistory(startGen) {
4943
  return {
4944
  // Arrays of history events. Doing something adds an event to
4945
  // done and clears undo. Undoing moves events from done to
4949
  // event
4950
  lastTime: 0, lastOp: null, lastOrigin: null,
4951
  // Used by the isClean() method
4952
+ generation: startGen || 1, maxGeneration: startGen || 1
4953
  };
4954
  }
4955
 
4977
  if (cur &&
4978
  (hist.lastOp == opId ||
4979
  hist.lastOrigin == change.origin && change.origin &&
4980
+ ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) ||
4981
+ change.origin.charAt(0) == "*"))) {
4982
  // Merge this change into the last event
4983
  var last = lst(cur.changes);
4984
  if (posEq(change.from, change.to) && posEq(change.from, last.to)) {
4993
  } else {
4994
  // Can not be merged, start a new event.
4995
  cur = {changes: [historyChangeFromChange(doc, change)],
4996
+ generation: hist.generation,
4997
  anchorBefore: doc.sel.anchor, headBefore: doc.sel.head,
4998
  anchorAfter: selAfter.anchor, headAfter: selAfter.head};
4999
  hist.done.push(cur);
5000
+ hist.generation = ++hist.maxGeneration;
5001
  while (hist.done.length > hist.undoDepth)
5002
  hist.done.shift();
 
 
 
 
 
 
5003
  }
5004
  hist.lastTime = time;
5005
  hist.lastOp = opId;
5114
  if (e.stopPropagation) e.stopPropagation();
5115
  else e.cancelBubble = true;
5116
  }
5117
+ function e_defaultPrevented(e) {
5118
+ return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false;
5119
+ }
5120
  function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
5121
  CodeMirror.e_stop = e_stop;
5122
  CodeMirror.e_preventDefault = e_preventDefault;
5183
  delayedCallbacks.push(bnd(arr[i]));
5184
  }
5185
 
5186
+ function signalDOMEvent(cm, e) {
5187
+ signal(cm, e.type, cm, e);
5188
+ return e_defaultPrevented(e);
5189
+ }
5190
+
5191
  function fireDelayed() {
5192
  --delayedCallbackDepth;
5193
  var delayed = delayedCallbacks;
5242
  if (ios) { // Mobile Safari apparently has a bug where select() is broken.
5243
  node.selectionStart = 0;
5244
  node.selectionEnd = node.value.length;
5245
+ } else {
5246
+ // Suppress mysterious IE10 errors
5247
+ try { node.select(); }
5248
+ catch(_e) {}
5249
+ }
5250
  }
5251
 
5252
  function indexOf(collection, elt) {
5280
  return function(){return f.apply(null, args);};
5281
  }
5282
 
5283
+ var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
5284
  function isWordChar(ch) {
5285
  return /\w/.test(ch) || ch > "\x80" &&
5286
  (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
5341
  // word wrapping between certain characters *only* if a new inline
5342
  // element is started between them. This makes it hard to reliably
5343
  // measure the position of things, since that requires inserting an
5344
+ // extra span. This terribly fragile set of tests matches the
5345
  // character combinations that suffer from this phenomenon on the
5346
  // various browsers.
5347
+ function spanAffectsWrapping() { return false; }
5348
+ if (gecko) // Only for "$'"
5349
+ spanAffectsWrapping = function(str, i) {
5350
+ return str.charCodeAt(i - 1) == 36 && str.charCodeAt(i) == 39;
5351
+ };
5352
+ else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent))
5353
+ spanAffectsWrapping = function(str, i) {
5354
+ return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1));
5355
+ };
5356
+ else if (webkit)
5357
+ spanAffectsWrapping = function(str, i) {
5358
+ if (i > 1 && str.charCodeAt(i - 1) == 45 && /\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i)))
5359
+ return true;
5360
+ return /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1));
5361
+ };
5362
 
5363
  var knownScrollbarWidth;
5364
  function scrollbarWidth(measure) {
5477
  return Pos(lineN, ch);
5478
  }
5479
 
5480
+ function compareBidiLevel(order, a, b) {
5481
+ var linedir = order[0].level;
5482
+ if (a == linedir) return true;
5483
+ if (b == linedir) return false;
5484
+ return a < b;
5485
+ }
5486
+ var bidiOther;
5487
+ function getBidiPartAt(order, pos) {
5488
+ for (var i = 0, found; i < order.length; ++i) {
5489
+ var cur = order[i];
5490
+ if (cur.from < pos && cur.to > pos) { bidiOther = null; return i; }
5491
+ if (cur.from == pos || cur.to == pos) {
5492
+ if (found == null) {
5493
+ found = i;
5494
+ } else if (compareBidiLevel(order, cur.level, order[found].level)) {
5495
+ bidiOther = found;
5496
+ return i;
5497
+ } else {
5498
+ bidiOther = i;
5499
+ return found;
5500
+ }
5501
+ }
5502
+ }
5503
+ bidiOther = null;
5504
+ return found;
5505
+ }
5506
+
5507
+ function moveInLine(line, pos, dir, byUnit) {
5508
+ if (!byUnit) return pos + dir;
5509
+ do pos += dir;
5510
+ while (pos > 0 && isExtendingChar.test(line.text.charAt(pos)));
5511
+ return pos;
5512
+ }
5513
+
5514
  // This is somewhat involved. It is needed in order to move
5515
  // 'visually' through bi-directional text -- i.e., pressing left
5516
  // should make the cursor go left, even when in RTL text. The
5520
  function moveVisually(line, start, dir, byUnit) {
5521
  var bidi = getOrder(line);
5522
  if (!bidi) return moveLogically(line, start, dir, byUnit);
5523
+ var pos = getBidiPartAt(bidi, start), part = bidi[pos];
5524
+ var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit);
5525
+
5526
+ for (;;) {
5527
+ if (target > part.from && target < part.to) return target;
5528
+ if (target == part.from || target == part.to) {
5529
+ if (getBidiPartAt(bidi, target) == pos) return target;
5530
+ part = bidi[pos += dir];
5531
+ return (dir > 0) == part.level % 2 ? part.to : part.from;
 
 
 
 
 
 
 
 
 
 
5532
  } else {
5533
+ part = bidi[pos += dir];
5534
+ if (!part) return null;
5535
+ if ((dir > 0) == part.level % 2)
5536
+ target = moveInLine(line, part.to, -1, byUnit);
5537
+ else
5538
+ target = moveInLine(line, part.from, 1, byUnit);
 
5539
  }
5540
  }
 
 
5541
  }
5542
 
5543
  function moveLogically(line, start, dir, byUnit) {
5709
 
5710
  // THE END
5711
 
5712
+ CodeMirror.version = "3.14.0";
5713
 
5714
  return CodeMirror;
5715
  })();
vendor/codemirror/mode/clike.js CHANGED
@@ -1,302 +1,361 @@
1
- CodeMirror.defineMode("clike", function(config, parserConfig) {
2
- var indentUnit = config.indentUnit,
3
- statementIndentUnit = parserConfig.statementIndentUnit || indentUnit,
4
- dontAlignCalls = parserConfig.dontAlignCalls,
5
- keywords = parserConfig.keywords || {},
6
- builtin = parserConfig.builtin || {},
7
- blockKeywords = parserConfig.blockKeywords || {},
8
- atoms = parserConfig.atoms || {},
9
- hooks = parserConfig.hooks || {},
10
- multiLineStrings = parserConfig.multiLineStrings;
11
- var isOperatorChar = /[+\-*&%=<>!?|\/]/;
12
-
13
- var curPunc;
14
-
15
- function tokenBase(stream, state) {
16
- var ch = stream.next();
17
- if (hooks[ch]) {
18
- var result = hooks[ch](stream, state);
19
- if (result !== false) return result;
20
- }
21
- if (ch == '"' || ch == "'") {
22
- state.tokenize = tokenString(ch);
23
- return state.tokenize(stream, state);
24
- }
25
- if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
26
- curPunc = ch;
27
- return null;
28
- }
29
- if (/\d/.test(ch)) {
30
- stream.eatWhile(/[\w\.]/);
31
- return "number";
32
- }
33
- if (ch == "/") {
34
- if (stream.eat("*")) {
35
- state.tokenize = tokenComment;
36
- return tokenComment(stream, state);
37
- }
38
- if (stream.eat("/")) {
39
- stream.skipToEnd();
40
- return "comment";
41
- }
42
- }
43
- if (isOperatorChar.test(ch)) {
44
- stream.eatWhile(isOperatorChar);
45
- return "operator";
46
- }
47
- stream.eatWhile(/[\w\$_]/);
48
- var cur = stream.current();
49
- if (keywords.propertyIsEnumerable(cur)) {
50
- if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
51
- return "keyword";
52
- }
53
- if (builtin.propertyIsEnumerable(cur)) {
54
- if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
55
- return "builtin";
56
- }
57
- if (atoms.propertyIsEnumerable(cur)) return "atom";
58
- return "variable";
59
- }
60
-
61
- function tokenString(quote) {
62
- return function(stream, state) {
63
- var escaped = false, next, end = false;
64
- while ((next = stream.next()) != null) {
65
- if (next == quote && !escaped) {end = true; break;}
66
- escaped = !escaped && next == "\\";
67
- }
68
- if (end || !(escaped || multiLineStrings))
69
- state.tokenize = null;
70
- return "string";
71
- };
72
- }
73
-
74
- function tokenComment(stream, state) {
75
- var maybeEnd = false, ch;
76
- while (ch = stream.next()) {
77
- if (ch == "/" && maybeEnd) {
78
- state.tokenize = null;
79
- break;
80
- }
81
- maybeEnd = (ch == "*");
82
- }
83
- return "comment";
84
- }
85
-
86
- function Context(indented, column, type, align, prev) {
87
- this.indented = indented;
88
- this.column = column;
89
- this.type = type;
90
- this.align = align;
91
- this.prev = prev;
92
- }
93
- function pushContext(state, col, type) {
94
- var indent = state.indented;
95
- if (state.context && state.context.type == "statement")
96
- indent = state.context.indented;
97
- return state.context = new Context(indent, col, type, null, state.context);
98
- }
99
- function popContext(state) {
100
- var t = state.context.type;
101
- if (t == ")" || t == "]" || t == "}")
102
- state.indented = state.context.indented;
103
- return state.context = state.context.prev;
104
- }
105
-
106
- // Interface
107
-
108
- return {
109
- startState: function(basecolumn) {
110
- return {
111
- tokenize: null,
112
- context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
113
- indented: 0,
114
- startOfLine: true
115
- };
116
- },
117
-
118
- token: function(stream, state) {
119
- var ctx = state.context;
120
- if (stream.sol()) {
121
- if (ctx.align == null) ctx.align = false;
122
- state.indented = stream.indentation();
123
- state.startOfLine = true;
124
- }
125
- if (stream.eatSpace()) return null;
126
- curPunc = null;
127
- var style = (state.tokenize || tokenBase)(stream, state);
128
- if (style == "comment" || style == "meta") return style;
129
- if (ctx.align == null) ctx.align = true;
130
-
131
- if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement") popContext(state);
132
- else if (curPunc == "{") pushContext(state, stream.column(), "}");
133
- else if (curPunc == "[") pushContext(state, stream.column(), "]");
134
- else if (curPunc == "(") pushContext(state, stream.column(), ")");
135
- else if (curPunc == "}") {
136
- while (ctx.type == "statement") ctx = popContext(state);
137
- if (ctx.type == "}") ctx = popContext(state);
138
- while (ctx.type == "statement") ctx = popContext(state);
139
- }
140
- else if (curPunc == ctx.type) popContext(state);
141
- else if (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement"))
142
- pushContext(state, stream.column(), "statement");
143
- state.startOfLine = false;
144
- return style;
145
- },
146
-
147
- indent: function(state, textAfter) {
148
- if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;
149
- var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
150
- if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
151
- var closing = firstChar == ctx.type;
152
- if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
153
- else if (dontAlignCalls && ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit;
154
- else if (ctx.align) return ctx.column + (closing ? 0 : 1);
155
- else return ctx.indented + (closing ? 0 : indentUnit);
156
- },
157
-
158
- electricChars: "{}"
159
- };
160
- });
161
-
162
- (function() {
163
- function words(str) {
164
- var obj = {}, words = str.split(" ");
165
- for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
166
- return obj;
167
- }
168
- var cKeywords = "auto if break int case long char register continue return default short do sizeof " +
169
- "double static else struct entry switch extern typedef float union for unsigned " +
170
- "goto while enum void const signed volatile";
171
-
172
- function cppHook(stream, state) {
173
- if (!state.startOfLine) return false;
174
- for (;;) {
175
- if (stream.skipTo("\\")) {
176
- stream.next();
177
- if (stream.eol()) {
178
- state.tokenize = cppHook;
179
- break;
180
- }
181
- } else {
182
- stream.skipToEnd();
183
- state.tokenize = null;
184
- break;
185
- }
186
- }
187
- return "meta";
188
- }
189
-
190
- // C#-style strings where "" escapes a quote.
191
- function tokenAtString(stream, state) {
192
- var next;
193
- while ((next = stream.next()) != null) {
194
- if (next == '"' && !stream.eat('"')) {
195
- state.tokenize = null;
196
- break;
197
- }
198
- }
199
- return "string";
200
- }
201
-
202
- function mimes(ms, mode) {
203
- for (var i = 0; i < ms.length; ++i) CodeMirror.defineMIME(ms[i], mode);
204
- }
205
-
206
- mimes(["text/x-csrc", "text/x-c", "text/x-chdr"], {
207
- name: "clike",
208
- keywords: words(cKeywords),
209
- blockKeywords: words("case do else for if switch while struct"),
210
- atoms: words("null"),
211
- hooks: {"#": cppHook}
212
- });
213
- mimes(["text/x-c++src", "text/x-c++hdr"], {
214
- name: "clike",
215
- keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " +
216
- "static_cast typeid catch operator template typename class friend private " +
217
- "this using const_cast inline public throw virtual delete mutable protected " +
218
- "wchar_t"),
219
- blockKeywords: words("catch class do else finally for if struct switch try while"),
220
- atoms: words("true false null"),
221
- hooks: {"#": cppHook}
222
- });
223
- CodeMirror.defineMIME("text/x-java", {
224
- name: "clike",
225
- keywords: words("abstract assert boolean break byte case catch char class const continue default " +
226
- "do double else enum extends final finally float for goto if implements import " +
227
- "instanceof int interface long native new package private protected public " +
228
- "return short static strictfp super switch synchronized this throw throws transient " +
229
- "try void volatile while"),
230
- blockKeywords: words("catch class do else finally for if switch try while"),
231
- atoms: words("true false null"),
232
- hooks: {
233
- "@": function(stream) {
234
- stream.eatWhile(/[\w\$_]/);
235
- return "meta";
236
- }
237
- }
238
- });
239
- CodeMirror.defineMIME("text/x-csharp", {
240
- name: "clike",
241
- keywords: words("abstract as base break case catch checked class const continue" +
242
- " default delegate do else enum event explicit extern finally fixed for" +
243
- " foreach goto if implicit in interface internal is lock namespace new" +
244
- " operator out override params private protected public readonly ref return sealed" +
245
- " sizeof stackalloc static struct switch this throw try typeof unchecked" +
246
- " unsafe using virtual void volatile while add alias ascending descending dynamic from get" +
247
- " global group into join let orderby partial remove select set value var yield"),
248
- blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
249
- builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" +
250
- " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" +
251
- " UInt64 bool byte char decimal double short int long object" +
252
- " sbyte float string ushort uint ulong"),
253
- atoms: words("true false null"),
254
- hooks: {
255
- "@": function(stream, state) {
256
- if (stream.eat('"')) {
257
- state.tokenize = tokenAtString;
258
- return tokenAtString(stream, state);
259
- }
260
- stream.eatWhile(/[\w\$_]/);
261
- return "meta";
262
- }
263
- }
264
- });
265
- CodeMirror.defineMIME("text/x-scala", {
266
- name: "clike",
267
- keywords: words(
268
-
269
- /* scala */
270
- "abstract case catch class def do else extends false final finally for forSome if " +
271
- "implicit import lazy match new null object override package private protected return " +
272
- "sealed super this throw trait try trye type val var while with yield _ : = => <- <: " +
273
- "<% >: # @ " +
274
-
275
- /* package scala */
276
- "assert assume require print println printf readLine readBoolean readByte readShort " +
277
- "readChar readInt readLong readFloat readDouble " +
278
-
279
- "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " +
280
- "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " +
281
- "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " +
282
- "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " +
283
- "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector :: #:: " +
284
-
285
- /* package java.lang */
286
- "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
287
- "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
288
- "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
289
- "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
290
-
291
-
292
- ),
293
- blockKeywords: words("catch class do else finally for forSome if match switch try while"),
294
- atoms: words("true false null"),
295
- hooks: {
296
- "@": function(stream) {
297
- stream.eatWhile(/[\w\$_]/);
298
- return "meta";
299
- }
300
- }
301
- });
302
- }());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.defineMode("clike", function(config, parserConfig) {
2
+ var indentUnit = config.indentUnit,
3
+ statementIndentUnit = parserConfig.statementIndentUnit || indentUnit,
4
+ dontAlignCalls = parserConfig.dontAlignCalls,
5
+ keywords = parserConfig.keywords || {},
6
+ builtin = parserConfig.builtin || {},
7
+ blockKeywords = parserConfig.blockKeywords || {},
8
+ atoms = parserConfig.atoms || {},
9
+ hooks = parserConfig.hooks || {},
10
+ multiLineStrings = parserConfig.multiLineStrings;
11
+ var isOperatorChar = /[+\-*&%=<>!?|\/]/;
12
+
13
+ var curPunc;
14
+
15
+ function tokenBase(stream, state) {
16
+ var ch = stream.next();
17
+ if (hooks[ch]) {
18
+ var result = hooks[ch](stream, state);
19
+ if (result !== false) return result;
20
+ }
21
+ if (ch == '"' || ch == "'") {
22
+ state.tokenize = tokenString(ch);
23
+ return state.tokenize(stream, state);
24
+ }
25
+ if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
26
+ curPunc = ch;
27
+ return null;
28
+ }
29
+ if (/\d/.test(ch)) {
30
+ stream.eatWhile(/[\w\.]/);
31
+ return "number";
32
+ }
33
+ if (ch == "/") {
34
+ if (stream.eat("*")) {
35
+ state.tokenize = tokenComment;
36
+ return tokenComment(stream, state);
37
+ }
38
+ if (stream.eat("/")) {
39
+ stream.skipToEnd();
40
+ return "comment";
41
+ }
42
+ }
43
+ if (isOperatorChar.test(ch)) {
44
+ stream.eatWhile(isOperatorChar);
45
+ return "operator";
46
+ }
47
+ stream.eatWhile(/[\w\$_]/);
48
+ var cur = stream.current();
49
+ if (keywords.propertyIsEnumerable(cur)) {
50
+ if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
51
+ return "keyword";
52
+ }
53
+ if (builtin.propertyIsEnumerable(cur)) {
54
+ if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
55
+ return "builtin";
56
+ }
57
+ if (atoms.propertyIsEnumerable(cur)) return "atom";
58
+ return "variable";
59
+ }
60
+
61
+ function tokenString(quote) {
62
+ return function(stream, state) {
63
+ var escaped = false, next, end = false;
64
+ while ((next = stream.next()) != null) {
65
+ if (next == quote && !escaped) {end = true; break;}
66
+ escaped = !escaped && next == "\\";
67
+ }
68
+ if (end || !(escaped || multiLineStrings))
69
+ state.tokenize = null;
70
+ return "string";
71
+ };
72
+ }
73
+
74
+ function tokenComment(stream, state) {
75
+ var maybeEnd = false, ch;
76
+ while (ch = stream.next()) {
77
+ if (ch == "/" && maybeEnd) {
78
+ state.tokenize = null;
79
+ break;
80
+ }
81
+ maybeEnd = (ch == "*");
82
+ }
83
+ return "comment";
84
+ }
85
+
86
+ function Context(indented, column, type, align, prev) {
87
+ this.indented = indented;
88
+ this.column = column;
89
+ this.type = type;
90
+ this.align = align;
91
+ this.prev = prev;
92
+ }
93
+ function pushContext(state, col, type) {
94
+ var indent = state.indented;
95
+ if (state.context && state.context.type == "statement")
96
+ indent = state.context.indented;
97
+ return state.context = new Context(indent, col, type, null, state.context);
98
+ }
99
+ function popContext(state) {
100
+ var t = state.context.type;
101
+ if (t == ")" || t == "]" || t == "}")
102
+ state.indented = state.context.indented;
103
+ return state.context = state.context.prev;
104
+ }
105
+
106
+ // Interface
107
+
108
+ return {
109
+ startState: function(basecolumn) {
110
+ return {
111
+ tokenize: null,
112
+ context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
113
+ indented: 0,
114
+ startOfLine: true
115
+ };
116
+ },
117
+
118
+ token: function(stream, state) {
119
+ var ctx = state.context;
120
+ if (stream.sol()) {
121
+ if (ctx.align == null) ctx.align = false;
122
+ state.indented = stream.indentation();
123
+ state.startOfLine = true;
124
+ }
125
+ if (stream.eatSpace()) return null;
126
+ curPunc = null;
127
+ var style = (state.tokenize || tokenBase)(stream, state);
128
+ if (style == "comment" || style == "meta") return style;
129
+ if (ctx.align == null) ctx.align = true;
130
+
131
+ if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement") popContext(state);
132
+ else if (curPunc == "{") pushContext(state, stream.column(), "}");
133
+ else if (curPunc == "[") pushContext(state, stream.column(), "]");
134
+ else if (curPunc == "(") pushContext(state, stream.column(), ")");
135
+ else if (curPunc == "}") {
136
+ while (ctx.type == "statement") ctx = popContext(state);
137
+ if (ctx.type == "}") ctx = popContext(state);
138
+ while (ctx.type == "statement") ctx = popContext(state);
139
+ }
140
+ else if (curPunc == ctx.type) popContext(state);
141
+ else if (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement"))
142
+ pushContext(state, stream.column(), "statement");
143
+ state.startOfLine = false;
144
+ return style;
145
+ },
146
+
147
+ indent: function(state, textAfter) {
148
+ if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;
149
+ var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
150
+ if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
151
+ var closing = firstChar == ctx.type;
152
+ if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
153
+ else if (ctx.align && (!dontAlignCalls || ctx.type != ")")) return ctx.column + (closing ? 0 : 1);
154
+ else if (ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit;
155
+ else return ctx.indented + (closing ? 0 : indentUnit);
156
+ },
157
+
158
+ electricChars: "{}",
159
+ blockCommentStart: "/*",
160
+ blockCommentEnd: "*/",
161
+ lineComment: "//"
162
+ };
163
+ });
164
+
165
+ (function() {
166
+ function words(str) {
167
+ var obj = {}, words = str.split(" ");
168
+ for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
169
+ return obj;
170
+ }
171
+ var cKeywords = "auto if break int case long char register continue return default short do sizeof " +
172
+ "double static else struct entry switch extern typedef float union for unsigned " +
173
+ "goto while enum void const signed volatile";
174
+
175
+ function cppHook(stream, state) {
176
+ if (!state.startOfLine) return false;
177
+ for (;;) {
178
+ if (stream.skipTo("\\")) {
179
+ stream.next();
180
+ if (stream.eol()) {
181
+ state.tokenize = cppHook;
182
+ break;
183
+ }
184
+ } else {
185
+ stream.skipToEnd();
186
+ state.tokenize = null;
187
+ break;
188
+ }
189
+ }
190
+ return "meta";
191
+ }
192
+
193
+ // C#-style strings where "" escapes a quote.
194
+ function tokenAtString(stream, state) {
195
+ var next;
196
+ while ((next = stream.next()) != null) {
197
+ if (next == '"' && !stream.eat('"')) {
198
+ state.tokenize = null;
199
+ break;
200
+ }
201
+ }
202
+ return "string";
203
+ }
204
+
205
+ function mimes(ms, mode) {
206
+ for (var i = 0; i < ms.length; ++i) CodeMirror.defineMIME(ms[i], mode);
207
+ }
208
+
209
+ mimes(["text/x-csrc", "text/x-c", "text/x-chdr"], {
210
+ name: "clike",
211
+ keywords: words(cKeywords),
212
+ blockKeywords: words("case do else for if switch while struct"),
213
+ atoms: words("null"),
214
+ hooks: {"#": cppHook}
215
+ });
216
+ mimes(["text/x-c++src", "text/x-c++hdr"], {
217
+ name: "clike",
218
+ keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " +
219
+ "static_cast typeid catch operator template typename class friend private " +
220
+ "this using const_cast inline public throw virtual delete mutable protected " +
221
+ "wchar_t"),
222
+ blockKeywords: words("catch class do else finally for if struct switch try while"),
223
+ atoms: words("true false null"),
224
+ hooks: {"#": cppHook}
225
+ });
226
+ CodeMirror.defineMIME("text/x-java", {
227
+ name: "clike",
228
+ keywords: words("abstract assert boolean break byte case catch char class const continue default " +
229
+ "do double else enum extends final finally float for goto if implements import " +
230
+ "instanceof int interface long native new package private protected public " +
231
+ "return short static strictfp super switch synchronized this throw throws transient " +
232
+ "try void volatile while"),
233
+ blockKeywords: words("catch class do else finally for if switch try while"),
234
+ atoms: words("true false null"),
235
+ hooks: {
236
+ "@": function(stream) {
237
+ stream.eatWhile(/[\w\$_]/);
238
+ return "meta";
239
+ }
240
+ }
241
+ });
242
+ CodeMirror.defineMIME("text/x-csharp", {
243
+ name: "clike",
244
+ keywords: words("abstract as base break case catch checked class const continue" +
245
+ " default delegate do else enum event explicit extern finally fixed for" +
246
+ " foreach goto if implicit in interface internal is lock namespace new" +
247
+ " operator out override params private protected public readonly ref return sealed" +
248
+ " sizeof stackalloc static struct switch this throw try typeof unchecked" +
249
+ " unsafe using virtual void volatile while add alias ascending descending dynamic from get" +
250
+ " global group into join let orderby partial remove select set value var yield"),
251
+ blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
252
+ builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" +
253
+ " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" +
254
+ " UInt64 bool byte char decimal double short int long object" +
255
+ " sbyte float string ushort uint ulong"),
256
+ atoms: words("true false null"),
257
+ hooks: {
258
+ "@": function(stream, state) {
259
+ if (stream.eat('"')) {
260
+ state.tokenize = tokenAtString;
261
+ return tokenAtString(stream, state);
262
+ }
263
+ stream.eatWhile(/[\w\$_]/);
264
+ return "meta";
265
+ }
266
+ }
267
+ });
268
+ CodeMirror.defineMIME("text/x-scala", {
269
+ name: "clike",
270
+ keywords: words(
271
+
272
+ /* scala */
273
+ "abstract case catch class def do else extends false final finally for forSome if " +
274
+ "implicit import lazy match new null object override package private protected return " +
275
+ "sealed super this throw trait try trye type val var while with yield _ : = => <- <: " +
276
+ "<% >: # @ " +
277
+
278
+ /* package scala */
279
+ "assert assume require print println printf readLine readBoolean readByte readShort " +
280
+ "readChar readInt readLong readFloat readDouble " +
281
+
282
+ "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " +
283
+ "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " +
284
+ "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " +
285
+ "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " +
286
+ "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector :: #:: " +
287
+
288
+ /* package java.lang */
289
+ "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
290
+ "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
291
+ "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
292
+ "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
293
+
294
+
295
+ ),
296
+ blockKeywords: words("catch class do else finally for forSome if match switch try while"),
297
+ atoms: words("true false null"),
298
+ hooks: {
299
+ "@": function(stream) {
300
+ stream.eatWhile(/[\w\$_]/);
301
+ return "meta";
302
+ }
303
+ }
304
+ });
305
+ mimes(["x-shader/x-vertex", "x-shader/x-fragment"], {
306
+ name: "clike",
307
+ keywords: words("float int bool void " +
308
+ "vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " +
309
+ "mat2 mat3 mat4 " +
310
+ "sampler1D sampler2D sampler3D samplerCube " +
311
+ "sampler1DShadow sampler2DShadow" +
312
+ "const attribute uniform varying " +
313
+ "break continue discard return " +
314
+ "for while do if else struct " +
315
+ "in out inout"),
316
+ blockKeywords: words("for while do if else struct"),
317
+ builtin: words("radians degrees sin cos tan asin acos atan " +
318
+ "pow exp log exp2 sqrt inversesqrt " +
319
+ "abs sign floor ceil fract mod min max clamp mix step smootstep " +
320
+ "length distance dot cross normalize ftransform faceforward " +
321
+ "reflect refract matrixCompMult " +
322
+ "lessThan lessThanEqual greaterThan greaterThanEqual " +
323
+ "equal notEqual any all not " +
324
+ "texture1D texture1DProj texture1DLod texture1DProjLod " +
325
+ "texture2D texture2DProj texture2DLod texture2DProjLod " +
326
+ "texture3D texture3DProj texture3DLod texture3DProjLod " +
327
+ "textureCube textureCubeLod " +
328
+ "shadow1D shadow2D shadow1DProj shadow2DProj " +
329
+ "shadow1DLod shadow2DLod shadow1DProjLod shadow2DProjLod " +
330
+ "dFdx dFdy fwidth " +
331
+ "noise1 noise2 noise3 noise4"),
332
+ atoms: words("true false " +
333
+ "gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex " +
334
+ "gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 " +
335
+ "gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 " +
336
+ "gl_FogCoord " +
337
+ "gl_Position gl_PointSize gl_ClipVertex " +
338
+ "gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor " +
339
+ "gl_TexCoord gl_FogFragCoord " +
340
+ "gl_FragCoord gl_FrontFacing " +
341
+ "gl_FragColor gl_FragData gl_FragDepth " +
342
+ "gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix " +
343
+ "gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse " +
344
+ "gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse " +
345
+ "gl_TexureMatrixTranspose gl_ModelViewMatrixInverseTranspose " +
346
+ "gl_ProjectionMatrixInverseTranspose " +
347
+ "gl_ModelViewProjectionMatrixInverseTranspose " +
348
+ "gl_TextureMatrixInverseTranspose " +
349
+ "gl_NormalScale gl_DepthRange gl_ClipPlane " +
350
+ "gl_Point gl_FrontMaterial gl_BackMaterial gl_LightSource gl_LightModel " +
351
+ "gl_FrontLightModelProduct gl_BackLightModelProduct " +
352
+ "gl_TextureColor gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ " +
353
+ "gl_FogParameters " +
354
+ "gl_MaxLights gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords " +
355
+ "gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats " +
356
+ "gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits " +
357
+ "gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits " +
358
+ "gl_MaxDrawBuffers"),
359
+ hooks: {"#": cppHook}
360
+ });
361
+ }());
vendor/codemirror/mode/php.js CHANGED
@@ -1,129 +1,132 @@
1
- (function() {
2
- function keywords(str) {
3
- var obj = {}, words = str.split(" ");
4
- for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
5
- return obj;
6
- }
7
- function heredoc(delim) {
8
- return function(stream, state) {
9
- if (stream.match(delim)) state.tokenize = null;
10
- else stream.skipToEnd();
11
- return "string";
12
- };
13
- }
14
- var phpConfig = {
15
- name: "clike",
16
- keywords: keywords("abstract and array as break case catch class clone const continue declare default " +
17
- "do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final " +
18
- "for foreach function global goto if implements interface instanceof namespace " +
19
- "new or private protected public static switch throw trait try use var while xor " +
20
- "die echo empty exit eval include include_once isset list require require_once return " +
21
- "print unset __halt_compiler self static parent"),
22
- blockKeywords: keywords("catch do else elseif for foreach if switch try while"),
23
- atoms: keywords("true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__"),
24
- builtin: keywords("func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport echo print global static exit array empty eval isset unset die include require include_once require_once"),
25
- multiLineStrings: true,
26
- hooks: {
27
- "$": function(stream) {
28
- stream.eatWhile(/[\w\$_]/);
29
- return "variable-2";
30
- },
31
- "<": function(stream, state) {
32
- if (stream.match(/<</)) {
33
- stream.eatWhile(/[\w\.]/);
34
- state.tokenize = heredoc(stream.current().slice(3));
35
- return state.tokenize(stream, state);
36
- }
37
- return false;
38
- },
39
- "#": function(stream) {
40
- while (!stream.eol() && !stream.match("?>", false)) stream.next();
41
- return "comment";
42
- },
43
- "/": function(stream) {
44
- if (stream.eat("/")) {
45
- while (!stream.eol() && !stream.match("?>", false)) stream.next();
46
- return "comment";
47
- }
48
- return false;
49
- }
50
- }
51
- };
52
-
53
- CodeMirror.defineMode("php", function(config, parserConfig) {
54
- var htmlMode = CodeMirror.getMode(config, "text/html");
55
- var phpMode = CodeMirror.getMode(config, phpConfig);
56
-
57
- function dispatch(stream, state) {
58
- var isPHP = state.curMode == phpMode;
59
- if (stream.sol() && state.pending != '"') state.pending = null;
60
- if (!isPHP) {
61
- if (stream.match(/^<\?\w*/)) {
62
- state.curMode = phpMode;
63
- state.curState = state.php;
64
- return "meta";
65
- }
66
- if (state.pending == '"') {
67
- while (!stream.eol() && stream.next() != '"') {}
68
- var style = "string";
69
- } else if (state.pending && stream.pos < state.pending.end) {
70
- stream.pos = state.pending.end;
71
- var style = state.pending.style;
72
- } else {
73
- var style = htmlMode.token(stream, state.curState);
74
- }
75
- state.pending = null;
76
- var cur = stream.current(), openPHP = cur.search(/<\?/);
77
- if (openPHP != -1) {
78
- if (style == "string" && /\"$/.test(cur) && !/\?>/.test(cur)) state.pending = '"';
79
- else state.pending = {end: stream.pos, style: style};
80
- stream.backUp(cur.length - openPHP);
81
- }
82
- return style;
83
- } else if (isPHP && state.php.tokenize == null && stream.match("?>")) {
84
- state.curMode = htmlMode;
85
- state.curState = state.html;
86
- return "meta";
87
- } else {
88
- return phpMode.token(stream, state.curState);
89
- }
90
- }
91
-
92
- return {
93
- startState: function() {
94
- var html = CodeMirror.startState(htmlMode), php = CodeMirror.startState(phpMode);
95
- return {html: html,
96
- php: php,
97
- curMode: parserConfig.startOpen ? phpMode : htmlMode,
98
- curState: parserConfig.startOpen ? php : html,
99
- pending: null};
100
- },
101
-
102
- copyState: function(state) {
103
- var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html),
104
- php = state.php, phpNew = CodeMirror.copyState(phpMode, php), cur;
105
- if (state.curMode == htmlMode) cur = htmlNew;
106
- else cur = phpNew;
107
- return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur,
108
- pending: state.pending};
109
- },
110
-
111
- token: dispatch,
112
-
113
- indent: function(state, textAfter) {
114
- if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) ||
115
- (state.curMode == phpMode && /^\?>/.test(textAfter)))
116
- return htmlMode.indent(state.html, textAfter);
117
- return state.curMode.indent(state.curState, textAfter);
118
- },
119
-
120
- electricChars: "/{}:",
121
-
122
- innerMode: function(state) { return {state: state.curState, mode: state.curMode}; }
123
- };
124
- }, "htmlmixed", "clike");
125
-
126
- CodeMirror.defineMIME("application/x-httpd-php", "php");
127
- CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true});
128
- CodeMirror.defineMIME("text/x-php", phpConfig);
129
- })();
 
 
 
1
+ (function() {
2
+ function keywords(str) {
3
+ var obj = {}, words = str.split(" ");
4
+ for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
5
+ return obj;
6
+ }
7
+ function heredoc(delim) {
8
+ return function(stream, state) {
9
+ if (stream.match(delim)) state.tokenize = null;
10
+ else stream.skipToEnd();
11
+ return "string";
12
+ };
13
+ }
14
+ var phpConfig = {
15
+ name: "clike",
16
+ keywords: keywords("abstract and array as break case catch class clone const continue declare default " +
17
+ "do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final " +
18
+ "for foreach function global goto if implements interface instanceof namespace " +
19
+ "new or private protected public static switch throw trait try use var while xor " +
20
+ "die echo empty exit eval include include_once isset list require require_once return " +
21
+ "print unset __halt_compiler self static parent"),
22
+ blockKeywords: keywords("catch do else elseif for foreach if switch try while"),
23
+ atoms: keywords("true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__"),
24
+ builtin: keywords("func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport echo print global static exit array empty eval isset unset die include require include_once require_once"),
25
+ multiLineStrings: true,
26
+ hooks: {
27
+ "$": function(stream) {
28
+ stream.eatWhile(/[\w\$_]/);
29
+ return "variable-2";
30
+ },
31
+ "<": function(stream, state) {
32
+ if (stream.match(/<</)) {
33
+ stream.eatWhile(/[\w\.]/);
34
+ state.tokenize = heredoc(stream.current().slice(3));
35
+ return state.tokenize(stream, state);
36
+ }
37
+ return false;
38
+ },
39
+ "#": function(stream) {
40
+ while (!stream.eol() && !stream.match("?>", false)) stream.next();
41
+ return "comment";
42
+ },
43
+ "/": function(stream) {
44
+ if (stream.eat("/")) {
45
+ while (!stream.eol() && !stream.match("?>", false)) stream.next();
46
+ return "comment";
47
+ }
48
+ return false;
49
+ }
50
+ }
51
+ };
52
+
53
+ CodeMirror.defineMode("php", function(config, parserConfig) {
54
+ var htmlMode = CodeMirror.getMode(config, "text/html");
55
+ var phpMode = CodeMirror.getMode(config, phpConfig);
56
+
57
+ function dispatch(stream, state) {
58
+ var isPHP = state.curMode == phpMode;
59
+ if (stream.sol() && state.pending != '"') state.pending = null;
60
+ if (!isPHP) {
61
+ if (stream.match(/^<\?\w*/)) {
62
+ state.curMode = phpMode;
63
+ state.curState = state.php;
64
+ return "meta";
65
+ }
66
+ if (state.pending == '"') {
67
+ while (!stream.eol() && stream.next() != '"') {}
68
+ var style = "string";
69
+ } else if (state.pending && stream.pos < state.pending.end) {
70
+ stream.pos = state.pending.end;
71
+ var style = state.pending.style;
72
+ } else {
73
+ var style = htmlMode.token(stream, state.curState);
74
+ }
75
+ state.pending = null;
76
+ var cur = stream.current(), openPHP = cur.search(/<\?/);
77
+ if (openPHP != -1) {
78
+ if (style == "string" && /\"$/.test(cur) && !/\?>/.test(cur)) state.pending = '"';
79
+ else state.pending = {end: stream.pos, style: style};
80
+ stream.backUp(cur.length - openPHP);
81
+ }
82
+ return style;
83
+ } else if (isPHP && state.php.tokenize == null && stream.match("?>")) {
84
+ state.curMode = htmlMode;
85
+ state.curState = state.html;
86
+ return "meta";
87
+ } else {
88
+ return phpMode.token(stream, state.curState);
89
+ }
90
+ }
91
+
92
+ return {
93
+ startState: function() {
94
+ var html = CodeMirror.startState(htmlMode), php = CodeMirror.startState(phpMode);
95
+ return {html: html,
96
+ php: php,
97
+ curMode: parserConfig.startOpen ? phpMode : htmlMode,
98
+ curState: parserConfig.startOpen ? php : html,
99
+ pending: null};
100
+ },
101
+
102
+ copyState: function(state) {
103
+ var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html),
104
+ php = state.php, phpNew = CodeMirror.copyState(phpMode, php), cur;
105
+ if (state.curMode == htmlMode) cur = htmlNew;
106
+ else cur = phpNew;
107
+ return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur,
108
+ pending: state.pending};
109
+ },
110
+
111
+ token: dispatch,
112
+
113
+ indent: function(state, textAfter) {
114
+ if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) ||
115
+ (state.curMode == phpMode && /^\?>/.test(textAfter)))
116
+ return htmlMode.indent(state.html, textAfter);
117
+ return state.curMode.indent(state.curState, textAfter);
118
+ },
119
+
120
+ electricChars: "/{}:",
121
+ blockCommentStart: "/*",
122
+ blockCommentEnd: "*/",
123
+ lineComment: "//",
124
+
125
+ innerMode: function(state) { return {state: state.curState, mode: state.curMode}; }
126
+ };
127
+ }, "htmlmixed", "clike");
128
+
129
+ CodeMirror.defineMIME("application/x-httpd-php", "php");
130
+ CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true});
131
+ CodeMirror.defineMIME("text/x-php", phpConfig);
132
+ })();