Toolset Types – Custom Post Types, Custom Fields and Taxonomies - Version 2.2.16

Version Description

  • Fixed a bug with numeric validation used a different decimal sign than a dot.
  • Fixed a compatibility issue with the The7 theme.
  • Display an admin message if there are other Toolset plugins that are not registered. This should not affect local and staging sites.
Download this release

Release Info

Developer zaantar
Plugin Icon 128x128 Toolset Types – Custom Post Types, Custom Fields and Taxonomies
Version 2.2.16
Comparing to
See all releases

Code changes from version 2.2.15.1 to 2.2.16

Files changed (115) hide show
  1. application/autoload_classmap.php +212 -211
  2. application/bootstrap.php +8 -8
  3. application/controllers/ajax.php +19 -1
  4. application/controllers/asset/manager.php +0 -7
  5. application/controllers/interop/handler/the7.php +81 -0
  6. application/controllers/interop/handler/wpml.php +1 -1
  7. application/controllers/interop/mediator.php +30 -0
  8. application/controllers/twig_autoloader.php +1 -1
  9. application/controllers/upgrade.php +15 -0
  10. application/models/helper/create/layout.php +1 -1
  11. library/toolset/onthego-resources/onthegosystems-icons/.fontcustom-manifest.json +0 -204
  12. library/toolset/toolset-common/res/lib/knockout/knockout-3.4.0.debug.js +0 -5871
  13. library/toolset/toolset-common/res/lib/knockout/knockout-3.4.0.js +0 -123
  14. library/toolset/toolset-common/visual-editor/res/js/codemirror/.gitattributes +0 -8
  15. library/toolset/toolset-common/visual-editor/res/js/codemirror/.npmignore +0 -9
  16. library/toolset/toolset-common/visual-editor/res/js/codemirror/.travis.yml +0 -4
  17. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/comment_test.js +0 -100
  18. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/doc_test.js +0 -371
  19. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/driver.js +0 -138
  20. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/emacs_test.js +0 -147
  21. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/index.html +0 -241
  22. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint.js +0 -11
  23. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint/acorn.js +0 -1782
  24. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint/lint.js +0 -166
  25. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint/walk.js +0 -313
  26. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/mode_test.css +0 -23
  27. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/mode_test.js +0 -192
  28. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/multi_test.js +0 -285
  29. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/phantom_driver.js +0 -31
  30. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/run.js +0 -31
  31. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/scroll_test.js +0 -115
  32. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/search_test.js +0 -62
  33. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/sql-hint-test.js +0 -189
  34. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/sublime_test.js +0 -303
  35. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/test.js +0 -2142
  36. library/toolset/toolset-common/visual-editor/res/js/codemirror/test/vim_test.js +0 -3955
  37. library/toolset/types/.travis.yml +0 -29
  38. library/twig/twig/.editorconfig +0 -18
  39. library/twig/twig/.php_cs.dist +0 -15
  40. library/twig/twig/.travis.yml +0 -43
  41. library/twig/twig/doc/advanced.rst +0 -962
  42. library/twig/twig/doc/advanced_legacy.rst +0 -885
  43. library/twig/twig/doc/api.rst +0 -590
  44. library/twig/twig/doc/coding_standards.rst +0 -101
  45. library/twig/twig/doc/deprecated.rst +0 -224
  46. library/twig/twig/doc/filters/abs.rst +0 -18
  47. library/twig/twig/doc/filters/batch.rst +0 -51
  48. library/twig/twig/doc/filters/capitalize.rst +0 -11
  49. library/twig/twig/doc/filters/convert_encoding.rst +0 -28
  50. library/twig/twig/doc/filters/date.rst +0 -100
  51. library/twig/twig/doc/filters/date_modify.rst +0 -23
  52. library/twig/twig/doc/filters/default.rst +0 -33
  53. library/twig/twig/doc/filters/escape.rst +0 -119
  54. library/twig/twig/doc/filters/first.rst +0 -25
  55. library/twig/twig/doc/filters/format.rst +0 -16
  56. library/twig/twig/doc/filters/index.rst +0 -37
  57. library/twig/twig/doc/filters/join.rst +0 -23
  58. library/twig/twig/doc/filters/json_encode.rst +0 -21
  59. library/twig/twig/doc/filters/keys.rst +0 -11
  60. library/twig/twig/doc/filters/last.rst +0 -25
  61. library/twig/twig/doc/filters/length.rst +0 -21
  62. library/twig/twig/doc/filters/lower.rst +0 -10
  63. library/twig/twig/doc/filters/merge.rst +0 -48
  64. library/twig/twig/doc/filters/nl2br.rst +0 -22
  65. library/twig/twig/doc/filters/number_format.rst +0 -48
  66. library/twig/twig/doc/filters/raw.rst +0 -36
  67. library/twig/twig/doc/filters/replace.rst +0 -19
  68. library/twig/twig/doc/filters/reverse.rst +0 -47
  69. library/twig/twig/doc/filters/round.rst +0 -37
  70. library/twig/twig/doc/filters/slice.rst +0 -71
  71. library/twig/twig/doc/filters/sort.rst +0 -18
  72. library/twig/twig/doc/filters/split.rst +0 -53
  73. library/twig/twig/doc/filters/striptags.rst +0 -29
  74. library/twig/twig/doc/filters/title.rst +0 -11
  75. library/twig/twig/doc/filters/trim.rst +0 -45
  76. library/twig/twig/doc/filters/upper.rst +0 -10
  77. library/twig/twig/doc/filters/url_encode.rst +0 -34
  78. library/twig/twig/doc/functions/attribute.rst +0 -26
  79. library/twig/twig/doc/functions/block.rst +0 -41
  80. library/twig/twig/doc/functions/constant.rst +0 -29
  81. library/twig/twig/doc/functions/cycle.rst +0 -28
  82. library/twig/twig/doc/functions/date.rst +0 -55
  83. library/twig/twig/doc/functions/dump.rst +0 -69
  84. library/twig/twig/doc/functions/include.rst +0 -84
  85. library/twig/twig/doc/functions/index.rst +0 -20
  86. library/twig/twig/doc/functions/max.rst +0 -20
  87. library/twig/twig/doc/functions/min.rst +0 -20
  88. library/twig/twig/doc/functions/parent.rst +0 -20
  89. library/twig/twig/doc/functions/random.rst +0 -29
  90. library/twig/twig/doc/functions/range.rst +0 -58
  91. library/twig/twig/doc/functions/source.rst +0 -32
  92. library/twig/twig/doc/functions/template_from_string.rst +0 -32
  93. library/twig/twig/doc/index.rst +0 -19
  94. library/twig/twig/doc/installation.rst +0 -116
  95. library/twig/twig/doc/internals.rst +0 -142
  96. library/twig/twig/doc/intro.rst +0 -85
  97. library/twig/twig/doc/recipes.rst +0 -568
  98. library/twig/twig/doc/tags/autoescape.rst +0 -81
  99. library/twig/twig/doc/tags/block.rst +0 -11
  100. library/twig/twig/doc/tags/do.rst +0 -12
  101. library/twig/twig/doc/tags/embed.rst +0 -178
  102. library/twig/twig/doc/tags/extends.rst +0 -272
  103. library/twig/twig/doc/tags/filter.rst +0 -21
  104. library/twig/twig/doc/tags/flush.rst +0 -17
  105. library/twig/twig/doc/tags/for.rst +0 -172
  106. library/twig/twig/doc/tags/from.rst +0 -8
  107. library/twig/twig/doc/tags/if.rst +0 -76
  108. library/twig/twig/doc/tags/import.rst +0 -57
  109. library/twig/twig/doc/tags/include.rst +0 -90
  110. library/twig/twig/doc/tags/index.rst +0 -25
  111. library/twig/twig/doc/tags/macro.rst +0 -103
  112. library/twig/twig/doc/tags/sandbox.rst +0 -30
  113. library/twig/twig/doc/tags/set.rst +0 -78
  114. library/twig/twig/doc/tags/spaceless.rst +0 -37
  115. library/twig/twig/doc/tags/use.rst +0 -77
application/autoload_classmap.php CHANGED
@@ -1,215 +1,216 @@
1
  <?php
2
  // Generated by ZF2's ./bin/classmap_generator.php
3
  return array(
4
- 'Types_Field_Type_Definition_Checkbox' => dirname( __FILE__ ) . '/models/field/type/definition/checkbox.php',
5
- 'Types_Field_Type_Definition_Radio' => dirname( __FILE__ ) . '/models/field/type/definition/radio.php',
6
- 'Types_Field_Type_Definition_Numeric' => dirname( __FILE__ ) . '/models/field/type/definition/numeric.php',
7
- 'Types_Field_Type_Definition_Checkboxes' => dirname( __FILE__ ) . '/models/field/type/definition/checkboxes.php',
8
- 'Types_Field_Type_Definition_Singular' => dirname( __FILE__ ) . '/models/field/type/definition/singular.php',
9
- 'Types_Field_Type_Definition_Select' => dirname( __FILE__ ) . '/models/field/type/definition/select.php',
10
- 'Types_Field_Type_Definition_Date' => dirname( __FILE__ ) . '/models/field/type/definition/date.php',
11
- 'Types_Field_Type_Definition' => dirname( __FILE__ ) . '/models/field/type/definition.php',
12
- 'Types_Field_Type_Definition_Factory' => dirname( __FILE__ ) . '/models/field/type/definition_factory.php',
13
- 'Types_Field_Group_User_Factory' => dirname( __FILE__ ) . '/models/field/group/user_factory.php',
14
- 'Types_Field_Group_Post' => dirname( __FILE__ ) . '/models/field/group/post.php',
15
- 'Types_Field_Group_Factory' => dirname( __FILE__ ) . '/models/field/group/factory.php',
16
- 'Types_Field_Group_User' => dirname( __FILE__ ) . '/models/field/group/user.php',
17
- 'Types_Field_Group_Post_Factory' => dirname( __FILE__ ) . '/models/field/group/post_factory.php',
18
- 'Types_Field_Group_Term' => dirname( __FILE__ ) . '/models/field/group/term.php',
19
- 'Types_Field_Group_Term_Factory' => dirname( __FILE__ ) . '/models/field/group/term_factory.php',
20
- 'Types_Field_Group' => dirname( __FILE__ ) . '/models/field/group.php',
21
- 'Types_Setting_Option_Interface' => dirname( __FILE__ ) . '/models/setting/option/interface.php',
22
- 'Types_Setting_Interface' => dirname( __FILE__ ) . '/models/setting/interface.php',
23
- 'Types_Setting_Boolean' => dirname( __FILE__ ) . '/models/setting/boolean.php',
24
- 'Types_Setting_Option' => dirname( __FILE__ ) . '/models/setting/option.php',
25
- 'Types_Setting_Preset_Information_Table' => dirname( __FILE__ ) . '/models/setting/preset/information_table.php',
26
- 'Types_Taxonomy' => dirname( __FILE__ ) . '/models/taxonomy.php',
27
- 'Types_Post_Type' => dirname( __FILE__ ) . '/models/post_type.php',
28
- 'Types_Helper_Create_Content_Template' => dirname( __FILE__ ) . '/models/helper/create/content_template.php',
29
- 'Types_Helper_Create_Layout' => dirname( __FILE__ ) . '/models/helper/create/layout.php',
30
- 'Types_Helper_Create_View' => dirname( __FILE__ ) . '/models/helper/create/view.php',
31
- 'Types_Helper_Create_Wordpress_Archive' => dirname( __FILE__ ) . '/models/helper/create/wordpress_archive.php',
32
- 'Types_Helper_Create_Form' => dirname( __FILE__ ) . '/models/helper/create/form.php',
33
- 'Types_Helper_Output_Interface' => dirname( __FILE__ ) . '/models/helper/output/interface.php',
34
- 'Types_Helper_Output_Meta_Box' => dirname( __FILE__ ) . '/models/helper/output/meta_box.php',
35
- 'Types_Helper_Url' => dirname( __FILE__ ) . '/models/helper/url.php',
36
- 'Types_Helper_Twig' => dirname( __FILE__ ) . '/models/helper/twig.php',
37
- 'Types_Helper_Placeholder' => dirname( __FILE__ ) . '/models/helper/placeholder.php',
38
- 'Types_Helper_Condition' => dirname( __FILE__ ) . '/models/helper/condition.php',
39
- 'Types_Helper_Condition_Type_Post_Or_Page' => dirname( __FILE__ ) . '/models/helper/condition/type/post_or_page.php',
40
- 'Types_Helper_Condition_Type_No_Post_Or_Page' => dirname( __FILE__ ) . '/models/helper/condition/type/no_post_or_page.php',
41
- 'Types_Helper_Condition_Type_Fields_Assigned' => dirname( __FILE__ ) . '/models/helper/condition/type/fields_assigned.php',
42
- 'Types_Helper_Condition_Template' => dirname( __FILE__ ) . '/models/helper/condition/template.php',
43
- 'Types_Helper_Condition_Layouts_Template_Missing' => dirname( __FILE__ ) . '/models/helper/condition/layouts/template_missing.php',
44
- 'Types_Helper_Condition_Layouts_Active' => dirname( __FILE__ ) . '/models/helper/condition/layouts/active.php',
45
- 'Types_Helper_Condition_Layouts_Missing' => dirname( __FILE__ ) . '/models/helper/condition/layouts/missing.php',
46
- 'Types_Helper_Condition_Layouts_Template_Exists' => dirname( __FILE__ ) . '/models/helper/condition/layouts/template_exists.php',
47
- 'Types_Helper_Condition_Layouts_Archive_Exists' => dirname( __FILE__ ) . '/models/helper/condition/layouts/archive_exists.php',
48
- 'Types_Helper_Condition_Layouts_Compatible' => dirname( __FILE__ ) . '/models/helper/condition/layouts/compatible.php',
49
- 'Types_Helper_Condition_Layouts_Archive_Missing' => dirname( __FILE__ ) . '/models/helper/condition/layouts/archive_missing.php',
50
- 'Types_Helper_Condition_Single_Has_Fields' => dirname( __FILE__ ) . '/models/helper/condition/single/has_fields.php',
51
- 'Types_Helper_Condition_Single_Missing' => dirname( __FILE__ ) . '/models/helper/condition/single/missing.php',
52
- 'Types_Helper_Condition_Single_No_Fields' => dirname( __FILE__ ) . '/models/helper/condition/single/no_fields.php',
53
- 'Types_Helper_Condition_Single_Exists' => dirname( __FILE__ ) . '/models/helper/condition/single/exists.php',
54
- 'Types_Helper_Condition_Archive_Support' => dirname( __FILE__ ) . '/models/helper/condition/archive/support.php',
55
- 'Types_Helper_Condition_Archive_Has_Fields' => dirname( __FILE__ ) . '/models/helper/condition/archive/has_fields.php',
56
- 'Types_Helper_Condition_Archive_No_Support' => dirname( __FILE__ ) . '/models/helper/condition/archive/no_support.php',
57
- 'Types_Helper_Condition_Archive_Missing' => dirname( __FILE__ ) . '/models/helper/condition/archive/missing.php',
58
- 'Types_Helper_Condition_Archive_No_Fields' => dirname( __FILE__ ) . '/models/helper/condition/archive/no_fields.php',
59
- 'Types_Helper_Condition_Archive_Exists' => dirname( __FILE__ ) . '/models/helper/condition/archive/exists.php',
60
- 'Types_Helper_Condition_Screen' => dirname( __FILE__ ) . '/models/helper/condition/screen.php',
61
- 'Types_Helper_Condition_Views_Template_Missing' => dirname( __FILE__ ) . '/models/helper/condition/views/template_missing.php',
62
- 'Types_Helper_Condition_Views_Active' => dirname( __FILE__ ) . '/models/helper/condition/views/active.php',
63
- 'Types_Helper_Condition_Views_Missing' => dirname( __FILE__ ) . '/models/helper/condition/views/missing.php',
64
- 'Types_Helper_Condition_Views_Views_Exist' => dirname( __FILE__ ) . '/models/helper/condition/views/views_exist.php',
65
- 'Types_Helper_Condition_Views_Template_Exists' => dirname( __FILE__ ) . '/models/helper/condition/views/template_exists.php',
66
- 'Types_Helper_Condition_Views_Archive_Exists' => dirname( __FILE__ ) . '/models/helper/condition/views/archive_exists.php',
67
- 'Types_Helper_Condition_Views_Views_Missing' => dirname( __FILE__ ) . '/models/helper/condition/views/views_missing.php',
68
- 'Types_Helper_Condition_Views_Archive_Missing' => dirname( __FILE__ ) . '/models/helper/condition/views/archive_missing.php',
69
- 'Types_Helper_Condition_Cred_Active' => dirname( __FILE__ ) . '/models/helper/condition/cred/active.php',
70
- 'Types_Helper_Condition_Cred_Missing' => dirname( __FILE__ ) . '/models/helper/condition/cred/missing.php',
71
- 'Types_Helper_Condition_Cred_Forms_Exist' => dirname( __FILE__ ) . '/models/helper/condition/cred/forms_exist.php',
72
- 'Types_Helper_Condition_Cred_Forms_Missing' => dirname( __FILE__ ) . '/models/helper/condition/cred/forms_missing.php',
73
- 'Types_Setting' => dirname( __FILE__ ) . '/models/setting.php',
74
- 'Types_Information_Container' => dirname( __FILE__ ) . '/models/information/container.php',
75
- 'Types_Information_Message_Post_Type' => dirname( __FILE__ ) . '/models/information/message/post_type.php',
76
- 'Types_Information_Message' => dirname( __FILE__ ) . '/models/information/message.php',
77
- 'Types_Information_Table' => dirname( __FILE__ ) . '/models/information/table.php',
78
- 'Types_Wpml_Field_Group_String' => dirname( __FILE__ ) . '/models/wpml/field/group/string.php',
79
- 'Types_Wpml_Field_Group_String_Name' => dirname( __FILE__ ) . '/models/wpml/field/group/string/name.php',
80
- 'Types_Wpml_Field_Group_String_Description' => dirname( __FILE__ ) . '/models/wpml/field/group/string/description.php',
81
- 'Types_Wpml_Interface' => dirname( __FILE__ ) . '/models/wpml/interface.php',
82
- 'Types_Wpml_Field_Group' => dirname( __FILE__ ) . '/models/wpml/field_group.php',
83
- 'Types_Field_Type_Converter' => dirname( __FILE__ ) . '/controllers/field/type_converter.php',
84
- 'Types_Field_Utils' => dirname( __FILE__ ) . '/controllers/field/utils.php',
85
- 'Types_Frontend' => dirname( __FILE__ ) . '/controllers/frontend.php',
86
- 'Types_Import_Export' => dirname( __FILE__ ) . '/controllers/import_export.php',
87
- 'Types_Admin_Menu' => dirname( __FILE__ ) . '/controllers/admin_menu.php',
88
- 'Types_Interop_Handler_Interface' => dirname( __FILE__ ) . '/controllers/interop/handler_interface.php',
89
- 'Types_Interop_Handler_Wpml' => dirname( __FILE__ ) . '/controllers/interop/handler/wpml.php',
90
- 'Types_Interop_Handler_Use_Any_Font' => dirname( __FILE__ ) . '/controllers/interop/handler/use_any_font.php',
91
- 'Types_Interop_Handler_Divi' => dirname( __FILE__ ) . '/controllers/interop/handler/divi.php',
92
- 'Types_Interop_Mediator' => dirname( __FILE__ ) . '/controllers/interop/mediator.php',
93
- 'Types_Assets' => dirname( __FILE__ ) . '/controllers/assets.php',
94
- 'Types_Ajax' => dirname( __FILE__ ) . '/controllers/ajax.php',
95
- 'Types_Embedded' => dirname( __FILE__ ) . '/controllers/embedded.php',
96
- 'Types_Page_Extension_Edit_Post_Type' => dirname( __FILE__ ) . '/controllers/page/extension/edit_post_type.php',
97
- 'Types_Page_Extension_Edit_Post_Fields' => dirname( __FILE__ ) . '/controllers/page/extension/edit_post_fields.php',
98
- 'Types_Page_Extension_Edit_Post' => dirname( __FILE__ ) . '/controllers/page/extension/edit_post.php',
99
- 'Types_Page_Extension_Settings' => dirname( __FILE__ ) . '/controllers/page/extension/settings.php',
100
- 'Types_Page_Abstract' => dirname( __FILE__ ) . '/controllers/page/abstract.php',
101
- 'Types_Page_Hidden_Helper' => dirname( __FILE__ ) . '/controllers/page/hidden/helper.php',
102
- 'Types_Page_Dashboard' => dirname( __FILE__ ) . '/controllers/page/dashboard.php',
103
- 'Types_Page_Field_Control' => dirname( __FILE__ ) . '/controllers/page/field_control.php',
104
- 'Types_Dialog_Box' => dirname( __FILE__ ) . '/controllers/dialog_box.php',
105
- 'Types_Api_Handler_Interface' => dirname( __FILE__ ) . '/controllers/api/handler/interface.php',
106
- 'Types_Api_Handler_Filter_Get_Field_Group_Ids_By_Post_Type' => dirname( __FILE__ ) . '/controllers/api/handler/filter_get_field_group_ids_by_post_type.php',
107
- 'Types_Api_Handler_Query_Groups' => dirname( __FILE__ ) . '/controllers/api/handler/query_groups.php',
108
- 'Types_Api_Handler_Import_From_Zip_File' => dirname( __FILE__ ) . '/controllers/api/handler/import_from_zip_file.php',
109
- 'Types_Information_Controller' => dirname( __FILE__ ) . '/controllers/information/controller.php',
110
- 'Types_Admin' => dirname( __FILE__ ) . '/controllers/admin.php',
111
- 'Types_Asset_Help_Tab_Loader' => dirname( __FILE__ ) . '/controllers/asset/help_tab_loader.php',
112
- 'Types_Asset_Manager' => dirname( __FILE__ ) . '/controllers/asset/manager.php',
113
- 'Types_Main' => dirname( __FILE__ ) . '/controllers/main.php',
114
- 'Types_Upgrade' => dirname( __FILE__ ) . '/controllers/upgrade.php',
115
- 'Types_Api' => dirname( __FILE__ ) . '/controllers/api.php',
116
- 'Types_Ajax_Handler_Interface' => dirname( __FILE__ ) . '/controllers/ajax/handler_interface.php',
117
- 'Types_Ajax_Handler_Settings_Action' => dirname( __FILE__ ) . '/controllers/ajax/handler/settings_action.php',
118
- 'Types_Ajax_Handler_Abstract' => dirname( __FILE__ ) . '/controllers/ajax/handler/abstract.php',
119
- 'Types_Ajax_Handler_Field_Control_Action' => dirname( __FILE__ ) . '/controllers/ajax/handler/field_control_action.php',
120
- 'Types_Ajax_Handler_Check_Slug_Conflicts' => dirname( __FILE__ ) . '/controllers/ajax/handler/check_slug_conflicts.php',
121
- 'Types_Utils' => dirname( __FILE__ ) . '/controllers/utils.php',
122
- 'Types_Twig_Autoloader' => dirname( __FILE__ ) . '/controllers/twig_autoloader.php',
123
- 'WPCF_Loader' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/loader.php',
124
- 'WPCF_Usermeta_Repeater' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/usermeta_repeater.php',
125
- 'WPCF_Field_Renderer_Abstract' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/abstract.php',
126
- 'WPCF_Field_Renderer_Preview_Checkbox' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/preview/checkbox.php',
127
- 'WPCF_Field_Renderer_Preview_Radio' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/preview/radio.php',
128
- 'WPCF_Field_Renderer_Preview_URL' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/preview/url.php',
129
- 'WPCF_Field_Renderer_Preview_Image' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/preview/image.php',
130
- 'WPCF_Field_Renderer_Preview_Textfield' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/preview/textfield.php',
131
- 'WPCF_Field_Renderer_Preview_File' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/preview/file.php',
132
- 'WPCF_Field_Renderer_Preview_Checkboxes' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/preview/checkboxes.php',
133
- 'WPCF_Field_Renderer_Preview_Skype' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/preview/skype.php',
134
- 'WPCF_Field_Renderer_Preview_Base' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/preview/base.php',
135
- 'WPCF_Field_Renderer_Preview_Colorpicker' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/preview/colorpicker.php',
136
- 'WPCF_Field_Renderer_Preview_Address' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/preview/address.php',
137
- 'WPCF_Field_Renderer_Preview_Date' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/preview/date.php',
138
- 'WPCF_Field_Renderer_Factory' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/factory.php',
139
- 'WPCF_Field_Renderer_Toolset_Forms' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/renderer/toolset_forms.php',
140
- 'WPCF_Field_Accessor_Dummy' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/accessor/dummy.php',
141
- 'WPCF_Field_Accessor_Abstract' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/accessor/abstract.php',
142
- 'WPCF_Field_Accessor_Termmeta_Field' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/accessor/termmeta_field.php',
143
- 'WPCF_Field_Accessor_Termmeta' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/accessor/termmeta.php',
144
- 'WPCF_Field_Instance_Unsaved' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/instance_unsaved.php',
145
- 'WPCF_Field_DataMapper_Checkbox' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/datamapper/checkbox.php',
146
- 'WPCF_Field_DataMapper_Abstract' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/datamapper/abstract.php',
147
- 'WPCF_Field_DataMapper_Checkboxes' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/datamapper/checkboxes.php',
148
- 'WPCF_Field_DataMapper_Identity' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/datamapper/identity.php',
149
- 'WPCF_Field_Definition_Factory_Post' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/definition_factory_post.php',
150
- 'WPCF_Field_Hooks_API' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/hooks_api.php',
151
- 'WPCF_Field_Option_Radio' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/option_radio.php',
152
- 'WPCF_Field_Definition_Term' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/definition_term.php',
153
- 'WPCF_Field_Definition_Factory_Term' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/definition_factory_term.php',
154
- 'WPCF_Field_Definition_Generic' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/definition_generic.php',
155
- 'WPCF_Field_Definition_Abstract' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/definition_abstract.php',
156
- 'WPCF_Field_Instance' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/instance.php',
157
- 'WPCF_Field_Option_Select' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/option_select.php',
158
- 'WPCF_Field_Definition_Factory_User' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/definition_factory_user.php',
159
- 'WPCF_Field_Definition' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/definition.php',
160
- 'WPCF_Field_Option_Checkboxes' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/option_checkboxes.php',
161
- 'WPCF_Field_Definition_Factory' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/definition_factory.php',
162
- 'WPCF_Field_Definition_User' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/definition_user.php',
163
- 'WPCF_Field_Data_Saver' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/data_saver.php',
164
- 'WPCF_Field_Instance_Term' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/instance_term.php',
165
- 'WPCF_Field_Definition_Post' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/definition_post.php',
166
- 'WPCF_Field_Instance_Abstract' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field/instance_abstract.php',
167
- 'WPCF_Path' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/path.php',
168
- 'WPCF_Repeater' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/repeater.php',
169
- 'WPCF_Termmeta_Repeater' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/repeater.php',
170
- 'WPCF_GUI_Term_Field_Editing' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/gui/term_field_editing.php',
171
- 'WPCF_Validation' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/validation.php',
172
- 'WPCF_Import_Export' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/class.wpcf-import-export.php',
173
- 'WPCF_WPViews' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/wpviews.php',
174
- 'WPCF_Usermeta_Field' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/usermeta_field.php',
175
- 'Enlimbo_Forms_Wpcf' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/forms.php',
176
- 'WPCF_Fields' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/fields.php',
177
- 'WPCF_Relationship' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/relationship.php',
178
- 'Wpcf_Cake_Validation' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/validation-cakephp.php',
179
- 'WPCF_Post_Types' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/class.wpcf-post-types.php',
180
- 'WPCF_Evaluate' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/evaluate.php',
181
- 'WPCF_Conditional' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/conditional.php',
182
- 'WPCF_Helper_Ajax' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/helper.ajax.php',
183
- 'WPCF_Editor' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/editor.php',
184
- 'WPCF_Field' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field.php',
185
- 'WPCF_Termmeta_Field' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/field.php',
186
- 'WPCF_Relationship_Child_Form' => dirname( __FILE__ ) . '/../library/toolset/types/embedded/classes/relationship/form-child.php',
187
- 'WPCF_Types_Marketing_Messages' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.wpcf.marketing.messages.php',
188
- 'WPCF_Custom_Fields_List_Table' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.wpcf.custom.fields.list.table.php',
189
- 'Types_Admin_Page' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.page.php',
190
- 'Types_Admin_Post_Types_List_Table' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.post.types.list.table.php',
191
- 'WPCF_Roles' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.wpcf.roles.php',
192
- 'Types_Admin_Usermeta_Control_Table' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.usermeta.table.php',
193
- 'Types_Admin_Taxonomies' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.taxonomies.php',
194
- 'Types_Admin_Post_Type' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.post-type.php',
195
- 'WPCF_Types_Marketing' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.wpcf.marketing.php',
196
- 'Types_Admin_Fields' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.fields.php',
197
- 'Types_Admin_Taxonomies_List_Table' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.taxonomies.list.table.php',
198
- 'WPCF_Page_Listing_Abstract' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/page/listing/abstract.php',
199
- 'WPCF_Page_Listing_Table' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/page/listing/table.php',
200
- 'WPCF_Page_Listing_Termmeta_Table' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/page/listing/termmeta_table.php',
201
- 'WPCF_Page_Listing_Termmeta' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/page/listing/termmeta.php',
202
- 'WPCF_Page_Abstract' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/page/abstract.php',
203
- 'WPCF_Page_Edit_Termmeta_Form' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/page/edit/termmeta_form.php',
204
- 'WPCF_Page_Edit_Termmeta' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/page/edit/termmeta.php',
205
- 'Types_Admin_Edit_Fields' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.edit.fields.php',
206
- 'Types_Admin_Edit_Taxonomy' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.edit.taxonomy.php',
207
- 'Types_Admin_Edit_Meta_Fields_Group' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.edit.meta.fields.group.php',
208
- 'Types_Admin_Edit_Custom_Fields_Group' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.edit.custom.fields.group.php',
209
- 'Types_Admin_Usermeta_Groups_List_Table' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.usermeta.groups.list.table.php',
210
- 'Types_Fields_Conditional' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.fields.conditional.php',
211
- 'Types_Admin_Edit_Post_Type' => dirname( __FILE__ ) . '/../library/toolset/types/includes/classes/class.types.admin.edit.post.type.php',
212
- 'Toolset_Filesystem_Exception' => dirname( __FILE__ ) . '/../library/toolset/filesystem/exception.php',
213
- 'Toolset_Filesystem_Directory' => dirname( __FILE__ ) . '/../library/toolset/filesystem/directory.php',
214
- 'Toolset_Filesystem_File' => dirname( __FILE__ ) . '/../library/toolset/filesystem/file.php',
 
215
  );
1
  <?php
2
  // Generated by ZF2's ./bin/classmap_generator.php
3
  return array(
4
+ 'Enlimbo_Forms_Wpcf' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/forms.php',
5
+ 'Toolset_Filesystem_Directory' => dirname( __FILE__ ) . '/../vendor/toolset/filesystem/directory.php',
6
+ 'Toolset_Filesystem_Exception' => dirname( __FILE__ ) . '/../vendor/toolset/filesystem/exception.php',
7
+ 'Toolset_Filesystem_File' => dirname( __FILE__ ) . '/../vendor/toolset/filesystem/file.php',
8
+ 'Types_Admin' => dirname( __FILE__ ) . '/controllers/admin.php',
9
+ 'Types_Admin_Edit_Custom_Fields_Group' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.edit.custom.fields.group.php',
10
+ 'Types_Admin_Edit_Fields' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.edit.fields.php',
11
+ 'Types_Admin_Edit_Meta_Fields_Group' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.edit.meta.fields.group.php',
12
+ 'Types_Admin_Edit_Post_Type' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.edit.post.type.php',
13
+ 'Types_Admin_Edit_Taxonomy' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.edit.taxonomy.php',
14
+ 'Types_Admin_Fields' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.fields.php',
15
+ 'Types_Admin_Menu' => dirname( __FILE__ ) . '/controllers/admin_menu.php',
16
+ 'Types_Admin_Page' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.page.php',
17
+ 'Types_Admin_Post_Type' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.post-type.php',
18
+ 'Types_Admin_Post_Types_List_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.post.types.list.table.php',
19
+ 'Types_Admin_Taxonomies' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.taxonomies.php',
20
+ 'Types_Admin_Taxonomies_List_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.taxonomies.list.table.php',
21
+ 'Types_Admin_Usermeta_Control_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.usermeta.table.php',
22
+ 'Types_Admin_Usermeta_Groups_List_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.usermeta.groups.list.table.php',
23
+ 'Types_Ajax' => dirname( __FILE__ ) . '/controllers/ajax.php',
24
+ 'Types_Ajax_Handler_Abstract' => dirname( __FILE__ ) . '/controllers/ajax/handler/abstract.php',
25
+ 'Types_Ajax_Handler_Check_Slug_Conflicts' => dirname( __FILE__ ) . '/controllers/ajax/handler/check_slug_conflicts.php',
26
+ 'Types_Ajax_Handler_Field_Control_Action' => dirname( __FILE__ ) . '/controllers/ajax/handler/field_control_action.php',
27
+ 'Types_Ajax_Handler_Interface' => dirname( __FILE__ ) . '/controllers/ajax/handler_interface.php',
28
+ 'Types_Ajax_Handler_Settings_Action' => dirname( __FILE__ ) . '/controllers/ajax/handler/settings_action.php',
29
+ 'Types_Api' => dirname( __FILE__ ) . '/controllers/api.php',
30
+ 'Types_Api_Handler_Filter_Get_Field_Group_Ids_By_Post_Type' => dirname( __FILE__ ) . '/controllers/api/handler/filter_get_field_group_ids_by_post_type.php',
31
+ 'Types_Api_Handler_Import_From_Zip_File' => dirname( __FILE__ ) . '/controllers/api/handler/import_from_zip_file.php',
32
+ 'Types_Api_Handler_Interface' => dirname( __FILE__ ) . '/controllers/api/handler/interface.php',
33
+ 'Types_Api_Handler_Query_Groups' => dirname( __FILE__ ) . '/controllers/api/handler/query_groups.php',
34
+ 'Types_Asset_Help_Tab_Loader' => dirname( __FILE__ ) . '/controllers/asset/help_tab_loader.php',
35
+ 'Types_Asset_Manager' => dirname( __FILE__ ) . '/controllers/asset/manager.php',
36
+ 'Types_Assets' => dirname( __FILE__ ) . '/controllers/assets.php',
37
+ 'Types_Dialog_Box' => dirname( __FILE__ ) . '/controllers/dialog_box.php',
38
+ 'Types_Embedded' => dirname( __FILE__ ) . '/controllers/embedded.php',
39
+ 'Types_Field_Group' => dirname( __FILE__ ) . '/models/field/group.php',
40
+ 'Types_Field_Group_Factory' => dirname( __FILE__ ) . '/models/field/group/factory.php',
41
+ 'Types_Field_Group_Post' => dirname( __FILE__ ) . '/models/field/group/post.php',
42
+ 'Types_Field_Group_Post_Factory' => dirname( __FILE__ ) . '/models/field/group/post_factory.php',
43
+ 'Types_Field_Group_Term' => dirname( __FILE__ ) . '/models/field/group/term.php',
44
+ 'Types_Field_Group_Term_Factory' => dirname( __FILE__ ) . '/models/field/group/term_factory.php',
45
+ 'Types_Field_Group_User' => dirname( __FILE__ ) . '/models/field/group/user.php',
46
+ 'Types_Field_Group_User_Factory' => dirname( __FILE__ ) . '/models/field/group/user_factory.php',
47
+ 'Types_Fields_Conditional' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.fields.conditional.php',
48
+ 'Types_Field_Type_Converter' => dirname( __FILE__ ) . '/controllers/field/type_converter.php',
49
+ 'Types_Field_Type_Definition_Checkbox' => dirname( __FILE__ ) . '/models/field/type/definition/checkbox.php',
50
+ 'Types_Field_Type_Definition_Checkboxes' => dirname( __FILE__ ) . '/models/field/type/definition/checkboxes.php',
51
+ 'Types_Field_Type_Definition_Date' => dirname( __FILE__ ) . '/models/field/type/definition/date.php',
52
+ 'Types_Field_Type_Definition' => dirname( __FILE__ ) . '/models/field/type/definition.php',
53
+ 'Types_Field_Type_Definition_Factory' => dirname( __FILE__ ) . '/models/field/type/definition_factory.php',
54
+ 'Types_Field_Type_Definition_Numeric' => dirname( __FILE__ ) . '/models/field/type/definition/numeric.php',
55
+ 'Types_Field_Type_Definition_Radio' => dirname( __FILE__ ) . '/models/field/type/definition/radio.php',
56
+ 'Types_Field_Type_Definition_Select' => dirname( __FILE__ ) . '/models/field/type/definition/select.php',
57
+ 'Types_Field_Type_Definition_Singular' => dirname( __FILE__ ) . '/models/field/type/definition/singular.php',
58
+ 'Types_Field_Utils' => dirname( __FILE__ ) . '/controllers/field/utils.php',
59
+ 'Types_Frontend' => dirname( __FILE__ ) . '/controllers/frontend.php',
60
+ 'Types_Helper_Condition_Archive_Exists' => dirname( __FILE__ ) . '/models/helper/condition/archive/exists.php',
61
+ 'Types_Helper_Condition_Archive_Has_Fields' => dirname( __FILE__ ) . '/models/helper/condition/archive/has_fields.php',
62
+ 'Types_Helper_Condition_Archive_Missing' => dirname( __FILE__ ) . '/models/helper/condition/archive/missing.php',
63
+ 'Types_Helper_Condition_Archive_No_Fields' => dirname( __FILE__ ) . '/models/helper/condition/archive/no_fields.php',
64
+ 'Types_Helper_Condition_Archive_No_Support' => dirname( __FILE__ ) . '/models/helper/condition/archive/no_support.php',
65
+ 'Types_Helper_Condition_Archive_Support' => dirname( __FILE__ ) . '/models/helper/condition/archive/support.php',
66
+ 'Types_Helper_Condition_Cred_Active' => dirname( __FILE__ ) . '/models/helper/condition/cred/active.php',
67
+ 'Types_Helper_Condition_Cred_Forms_Exist' => dirname( __FILE__ ) . '/models/helper/condition/cred/forms_exist.php',
68
+ 'Types_Helper_Condition_Cred_Forms_Missing' => dirname( __FILE__ ) . '/models/helper/condition/cred/forms_missing.php',
69
+ 'Types_Helper_Condition_Cred_Missing' => dirname( __FILE__ ) . '/models/helper/condition/cred/missing.php',
70
+ 'Types_Helper_Condition' => dirname( __FILE__ ) . '/models/helper/condition.php',
71
+ 'Types_Helper_Condition_Layouts_Active' => dirname( __FILE__ ) . '/models/helper/condition/layouts/active.php',
72
+ 'Types_Helper_Condition_Layouts_Archive_Exists' => dirname( __FILE__ ) . '/models/helper/condition/layouts/archive_exists.php',
73
+ 'Types_Helper_Condition_Layouts_Archive_Missing' => dirname( __FILE__ ) . '/models/helper/condition/layouts/archive_missing.php',
74
+ 'Types_Helper_Condition_Layouts_Compatible' => dirname( __FILE__ ) . '/models/helper/condition/layouts/compatible.php',
75
+ 'Types_Helper_Condition_Layouts_Missing' => dirname( __FILE__ ) . '/models/helper/condition/layouts/missing.php',
76
+ 'Types_Helper_Condition_Layouts_Template_Exists' => dirname( __FILE__ ) . '/models/helper/condition/layouts/template_exists.php',
77
+ 'Types_Helper_Condition_Layouts_Template_Missing' => dirname( __FILE__ ) . '/models/helper/condition/layouts/template_missing.php',
78
+ 'Types_Helper_Condition_Screen' => dirname( __FILE__ ) . '/models/helper/condition/screen.php',
79
+ 'Types_Helper_Condition_Single_Exists' => dirname( __FILE__ ) . '/models/helper/condition/single/exists.php',
80
+ 'Types_Helper_Condition_Single_Has_Fields' => dirname( __FILE__ ) . '/models/helper/condition/single/has_fields.php',
81
+ 'Types_Helper_Condition_Single_Missing' => dirname( __FILE__ ) . '/models/helper/condition/single/missing.php',
82
+ 'Types_Helper_Condition_Single_No_Fields' => dirname( __FILE__ ) . '/models/helper/condition/single/no_fields.php',
83
+ 'Types_Helper_Condition_Template' => dirname( __FILE__ ) . '/models/helper/condition/template.php',
84
+ 'Types_Helper_Condition_Type_Fields_Assigned' => dirname( __FILE__ ) . '/models/helper/condition/type/fields_assigned.php',
85
+ 'Types_Helper_Condition_Type_No_Post_Or_Page' => dirname( __FILE__ ) . '/models/helper/condition/type/no_post_or_page.php',
86
+ 'Types_Helper_Condition_Type_Post_Or_Page' => dirname( __FILE__ ) . '/models/helper/condition/type/post_or_page.php',
87
+ 'Types_Helper_Condition_Views_Active' => dirname( __FILE__ ) . '/models/helper/condition/views/active.php',
88
+ 'Types_Helper_Condition_Views_Archive_Exists' => dirname( __FILE__ ) . '/models/helper/condition/views/archive_exists.php',
89
+ 'Types_Helper_Condition_Views_Archive_Missing' => dirname( __FILE__ ) . '/models/helper/condition/views/archive_missing.php',
90
+ 'Types_Helper_Condition_Views_Missing' => dirname( __FILE__ ) . '/models/helper/condition/views/missing.php',
91
+ 'Types_Helper_Condition_Views_Template_Exists' => dirname( __FILE__ ) . '/models/helper/condition/views/template_exists.php',
92
+ 'Types_Helper_Condition_Views_Template_Missing' => dirname( __FILE__ ) . '/models/helper/condition/views/template_missing.php',
93
+ 'Types_Helper_Condition_Views_Views_Exist' => dirname( __FILE__ ) . '/models/helper/condition/views/views_exist.php',
94
+ 'Types_Helper_Condition_Views_Views_Missing' => dirname( __FILE__ ) . '/models/helper/condition/views/views_missing.php',
95
+ 'Types_Helper_Create_Content_Template' => dirname( __FILE__ ) . '/models/helper/create/content_template.php',
96
+ 'Types_Helper_Create_Form' => dirname( __FILE__ ) . '/models/helper/create/form.php',
97
+ 'Types_Helper_Create_Layout' => dirname( __FILE__ ) . '/models/helper/create/layout.php',
98
+ 'Types_Helper_Create_View' => dirname( __FILE__ ) . '/models/helper/create/view.php',
99
+ 'Types_Helper_Create_Wordpress_Archive' => dirname( __FILE__ ) . '/models/helper/create/wordpress_archive.php',
100
+ 'Types_Helper_Output_Interface' => dirname( __FILE__ ) . '/models/helper/output/interface.php',
101
+ 'Types_Helper_Output_Meta_Box' => dirname( __FILE__ ) . '/models/helper/output/meta_box.php',
102
+ 'Types_Helper_Placeholder' => dirname( __FILE__ ) . '/models/helper/placeholder.php',
103
+ 'Types_Helper_Twig' => dirname( __FILE__ ) . '/models/helper/twig.php',
104
+ 'Types_Helper_Url' => dirname( __FILE__ ) . '/models/helper/url.php',
105
+ 'Types_Import_Export' => dirname( __FILE__ ) . '/controllers/import_export.php',
106
+ 'Types_Information_Container' => dirname( __FILE__ ) . '/models/information/container.php',
107
+ 'Types_Information_Controller' => dirname( __FILE__ ) . '/controllers/information/controller.php',
108
+ 'Types_Information_Message' => dirname( __FILE__ ) . '/models/information/message.php',
109
+ 'Types_Information_Message_Post_Type' => dirname( __FILE__ ) . '/models/information/message/post_type.php',
110
+ 'Types_Information_Table' => dirname( __FILE__ ) . '/models/information/table.php',
111
+ 'Types_Interop_Handler_Divi' => dirname( __FILE__ ) . '/controllers/interop/handler/divi.php',
112
+ 'Types_Interop_Handler_Interface' => dirname( __FILE__ ) . '/controllers/interop/handler_interface.php',
113
+ 'Types_Interop_Handler_The7' => dirname( __FILE__ ) . '/controllers/interop/handler/the7.php',
114
+ 'Types_Interop_Handler_Use_Any_Font' => dirname( __FILE__ ) . '/controllers/interop/handler/use_any_font.php',
115
+ 'Types_Interop_Handler_Wpml' => dirname( __FILE__ ) . '/controllers/interop/handler/wpml.php',
116
+ 'Types_Interop_Mediator' => dirname( __FILE__ ) . '/controllers/interop/mediator.php',
117
+ 'Types_Main' => dirname( __FILE__ ) . '/controllers/main.php',
118
+ 'Types_Page_Abstract' => dirname( __FILE__ ) . '/controllers/page/abstract.php',
119
+ 'Types_Page_Dashboard' => dirname( __FILE__ ) . '/controllers/page/dashboard.php',
120
+ 'Types_Page_Extension_Edit_Post' => dirname( __FILE__ ) . '/controllers/page/extension/edit_post.php',
121
+ 'Types_Page_Extension_Edit_Post_Fields' => dirname( __FILE__ ) . '/controllers/page/extension/edit_post_fields.php',
122
+ 'Types_Page_Extension_Edit_Post_Type' => dirname( __FILE__ ) . '/controllers/page/extension/edit_post_type.php',
123
+ 'Types_Page_Extension_Settings' => dirname( __FILE__ ) . '/controllers/page/extension/settings.php',
124
+ 'Types_Page_Field_Control' => dirname( __FILE__ ) . '/controllers/page/field_control.php',
125
+ 'Types_Page_Hidden_Helper' => dirname( __FILE__ ) . '/controllers/page/hidden/helper.php',
126
+ 'Types_Post_Type' => dirname( __FILE__ ) . '/models/post_type.php',
127
+ 'Types_Setting_Boolean' => dirname( __FILE__ ) . '/models/setting/boolean.php',
128
+ 'Types_Setting' => dirname( __FILE__ ) . '/models/setting.php',
129
+ 'Types_Setting_Interface' => dirname( __FILE__ ) . '/models/setting/interface.php',
130
+ 'Types_Setting_Option' => dirname( __FILE__ ) . '/models/setting/option.php',
131
+ 'Types_Setting_Option_Interface' => dirname( __FILE__ ) . '/models/setting/option/interface.php',
132
+ 'Types_Setting_Preset_Information_Table' => dirname( __FILE__ ) . '/models/setting/preset/information_table.php',
133
+ 'Types_Taxonomy' => dirname( __FILE__ ) . '/models/taxonomy.php',
134
+ 'Types_Twig_Autoloader' => dirname( __FILE__ ) . '/controllers/twig_autoloader.php',
135
+ 'Types_Upgrade' => dirname( __FILE__ ) . '/controllers/upgrade.php',
136
+ 'Types_Utils' => dirname( __FILE__ ) . '/controllers/utils.php',
137
+ 'Types_Wpml_Field_Group' => dirname( __FILE__ ) . '/models/wpml/field_group.php',
138
+ 'Types_Wpml_Field_Group_String_Description' => dirname( __FILE__ ) . '/models/wpml/field/group/string/description.php',
139
+ 'Types_Wpml_Field_Group_String' => dirname( __FILE__ ) . '/models/wpml/field/group/string.php',
140
+ 'Types_Wpml_Field_Group_String_Name' => dirname( __FILE__ ) . '/models/wpml/field/group/string/name.php',
141
+ 'Types_Wpml_Interface' => dirname( __FILE__ ) . '/models/wpml/interface.php',
142
+ 'Wpcf_Cake_Validation' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/validation-cakephp.php',
143
+ 'WPCF_Conditional' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/conditional.php',
144
+ 'WPCF_Custom_Fields_List_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.wpcf.custom.fields.list.table.php',
145
+ 'WPCF_Editor' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/editor.php',
146
+ 'WPCF_Evaluate' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/evaluate.php',
147
+ 'WPCF_Field_Accessor_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/accessor/abstract.php',
148
+ 'WPCF_Field_Accessor_Dummy' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/accessor/dummy.php',
149
+ 'WPCF_Field_Accessor_Termmeta' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/accessor/termmeta.php',
150
+ 'WPCF_Field_Accessor_Termmeta_Field' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/accessor/termmeta_field.php',
151
+ 'WPCF_Field_DataMapper_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/datamapper/abstract.php',
152
+ 'WPCF_Field_DataMapper_Checkbox' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/datamapper/checkbox.php',
153
+ 'WPCF_Field_DataMapper_Checkboxes' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/datamapper/checkboxes.php',
154
+ 'WPCF_Field_DataMapper_Identity' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/datamapper/identity.php',
155
+ 'WPCF_Field_Data_Saver' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/data_saver.php',
156
+ 'WPCF_Field_Definition_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_abstract.php',
157
+ 'WPCF_Field_Definition' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition.php',
158
+ 'WPCF_Field_Definition_Factory' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_factory.php',
159
+ 'WPCF_Field_Definition_Factory_Post' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_factory_post.php',
160
+ 'WPCF_Field_Definition_Factory_Term' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_factory_term.php',
161
+ 'WPCF_Field_Definition_Factory_User' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_factory_user.php',
162
+ 'WPCF_Field_Definition_Generic' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_generic.php',
163
+ 'WPCF_Field_Definition_Post' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_post.php',
164
+ 'WPCF_Field_Definition_Term' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_term.php',
165
+ 'WPCF_Field_Definition_User' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_user.php',
166
+ 'WPCF_Field' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field.php',
167
+ 'WPCF_Field_Hooks_API' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/hooks_api.php',
168
+ 'WPCF_Field_Instance_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/instance_abstract.php',
169
+ 'WPCF_Field_Instance' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/instance.php',
170
+ 'WPCF_Field_Instance_Term' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/instance_term.php',
171
+ 'WPCF_Field_Instance_Unsaved' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/instance_unsaved.php',
172
+ 'WPCF_Field_Option_Checkboxes' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/option_checkboxes.php',
173
+ 'WPCF_Field_Option_Radio' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/option_radio.php',
174
+ 'WPCF_Field_Option_Select' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/option_select.php',
175
+ 'WPCF_Field_Renderer_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/abstract.php',
176
+ 'WPCF_Field_Renderer_Factory' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/factory.php',
177
+ 'WPCF_Field_Renderer_Preview_Address' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/address.php',
178
+ 'WPCF_Field_Renderer_Preview_Base' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/base.php',
179
+ 'WPCF_Field_Renderer_Preview_Checkbox' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/checkbox.php',
180
+ 'WPCF_Field_Renderer_Preview_Checkboxes' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/checkboxes.php',
181
+ 'WPCF_Field_Renderer_Preview_Colorpicker' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/colorpicker.php',
182
+ 'WPCF_Field_Renderer_Preview_Date' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/date.php',
183
+ 'WPCF_Field_Renderer_Preview_File' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/file.php',
184
+ 'WPCF_Field_Renderer_Preview_Image' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/image.php',
185
+ 'WPCF_Field_Renderer_Preview_Radio' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/radio.php',
186
+ 'WPCF_Field_Renderer_Preview_Skype' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/skype.php',
187
+ 'WPCF_Field_Renderer_Preview_Textfield' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/textfield.php',
188
+ 'WPCF_Field_Renderer_Preview_URL' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/url.php',
189
+ 'WPCF_Field_Renderer_Toolset_Forms' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/toolset_forms.php',
190
+ 'WPCF_Fields' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/fields.php',
191
+ 'WPCF_GUI_Term_Field_Editing' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/gui/term_field_editing.php',
192
+ 'WPCF_Helper_Ajax' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/helper.ajax.php',
193
+ 'WPCF_Import_Export' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/class.wpcf-import-export.php',
194
+ 'WPCF_Loader' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/loader.php',
195
+ 'WPCF_Page_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/abstract.php',
196
+ 'WPCF_Page_Edit_Termmeta' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/edit/termmeta.php',
197
+ 'WPCF_Page_Edit_Termmeta_Form' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/edit/termmeta_form.php',
198
+ 'WPCF_Page_Listing_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/listing/abstract.php',
199
+ 'WPCF_Page_Listing_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/listing/table.php',
200
+ 'WPCF_Page_Listing_Termmeta' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/listing/termmeta.php',
201
+ 'WPCF_Page_Listing_Termmeta_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/listing/termmeta_table.php',
202
+ 'WPCF_Path' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/path.php',
203
+ 'WPCF_Post_Types' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/class.wpcf-post-types.php',
204
+ 'WPCF_Relationship_Child_Form' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/relationship/form-child.php',
205
+ 'WPCF_Relationship' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/relationship.php',
206
+ 'WPCF_Repeater' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/repeater.php',
207
+ 'WPCF_Roles' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.wpcf.roles.php',
208
+ 'WPCF_Termmeta_Field' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field.php',
209
+ 'WPCF_Termmeta_Repeater' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/repeater.php',
210
+ 'WPCF_Types_Marketing' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.wpcf.marketing.php',
211
+ 'WPCF_Types_Marketing_Messages' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.wpcf.marketing.messages.php',
212
+ 'WPCF_Usermeta_Field' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/usermeta_field.php',
213
+ 'WPCF_Usermeta_Repeater' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/usermeta_repeater.php',
214
+ 'WPCF_Validation' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/validation.php',
215
+ 'WPCF_WPViews' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/wpviews.php',
216
  );
application/bootstrap.php CHANGED
@@ -3,30 +3,30 @@
3
  /*
4
  * Autoloader
5
  */
6
- require_once( TYPES_ABSPATH . '/library/toolset/autoloader/autoloader.php' );
7
 
8
  $autoloader = Toolset_Autoloader::get_instance();
9
 
10
- $autoloader->add_path( 'Toolset', TYPES_ABSPATH . '/library/toolset' );
11
 
12
 
13
  /*
14
  * Load old Types
15
  */
16
  if( ! defined( 'WPCF_RELPATH' ) ) {
17
- define( 'WPCF_RELPATH', TYPES_RELPATH . '/library/toolset/types' );
18
  }
19
 
20
  if( ! defined( 'WPCF_EMBEDDED_TOOLSET_ABSPATH' ) ) {
21
- define( 'WPCF_EMBEDDED_TOOLSET_ABSPATH', TYPES_ABSPATH . '/library/toolset' );
22
  }
23
 
24
  if( ! defined( 'WPCF_EMBEDDED_TOOLSET_RELPATH') ) {
25
- define( 'WPCF_EMBEDDED_TOOLSET_RELPATH', TYPES_RELPATH . '/library/toolset' );
26
  }
27
 
28
  if( ! defined( 'WPTOOLSET_COMMON_PATH' ) ) {
29
- define( 'WPTOOLSET_COMMON_PATH', TYPES_ABSPATH . '/library/toolset/toolset-common' );
30
  }
31
 
32
  if ( !defined( 'EDITOR_ADDON_RELPATH' ) ) {
@@ -34,7 +34,7 @@ if ( !defined( 'EDITOR_ADDON_RELPATH' ) ) {
34
  }
35
 
36
  // installer
37
- $installer = TYPES_ABSPATH . '/library/otgs/installer/loader.php';
38
  if ( file_exists( $installer ) ) {
39
  /** @noinspection PhpIncludeInspection */
40
  include_once $installer;
@@ -54,7 +54,7 @@ if ( file_exists( $installer ) ) {
54
  require_once( dirname( __FILE__ ) . '/functions.php' );
55
 
56
  // Initialize legacy code
57
- require_once( dirname( __FILE__ ) . '/../library/toolset/types/wpcf.php' );
58
 
59
  // Public API
60
  require_once( dirname( __FILE__ ) . '/controllers/main.php' );
3
  /*
4
  * Autoloader
5
  */
6
+ require_once( TYPES_ABSPATH . '/vendor/toolset/autoloader/autoloader.php' );
7
 
8
  $autoloader = Toolset_Autoloader::get_instance();
9
 
10
+ $autoloader->add_path( 'Toolset', TYPES_ABSPATH . '/vendor/toolset' );
11
 
12
 
13
  /*
14
  * Load old Types
15
  */
16
  if( ! defined( 'WPCF_RELPATH' ) ) {
17
+ define( 'WPCF_RELPATH', TYPES_RELPATH . '/vendor/toolset/types' );
18
  }
19
 
20
  if( ! defined( 'WPCF_EMBEDDED_TOOLSET_ABSPATH' ) ) {
21
+ define( 'WPCF_EMBEDDED_TOOLSET_ABSPATH', TYPES_ABSPATH . '/vendor/toolset' );
22
  }
23
 
24
  if( ! defined( 'WPCF_EMBEDDED_TOOLSET_RELPATH') ) {
25
+ define( 'WPCF_EMBEDDED_TOOLSET_RELPATH', TYPES_RELPATH . '/vendor/toolset' );
26
  }
27
 
28
  if( ! defined( 'WPTOOLSET_COMMON_PATH' ) ) {
29
+ define( 'WPTOOLSET_COMMON_PATH', TYPES_ABSPATH . '/vendor/toolset/toolset-common' );
30
  }
31
 
32
  if ( !defined( 'EDITOR_ADDON_RELPATH' ) ) {
34
  }
35
 
36
  // installer
37
+ $installer = TYPES_ABSPATH . '/vendor/otgs/installer/loader.php';
38
  if ( file_exists( $installer ) ) {
39
  /** @noinspection PhpIncludeInspection */
40
  include_once $installer;
54
  require_once( dirname( __FILE__ ) . '/functions.php' );
55
 
56
  // Initialize legacy code
57
+ require_once( dirname( __FILE__ ) . '/../vendor/toolset/types/wpcf.php' );
58
 
59
  // Public API
60
  require_once( dirname( __FILE__ ) . '/controllers/main.php' );
application/controllers/ajax.php CHANGED
@@ -238,7 +238,7 @@ final class Types_Ajax {
238
 
239
 
240
  /**
241
- * Handles all initialization of except AJAX callbacks itself that is needed when
242
  * we're DOING_AJAX.
243
  *
244
  * Since this is executed on every AJAX call, make sure it's as lightweight as possible.
@@ -252,6 +252,11 @@ final class Types_Ajax {
252
  add_action( 'create_term', array( $this, 'prepare_for_term_creation' ) );
253
 
254
  add_action( 'updated_user_meta', array( $this, 'capture_columnshidden_update' ), 10, 4 );
 
 
 
 
 
255
  }
256
 
257
 
@@ -319,4 +324,17 @@ final class Types_Ajax {
319
  }
320
  }
321
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  }
238
 
239
 
240
  /**
241
+ * Handles all initialization of everything except AJAX callbacks itself that is needed when
242
  * we're DOING_AJAX.
243
  *
244
  * Since this is executed on every AJAX call, make sure it's as lightweight as possible.
252
  add_action( 'create_term', array( $this, 'prepare_for_term_creation' ) );
253
 
254
  add_action( 'updated_user_meta', array( $this, 'capture_columnshidden_update' ), 10, 4 );
255
+
256
+ // Handle partially refactored AJAX callbacks coming from wpcf_ajax_embedded()
257
+ // or from wpcf_ajax(). The wp_ajax_wpcf_ajax action will be reached only if the $fallthrough variables
258
+ // in those functions are set to true (which means that the call was not handled).
259
+ add_action( 'wp_ajax_wpcf_ajax', array( $this, 'do_legacy_wpcf_ajax' ) );
260
  }
261
 
262
 
324
  }
325
  }
326
  }
327
+
328
+
329
+ /**
330
+ * This offers a possibility to handle legacy AJAX wp_ajax_wpcf_ajax calls
331
+ * if they're not handled in the legacy code anymore.
332
+ *
333
+ * Note that the method needs to always finish with die() to keep consistency with the legacy code.
334
+ *
335
+ * @since 2.2.16
336
+ */
337
+ public function do_legacy_wpcf_ajax() {
338
+ die();
339
+ }
340
  }
application/controllers/asset/manager.php CHANGED
@@ -40,13 +40,6 @@ final class Types_Asset_Manager extends Toolset_Assets_Manager {
40
 
41
  protected function __initialize_scripts() {
42
 
43
- $this->register_script(
44
- self::SCRIPT_KNOCKOUT,
45
- TYPES_RELPATH . '/library/knockout/' . $this->choose_script_version( 'knockout-3.4.0.js', 'knockout-3.4.0.debug.js' ),
46
- array(),
47
- '3.4.0'
48
- );
49
-
50
  $this->register_script(
51
  self::SCRIPT_ADJUST_MENU_LINK,
52
  TYPES_RELPATH . '/public/page/adjust_submenu_links.js',
40
 
41
  protected function __initialize_scripts() {
42
 
 
 
 
 
 
 
 
43
  $this->register_script(
44
  self::SCRIPT_ADJUST_MENU_LINK,
45
  TYPES_RELPATH . '/public/page/adjust_submenu_links.js',
application/controllers/interop/handler/the7.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The7 theme interoperability handler.
4
+ *
5
+ * @since 2.2.16
6
+ */
7
+ class Types_Interop_Handler_The7 implements Types_Interop_Handler_Interface {
8
+
9
+ private static $instance;
10
+
11
+ public static function initialize() {
12
+
13
+ if ( null === self::$instance ) {
14
+ self::$instance = new self();
15
+ self::$instance->add_hooks();
16
+ }
17
+
18
+ // Not giving away the instance on purpose.
19
+
20
+ }
21
+
22
+
23
+ public function add_hooks() {
24
+ add_action( 'types_leagacy_editor_callback_init', array( $this, 'remove_presscore_hooks' ) );
25
+ }
26
+
27
+
28
+ /**
29
+ * Fix a compatibility issue caused, simply put, by our own terrible mess.
30
+ *
31
+ * The issue was visible when activating The7 theme, Views and Types, on the Edit Content Template page (for example).
32
+ * When trying to add a Types field, the AJAX call to render the dialog markup has failed due to a fatal error.
33
+ * The result was that the dialog never opened and the user was left with the grey overlay and no error message.
34
+ *
35
+ * The fatal error was caused by the fact that the legacy code in Types, namely in embedded/includes/ajax/admin-header.php
36
+ * is running do_action( 'admin_enqueue_scripts', $hook_suffix ); outside of the context of the
37
+ * standard admin page loading mechanism.
38
+ *
39
+ * That causes problems in The7_Demo_Content_Admin::enqueue_scripts() (which is hooked even during this
40
+ * non-standard procedure): It accesses the global $tgmpa variable which is assumed to be
41
+ * an instance of The7_TGMPA but for some reason, it's never initialized.
42
+ *
43
+ * The workaround is also extremely ugly: We intercept the AJAX call that renders the dialog markup and check
44
+ * all admin_enqueue_scripts hooks. If its callback has a prefix coming from The7 theme, we'll remove the action.
45
+ *
46
+ * It is safe because we're targeting only a very specific scenario, where the theme's admin assets
47
+ * aren't needed at all.
48
+ *
49
+ * This can be removed when the dialog is finally refactored into something sensible.
50
+ *
51
+ * @since 2.2.16
52
+ */
53
+ public function remove_presscore_hooks() {
54
+ if( toolset_getget( 'action' ) === 'wpcf_ajax' && toolset_getget( 'wpcf_action' ) === 'editor_callback' ) {
55
+ global $wp_filter;
56
+ /** @var WP_Hook $admin_enqueue_script_hooks */
57
+ $admin_enqueue_script_hooks = toolset_getarr( $wp_filter, 'admin_enqueue_scripts', array() );
58
+
59
+ foreach( $admin_enqueue_script_hooks->callbacks as $priority => $callbacks_for_priority ) {
60
+ foreach( $callbacks_for_priority as $callback_id => $callback ) {
61
+ $function = $callback['function'];
62
+
63
+ $the7_string_prefix = 'presscore_';
64
+ $is_the7_string_callback = ( is_string( $function ) && substr( $function, 0, strlen( $the7_string_prefix ) ) === $the7_string_prefix );
65
+
66
+ $the7_class_prefix = 'The7_';
67
+ $is_the7_class_callback = (
68
+ is_array( $function )
69
+ && count( $function ) === 2
70
+ && substr( get_class( $function[0] ), 0, strlen( $the7_class_prefix ) ) === $the7_class_prefix
71
+ );
72
+
73
+ if( $is_the7_class_callback || $is_the7_string_callback ) {
74
+ remove_action( 'admin_enqueue_scripts', $function, $priority );
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ }
application/controllers/interop/handler/wpml.php CHANGED
@@ -4,7 +4,7 @@
4
  * WPML interoperability handler.
5
  *
6
  * WIP - this stub is to be filled with everything WPML-related.
7
- * Look into: library/toolset/types/embedded/includes/wpml.php
8
  *
9
  * @since 2.2.9
10
  */
4
  * WPML interoperability handler.
5
  *
6
  * WIP - this stub is to be filled with everything WPML-related.
7
+ * Look into: vendor/toolset/types/embedded/includes/wpml.php
8
  *
9
  * @since 2.2.9
10
  */
application/controllers/interop/mediator.php CHANGED
@@ -57,6 +57,10 @@ class Types_Interop_Mediator {
57
  array(
58
  'is_needed' => array( $this, 'is_use_any_font_active' ),
59
  'class_name' => 'Use_Any_Font'
 
 
 
 
60
  )
61
  );
62
 
@@ -113,8 +117,34 @@ class Types_Interop_Mediator {
113
  }
114
 
115
 
 
 
 
 
 
116
  protected function is_use_any_font_active() {
117
  return function_exists( 'uaf_activate' );
118
  }
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  }
57
  array(
58
  'is_needed' => array( $this, 'is_use_any_font_active' ),
59
  'class_name' => 'Use_Any_Font'
60
+ ),
61
+ array(
62
+ 'is_needed' => array( $this, 'is_the7_active' ),
63
+ 'class_name' => 'The7'
64
  )
65
  );
66
 
117
  }
118
 
119
 
120
+ protected function is_the7_active() {
121
+ return ( 'the7' === $this->get_theme_slug() );
122
+ }
123
+
124
+
125
  protected function is_use_any_font_active() {
126
  return function_exists( 'uaf_activate' );
127
  }
128
 
129
+
130
+ /**
131
+ * Retrieve a "slugized" theme name.
132
+ *
133
+ * @return string
134
+ * @since 2.2.16
135
+ */
136
+ private function get_theme_slug( ){
137
+ $theme = wp_get_theme();
138
+ if( is_child_theme() ){
139
+ $theme_name = $theme->parent()->get('Name');
140
+ } else {
141
+ $theme_name = $theme->get('Name');
142
+ }
143
+
144
+ $slug = str_replace('-', '_', sanitize_title( $theme_name ) );
145
+
146
+ return $slug;
147
+ }
148
+
149
+
150
  }
application/controllers/twig_autoloader.php CHANGED
@@ -74,7 +74,7 @@ class Types_Twig_Autoloader
74
  }
75
 
76
  // Modified path to Twig in Types.
77
- $file = TYPES_ABSPATH . '/library/twig/twig/lib/' . str_replace( array( '_', "\0" ), array( '/', '' ), $class .'.php' );
78
 
79
  if( is_file( $file ) ) {
80
  /** @noinspection PhpIncludeInspection */
74
  }
75
 
76
  // Modified path to Twig in Types.
77
+ $file = TYPES_ABSPATH . '/vendor/twig/twig/lib/' . str_replace( array( '_', "\0" ), array( '/', '' ), $class .'.php' );
78
 
79
  if( is_file( $file ) ) {
80
  /** @noinspection PhpIncludeInspection */
application/controllers/upgrade.php CHANGED
@@ -179,6 +179,10 @@ class Types_Upgrade {
179
  array(
180
  'version' => 2010000,
181
  'callback' => array( $this, 'upgrade_db_to_2010000' )
 
 
 
 
182
  )
183
  );
184
 
@@ -260,5 +264,16 @@ class Types_Upgrade {
260
  $roles_manager->clean_the_mess_in_nonadmin_user_caps( $user );
261
  }
262
  }
 
 
 
 
 
 
 
 
 
 
 
263
 
264
  }
179
  array(
180
  'version' => 2010000,
181
  'callback' => array( $this, 'upgrade_db_to_2010000' )
182
+ ),
183
+ array(
184
+ 'version' => 2021600,
185
+ 'callback' => array( $this, 'upgrade_db_to_2021600' )
186
  )
187
  );
188
 
264
  $roles_manager->clean_the_mess_in_nonadmin_user_caps( $user );
265
  }
266
  }
267
+
268
+ /**
269
+ * Upgrade database to 2021600 (Types 2.2.16)
270
+ *
271
+ * Fix types-1142 for non admins with an 'admin' username.
272
+ */
273
+ function upgrade_db_to_2021600() {
274
+
275
+ $roles_manager = WPCF_Roles::getInstance();
276
+ $roles_manager->clean_the_mess_in_nonadmin_user_caps( 'admin' );
277
+ }
278
 
279
  }
application/models/helper/create/layout.php CHANGED
@@ -108,7 +108,7 @@ class Types_Helper_Create_Layout {
108
  $layout = WPDD_Layouts::create_layout( 12, 'fluid' );
109
 
110
  $parent_post_name = '';
111
- $parent_ID = apply_filters('ddl-get-default-' . WPDDL_Options::PARENTS_OPTIONS, WPDDL_Options::PARENTS_OPTIONS);
112
  if ($parent_ID) {
113
  $parent_post_name = WPDD_Layouts_Cache_Singleton::get_name_by_id($parent_ID);
114
  }
108
  $layout = WPDD_Layouts::create_layout( 12, 'fluid' );
109
 
110
  $parent_post_name = '';
111
+ $parent_ID = apply_filters('ddl-get-default-' . WPDDL_Options::PARENTS_OPTIONS, 0, WPDDL_Options::PARENTS_OPTIONS);
112
  if ($parent_ID) {
113
  $parent_post_name = WPDD_Layouts_Cache_Singleton::get_name_by_id($parent_ID);
114
  }
library/toolset/onthego-resources/onthegosystems-icons/.fontcustom-manifest.json DELETED
@@ -1,204 +0,0 @@
1
- {
2
- "checksum": {
3
- "previous": "c2bf5434fb5ba6db313a98fb4e1bf3479154a52d11ff65763e7707dfe4069a7b",
4
- "current": "c2bf5434fb5ba6db313a98fb4e1bf3479154a52d11ff65763e7707dfe4069a7b"
5
- },
6
- "fonts": [
7
- "fonts/onthegosystems-icons_c2bf5434fb5ba6db313a98fb4e1bf347.ttf",
8
- "fonts/onthegosystems-icons_c2bf5434fb5ba6db313a98fb4e1bf347.svg",
9
- "fonts/onthegosystems-icons_c2bf5434fb5ba6db313a98fb4e1bf347.woff",
10
- "fonts/onthegosystems-icons_c2bf5434fb5ba6db313a98fb4e1bf347.eot"
11
- ],
12
- "glyphs": {
13
- "access": {
14
- "codepoint": 61712,
15
- "source": "vectors/access.svg"
16
- },
17
- "access-logo": {
18
- "codepoint": 61713,
19
- "source": "vectors/access-logo.svg"
20
- },
21
- "bootstrap": {
22
- "codepoint": 61714,
23
- "source": "vectors/bootstrap.svg"
24
- },
25
- "bootstrap-logo": {
26
- "codepoint": 61715,
27
- "source": "vectors/bootstrap-logo.svg"
28
- },
29
- "bootstrap-original-logo": {
30
- "codepoint": 61750,
31
- "source": "vectors/bootstrap-original-logo.svg"
32
- },
33
- "bootstrap-pagination": {
34
- "codepoint": 61749,
35
- "source": "vectors/bootstrap-pagination.svg"
36
- },
37
- "breadcrumbs": {
38
- "codepoint": 61739,
39
- "source": "vectors/breadcrumbs.svg"
40
- },
41
- "btn-dropdowns": {
42
- "codepoint": 61740,
43
- "source": "vectors/btn-dropdowns.svg"
44
- },
45
- "buttons": {
46
- "codepoint": 61741,
47
- "source": "vectors/buttons.svg"
48
- },
49
- "conditional-alert": {
50
- "codepoint": 61730,
51
- "source": "vectors/conditional-alert.svg"
52
- },
53
- "conditional-if": {
54
- "codepoint": 61731,
55
- "source": "vectors/conditional-if.svg"
56
- },
57
- "cred": {
58
- "codepoint": 61716,
59
- "source": "vectors/cred.svg"
60
- },
61
- "cred-logo": {
62
- "codepoint": 61717,
63
- "source": "vectors/cred-logo.svg"
64
- },
65
- "input-groups": {
66
- "codepoint": 61742,
67
- "source": "vectors/input-groups.svg"
68
- },
69
- "labels": {
70
- "codepoint": 61743,
71
- "source": "vectors/labels.svg"
72
- },
73
- "layouts": {
74
- "codepoint": 61718,
75
- "source": "vectors/layouts.svg"
76
- },
77
- "layouts-genesis": {
78
- "codepoint": 61737,
79
- "source": "vectors/layouts-genesis.svg"
80
- },
81
- "layouts-genesis-logo": {
82
- "codepoint": 61735,
83
- "source": "vectors/layouts-genesis-logo.svg"
84
- },
85
- "layouts-logo": {
86
- "codepoint": 61719,
87
- "source": "vectors/layouts-logo.svg"
88
- },
89
- "list-group": {
90
- "codepoint": 61744,
91
- "source": "vectors/list-group.svg"
92
- },
93
- "module": {
94
- "codepoint": 61720,
95
- "source": "vectors/module.svg"
96
- },
97
- "module-logo": {
98
- "codepoint": 61721,
99
- "source": "vectors/module-logo.svg"
100
- },
101
- "navbar": {
102
- "codepoint": 61745,
103
- "source": "vectors/navbar.svg"
104
- },
105
- "packager": {
106
- "codepoint": 61728,
107
- "source": "vectors/packager.svg"
108
- },
109
- "packager-logo": {
110
- "codepoint": 61729,
111
- "source": "vectors/packager-logo.svg"
112
- },
113
- "panels": {
114
- "codepoint": 61747,
115
- "source": "vectors/panels.svg"
116
- },
117
- "toolset": {
118
- "codepoint": 61738,
119
- "source": "vectors/toolset.svg"
120
- },
121
- "toolset-export": {
122
- "codepoint": 61751,
123
- "source": "vectors/toolset-export.svg"
124
- },
125
- "toolset-genesis-logo": {
126
- "codepoint": 61736,
127
- "source": "vectors/toolset-genesis-logo.svg"
128
- },
129
- "toolset-logo": {
130
- "codepoint": 61722,
131
- "source": "vectors/toolset-logo.svg"
132
- },
133
- "toolset-map": {
134
- "codepoint": 61733,
135
- "source": "vectors/toolset-map.svg"
136
- },
137
- "toolset-map-logo": {
138
- "codepoint": 61732,
139
- "source": "vectors/toolset-map-logo.svg"
140
- },
141
- "types": {
142
- "codepoint": 61723,
143
- "source": "vectors/types.svg"
144
- },
145
- "types-logo": {
146
- "codepoint": 61724,
147
- "source": "vectors/types-logo.svg"
148
- },
149
- "views": {
150
- "codepoint": 61725,
151
- "source": "vectors/views.svg"
152
- },
153
- "views-logo": {
154
- "codepoint": 61726,
155
- "source": "vectors/views-logo.svg"
156
- },
157
- "wells": {
158
- "codepoint": 61748,
159
- "source": "vectors/wells.svg"
160
- },
161
- "wpml-logo": {
162
- "codepoint": 61727,
163
- "source": "vectors/wpml-logo.svg"
164
- }
165
- },
166
- "options": {
167
- "autowidth": false,
168
- "config": "fontcustom.yml",
169
- "css_selector": ".icon-{{glyph}}",
170
- "debug": false,
171
- "file_hash": false,
172
- "font_ascent": 448,
173
- "font_descent": 64,
174
- "font_design_size": 16,
175
- "font_em": 512,
176
- "font_name": "onthegosystems-icons",
177
- "force": false,
178
- "input": {
179
- "templates": "vectors",
180
- "vectors": "vectors"
181
- },
182
- "no_hash": false,
183
- "output": {
184
- "css": "css",
185
- "fonts": "fonts",
186
- "preview": "fonts"
187
- },
188
- "preprocessor_path": null,
189
- "project_root": {
190
- "pwd": null
191
- },
192
- "quiet": false,
193
- "templates": [
194
- "css",
195
- "scss",
196
- "preview"
197
- ]
198
- },
199
- "templates": [
200
- "css/onthegosystems-icons.css",
201
- "css/_onthegosystems-icons.scss",
202
- "fonts/onthegosystems-icons-preview.html"
203
- ]
204
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/res/lib/knockout/knockout-3.4.0.debug.js DELETED
@@ -1,5871 +0,0 @@
1
- /*!
2
- * Knockout JavaScript library v3.4.0
3
- * (c) Steven Sanderson - http://knockoutjs.com/
4
- * License: MIT (http://www.opensource.org/licenses/mit-license.php)
5
- */
6
-
7
- (function(){
8
- var DEBUG=true;
9
- (function(undefined){
10
- // (0, eval)('this') is a robust way of getting a reference to the global object
11
- // For details, see http://stackoverflow.com/questions/14119988/return-this-0-evalthis/14120023#14120023
12
- var window = this || (0, eval)('this'),
13
- document = window['document'],
14
- navigator = window['navigator'],
15
- jQueryInstance = window["jQuery"],
16
- JSON = window["JSON"];
17
- (function(factory) {
18
- // Support three module loading scenarios
19
- if (typeof define === 'function' && define['amd']) {
20
- // [1] AMD anonymous module
21
- define(['exports', 'require'], factory);
22
- } else if (typeof exports === 'object' && typeof module === 'object') {
23
- // [2] CommonJS/Node.js
24
- factory(module['exports'] || exports); // module.exports is for Node.js
25
- } else {
26
- // [3] No module loader (plain <script> tag) - put directly in global namespace
27
- factory(window['ko'] = {});
28
- }
29
- }(function(koExports, amdRequire){
30
- // Internally, all KO objects are attached to koExports (even the non-exported ones whose names will be minified by the closure compiler).
31
- // In the future, the following "ko" variable may be made distinct from "koExports" so that private objects are not externally reachable.
32
- var ko = typeof koExports !== 'undefined' ? koExports : {};
33
- // Google Closure Compiler helpers (used only to make the minified file smaller)
34
- ko.exportSymbol = function(koPath, object) {
35
- var tokens = koPath.split(".");
36
-
37
- // In the future, "ko" may become distinct from "koExports" (so that non-exported objects are not reachable)
38
- // At that point, "target" would be set to: (typeof koExports !== "undefined" ? koExports : ko)
39
- var target = ko;
40
-
41
- for (var i = 0; i < tokens.length - 1; i++)
42
- target = target[tokens[i]];
43
- target[tokens[tokens.length - 1]] = object;
44
- };
45
- ko.exportProperty = function(owner, publicName, object) {
46
- owner[publicName] = object;
47
- };
48
- ko.version = "3.4.0";
49
-
50
- ko.exportSymbol('version', ko.version);
51
- // For any options that may affect various areas of Knockout and aren't directly associated with data binding.
52
- ko.options = {
53
- 'deferUpdates': false,
54
- 'useOnlyNativeEvents': false
55
- };
56
-
57
- //ko.exportSymbol('options', ko.options); // 'options' isn't minified
58
- ko.utils = (function () {
59
- function objectForEach(obj, action) {
60
- for (var prop in obj) {
61
- if (obj.hasOwnProperty(prop)) {
62
- action(prop, obj[prop]);
63
- }
64
- }
65
- }
66
-
67
- function extend(target, source) {
68
- if (source) {
69
- for(var prop in source) {
70
- if(source.hasOwnProperty(prop)) {
71
- target[prop] = source[prop];
72
- }
73
- }
74
- }
75
- return target;
76
- }
77
-
78
- function setPrototypeOf(obj, proto) {
79
- obj.__proto__ = proto;
80
- return obj;
81
- }
82
-
83
- var canSetPrototype = ({ __proto__: [] } instanceof Array);
84
- var canUseSymbols = !DEBUG && typeof Symbol === 'function';
85
-
86
- // Represent the known event types in a compact way, then at runtime transform it into a hash with event name as key (for fast lookup)
87
- var knownEvents = {}, knownEventTypesByEventName = {};
88
- var keyEventTypeName = (navigator && /Firefox\/2/i.test(navigator.userAgent)) ? 'KeyboardEvent' : 'UIEvents';
89
- knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress'];
90
- knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave'];
91
- objectForEach(knownEvents, function(eventType, knownEventsForType) {
92
- if (knownEventsForType.length) {
93
- for (var i = 0, j = knownEventsForType.length; i < j; i++)
94
- knownEventTypesByEventName[knownEventsForType[i]] = eventType;
95
- }
96
- });
97
- var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406
98
-
99
- // Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
100
- // Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10.
101
- // Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser.
102
- // If there is a future need to detect specific versions of IE10+, we will amend this.
103
- var ieVersion = document && (function() {
104
- var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');
105
-
106
- // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
107
- while (
108
- div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
109
- iElems[0]
110
- ) {}
111
- return version > 4 ? version : undefined;
112
- }());
113
- var isIe6 = ieVersion === 6,
114
- isIe7 = ieVersion === 7;
115
-
116
- function isClickOnCheckableElement(element, eventType) {
117
- if ((ko.utils.tagNameLower(element) !== "input") || !element.type) return false;
118
- if (eventType.toLowerCase() != "click") return false;
119
- var inputType = element.type;
120
- return (inputType == "checkbox") || (inputType == "radio");
121
- }
122
-
123
- // For details on the pattern for changing node classes
124
- // see: https://github.com/knockout/knockout/issues/1597
125
- var cssClassNameRegex = /\S+/g;
126
-
127
- function toggleDomNodeCssClass(node, classNames, shouldHaveClass) {
128
- var addOrRemoveFn;
129
- if (classNames) {
130
- if (typeof node.classList === 'object') {
131
- addOrRemoveFn = node.classList[shouldHaveClass ? 'add' : 'remove'];
132
- ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
133
- addOrRemoveFn.call(node.classList, className);
134
- });
135
- } else if (typeof node.className['baseVal'] === 'string') {
136
- // SVG tag .classNames is an SVGAnimatedString instance
137
- toggleObjectClassPropertyString(node.className, 'baseVal', classNames, shouldHaveClass);
138
- } else {
139
- // node.className ought to be a string.
140
- toggleObjectClassPropertyString(node, 'className', classNames, shouldHaveClass);
141
- }
142
- }
143
- }
144
-
145
- function toggleObjectClassPropertyString(obj, prop, classNames, shouldHaveClass) {
146
- // obj/prop is either a node/'className' or a SVGAnimatedString/'baseVal'.
147
- var currentClassNames = obj[prop].match(cssClassNameRegex) || [];
148
- ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
149
- ko.utils.addOrRemoveItem(currentClassNames, className, shouldHaveClass);
150
- });
151
- obj[prop] = currentClassNames.join(" ");
152
- }
153
-
154
- return {
155
- fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
156
-
157
- arrayForEach: function (array, action) {
158
- for (var i = 0, j = array.length; i < j; i++)
159
- action(array[i], i);
160
- },
161
-
162
- arrayIndexOf: function (array, item) {
163
- if (typeof Array.prototype.indexOf == "function")
164
- return Array.prototype.indexOf.call(array, item);
165
- for (var i = 0, j = array.length; i < j; i++)
166
- if (array[i] === item)
167
- return i;
168
- return -1;
169
- },
170
-
171
- arrayFirst: function (array, predicate, predicateOwner) {
172
- for (var i = 0, j = array.length; i < j; i++)
173
- if (predicate.call(predicateOwner, array[i], i))
174
- return array[i];
175
- return null;
176
- },
177
-
178
- arrayRemoveItem: function (array, itemToRemove) {
179
- var index = ko.utils.arrayIndexOf(array, itemToRemove);
180
- if (index > 0) {
181
- array.splice(index, 1);
182
- }
183
- else if (index === 0) {
184
- array.shift();
185
- }
186
- },
187
-
188
- arrayGetDistinctValues: function (array) {
189
- array = array || [];
190
- var result = [];
191
- for (var i = 0, j = array.length; i < j; i++) {
192
- if (ko.utils.arrayIndexOf(result, array[i]) < 0)
193
- result.push(array[i]);
194
- }
195
- return result;
196
- },
197
-
198
- arrayMap: function (array, mapping) {
199
- array = array || [];
200
- var result = [];
201
- for (var i = 0, j = array.length; i < j; i++)
202
- result.push(mapping(array[i], i));
203
- return result;
204
- },
205
-
206
- arrayFilter: function (array, predicate) {
207
- array = array || [];
208
- var result = [];
209
- for (var i = 0, j = array.length; i < j; i++)
210
- if (predicate(array[i], i))
211
- result.push(array[i]);
212
- return result;
213
- },
214
-
215
- arrayPushAll: function (array, valuesToPush) {
216
- if (valuesToPush instanceof Array)
217
- array.push.apply(array, valuesToPush);
218
- else
219
- for (var i = 0, j = valuesToPush.length; i < j; i++)
220
- array.push(valuesToPush[i]);
221
- return array;
222
- },
223
-
224
- addOrRemoveItem: function(array, value, included) {
225
- var existingEntryIndex = ko.utils.arrayIndexOf(ko.utils.peekObservable(array), value);
226
- if (existingEntryIndex < 0) {
227
- if (included)
228
- array.push(value);
229
- } else {
230
- if (!included)
231
- array.splice(existingEntryIndex, 1);
232
- }
233
- },
234
-
235
- canSetPrototype: canSetPrototype,
236
-
237
- extend: extend,
238
-
239
- setPrototypeOf: setPrototypeOf,
240
-
241
- setPrototypeOfOrExtend: canSetPrototype ? setPrototypeOf : extend,
242
-
243
- objectForEach: objectForEach,
244
-
245
- objectMap: function(source, mapping) {
246
- if (!source)
247
- return source;
248
- var target = {};
249
- for (var prop in source) {
250
- if (source.hasOwnProperty(prop)) {
251
- target[prop] = mapping(source[prop], prop, source);
252
- }
253
- }
254
- return target;
255
- },
256
-
257
- emptyDomNode: function (domNode) {
258
- while (domNode.firstChild) {
259
- ko.removeNode(domNode.firstChild);
260
- }
261
- },
262
-
263
- moveCleanedNodesToContainerElement: function(nodes) {
264
- // Ensure it's a real array, as we're about to reparent the nodes and
265
- // we don't want the underlying collection to change while we're doing that.
266
- var nodesArray = ko.utils.makeArray(nodes);
267
- var templateDocument = (nodesArray[0] && nodesArray[0].ownerDocument) || document;
268
-
269
- var container = templateDocument.createElement('div');
270
- for (var i = 0, j = nodesArray.length; i < j; i++) {
271
- container.appendChild(ko.cleanNode(nodesArray[i]));
272
- }
273
- return container;
274
- },
275
-
276
- cloneNodes: function (nodesArray, shouldCleanNodes) {
277
- for (var i = 0, j = nodesArray.length, newNodesArray = []; i < j; i++) {
278
- var clonedNode = nodesArray[i].cloneNode(true);
279
- newNodesArray.push(shouldCleanNodes ? ko.cleanNode(clonedNode) : clonedNode);
280
- }
281
- return newNodesArray;
282
- },
283
-
284
- setDomNodeChildren: function (domNode, childNodes) {
285
- ko.utils.emptyDomNode(domNode);
286
- if (childNodes) {
287
- for (var i = 0, j = childNodes.length; i < j; i++)
288
- domNode.appendChild(childNodes[i]);
289
- }
290
- },
291
-
292
- replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) {
293
- var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray;
294
- if (nodesToReplaceArray.length > 0) {
295
- var insertionPoint = nodesToReplaceArray[0];
296
- var parent = insertionPoint.parentNode;
297
- for (var i = 0, j = newNodesArray.length; i < j; i++)
298
- parent.insertBefore(newNodesArray[i], insertionPoint);
299
- for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) {
300
- ko.removeNode(nodesToReplaceArray[i]);
301
- }
302
- }
303
- },
304
-
305
- fixUpContinuousNodeArray: function(continuousNodeArray, parentNode) {
306
- // Before acting on a set of nodes that were previously outputted by a template function, we have to reconcile
307
- // them against what is in the DOM right now. It may be that some of the nodes have already been removed, or that
308
- // new nodes might have been inserted in the middle, for example by a binding. Also, there may previously have been
309
- // leading comment nodes (created by rewritten string-based templates) that have since been removed during binding.
310
- // So, this function translates the old "map" output array into its best guess of the set of current DOM nodes.
311
- //
312
- // Rules:
313
- // [A] Any leading nodes that have been removed should be ignored
314
- // These most likely correspond to memoization nodes that were already removed during binding
315
- // See https://github.com/knockout/knockout/pull/440
316
- // [B] Any trailing nodes that have been remove should be ignored
317
- // This prevents the code here from adding unrelated nodes to the array while processing rule [C]
318
- // See https://github.com/knockout/knockout/pull/1903
319
- // [C] We want to output a continuous series of nodes. So, ignore any nodes that have already been removed,
320
- // and include any nodes that have been inserted among the previous collection
321
-
322
- if (continuousNodeArray.length) {
323
- // The parent node can be a virtual element; so get the real parent node
324
- parentNode = (parentNode.nodeType === 8 && parentNode.parentNode) || parentNode;
325
-
326
- // Rule [A]
327
- while (continuousNodeArray.length && continuousNodeArray[0].parentNode !== parentNode)
328
- continuousNodeArray.splice(0, 1);
329
-
330
- // Rule [B]
331
- while (continuousNodeArray.length > 1 && continuousNodeArray[continuousNodeArray.length - 1].parentNode !== parentNode)
332
- continuousNodeArray.length--;
333
-
334
- // Rule [C]
335
- if (continuousNodeArray.length > 1) {
336
- var current = continuousNodeArray[0], last = continuousNodeArray[continuousNodeArray.length - 1];
337
- // Replace with the actual new continuous node set
338
- continuousNodeArray.length = 0;
339
- while (current !== last) {
340
- continuousNodeArray.push(current);
341
- current = current.nextSibling;
342
- }
343
- continuousNodeArray.push(last);
344
- }
345
- }
346
- return continuousNodeArray;
347
- },
348
-
349
- setOptionNodeSelectionState: function (optionNode, isSelected) {
350
- // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser.
351
- if (ieVersion < 7)
352
- optionNode.setAttribute("selected", isSelected);
353
- else
354
- optionNode.selected = isSelected;
355
- },
356
-
357
- stringTrim: function (string) {
358
- return string === null || string === undefined ? '' :
359
- string.trim ?
360
- string.trim() :
361
- string.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
362
- },
363
-
364
- stringStartsWith: function (string, startsWith) {
365
- string = string || "";
366
- if (startsWith.length > string.length)
367
- return false;
368
- return string.substring(0, startsWith.length) === startsWith;
369
- },
370
-
371
- domNodeIsContainedBy: function (node, containedByNode) {
372
- if (node === containedByNode)
373
- return true;
374
- if (node.nodeType === 11)
375
- return false; // Fixes issue #1162 - can't use node.contains for document fragments on IE8
376
- if (containedByNode.contains)
377
- return containedByNode.contains(node.nodeType === 3 ? node.parentNode : node);
378
- if (containedByNode.compareDocumentPosition)
379
- return (containedByNode.compareDocumentPosition(node) & 16) == 16;
380
- while (node && node != containedByNode) {
381
- node = node.parentNode;
382
- }
383
- return !!node;
384
- },
385
-
386
- domNodeIsAttachedToDocument: function (node) {
387
- return ko.utils.domNodeIsContainedBy(node, node.ownerDocument.documentElement);
388
- },
389
-
390
- anyDomNodeIsAttachedToDocument: function(nodes) {
391
- return !!ko.utils.arrayFirst(nodes, ko.utils.domNodeIsAttachedToDocument);
392
- },
393
-
394
- tagNameLower: function(element) {
395
- // For HTML elements, tagName will always be upper case; for XHTML elements, it'll be lower case.
396
- // Possible future optimization: If we know it's an element from an XHTML document (not HTML),
397
- // we don't need to do the .toLowerCase() as it will always be lower case anyway.
398
- return element && element.tagName && element.tagName.toLowerCase();
399
- },
400
-
401
- catchFunctionErrors: function (delegate) {
402
- return ko['onError'] ? function () {
403
- try {
404
- return delegate.apply(this, arguments);
405
- } catch (e) {
406
- ko['onError'] && ko['onError'](e);
407
- throw e;
408
- }
409
- } : delegate;
410
- },
411
-
412
- setTimeout: function (handler, timeout) {
413
- return setTimeout(ko.utils.catchFunctionErrors(handler), timeout);
414
- },
415
-
416
- deferError: function (error) {
417
- setTimeout(function () {
418
- ko['onError'] && ko['onError'](error);
419
- throw error;
420
- }, 0);
421
- },
422
-
423
- registerEventHandler: function (element, eventType, handler) {
424
- var wrappedHandler = ko.utils.catchFunctionErrors(handler);
425
-
426
- var mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType];
427
- if (!ko.options['useOnlyNativeEvents'] && !mustUseAttachEvent && jQueryInstance) {
428
- jQueryInstance(element)['bind'](eventType, wrappedHandler);
429
- } else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
430
- element.addEventListener(eventType, wrappedHandler, false);
431
- else if (typeof element.attachEvent != "undefined") {
432
- var attachEventHandler = function (event) { wrappedHandler.call(element, event); },
433
- attachEventName = "on" + eventType;
434
- element.attachEvent(attachEventName, attachEventHandler);
435
-
436
- // IE does not dispose attachEvent handlers automatically (unlike with addEventListener)
437
- // so to avoid leaks, we have to remove them manually. See bug #856
438
- ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
439
- element.detachEvent(attachEventName, attachEventHandler);
440
- });
441
- } else
442
- throw new Error("Browser doesn't support addEventListener or attachEvent");
443
- },
444
-
445
- triggerEvent: function (element, eventType) {
446
- if (!(element && element.nodeType))
447
- throw new Error("element must be a DOM node when calling triggerEvent");
448
-
449
- // For click events on checkboxes and radio buttons, jQuery toggles the element checked state *after* the
450
- // event handler runs instead of *before*. (This was fixed in 1.9 for checkboxes but not for radio buttons.)
451
- // IE doesn't change the checked state when you trigger the click event using "fireEvent".
452
- // In both cases, we'll use the click method instead.
453
- var useClickWorkaround = isClickOnCheckableElement(element, eventType);
454
-
455
- if (!ko.options['useOnlyNativeEvents'] && jQueryInstance && !useClickWorkaround) {
456
- jQueryInstance(element)['trigger'](eventType);
457
- } else if (typeof document.createEvent == "function") {
458
- if (typeof element.dispatchEvent == "function") {
459
- var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents";
460
- var event = document.createEvent(eventCategory);
461
- event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
462
- element.dispatchEvent(event);
463
- }
464
- else
465
- throw new Error("The supplied element doesn't support dispatchEvent");
466
- } else if (useClickWorkaround && element.click) {
467
- element.click();
468
- } else if (typeof element.fireEvent != "undefined") {
469
- element.fireEvent("on" + eventType);
470
- } else {
471
- throw new Error("Browser doesn't support triggering events");
472
- }
473
- },
474
-
475
- unwrapObservable: function (value) {
476
- return ko.isObservable(value) ? value() : value;
477
- },
478
-
479
- peekObservable: function (value) {
480
- return ko.isObservable(value) ? value.peek() : value;
481
- },
482
-
483
- toggleDomNodeCssClass: toggleDomNodeCssClass,
484
-
485
- setTextContent: function(element, textContent) {
486
- var value = ko.utils.unwrapObservable(textContent);
487
- if ((value === null) || (value === undefined))
488
- value = "";
489
-
490
- // We need there to be exactly one child: a text node.
491
- // If there are no children, more than one, or if it's not a text node,
492
- // we'll clear everything and create a single text node.
493
- var innerTextNode = ko.virtualElements.firstChild(element);
494
- if (!innerTextNode || innerTextNode.nodeType != 3 || ko.virtualElements.nextSibling(innerTextNode)) {
495
- ko.virtualElements.setDomNodeChildren(element, [element.ownerDocument.createTextNode(value)]);
496
- } else {
497
- innerTextNode.data = value;
498
- }
499
-
500
- ko.utils.forceRefresh(element);
501
- },
502
-
503
- setElementName: function(element, name) {
504
- element.name = name;
505
-
506
- // Workaround IE 6/7 issue
507
- // - https://github.com/SteveSanderson/knockout/issues/197
508
- // - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
509
- if (ieVersion <= 7) {
510
- try {
511
- element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
512
- }
513
- catch(e) {} // For IE9 with doc mode "IE9 Standards" and browser mode "IE9 Compatibility View"
514
- }
515
- },
516
-
517
- forceRefresh: function(node) {
518
- // Workaround for an IE9 rendering bug - https://github.com/SteveSanderson/knockout/issues/209
519
- if (ieVersion >= 9) {
520
- // For text nodes and comment nodes (most likely virtual elements), we will have to refresh the container
521
- var elem = node.nodeType == 1 ? node : node.parentNode;
522
- if (elem.style)
523
- elem.style.zoom = elem.style.zoom;
524
- }
525
- },
526
-
527
- ensureSelectElementIsRenderedCorrectly: function(selectElement) {
528
- // Workaround for IE9 rendering bug - it doesn't reliably display all the text in dynamically-added select boxes unless you force it to re-render by updating the width.
529
- // (See https://github.com/SteveSanderson/knockout/issues/312, http://stackoverflow.com/questions/5908494/select-only-shows-first-char-of-selected-option)
530
- // Also fixes IE7 and IE8 bug that causes selects to be zero width if enclosed by 'if' or 'with'. (See issue #839)
531
- if (ieVersion) {
532
- var originalWidth = selectElement.style.width;
533
- selectElement.style.width = 0;
534
- selectElement.style.width = originalWidth;
535
- }
536
- },
537
-
538
- range: function (min, max) {
539
- min = ko.utils.unwrapObservable(min);
540
- max = ko.utils.unwrapObservable(max);
541
- var result = [];
542
- for (var i = min; i <= max; i++)
543
- result.push(i);
544
- return result;
545
- },
546
-
547
- makeArray: function(arrayLikeObject) {
548
- var result = [];
549
- for (var i = 0, j = arrayLikeObject.length; i < j; i++) {
550
- result.push(arrayLikeObject[i]);
551
- };
552
- return result;
553
- },
554
-
555
- createSymbolOrString: function(identifier) {
556
- return canUseSymbols ? Symbol(identifier) : identifier;
557
- },
558
-
559
- isIe6 : isIe6,
560
- isIe7 : isIe7,
561
- ieVersion : ieVersion,
562
-
563
- getFormFields: function(form, fieldName) {
564
- var fields = ko.utils.makeArray(form.getElementsByTagName("input")).concat(ko.utils.makeArray(form.getElementsByTagName("textarea")));
565
- var isMatchingField = (typeof fieldName == 'string')
566
- ? function(field) { return field.name === fieldName }
567
- : function(field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate
568
- var matches = [];
569
- for (var i = fields.length - 1; i >= 0; i--) {
570
- if (isMatchingField(fields[i]))
571
- matches.push(fields[i]);
572
- };
573
- return matches;
574
- },
575
-
576
- parseJson: function (jsonString) {
577
- if (typeof jsonString == "string") {
578
- jsonString = ko.utils.stringTrim(jsonString);
579
- if (jsonString) {
580
- if (JSON && JSON.parse) // Use native parsing where available
581
- return JSON.parse(jsonString);
582
- return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers
583
- }
584
- }
585
- return null;
586
- },
587
-
588
- stringifyJson: function (data, replacer, space) { // replacer and space are optional
589
- if (!JSON || !JSON.stringify)
590
- throw new Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
591
- return JSON.stringify(ko.utils.unwrapObservable(data), replacer, space);
592
- },
593
-
594
- postJson: function (urlOrForm, data, options) {
595
- options = options || {};
596
- var params = options['params'] || {};
597
- var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost;
598
- var url = urlOrForm;
599
-
600
- // If we were given a form, use its 'action' URL and pick out any requested field values
601
- if((typeof urlOrForm == 'object') && (ko.utils.tagNameLower(urlOrForm) === "form")) {
602
- var originalForm = urlOrForm;
603
- url = originalForm.action;
604
- for (var i = includeFields.length - 1; i >= 0; i--) {
605
- var fields = ko.utils.getFormFields(originalForm, includeFields[i]);
606
- for (var j = fields.length - 1; j >= 0; j--)
607
- params[fields[j].name] = fields[j].value;
608
- }
609
- }
610
-
611
- data = ko.utils.unwrapObservable(data);
612
- var form = document.createElement("form");
613
- form.style.display = "none";
614
- form.action = url;
615
- form.method = "post";
616
- for (var key in data) {
617
- // Since 'data' this is a model object, we include all properties including those inherited from its prototype
618
- var input = document.createElement("input");
619
- input.type = "hidden";
620
- input.name = key;
621
- input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
622
- form.appendChild(input);
623
- }
624
- objectForEach(params, function(key, value) {
625
- var input = document.createElement("input");
626
- input.type = "hidden";
627
- input.name = key;
628
- input.value = value;
629
- form.appendChild(input);
630
- });
631
- document.body.appendChild(form);
632
- options['submitter'] ? options['submitter'](form) : form.submit();
633
- setTimeout(function () { form.parentNode.removeChild(form); }, 0);
634
- }
635
- }
636
- }());
637
-
638
- ko.exportSymbol('utils', ko.utils);
639
- ko.exportSymbol('utils.arrayForEach', ko.utils.arrayForEach);
640
- ko.exportSymbol('utils.arrayFirst', ko.utils.arrayFirst);
641
- ko.exportSymbol('utils.arrayFilter', ko.utils.arrayFilter);
642
- ko.exportSymbol('utils.arrayGetDistinctValues', ko.utils.arrayGetDistinctValues);
643
- ko.exportSymbol('utils.arrayIndexOf', ko.utils.arrayIndexOf);
644
- ko.exportSymbol('utils.arrayMap', ko.utils.arrayMap);
645
- ko.exportSymbol('utils.arrayPushAll', ko.utils.arrayPushAll);
646
- ko.exportSymbol('utils.arrayRemoveItem', ko.utils.arrayRemoveItem);
647
- ko.exportSymbol('utils.extend', ko.utils.extend);
648
- ko.exportSymbol('utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost);
649
- ko.exportSymbol('utils.getFormFields', ko.utils.getFormFields);
650
- ko.exportSymbol('utils.peekObservable', ko.utils.peekObservable);
651
- ko.exportSymbol('utils.postJson', ko.utils.postJson);
652
- ko.exportSymbol('utils.parseJson', ko.utils.parseJson);
653
- ko.exportSymbol('utils.registerEventHandler', ko.utils.registerEventHandler);
654
- ko.exportSymbol('utils.stringifyJson', ko.utils.stringifyJson);
655
- ko.exportSymbol('utils.range', ko.utils.range);
656
- ko.exportSymbol('utils.toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass);
657
- ko.exportSymbol('utils.triggerEvent', ko.utils.triggerEvent);
658
- ko.exportSymbol('utils.unwrapObservable', ko.utils.unwrapObservable);
659
- ko.exportSymbol('utils.objectForEach', ko.utils.objectForEach);
660
- ko.exportSymbol('utils.addOrRemoveItem', ko.utils.addOrRemoveItem);
661
- ko.exportSymbol('utils.setTextContent', ko.utils.setTextContent);
662
- ko.exportSymbol('unwrap', ko.utils.unwrapObservable); // Convenient shorthand, because this is used so commonly
663
-
664
- if (!Function.prototype['bind']) {
665
- // Function.prototype.bind is a standard part of ECMAScript 5th Edition (December 2009, http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf)
666
- // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js
667
- Function.prototype['bind'] = function (object) {
668
- var originalFunction = this;
669
- if (arguments.length === 1) {
670
- return function () {
671
- return originalFunction.apply(object, arguments);
672
- };
673
- } else {
674
- var partialArgs = Array.prototype.slice.call(arguments, 1);
675
- return function () {
676
- var args = partialArgs.slice(0);
677
- args.push.apply(args, arguments);
678
- return originalFunction.apply(object, args);
679
- };
680
- }
681
- };
682
- }
683
-
684
- ko.utils.domData = new (function () {
685
- var uniqueId = 0;
686
- var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();
687
- var dataStore = {};
688
-
689
- function getAll(node, createIfNotFound) {
690
- var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
691
- var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null") && dataStore[dataStoreKey];
692
- if (!hasExistingDataStore) {
693
- if (!createIfNotFound)
694
- return undefined;
695
- dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;
696
- dataStore[dataStoreKey] = {};
697
- }
698
- return dataStore[dataStoreKey];
699
- }
700
-
701
- return {
702
- get: function (node, key) {
703
- var allDataForNode = getAll(node, false);
704
- return allDataForNode === undefined ? undefined : allDataForNode[key];
705
- },
706
- set: function (node, key, value) {
707
- if (value === undefined) {
708
- // Make sure we don't actually create a new domData key if we are actually deleting a value
709
- if (getAll(node, false) === undefined)
710
- return;
711
- }
712
- var allDataForNode = getAll(node, true);
713
- allDataForNode[key] = value;
714
- },
715
- clear: function (node) {
716
- var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
717
- if (dataStoreKey) {
718
- delete dataStore[dataStoreKey];
719
- node[dataStoreKeyExpandoPropertyName] = null;
720
- return true; // Exposing "did clean" flag purely so specs can infer whether things have been cleaned up as intended
721
- }
722
- return false;
723
- },
724
-
725
- nextKey: function () {
726
- return (uniqueId++) + dataStoreKeyExpandoPropertyName;
727
- }
728
- };
729
- })();
730
-
731
- ko.exportSymbol('utils.domData', ko.utils.domData);
732
- ko.exportSymbol('utils.domData.clear', ko.utils.domData.clear); // Exporting only so specs can clear up after themselves fully
733
-
734
- ko.utils.domNodeDisposal = new (function () {
735
- var domDataKey = ko.utils.domData.nextKey();
736
- var cleanableNodeTypes = { 1: true, 8: true, 9: true }; // Element, Comment, Document
737
- var cleanableNodeTypesWithDescendants = { 1: true, 9: true }; // Element, Document
738
-
739
- function getDisposeCallbacksCollection(node, createIfNotFound) {
740
- var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey);
741
- if ((allDisposeCallbacks === undefined) && createIfNotFound) {
742
- allDisposeCallbacks = [];
743
- ko.utils.domData.set(node, domDataKey, allDisposeCallbacks);
744
- }
745
- return allDisposeCallbacks;
746
- }
747
- function destroyCallbacksCollection(node) {
748
- ko.utils.domData.set(node, domDataKey, undefined);
749
- }
750
-
751
- function cleanSingleNode(node) {
752
- // Run all the dispose callbacks
753
- var callbacks = getDisposeCallbacksCollection(node, false);
754
- if (callbacks) {
755
- callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves)
756
- for (var i = 0; i < callbacks.length; i++)
757
- callbacks[i](node);
758
- }
759
-
760
- // Erase the DOM data
761
- ko.utils.domData.clear(node);
762
-
763
- // Perform cleanup needed by external libraries (currently only jQuery, but can be extended)
764
- ko.utils.domNodeDisposal["cleanExternalData"](node);
765
-
766
- // Clear any immediate-child comment nodes, as these wouldn't have been found by
767
- // node.getElementsByTagName("*") in cleanNode() (comment nodes aren't elements)
768
- if (cleanableNodeTypesWithDescendants[node.nodeType])
769
- cleanImmediateCommentTypeChildren(node);
770
- }
771
-
772
- function cleanImmediateCommentTypeChildren(nodeWithChildren) {
773
- var child, nextChild = nodeWithChildren.firstChild;
774
- while (child = nextChild) {
775
- nextChild = child.nextSibling;
776
- if (child.nodeType === 8)
777
- cleanSingleNode(child);
778
- }
779
- }
780
-
781
- return {
782
- addDisposeCallback : function(node, callback) {
783
- if (typeof callback != "function")
784
- throw new Error("Callback must be a function");
785
- getDisposeCallbacksCollection(node, true).push(callback);
786
- },
787
-
788
- removeDisposeCallback : function(node, callback) {
789
- var callbacksCollection = getDisposeCallbacksCollection(node, false);
790
- if (callbacksCollection) {
791
- ko.utils.arrayRemoveItem(callbacksCollection, callback);
792
- if (callbacksCollection.length == 0)
793
- destroyCallbacksCollection(node);
794
- }
795
- },
796
-
797
- cleanNode : function(node) {
798
- // First clean this node, where applicable
799
- if (cleanableNodeTypes[node.nodeType]) {
800
- cleanSingleNode(node);
801
-
802
- // ... then its descendants, where applicable
803
- if (cleanableNodeTypesWithDescendants[node.nodeType]) {
804
- // Clone the descendants list in case it changes during iteration
805
- var descendants = [];
806
- ko.utils.arrayPushAll(descendants, node.getElementsByTagName("*"));
807
- for (var i = 0, j = descendants.length; i < j; i++)
808
- cleanSingleNode(descendants[i]);
809
- }
810
- }
811
- return node;
812
- },
813
-
814
- removeNode : function(node) {
815
- ko.cleanNode(node);
816
- if (node.parentNode)
817
- node.parentNode.removeChild(node);
818
- },
819
-
820
- "cleanExternalData" : function (node) {
821
- // Special support for jQuery here because it's so commonly used.
822
- // Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData
823
- // so notify it to tear down any resources associated with the node & descendants here.
824
- if (jQueryInstance && (typeof jQueryInstance['cleanData'] == "function"))
825
- jQueryInstance['cleanData']([node]);
826
- }
827
- };
828
- })();
829
- ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience
830
- ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience
831
- ko.exportSymbol('cleanNode', ko.cleanNode);
832
- ko.exportSymbol('removeNode', ko.removeNode);
833
- ko.exportSymbol('utils.domNodeDisposal', ko.utils.domNodeDisposal);
834
- ko.exportSymbol('utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback);
835
- ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeDisposal.removeDisposeCallback);
836
- (function () {
837
- var none = [0, "", ""],
838
- table = [1, "<table>", "</table>"],
839
- tbody = [2, "<table><tbody>", "</tbody></table>"],
840
- tr = [3, "<table><tbody><tr>", "</tr></tbody></table>"],
841
- select = [1, "<select multiple='multiple'>", "</select>"],
842
- lookup = {
843
- 'thead': table,
844
- 'tbody': table,
845
- 'tfoot': table,
846
- 'tr': tbody,
847
- 'td': tr,
848
- 'th': tr,
849
- 'option': select,
850
- 'optgroup': select
851
- },
852
-
853
- // This is needed for old IE if you're *not* using either jQuery or innerShiv. Doesn't affect other cases.
854
- mayRequireCreateElementHack = ko.utils.ieVersion <= 8;
855
-
856
- function getWrap(tags) {
857
- var m = tags.match(/^<([a-z]+)[ >]/);
858
- return (m && lookup[m[1]]) || none;
859
- }
860
-
861
- function simpleHtmlParse(html, documentContext) {
862
- documentContext || (documentContext = document);
863
- var windowContext = documentContext['parentWindow'] || documentContext['defaultView'] || window;
864
-
865
- // Based on jQuery's "clean" function, but only accounting for table-related elements.
866
- // If you have referenced jQuery, this won't be used anyway - KO will use jQuery's "clean" function directly
867
-
868
- // Note that there's still an issue in IE < 9 whereby it will discard comment nodes that are the first child of
869
- // a descendant node. For example: "<div><!-- mycomment -->abc</div>" will get parsed as "<div>abc</div>"
870
- // This won't affect anyone who has referenced jQuery, and there's always the workaround of inserting a dummy node
871
- // (possibly a text node) in front of the comment. So, KO does not attempt to workaround this IE issue automatically at present.
872
-
873
- // Trim whitespace, otherwise indexOf won't work as expected
874
- var tags = ko.utils.stringTrim(html).toLowerCase(), div = documentContext.createElement("div"),
875
- wrap = getWrap(tags),
876
- depth = wrap[0];
877
-
878
- // Go to html and back, then peel off extra wrappers
879
- // Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness.
880
- var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";
881
- if (typeof windowContext['innerShiv'] == "function") {
882
- // Note that innerShiv is deprecated in favour of html5shiv. We should consider adding
883
- // support for html5shiv (except if no explicit support is needed, e.g., if html5shiv
884
- // somehow shims the native APIs so it just works anyway)
885
- div.appendChild(windowContext['innerShiv'](markup));
886
- } else {
887
- if (mayRequireCreateElementHack) {
888
- // The document.createElement('my-element') trick to enable custom elements in IE6-8
889
- // only works if we assign innerHTML on an element associated with that document.
890
- documentContext.appendChild(div);
891
- }
892
-
893
- div.innerHTML = markup;
894
-
895
- if (mayRequireCreateElementHack) {
896
- div.parentNode.removeChild(div);
897
- }
898
- }
899
-
900
- // Move to the right depth
901
- while (depth--)
902
- div = div.lastChild;
903
-
904
- return ko.utils.makeArray(div.lastChild.childNodes);
905
- }
906
-
907
- function jQueryHtmlParse(html, documentContext) {
908
- // jQuery's "parseHTML" function was introduced in jQuery 1.8.0 and is a documented public API.
909
- if (jQueryInstance['parseHTML']) {
910
- return jQueryInstance['parseHTML'](html, documentContext) || []; // Ensure we always return an array and never null
911
- } else {
912
- // For jQuery < 1.8.0, we fall back on the undocumented internal "clean" function.
913
- var elems = jQueryInstance['clean']([html], documentContext);
914
-
915
- // As of jQuery 1.7.1, jQuery parses the HTML by appending it to some dummy parent nodes held in an in-memory document fragment.
916
- // Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time.
917
- // Fix this by finding the top-most dummy parent element, and detaching it from its owner fragment.
918
- if (elems && elems[0]) {
919
- // Find the top-most parent element that's a direct child of a document fragment
920
- var elem = elems[0];
921
- while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */)
922
- elem = elem.parentNode;
923
- // ... then detach it
924
- if (elem.parentNode)
925
- elem.parentNode.removeChild(elem);
926
- }
927
-
928
- return elems;
929
- }
930
- }
931
-
932
- ko.utils.parseHtmlFragment = function(html, documentContext) {
933
- return jQueryInstance ?
934
- jQueryHtmlParse(html, documentContext) : // As below, benefit from jQuery's optimisations where possible
935
- simpleHtmlParse(html, documentContext); // ... otherwise, this simple logic will do in most common cases.
936
- };
937
-
938
- ko.utils.setHtml = function(node, html) {
939
- ko.utils.emptyDomNode(node);
940
-
941
- // There's no legitimate reason to display a stringified observable without unwrapping it, so we'll unwrap it
942
- html = ko.utils.unwrapObservable(html);
943
-
944
- if ((html !== null) && (html !== undefined)) {
945
- if (typeof html != 'string')
946
- html = html.toString();
947
-
948
- // jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments,
949
- // for example <tr> elements which are not normally allowed to exist on their own.
950
- // If you've referenced jQuery we'll use that rather than duplicating its code.
951
- if (jQueryInstance) {
952
- jQueryInstance(node)['html'](html);
953
- } else {
954
- // ... otherwise, use KO's own parsing logic.
955
- var parsedNodes = ko.utils.parseHtmlFragment(html, node.ownerDocument);
956
- for (var i = 0; i < parsedNodes.length; i++)
957
- node.appendChild(parsedNodes[i]);
958
- }
959
- }
960
- };
961
- })();
962
-
963
- ko.exportSymbol('utils.parseHtmlFragment', ko.utils.parseHtmlFragment);
964
- ko.exportSymbol('utils.setHtml', ko.utils.setHtml);
965
-
966
- ko.memoization = (function () {
967
- var memos = {};
968
-
969
- function randomMax8HexChars() {
970
- return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
971
- }
972
- function generateRandomId() {
973
- return randomMax8HexChars() + randomMax8HexChars();
974
- }
975
- function findMemoNodes(rootNode, appendToArray) {
976
- if (!rootNode)
977
- return;
978
- if (rootNode.nodeType == 8) {
979
- var memoId = ko.memoization.parseMemoText(rootNode.nodeValue);
980
- if (memoId != null)
981
- appendToArray.push({ domNode: rootNode, memoId: memoId });
982
- } else if (rootNode.nodeType == 1) {
983
- for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++)
984
- findMemoNodes(childNodes[i], appendToArray);
985
- }
986
- }
987
-
988
- return {
989
- memoize: function (callback) {
990
- if (typeof callback != "function")
991
- throw new Error("You can only pass a function to ko.memoization.memoize()");
992
- var memoId = generateRandomId();
993
- memos[memoId] = callback;
994
- return "<!--[ko_memo:" + memoId + "]-->";
995
- },
996
-
997
- unmemoize: function (memoId, callbackParams) {
998
- var callback = memos[memoId];
999
- if (callback === undefined)
1000
- throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized.");
1001
- try {
1002
- callback.apply(null, callbackParams || []);
1003
- return true;
1004
- }
1005
- finally { delete memos[memoId]; }
1006
- },
1007
-
1008
- unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) {
1009
- var memos = [];
1010
- findMemoNodes(domNode, memos);
1011
- for (var i = 0, j = memos.length; i < j; i++) {
1012
- var node = memos[i].domNode;
1013
- var combinedParams = [node];
1014
- if (extraCallbackParamsArray)
1015
- ko.utils.arrayPushAll(combinedParams, extraCallbackParamsArray);
1016
- ko.memoization.unmemoize(memos[i].memoId, combinedParams);
1017
- node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again
1018
- if (node.parentNode)
1019
- node.parentNode.removeChild(node); // If possible, erase it totally (not always possible - someone else might just hold a reference to it then call unmemoizeDomNodeAndDescendants again)
1020
- }
1021
- },
1022
-
1023
- parseMemoText: function (memoText) {
1024
- var match = memoText.match(/^\[ko_memo\:(.*?)\]$/);
1025
- return match ? match[1] : null;
1026
- }
1027
- };
1028
- })();
1029
-
1030
- ko.exportSymbol('memoization', ko.memoization);
1031
- ko.exportSymbol('memoization.memoize', ko.memoization.memoize);
1032
- ko.exportSymbol('memoization.unmemoize', ko.memoization.unmemoize);
1033
- ko.exportSymbol('memoization.parseMemoText', ko.memoization.parseMemoText);
1034
- ko.exportSymbol('memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants);
1035
- ko.tasks = (function () {
1036
- var scheduler,
1037
- taskQueue = [],
1038
- taskQueueLength = 0,
1039
- nextHandle = 1,
1040
- nextIndexToProcess = 0;
1041
-
1042
- if (window['MutationObserver']) {
1043
- // Chrome 27+, Firefox 14+, IE 11+, Opera 15+, Safari 6.1+
1044
- // From https://github.com/petkaantonov/bluebird * Copyright (c) 2014 Petka Antonov * License: MIT
1045
- scheduler = (function (callback) {
1046
- var div = document.createElement("div");
1047
- new MutationObserver(callback).observe(div, {attributes: true});
1048
- return function () { div.classList.toggle("foo"); };
1049
- })(scheduledProcess);
1050
- } else if (document && "onreadystatechange" in document.createElement("script")) {
1051
- // IE 6-10
1052
- // From https://github.com/YuzuJS/setImmediate * Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola * License: MIT
1053
- scheduler = function (callback) {
1054
- var script = document.createElement("script");
1055
- script.onreadystatechange = function () {
1056
- script.onreadystatechange = null;
1057
- document.documentElement.removeChild(script);
1058
- script = null;
1059
- callback();
1060
- };
1061
- document.documentElement.appendChild(script);
1062
- };
1063
- } else {
1064
- scheduler = function (callback) {
1065
- setTimeout(callback, 0);
1066
- };
1067
- }
1068
-
1069
- function processTasks() {
1070
- if (taskQueueLength) {
1071
- // Each mark represents the end of a logical group of tasks and the number of these groups is
1072
- // limited to prevent unchecked recursion.
1073
- var mark = taskQueueLength, countMarks = 0;
1074
-
1075
- // nextIndexToProcess keeps track of where we are in the queue; processTasks can be called recursively without issue
1076
- for (var task; nextIndexToProcess < taskQueueLength; ) {
1077
- if (task = taskQueue[nextIndexToProcess++]) {
1078
- if (nextIndexToProcess > mark) {
1079
- if (++countMarks >= 5000) {
1080
- nextIndexToProcess = taskQueueLength; // skip all tasks remaining in the queue since any of them could be causing the recursion
1081
- ko.utils.deferError(Error("'Too much recursion' after processing " + countMarks + " task groups."));
1082
- break;
1083
- }
1084
- mark = taskQueueLength;
1085
- }
1086
- try {
1087
- task();
1088
- } catch (ex) {
1089
- ko.utils.deferError(ex);
1090
- }
1091
- }
1092
- }
1093
- }
1094
- }
1095
-
1096
- function scheduledProcess() {
1097
- processTasks();
1098
-
1099
- // Reset the queue
1100
- nextIndexToProcess = taskQueueLength = taskQueue.length = 0;
1101
- }
1102
-
1103
- function scheduleTaskProcessing() {
1104
- ko.tasks['scheduler'](scheduledProcess);
1105
- }
1106
-
1107
- var tasks = {
1108
- 'scheduler': scheduler, // Allow overriding the scheduler
1109
-
1110
- schedule: function (func) {
1111
- if (!taskQueueLength) {
1112
- scheduleTaskProcessing();
1113
- }
1114
-
1115
- taskQueue[taskQueueLength++] = func;
1116
- return nextHandle++;
1117
- },
1118
-
1119
- cancel: function (handle) {
1120
- var index = handle - (nextHandle - taskQueueLength);
1121
- if (index >= nextIndexToProcess && index < taskQueueLength) {
1122
- taskQueue[index] = null;
1123
- }
1124
- },
1125
-
1126
- // For testing only: reset the queue and return the previous queue length
1127
- 'resetForTesting': function () {
1128
- var length = taskQueueLength - nextIndexToProcess;
1129
- nextIndexToProcess = taskQueueLength = taskQueue.length = 0;
1130
- return length;
1131
- },
1132
-
1133
- runEarly: processTasks
1134
- };
1135
-
1136
- return tasks;
1137
- })();
1138
-
1139
- ko.exportSymbol('tasks', ko.tasks);
1140
- ko.exportSymbol('tasks.schedule', ko.tasks.schedule);
1141
- //ko.exportSymbol('tasks.cancel', ko.tasks.cancel); "cancel" isn't minified
1142
- ko.exportSymbol('tasks.runEarly', ko.tasks.runEarly);
1143
- ko.extenders = {
1144
- 'throttle': function(target, timeout) {
1145
- // Throttling means two things:
1146
-
1147
- // (1) For dependent observables, we throttle *evaluations* so that, no matter how fast its dependencies
1148
- // notify updates, the target doesn't re-evaluate (and hence doesn't notify) faster than a certain rate
1149
- target['throttleEvaluation'] = timeout;
1150
-
1151
- // (2) For writable targets (observables, or writable dependent observables), we throttle *writes*
1152
- // so the target cannot change value synchronously or faster than a certain rate
1153
- var writeTimeoutInstance = null;
1154
- return ko.dependentObservable({
1155
- 'read': target,
1156
- 'write': function(value) {
1157
- clearTimeout(writeTimeoutInstance);
1158
- writeTimeoutInstance = ko.utils.setTimeout(function() {
1159
- target(value);
1160
- }, timeout);
1161
- }
1162
- });
1163
- },
1164
-
1165
- 'rateLimit': function(target, options) {
1166
- var timeout, method, limitFunction;
1167
-
1168
- if (typeof options == 'number') {
1169
- timeout = options;
1170
- } else {
1171
- timeout = options['timeout'];
1172
- method = options['method'];
1173
- }
1174
-
1175
- // rateLimit supersedes deferred updates
1176
- target._deferUpdates = false;
1177
-
1178
- limitFunction = method == 'notifyWhenChangesStop' ? debounce : throttle;
1179
- target.limit(function(callback) {
1180
- return limitFunction(callback, timeout);
1181
- });
1182
- },
1183
-
1184
- 'deferred': function(target, options) {
1185
- if (options !== true) {
1186
- throw new Error('The \'deferred\' extender only accepts the value \'true\', because it is not supported to turn deferral off once enabled.')
1187
- }
1188
-
1189
- if (!target._deferUpdates) {
1190
- target._deferUpdates = true;
1191
- target.limit(function (callback) {
1192
- var handle;
1193
- return function () {
1194
- ko.tasks.cancel(handle);
1195
- handle = ko.tasks.schedule(callback);
1196
- target['notifySubscribers'](undefined, 'dirty');
1197
- };
1198
- });
1199
- }
1200
- },
1201
-
1202
- 'notify': function(target, notifyWhen) {
1203
- target["equalityComparer"] = notifyWhen == "always" ?
1204
- null : // null equalityComparer means to always notify
1205
- valuesArePrimitiveAndEqual;
1206
- }
1207
- };
1208
-
1209
- var primitiveTypes = { 'undefined':1, 'boolean':1, 'number':1, 'string':1 };
1210
- function valuesArePrimitiveAndEqual(a, b) {
1211
- var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
1212
- return oldValueIsPrimitive ? (a === b) : false;
1213
- }
1214
-
1215
- function throttle(callback, timeout) {
1216
- var timeoutInstance;
1217
- return function () {
1218
- if (!timeoutInstance) {
1219
- timeoutInstance = ko.utils.setTimeout(function () {
1220
- timeoutInstance = undefined;
1221
- callback();
1222
- }, timeout);
1223
- }
1224
- };
1225
- }
1226
-
1227
- function debounce(callback, timeout) {
1228
- var timeoutInstance;
1229
- return function () {
1230
- clearTimeout(timeoutInstance);
1231
- timeoutInstance = ko.utils.setTimeout(callback, timeout);
1232
- };
1233
- }
1234
-
1235
- function applyExtenders(requestedExtenders) {
1236
- var target = this;
1237
- if (requestedExtenders) {
1238
- ko.utils.objectForEach(requestedExtenders, function(key, value) {
1239
- var extenderHandler = ko.extenders[key];
1240
- if (typeof extenderHandler == 'function') {
1241
- target = extenderHandler(target, value) || target;
1242
- }
1243
- });
1244
- }
1245
- return target;
1246
- }
1247
-
1248
- ko.exportSymbol('extenders', ko.extenders);
1249
-
1250
- ko.subscription = function (target, callback, disposeCallback) {
1251
- this._target = target;
1252
- this.callback = callback;
1253
- this.disposeCallback = disposeCallback;
1254
- this.isDisposed = false;
1255
- ko.exportProperty(this, 'dispose', this.dispose);
1256
- };
1257
- ko.subscription.prototype.dispose = function () {
1258
- this.isDisposed = true;
1259
- this.disposeCallback();
1260
- };
1261
-
1262
- ko.subscribable = function () {
1263
- ko.utils.setPrototypeOfOrExtend(this, ko_subscribable_fn);
1264
- ko_subscribable_fn.init(this);
1265
- }
1266
-
1267
- var defaultEvent = "change";
1268
-
1269
- // Moved out of "limit" to avoid the extra closure
1270
- function limitNotifySubscribers(value, event) {
1271
- if (!event || event === defaultEvent) {
1272
- this._limitChange(value);
1273
- } else if (event === 'beforeChange') {
1274
- this._limitBeforeChange(value);
1275
- } else {
1276
- this._origNotifySubscribers(value, event);
1277
- }
1278
- }
1279
-
1280
- var ko_subscribable_fn = {
1281
- init: function(instance) {
1282
- instance._subscriptions = {};
1283
- instance._versionNumber = 1;
1284
- },
1285
-
1286
- subscribe: function (callback, callbackTarget, event) {
1287
- var self = this;
1288
-
1289
- event = event || defaultEvent;
1290
- var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
1291
-
1292
- var subscription = new ko.subscription(self, boundCallback, function () {
1293
- ko.utils.arrayRemoveItem(self._subscriptions[event], subscription);
1294
- if (self.afterSubscriptionRemove)
1295
- self.afterSubscriptionRemove(event);
1296
- });
1297
-
1298
- if (self.beforeSubscriptionAdd)
1299
- self.beforeSubscriptionAdd(event);
1300
-
1301
- if (!self._subscriptions[event])
1302
- self._subscriptions[event] = [];
1303
- self._subscriptions[event].push(subscription);
1304
-
1305
- return subscription;
1306
- },
1307
-
1308
- "notifySubscribers": function (valueToNotify, event) {
1309
- event = event || defaultEvent;
1310
- if (event === defaultEvent) {
1311
- this.updateVersion();
1312
- }
1313
- if (this.hasSubscriptionsForEvent(event)) {
1314
- try {
1315
- ko.dependencyDetection.begin(); // Begin suppressing dependency detection (by setting the top frame to undefined)
1316
- for (var a = this._subscriptions[event].slice(0), i = 0, subscription; subscription = a[i]; ++i) {
1317
- // In case a subscription was disposed during the arrayForEach cycle, check
1318
- // for isDisposed on each subscription before invoking its callback
1319
- if (!subscription.isDisposed)
1320
- subscription.callback(valueToNotify);
1321
- }
1322
- } finally {
1323
- ko.dependencyDetection.end(); // End suppressing dependency detection
1324
- }
1325
- }
1326
- },
1327
-
1328
- getVersion: function () {
1329
- return this._versionNumber;
1330
- },
1331
-
1332
- hasChanged: function (versionToCheck) {
1333
- return this.getVersion() !== versionToCheck;
1334
- },
1335
-
1336
- updateVersion: function () {
1337
- ++this._versionNumber;
1338
- },
1339
-
1340
- limit: function(limitFunction) {
1341
- var self = this, selfIsObservable = ko.isObservable(self),
1342
- ignoreBeforeChange, previousValue, pendingValue, beforeChange = 'beforeChange';
1343
-
1344
- if (!self._origNotifySubscribers) {
1345
- self._origNotifySubscribers = self["notifySubscribers"];
1346
- self["notifySubscribers"] = limitNotifySubscribers;
1347
- }
1348
-
1349
- var finish = limitFunction(function() {
1350
- self._notificationIsPending = false;
1351
-
1352
- // If an observable provided a reference to itself, access it to get the latest value.
1353
- // This allows computed observables to delay calculating their value until needed.
1354
- if (selfIsObservable && pendingValue === self) {
1355
- pendingValue = self();
1356
- }
1357
- ignoreBeforeChange = false;
1358
- if (self.isDifferent(previousValue, pendingValue)) {
1359
- self._origNotifySubscribers(previousValue = pendingValue);
1360
- }
1361
- });
1362
-
1363
- self._limitChange = function(value) {
1364
- self._notificationIsPending = ignoreBeforeChange = true;
1365
- pendingValue = value;
1366
- finish();
1367
- };
1368
- self._limitBeforeChange = function(value) {
1369
- if (!ignoreBeforeChange) {
1370
- previousValue = value;
1371
- self._origNotifySubscribers(value, beforeChange);
1372
- }
1373
- };
1374
- },
1375
-
1376
- hasSubscriptionsForEvent: function(event) {
1377
- return this._subscriptions[event] && this._subscriptions[event].length;
1378
- },
1379
-
1380
- getSubscriptionsCount: function (event) {
1381
- if (event) {
1382
- return this._subscriptions[event] && this._subscriptions[event].length || 0;
1383
- } else {
1384
- var total = 0;
1385
- ko.utils.objectForEach(this._subscriptions, function(eventName, subscriptions) {
1386
- if (eventName !== 'dirty')
1387
- total += subscriptions.length;
1388
- });
1389
- return total;
1390
- }
1391
- },
1392
-
1393
- isDifferent: function(oldValue, newValue) {
1394
- return !this['equalityComparer'] || !this['equalityComparer'](oldValue, newValue);
1395
- },
1396
-
1397
- extend: applyExtenders
1398
- };
1399
-
1400
- ko.exportProperty(ko_subscribable_fn, 'subscribe', ko_subscribable_fn.subscribe);
1401
- ko.exportProperty(ko_subscribable_fn, 'extend', ko_subscribable_fn.extend);
1402
- ko.exportProperty(ko_subscribable_fn, 'getSubscriptionsCount', ko_subscribable_fn.getSubscriptionsCount);
1403
-
1404
- // For browsers that support proto assignment, we overwrite the prototype of each
1405
- // observable instance. Since observables are functions, we need Function.prototype
1406
- // to still be in the prototype chain.
1407
- if (ko.utils.canSetPrototype) {
1408
- ko.utils.setPrototypeOf(ko_subscribable_fn, Function.prototype);
1409
- }
1410
-
1411
- ko.subscribable['fn'] = ko_subscribable_fn;
1412
-
1413
-
1414
- ko.isSubscribable = function (instance) {
1415
- return instance != null && typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";
1416
- };
1417
-
1418
- ko.exportSymbol('subscribable', ko.subscribable);
1419
- ko.exportSymbol('isSubscribable', ko.isSubscribable);
1420
-
1421
- ko.computedContext = ko.dependencyDetection = (function () {
1422
- var outerFrames = [],
1423
- currentFrame,
1424
- lastId = 0;
1425
-
1426
- // Return a unique ID that can be assigned to an observable for dependency tracking.
1427
- // Theoretically, you could eventually overflow the number storage size, resulting
1428
- // in duplicate IDs. But in JavaScript, the largest exact integral value is 2^53
1429
- // or 9,007,199,254,740,992. If you created 1,000,000 IDs per second, it would
1430
- // take over 285 years to reach that number.
1431
- // Reference http://blog.vjeux.com/2010/javascript/javascript-max_int-number-limits.html
1432
- function getId() {
1433
- return ++lastId;
1434
- }
1435
-
1436
- function begin(options) {
1437
- outerFrames.push(currentFrame);
1438
- currentFrame = options;
1439
- }
1440
-
1441
- function end() {
1442
- currentFrame = outerFrames.pop();
1443
- }
1444
-
1445
- return {
1446
- begin: begin,
1447
-
1448
- end: end,
1449
-
1450
- registerDependency: function (subscribable) {
1451
- if (currentFrame) {
1452
- if (!ko.isSubscribable(subscribable))
1453
- throw new Error("Only subscribable things can act as dependencies");
1454
- currentFrame.callback.call(currentFrame.callbackTarget, subscribable, subscribable._id || (subscribable._id = getId()));
1455
- }
1456
- },
1457
-
1458
- ignore: function (callback, callbackTarget, callbackArgs) {
1459
- try {
1460
- begin();
1461
- return callback.apply(callbackTarget, callbackArgs || []);
1462
- } finally {
1463
- end();
1464
- }
1465
- },
1466
-
1467
- getDependenciesCount: function () {
1468
- if (currentFrame)
1469
- return currentFrame.computed.getDependenciesCount();
1470
- },
1471
-
1472
- isInitial: function() {
1473
- if (currentFrame)
1474
- return currentFrame.isInitial;
1475
- }
1476
- };
1477
- })();
1478
-
1479
- ko.exportSymbol('computedContext', ko.computedContext);
1480
- ko.exportSymbol('computedContext.getDependenciesCount', ko.computedContext.getDependenciesCount);
1481
- ko.exportSymbol('computedContext.isInitial', ko.computedContext.isInitial);
1482
-
1483
- ko.exportSymbol('ignoreDependencies', ko.ignoreDependencies = ko.dependencyDetection.ignore);
1484
- var observableLatestValue = ko.utils.createSymbolOrString('_latestValue');
1485
-
1486
- ko.observable = function (initialValue) {
1487
- function observable() {
1488
- if (arguments.length > 0) {
1489
- // Write
1490
-
1491
- // Ignore writes if the value hasn't changed
1492
- if (observable.isDifferent(observable[observableLatestValue], arguments[0])) {
1493
- observable.valueWillMutate();
1494
- observable[observableLatestValue] = arguments[0];
1495
- observable.valueHasMutated();
1496
- }
1497
- return this; // Permits chained assignments
1498
- }
1499
- else {
1500
- // Read
1501
- ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
1502
- return observable[observableLatestValue];
1503
- }
1504
- }
1505
-
1506
- observable[observableLatestValue] = initialValue;
1507
-
1508
- // Inherit from 'subscribable'
1509
- if (!ko.utils.canSetPrototype) {
1510
- // 'subscribable' won't be on the prototype chain unless we put it there directly
1511
- ko.utils.extend(observable, ko.subscribable['fn']);
1512
- }
1513
- ko.subscribable['fn'].init(observable);
1514
-
1515
- // Inherit from 'observable'
1516
- ko.utils.setPrototypeOfOrExtend(observable, observableFn);
1517
-
1518
- if (ko.options['deferUpdates']) {
1519
- ko.extenders['deferred'](observable, true);
1520
- }
1521
-
1522
- return observable;
1523
- }
1524
-
1525
- // Define prototype for observables
1526
- var observableFn = {
1527
- 'equalityComparer': valuesArePrimitiveAndEqual,
1528
- peek: function() { return this[observableLatestValue]; },
1529
- valueHasMutated: function () { this['notifySubscribers'](this[observableLatestValue]); },
1530
- valueWillMutate: function () { this['notifySubscribers'](this[observableLatestValue], 'beforeChange'); }
1531
- };
1532
-
1533
- // Note that for browsers that don't support proto assignment, the
1534
- // inheritance chain is created manually in the ko.observable constructor
1535
- if (ko.utils.canSetPrototype) {
1536
- ko.utils.setPrototypeOf(observableFn, ko.subscribable['fn']);
1537
- }
1538
-
1539
- var protoProperty = ko.observable.protoProperty = '__ko_proto__';
1540
- observableFn[protoProperty] = ko.observable;
1541
-
1542
- ko.hasPrototype = function(instance, prototype) {
1543
- if ((instance === null) || (instance === undefined) || (instance[protoProperty] === undefined)) return false;
1544
- if (instance[protoProperty] === prototype) return true;
1545
- return ko.hasPrototype(instance[protoProperty], prototype); // Walk the prototype chain
1546
- };
1547
-
1548
- ko.isObservable = function (instance) {
1549
- return ko.hasPrototype(instance, ko.observable);
1550
- }
1551
- ko.isWriteableObservable = function (instance) {
1552
- // Observable
1553
- if ((typeof instance == 'function') && instance[protoProperty] === ko.observable)
1554
- return true;
1555
- // Writeable dependent observable
1556
- if ((typeof instance == 'function') && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
1557
- return true;
1558
- // Anything else
1559
- return false;
1560
- }
1561
-
1562
- ko.exportSymbol('observable', ko.observable);
1563
- ko.exportSymbol('isObservable', ko.isObservable);
1564
- ko.exportSymbol('isWriteableObservable', ko.isWriteableObservable);
1565
- ko.exportSymbol('isWritableObservable', ko.isWriteableObservable);
1566
- ko.exportSymbol('observable.fn', observableFn);
1567
- ko.exportProperty(observableFn, 'peek', observableFn.peek);
1568
- ko.exportProperty(observableFn, 'valueHasMutated', observableFn.valueHasMutated);
1569
- ko.exportProperty(observableFn, 'valueWillMutate', observableFn.valueWillMutate);
1570
- ko.observableArray = function (initialValues) {
1571
- initialValues = initialValues || [];
1572
-
1573
- if (typeof initialValues != 'object' || !('length' in initialValues))
1574
- throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");
1575
-
1576
- var result = ko.observable(initialValues);
1577
- ko.utils.setPrototypeOfOrExtend(result, ko.observableArray['fn']);
1578
- return result.extend({'trackArrayChanges':true});
1579
- };
1580
-
1581
- ko.observableArray['fn'] = {
1582
- 'remove': function (valueOrPredicate) {
1583
- var underlyingArray = this.peek();
1584
- var removedValues = [];
1585
- var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
1586
- for (var i = 0; i < underlyingArray.length; i++) {
1587
- var value = underlyingArray[i];
1588
- if (predicate(value)) {
1589
- if (removedValues.length === 0) {
1590
- this.valueWillMutate();
1591
- }
1592
- removedValues.push(value);
1593
- underlyingArray.splice(i, 1);
1594
- i--;
1595
- }
1596
- }
1597
- if (removedValues.length) {
1598
- this.valueHasMutated();
1599
- }
1600
- return removedValues;
1601
- },
1602
-
1603
- 'removeAll': function (arrayOfValues) {
1604
- // If you passed zero args, we remove everything
1605
- if (arrayOfValues === undefined) {
1606
- var underlyingArray = this.peek();
1607
- var allValues = underlyingArray.slice(0);
1608
- this.valueWillMutate();
1609
- underlyingArray.splice(0, underlyingArray.length);
1610
- this.valueHasMutated();
1611
- return allValues;
1612
- }
1613
- // If you passed an arg, we interpret it as an array of entries to remove
1614
- if (!arrayOfValues)
1615
- return [];
1616
- return this['remove'](function (value) {
1617
- return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
1618
- });
1619
- },
1620
-
1621
- 'destroy': function (valueOrPredicate) {
1622
- var underlyingArray = this.peek();
1623
- var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
1624
- this.valueWillMutate();
1625
- for (var i = underlyingArray.length - 1; i >= 0; i--) {
1626
- var value = underlyingArray[i];
1627
- if (predicate(value))
1628
- underlyingArray[i]["_destroy"] = true;
1629
- }
1630
- this.valueHasMutated();
1631
- },
1632
-
1633
- 'destroyAll': function (arrayOfValues) {
1634
- // If you passed zero args, we destroy everything
1635
- if (arrayOfValues === undefined)
1636
- return this['destroy'](function() { return true });
1637
-
1638
- // If you passed an arg, we interpret it as an array of entries to destroy
1639
- if (!arrayOfValues)
1640
- return [];
1641
- return this['destroy'](function (value) {
1642
- return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
1643
- });
1644
- },
1645
-
1646
- 'indexOf': function (item) {
1647
- var underlyingArray = this();
1648
- return ko.utils.arrayIndexOf(underlyingArray, item);
1649
- },
1650
-
1651
- 'replace': function(oldItem, newItem) {
1652
- var index = this['indexOf'](oldItem);
1653
- if (index >= 0) {
1654
- this.valueWillMutate();
1655
- this.peek()[index] = newItem;
1656
- this.valueHasMutated();
1657
- }
1658
- }
1659
- };
1660
-
1661
- // Note that for browsers that don't support proto assignment, the
1662
- // inheritance chain is created manually in the ko.observableArray constructor
1663
- if (ko.utils.canSetPrototype) {
1664
- ko.utils.setPrototypeOf(ko.observableArray['fn'], ko.observable['fn']);
1665
- }
1666
-
1667
- // Populate ko.observableArray.fn with read/write functions from native arrays
1668
- // Important: Do not add any additional functions here that may reasonably be used to *read* data from the array
1669
- // because we'll eval them without causing subscriptions, so ko.computed output could end up getting stale
1670
- ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) {
1671
- ko.observableArray['fn'][methodName] = function () {
1672
- // Use "peek" to avoid creating a subscription in any computed that we're executing in the context of
1673
- // (for consistency with mutating regular observables)
1674
- var underlyingArray = this.peek();
1675
- this.valueWillMutate();
1676
- this.cacheDiffForKnownOperation(underlyingArray, methodName, arguments);
1677
- var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
1678
- this.valueHasMutated();
1679
- // The native sort and reverse methods return a reference to the array, but it makes more sense to return the observable array instead.
1680
- return methodCallResult === underlyingArray ? this : methodCallResult;
1681
- };
1682
- });
1683
-
1684
- // Populate ko.observableArray.fn with read-only functions from native arrays
1685
- ko.utils.arrayForEach(["slice"], function (methodName) {
1686
- ko.observableArray['fn'][methodName] = function () {
1687
- var underlyingArray = this();
1688
- return underlyingArray[methodName].apply(underlyingArray, arguments);
1689
- };
1690
- });
1691
-
1692
- ko.exportSymbol('observableArray', ko.observableArray);
1693
- var arrayChangeEventName = 'arrayChange';
1694
- ko.extenders['trackArrayChanges'] = function(target, options) {
1695
- // Use the provided options--each call to trackArrayChanges overwrites the previously set options
1696
- target.compareArrayOptions = {};
1697
- if (options && typeof options == "object") {
1698
- ko.utils.extend(target.compareArrayOptions, options);
1699
- }
1700
- target.compareArrayOptions['sparse'] = true;
1701
-
1702
- // Only modify the target observable once
1703
- if (target.cacheDiffForKnownOperation) {
1704
- return;
1705
- }
1706
- var trackingChanges = false,
1707
- cachedDiff = null,
1708
- arrayChangeSubscription,
1709
- pendingNotifications = 0,
1710
- underlyingBeforeSubscriptionAddFunction = target.beforeSubscriptionAdd,
1711
- underlyingAfterSubscriptionRemoveFunction = target.afterSubscriptionRemove;
1712
-
1713
- // Watch "subscribe" calls, and for array change events, ensure change tracking is enabled
1714
- target.beforeSubscriptionAdd = function (event) {
1715
- if (underlyingBeforeSubscriptionAddFunction)
1716
- underlyingBeforeSubscriptionAddFunction.call(target, event);
1717
- if (event === arrayChangeEventName) {
1718
- trackChanges();
1719
- }
1720
- };
1721
- // Watch "dispose" calls, and for array change events, ensure change tracking is disabled when all are disposed
1722
- target.afterSubscriptionRemove = function (event) {
1723
- if (underlyingAfterSubscriptionRemoveFunction)
1724
- underlyingAfterSubscriptionRemoveFunction.call(target, event);
1725
- if (event === arrayChangeEventName && !target.hasSubscriptionsForEvent(arrayChangeEventName)) {
1726
- arrayChangeSubscription.dispose();
1727
- trackingChanges = false;
1728
- }
1729
- };
1730
-
1731
- function trackChanges() {
1732
- // Calling 'trackChanges' multiple times is the same as calling it once
1733
- if (trackingChanges) {
1734
- return;
1735
- }
1736
-
1737
- trackingChanges = true;
1738
-
1739
- // Intercept "notifySubscribers" to track how many times it was called.
1740
- var underlyingNotifySubscribersFunction = target['notifySubscribers'];
1741
- target['notifySubscribers'] = function(valueToNotify, event) {
1742
- if (!event || event === defaultEvent) {
1743
- ++pendingNotifications;
1744
- }
1745
- return underlyingNotifySubscribersFunction.apply(this, arguments);
1746
- };
1747
-
1748
- // Each time the array changes value, capture a clone so that on the next
1749
- // change it's possible to produce a diff
1750
- var previousContents = [].concat(target.peek() || []);
1751
- cachedDiff = null;
1752
- arrayChangeSubscription = target.subscribe(function(currentContents) {
1753
- // Make a copy of the current contents and ensure it's an array
1754
- currentContents = [].concat(currentContents || []);
1755
-
1756
- // Compute the diff and issue notifications, but only if someone is listening
1757
- if (target.hasSubscriptionsForEvent(arrayChangeEventName)) {
1758
- var changes = getChanges(previousContents, currentContents);
1759
- }
1760
-
1761
- // Eliminate references to the old, removed items, so they can be GCed
1762
- previousContents = currentContents;
1763
- cachedDiff = null;
1764
- pendingNotifications = 0;
1765
-
1766
- if (changes && changes.length) {
1767
- target['notifySubscribers'](changes, arrayChangeEventName);
1768
- }
1769
- });
1770
- }
1771
-
1772
- function getChanges(previousContents, currentContents) {
1773
- // We try to re-use cached diffs.
1774
- // The scenarios where pendingNotifications > 1 are when using rate-limiting or the Deferred Updates
1775
- // plugin, which without this check would not be compatible with arrayChange notifications. Normally,
1776
- // notifications are issued immediately so we wouldn't be queueing up more than one.
1777
- if (!cachedDiff || pendingNotifications > 1) {
1778
- cachedDiff = ko.utils.compareArrays(previousContents, currentContents, target.compareArrayOptions);
1779
- }
1780
-
1781
- return cachedDiff;
1782
- }
1783
-
1784
- target.cacheDiffForKnownOperation = function(rawArray, operationName, args) {
1785
- // Only run if we're currently tracking changes for this observable array
1786
- // and there aren't any pending deferred notifications.
1787
- if (!trackingChanges || pendingNotifications) {
1788
- return;
1789
- }
1790
- var diff = [],
1791
- arrayLength = rawArray.length,
1792
- argsLength = args.length,
1793
- offset = 0;
1794
-
1795
- function pushDiff(status, value, index) {
1796
- return diff[diff.length] = { 'status': status, 'value': value, 'index': index };
1797
- }
1798
- switch (operationName) {
1799
- case 'push':
1800
- offset = arrayLength;
1801
- case 'unshift':
1802
- for (var index = 0; index < argsLength; index++) {
1803
- pushDiff('added', args[index], offset + index);
1804
- }
1805
- break;
1806
-
1807
- case 'pop':
1808
- offset = arrayLength - 1;
1809
- case 'shift':
1810
- if (arrayLength) {
1811
- pushDiff('deleted', rawArray[offset], offset);
1812
- }
1813
- break;
1814
-
1815
- case 'splice':
1816
- // Negative start index means 'from end of array'. After that we clamp to [0...arrayLength].
1817
- // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
1818
- var startIndex = Math.min(Math.max(0, args[0] < 0 ? arrayLength + args[0] : args[0]), arrayLength),
1819
- endDeleteIndex = argsLength === 1 ? arrayLength : Math.min(startIndex + (args[1] || 0), arrayLength),
1820
- endAddIndex = startIndex + argsLength - 2,
1821
- endIndex = Math.max(endDeleteIndex, endAddIndex),
1822
- additions = [], deletions = [];
1823
- for (var index = startIndex, argsIndex = 2; index < endIndex; ++index, ++argsIndex) {
1824
- if (index < endDeleteIndex)
1825
- deletions.push(pushDiff('deleted', rawArray[index], index));
1826
- if (index < endAddIndex)
1827
- additions.push(pushDiff('added', args[argsIndex], index));
1828
- }
1829
- ko.utils.findMovesInArrayComparison(deletions, additions);
1830
- break;
1831
-
1832
- default:
1833
- return;
1834
- }
1835
- cachedDiff = diff;
1836
- };
1837
- };
1838
- var computedState = ko.utils.createSymbolOrString('_state');
1839
-
1840
- ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
1841
- if (typeof evaluatorFunctionOrOptions === "object") {
1842
- // Single-parameter syntax - everything is on this "options" param
1843
- options = evaluatorFunctionOrOptions;
1844
- } else {
1845
- // Multi-parameter syntax - construct the options according to the params passed
1846
- options = options || {};
1847
- if (evaluatorFunctionOrOptions) {
1848
- options["read"] = evaluatorFunctionOrOptions;
1849
- }
1850
- }
1851
- if (typeof options["read"] != "function")
1852
- throw Error("Pass a function that returns the value of the ko.computed");
1853
-
1854
- var writeFunction = options["write"];
1855
- var state = {
1856
- latestValue: undefined,
1857
- isStale: true,
1858
- isBeingEvaluated: false,
1859
- suppressDisposalUntilDisposeWhenReturnsFalse: false,
1860
- isDisposed: false,
1861
- pure: false,
1862
- isSleeping: false,
1863
- readFunction: options["read"],
1864
- evaluatorFunctionTarget: evaluatorFunctionTarget || options["owner"],
1865
- disposeWhenNodeIsRemoved: options["disposeWhenNodeIsRemoved"] || options.disposeWhenNodeIsRemoved || null,
1866
- disposeWhen: options["disposeWhen"] || options.disposeWhen,
1867
- domNodeDisposalCallback: null,
1868
- dependencyTracking: {},
1869
- dependenciesCount: 0,
1870
- evaluationTimeoutInstance: null
1871
- };
1872
-
1873
- function computedObservable() {
1874
- if (arguments.length > 0) {
1875
- if (typeof writeFunction === "function") {
1876
- // Writing a value
1877
- writeFunction.apply(state.evaluatorFunctionTarget, arguments);
1878
- } else {
1879
- throw new Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
1880
- }
1881
- return this; // Permits chained assignments
1882
- } else {
1883
- // Reading the value
1884
- ko.dependencyDetection.registerDependency(computedObservable);
1885
- if (state.isStale || (state.isSleeping && computedObservable.haveDependenciesChanged())) {
1886
- computedObservable.evaluateImmediate();
1887
- }
1888
- return state.latestValue;
1889
- }
1890
- }
1891
-
1892
- computedObservable[computedState] = state;
1893
- computedObservable.hasWriteFunction = typeof writeFunction === "function";
1894
-
1895
- // Inherit from 'subscribable'
1896
- if (!ko.utils.canSetPrototype) {
1897
- // 'subscribable' won't be on the prototype chain unless we put it there directly
1898
- ko.utils.extend(computedObservable, ko.subscribable['fn']);
1899
- }
1900
- ko.subscribable['fn'].init(computedObservable);
1901
-
1902
- // Inherit from 'computed'
1903
- ko.utils.setPrototypeOfOrExtend(computedObservable, computedFn);
1904
-
1905
- if (options['pure']) {
1906
- state.pure = true;
1907
- state.isSleeping = true; // Starts off sleeping; will awake on the first subscription
1908
- ko.utils.extend(computedObservable, pureComputedOverrides);
1909
- } else if (options['deferEvaluation']) {
1910
- ko.utils.extend(computedObservable, deferEvaluationOverrides);
1911
- }
1912
-
1913
- if (ko.options['deferUpdates']) {
1914
- ko.extenders['deferred'](computedObservable, true);
1915
- }
1916
-
1917
- if (DEBUG) {
1918
- // #1731 - Aid debugging by exposing the computed's options
1919
- computedObservable["_options"] = options;
1920
- }
1921
-
1922
- if (state.disposeWhenNodeIsRemoved) {
1923
- // Since this computed is associated with a DOM node, and we don't want to dispose the computed
1924
- // until the DOM node is *removed* from the document (as opposed to never having been in the document),
1925
- // we'll prevent disposal until "disposeWhen" first returns false.
1926
- state.suppressDisposalUntilDisposeWhenReturnsFalse = true;
1927
-
1928
- // disposeWhenNodeIsRemoved: true can be used to opt into the "only dispose after first false result"
1929
- // behaviour even if there's no specific node to watch. In that case, clear the option so we don't try
1930
- // to watch for a non-node's disposal. This technique is intended for KO's internal use only and shouldn't
1931
- // be documented or used by application code, as it's likely to change in a future version of KO.
1932
- if (!state.disposeWhenNodeIsRemoved.nodeType) {
1933
- state.disposeWhenNodeIsRemoved = null;
1934
- }
1935
- }
1936
-
1937
- // Evaluate, unless sleeping or deferEvaluation is true
1938
- if (!state.isSleeping && !options['deferEvaluation']) {
1939
- computedObservable.evaluateImmediate();
1940
- }
1941
-
1942
- // Attach a DOM node disposal callback so that the computed will be proactively disposed as soon as the node is
1943
- // removed using ko.removeNode. But skip if isActive is false (there will never be any dependencies to dispose).
1944
- if (state.disposeWhenNodeIsRemoved && computedObservable.isActive()) {
1945
- ko.utils.domNodeDisposal.addDisposeCallback(state.disposeWhenNodeIsRemoved, state.domNodeDisposalCallback = function () {
1946
- computedObservable.dispose();
1947
- });
1948
- }
1949
-
1950
- return computedObservable;
1951
- };
1952
-
1953
- // Utility function that disposes a given dependencyTracking entry
1954
- function computedDisposeDependencyCallback(id, entryToDispose) {
1955
- if (entryToDispose !== null && entryToDispose.dispose) {
1956
- entryToDispose.dispose();
1957
- }
1958
- }
1959
-
1960
- // This function gets called each time a dependency is detected while evaluating a computed.
1961
- // It's factored out as a shared function to avoid creating unnecessary function instances during evaluation.
1962
- function computedBeginDependencyDetectionCallback(subscribable, id) {
1963
- var computedObservable = this.computedObservable,
1964
- state = computedObservable[computedState];
1965
- if (!state.isDisposed) {
1966
- if (this.disposalCount && this.disposalCandidates[id]) {
1967
- // Don't want to dispose this subscription, as it's still being used
1968
- computedObservable.addDependencyTracking(id, subscribable, this.disposalCandidates[id]);
1969
- this.disposalCandidates[id] = null; // No need to actually delete the property - disposalCandidates is a transient object anyway
1970
- --this.disposalCount;
1971
- } else if (!state.dependencyTracking[id]) {
1972
- // Brand new subscription - add it
1973
- computedObservable.addDependencyTracking(id, subscribable, state.isSleeping ? { _target: subscribable } : computedObservable.subscribeToDependency(subscribable));
1974
- }
1975
- }
1976
- }
1977
-
1978
- var computedFn = {
1979
- "equalityComparer": valuesArePrimitiveAndEqual,
1980
- getDependenciesCount: function () {
1981
- return this[computedState].dependenciesCount;
1982
- },
1983
- addDependencyTracking: function (id, target, trackingObj) {
1984
- if (this[computedState].pure && target === this) {
1985
- throw Error("A 'pure' computed must not be called recursively");
1986
- }
1987
-
1988
- this[computedState].dependencyTracking[id] = trackingObj;
1989
- trackingObj._order = this[computedState].dependenciesCount++;
1990
- trackingObj._version = target.getVersion();
1991
- },
1992
- haveDependenciesChanged: function () {
1993
- var id, dependency, dependencyTracking = this[computedState].dependencyTracking;
1994
- for (id in dependencyTracking) {
1995
- if (dependencyTracking.hasOwnProperty(id)) {
1996
- dependency = dependencyTracking[id];
1997
- if (dependency._target.hasChanged(dependency._version)) {
1998
- return true;
1999
- }
2000
- }
2001
- }
2002
- },
2003
- markDirty: function () {
2004
- // Process "dirty" events if we can handle delayed notifications
2005
- if (this._evalDelayed && !this[computedState].isBeingEvaluated) {
2006
- this._evalDelayed();
2007
- }
2008
- },
2009
- isActive: function () {
2010
- return this[computedState].isStale || this[computedState].dependenciesCount > 0;
2011
- },
2012
- respondToChange: function () {
2013
- // Ignore "change" events if we've already scheduled a delayed notification
2014
- if (!this._notificationIsPending) {
2015
- this.evaluatePossiblyAsync();
2016
- }
2017
- },
2018
- subscribeToDependency: function (target) {
2019
- if (target._deferUpdates && !this[computedState].disposeWhenNodeIsRemoved) {
2020
- var dirtySub = target.subscribe(this.markDirty, this, 'dirty'),
2021
- changeSub = target.subscribe(this.respondToChange, this);
2022
- return {
2023
- _target: target,
2024
- dispose: function () {
2025
- dirtySub.dispose();
2026
- changeSub.dispose();
2027
- }
2028
- };
2029
- } else {
2030
- return target.subscribe(this.evaluatePossiblyAsync, this);
2031
- }
2032
- },
2033
- evaluatePossiblyAsync: function () {
2034
- var computedObservable = this,
2035
- throttleEvaluationTimeout = computedObservable['throttleEvaluation'];
2036
- if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
2037
- clearTimeout(this[computedState].evaluationTimeoutInstance);
2038
- this[computedState].evaluationTimeoutInstance = ko.utils.setTimeout(function () {
2039
- computedObservable.evaluateImmediate(true /*notifyChange*/);
2040
- }, throttleEvaluationTimeout);
2041
- } else if (computedObservable._evalDelayed) {
2042
- computedObservable._evalDelayed();
2043
- } else {
2044
- computedObservable.evaluateImmediate(true /*notifyChange*/);
2045
- }
2046
- },
2047
- evaluateImmediate: function (notifyChange) {
2048
- var computedObservable = this,
2049
- state = computedObservable[computedState],
2050
- disposeWhen = state.disposeWhen;
2051
-
2052
- if (state.isBeingEvaluated) {
2053
- // If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation.
2054
- // This is not desirable (it's hard for a developer to realise a chain of dependencies might cause this, and they almost
2055
- // certainly didn't intend infinite re-evaluations). So, for predictability, we simply prevent ko.computeds from causing
2056
- // their own re-evaluation. Further discussion at https://github.com/SteveSanderson/knockout/pull/387
2057
- return;
2058
- }
2059
-
2060
- // Do not evaluate (and possibly capture new dependencies) if disposed
2061
- if (state.isDisposed) {
2062
- return;
2063
- }
2064
-
2065
- if (state.disposeWhenNodeIsRemoved && !ko.utils.domNodeIsAttachedToDocument(state.disposeWhenNodeIsRemoved) || disposeWhen && disposeWhen()) {
2066
- // See comment above about suppressDisposalUntilDisposeWhenReturnsFalse
2067
- if (!state.suppressDisposalUntilDisposeWhenReturnsFalse) {
2068
- computedObservable.dispose();
2069
- return;
2070
- }
2071
- } else {
2072
- // It just did return false, so we can stop suppressing now
2073
- state.suppressDisposalUntilDisposeWhenReturnsFalse = false;
2074
- }
2075
-
2076
- state.isBeingEvaluated = true;
2077
- try {
2078
- this.evaluateImmediate_CallReadWithDependencyDetection(notifyChange);
2079
- } finally {
2080
- state.isBeingEvaluated = false;
2081
- }
2082
-
2083
- if (!state.dependenciesCount) {
2084
- computedObservable.dispose();
2085
- }
2086
- },
2087
- evaluateImmediate_CallReadWithDependencyDetection: function (notifyChange) {
2088
- // This function is really just part of the evaluateImmediate logic. You would never call it from anywhere else.
2089
- // Factoring it out into a separate function means it can be independent of the try/catch block in evaluateImmediate,
2090
- // which contributes to saving about 40% off the CPU overhead of computed evaluation (on V8 at least).
2091
-
2092
- var computedObservable = this,
2093
- state = computedObservable[computedState];
2094
-
2095
- // Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
2096
- // Then, during evaluation, we cross off any that are in fact still being used.
2097
- var isInitial = state.pure ? undefined : !state.dependenciesCount, // If we're evaluating when there are no previous dependencies, it must be the first time
2098
- dependencyDetectionContext = {
2099
- computedObservable: computedObservable,
2100
- disposalCandidates: state.dependencyTracking,
2101
- disposalCount: state.dependenciesCount
2102
- };
2103
-
2104
- ko.dependencyDetection.begin({
2105
- callbackTarget: dependencyDetectionContext,
2106
- callback: computedBeginDependencyDetectionCallback,
2107
- computed: computedObservable,
2108
- isInitial: isInitial
2109
- });
2110
-
2111
- state.dependencyTracking = {};
2112
- state.dependenciesCount = 0;
2113
-
2114
- var newValue = this.evaluateImmediate_CallReadThenEndDependencyDetection(state, dependencyDetectionContext);
2115
-
2116
- if (computedObservable.isDifferent(state.latestValue, newValue)) {
2117
- if (!state.isSleeping) {
2118
- computedObservable["notifySubscribers"](state.latestValue, "beforeChange");
2119
- }
2120
-
2121
- state.latestValue = newValue;
2122
-
2123
- if (state.isSleeping) {
2124
- computedObservable.updateVersion();
2125
- } else if (notifyChange) {
2126
- computedObservable["notifySubscribers"](state.latestValue);
2127
- }
2128
- }
2129
-
2130
- if (isInitial) {
2131
- computedObservable["notifySubscribers"](state.latestValue, "awake");
2132
- }
2133
- },
2134
- evaluateImmediate_CallReadThenEndDependencyDetection: function (state, dependencyDetectionContext) {
2135
- // This function is really part of the evaluateImmediate_CallReadWithDependencyDetection logic.
2136
- // You'd never call it from anywhere else. Factoring it out means that evaluateImmediate_CallReadWithDependencyDetection
2137
- // can be independent of try/finally blocks, which contributes to saving about 40% off the CPU
2138
- // overhead of computed evaluation (on V8 at least).
2139
-
2140
- try {
2141
- var readFunction = state.readFunction;
2142
- return state.evaluatorFunctionTarget ? readFunction.call(state.evaluatorFunctionTarget) : readFunction();
2143
- } finally {
2144
- ko.dependencyDetection.end();
2145
-
2146
- // For each subscription no longer being used, remove it from the active subscriptions list and dispose it
2147
- if (dependencyDetectionContext.disposalCount && !state.isSleeping) {
2148
- ko.utils.objectForEach(dependencyDetectionContext.disposalCandidates, computedDisposeDependencyCallback);
2149
- }
2150
-
2151
- state.isStale = false;
2152
- }
2153
- },
2154
- peek: function () {
2155
- // Peek won't re-evaluate, except while the computed is sleeping or to get the initial value when "deferEvaluation" is set.
2156
- var state = this[computedState];
2157
- if ((state.isStale && !state.dependenciesCount) || (state.isSleeping && this.haveDependenciesChanged())) {
2158
- this.evaluateImmediate();
2159
- }
2160
- return state.latestValue;
2161
- },
2162
- limit: function (limitFunction) {
2163
- // Override the limit function with one that delays evaluation as well
2164
- ko.subscribable['fn'].limit.call(this, limitFunction);
2165
- this._evalDelayed = function () {
2166
- this._limitBeforeChange(this[computedState].latestValue);
2167
-
2168
- this[computedState].isStale = true; // Mark as dirty
2169
-
2170
- // Pass the observable to the "limit" code, which will access it when
2171
- // it's time to do the notification.
2172
- this._limitChange(this);
2173
- }
2174
- },
2175
- dispose: function () {
2176
- var state = this[computedState];
2177
- if (!state.isSleeping && state.dependencyTracking) {
2178
- ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) {
2179
- if (dependency.dispose)
2180
- dependency.dispose();
2181
- });
2182
- }
2183
- if (state.disposeWhenNodeIsRemoved && state.domNodeDisposalCallback) {
2184
- ko.utils.domNodeDisposal.removeDisposeCallback(state.disposeWhenNodeIsRemoved, state.domNodeDisposalCallback);
2185
- }
2186
- state.dependencyTracking = null;
2187
- state.dependenciesCount = 0;
2188
- state.isDisposed = true;
2189
- state.isStale = false;
2190
- state.isSleeping = false;
2191
- state.disposeWhenNodeIsRemoved = null;
2192
- }
2193
- };
2194
-
2195
- var pureComputedOverrides = {
2196
- beforeSubscriptionAdd: function (event) {
2197
- // If asleep, wake up the computed by subscribing to any dependencies.
2198
- var computedObservable = this,
2199
- state = computedObservable[computedState];
2200
- if (!state.isDisposed && state.isSleeping && event == 'change') {
2201
- state.isSleeping = false;
2202
- if (state.isStale || computedObservable.haveDependenciesChanged()) {
2203
- state.dependencyTracking = null;
2204
- state.dependenciesCount = 0;
2205
- state.isStale = true;
2206
- computedObservable.evaluateImmediate();
2207
- } else {
2208
- // First put the dependencies in order
2209
- var dependeciesOrder = [];
2210
- ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) {
2211
- dependeciesOrder[dependency._order] = id;
2212
- });
2213
- // Next, subscribe to each one
2214
- ko.utils.arrayForEach(dependeciesOrder, function (id, order) {
2215
- var dependency = state.dependencyTracking[id],
2216
- subscription = computedObservable.subscribeToDependency(dependency._target);
2217
- subscription._order = order;
2218
- subscription._version = dependency._version;
2219
- state.dependencyTracking[id] = subscription;
2220
- });
2221
- }
2222
- if (!state.isDisposed) { // test since evaluating could trigger disposal
2223
- computedObservable["notifySubscribers"](state.latestValue, "awake");
2224
- }
2225
- }
2226
- },
2227
- afterSubscriptionRemove: function (event) {
2228
- var state = this[computedState];
2229
- if (!state.isDisposed && event == 'change' && !this.hasSubscriptionsForEvent('change')) {
2230
- ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) {
2231
- if (dependency.dispose) {
2232
- state.dependencyTracking[id] = {
2233
- _target: dependency._target,
2234
- _order: dependency._order,
2235
- _version: dependency._version
2236
- };
2237
- dependency.dispose();
2238
- }
2239
- });
2240
- state.isSleeping = true;
2241
- this["notifySubscribers"](undefined, "asleep");
2242
- }
2243
- },
2244
- getVersion: function () {
2245
- // Because a pure computed is not automatically updated while it is sleeping, we can't
2246
- // simply return the version number. Instead, we check if any of the dependencies have
2247
- // changed and conditionally re-evaluate the computed observable.
2248
- var state = this[computedState];
2249
- if (state.isSleeping && (state.isStale || this.haveDependenciesChanged())) {
2250
- this.evaluateImmediate();
2251
- }
2252
- return ko.subscribable['fn'].getVersion.call(this);
2253
- }
2254
- };
2255
-
2256
- var deferEvaluationOverrides = {
2257
- beforeSubscriptionAdd: function (event) {
2258
- // This will force a computed with deferEvaluation to evaluate when the first subscription is registered.
2259
- if (event == 'change' || event == 'beforeChange') {
2260
- this.peek();
2261
- }
2262
- }
2263
- };
2264
-
2265
- // Note that for browsers that don't support proto assignment, the
2266
- // inheritance chain is created manually in the ko.computed constructor
2267
- if (ko.utils.canSetPrototype) {
2268
- ko.utils.setPrototypeOf(computedFn, ko.subscribable['fn']);
2269
- }
2270
-
2271
- // Set the proto chain values for ko.hasPrototype
2272
- var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
2273
- ko.computed[protoProp] = ko.observable;
2274
- computedFn[protoProp] = ko.computed;
2275
-
2276
- ko.isComputed = function (instance) {
2277
- return ko.hasPrototype(instance, ko.computed);
2278
- };
2279
-
2280
- ko.isPureComputed = function (instance) {
2281
- return ko.hasPrototype(instance, ko.computed)
2282
- && instance[computedState] && instance[computedState].pure;
2283
- };
2284
-
2285
- ko.exportSymbol('computed', ko.computed);
2286
- ko.exportSymbol('dependentObservable', ko.computed); // export ko.dependentObservable for backwards compatibility (1.x)
2287
- ko.exportSymbol('isComputed', ko.isComputed);
2288
- ko.exportSymbol('isPureComputed', ko.isPureComputed);
2289
- ko.exportSymbol('computed.fn', computedFn);
2290
- ko.exportProperty(computedFn, 'peek', computedFn.peek);
2291
- ko.exportProperty(computedFn, 'dispose', computedFn.dispose);
2292
- ko.exportProperty(computedFn, 'isActive', computedFn.isActive);
2293
- ko.exportProperty(computedFn, 'getDependenciesCount', computedFn.getDependenciesCount);
2294
-
2295
- ko.pureComputed = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget) {
2296
- if (typeof evaluatorFunctionOrOptions === 'function') {
2297
- return ko.computed(evaluatorFunctionOrOptions, evaluatorFunctionTarget, {'pure':true});
2298
- } else {
2299
- evaluatorFunctionOrOptions = ko.utils.extend({}, evaluatorFunctionOrOptions); // make a copy of the parameter object
2300
- evaluatorFunctionOrOptions['pure'] = true;
2301
- return ko.computed(evaluatorFunctionOrOptions, evaluatorFunctionTarget);
2302
- }
2303
- }
2304
- ko.exportSymbol('pureComputed', ko.pureComputed);
2305
-
2306
- (function() {
2307
- var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle)
2308
-
2309
- ko.toJS = function(rootObject) {
2310
- if (arguments.length == 0)
2311
- throw new Error("When calling ko.toJS, pass the object you want to convert.");
2312
-
2313
- // We just unwrap everything at every level in the object graph
2314
- return mapJsObjectGraph(rootObject, function(valueToMap) {
2315
- // Loop because an observable's value might in turn be another observable wrapper
2316
- for (var i = 0; ko.isObservable(valueToMap) && (i < maxNestedObservableDepth); i++)
2317
- valueToMap = valueToMap();
2318
- return valueToMap;
2319
- });
2320
- };
2321
-
2322
- ko.toJSON = function(rootObject, replacer, space) { // replacer and space are optional
2323
- var plainJavaScriptObject = ko.toJS(rootObject);
2324
- return ko.utils.stringifyJson(plainJavaScriptObject, replacer, space);
2325
- };
2326
-
2327
- function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) {
2328
- visitedObjects = visitedObjects || new objectLookup();
2329
-
2330
- rootObject = mapInputCallback(rootObject);
2331
- var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof RegExp)) && (!(rootObject instanceof Date)) && (!(rootObject instanceof String)) && (!(rootObject instanceof Number)) && (!(rootObject instanceof Boolean));
2332
- if (!canHaveProperties)
2333
- return rootObject;
2334
-
2335
- var outputProperties = rootObject instanceof Array ? [] : {};
2336
- visitedObjects.save(rootObject, outputProperties);
2337
-
2338
- visitPropertiesOrArrayEntries(rootObject, function(indexer) {
2339
- var propertyValue = mapInputCallback(rootObject[indexer]);
2340
-
2341
- switch (typeof propertyValue) {
2342
- case "boolean":
2343
- case "number":
2344
- case "string":
2345
- case "function":
2346
- outputProperties[indexer] = propertyValue;
2347
- break;
2348
- case "object":
2349
- case "undefined":
2350
- var previouslyMappedValue = visitedObjects.get(propertyValue);
2351
- outputProperties[indexer] = (previouslyMappedValue !== undefined)
2352
- ? previouslyMappedValue
2353
- : mapJsObjectGraph(propertyValue, mapInputCallback, visitedObjects);
2354
- break;
2355
- }
2356
- });
2357
-
2358
- return outputProperties;
2359
- }
2360
-
2361
- function visitPropertiesOrArrayEntries(rootObject, visitorCallback) {
2362
- if (rootObject instanceof Array) {
2363
- for (var i = 0; i < rootObject.length; i++)
2364
- visitorCallback(i);
2365
-
2366
- // For arrays, also respect toJSON property for custom mappings (fixes #278)
2367
- if (typeof rootObject['toJSON'] == 'function')
2368
- visitorCallback('toJSON');
2369
- } else {
2370
- for (var propertyName in rootObject) {
2371
- visitorCallback(propertyName);
2372
- }
2373
- }
2374
- };
2375
-
2376
- function objectLookup() {
2377
- this.keys = [];
2378
- this.values = [];
2379
- };
2380
-
2381
- objectLookup.prototype = {
2382
- constructor: objectLookup,
2383
- save: function(key, value) {
2384
- var existingIndex = ko.utils.arrayIndexOf(this.keys, key);
2385
- if (existingIndex >= 0)
2386
- this.values[existingIndex] = value;
2387
- else {
2388
- this.keys.push(key);
2389
- this.values.push(value);
2390
- }
2391
- },
2392
- get: function(key) {
2393
- var existingIndex = ko.utils.arrayIndexOf(this.keys, key);
2394
- return (existingIndex >= 0) ? this.values[existingIndex] : undefined;
2395
- }
2396
- };
2397
- })();
2398
-
2399
- ko.exportSymbol('toJS', ko.toJS);
2400
- ko.exportSymbol('toJSON', ko.toJSON);
2401
- (function () {
2402
- var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__';
2403
-
2404
- // Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values
2405
- // are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values
2406
- // that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns.
2407
- ko.selectExtensions = {
2408
- readValue : function(element) {
2409
- switch (ko.utils.tagNameLower(element)) {
2410
- case 'option':
2411
- if (element[hasDomDataExpandoProperty] === true)
2412
- return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
2413
- return ko.utils.ieVersion <= 7
2414
- ? (element.getAttributeNode('value') && element.getAttributeNode('value').specified ? element.value : element.text)
2415
- : element.value;
2416
- case 'select':
2417
- return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
2418
- default:
2419
- return element.value;
2420
- }
2421
- },
2422
-
2423
- writeValue: function(element, value, allowUnset) {
2424
- switch (ko.utils.tagNameLower(element)) {
2425
- case 'option':
2426
- switch(typeof value) {
2427
- case "string":
2428
- ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
2429
- if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
2430
- delete element[hasDomDataExpandoProperty];
2431
- }
2432
- element.value = value;
2433
- break;
2434
- default:
2435
- // Store arbitrary object using DomData
2436
- ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
2437
- element[hasDomDataExpandoProperty] = true;
2438
-
2439
- // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
2440
- element.value = typeof value === "number" ? value : "";
2441
- break;
2442
- }
2443
- break;
2444
- case 'select':
2445
- if (value === "" || value === null) // A blank string or null value will select the caption
2446
- value = undefined;
2447
- var selection = -1;
2448
- for (var i = 0, n = element.options.length, optionValue; i < n; ++i) {
2449
- optionValue = ko.selectExtensions.readValue(element.options[i]);
2450
- // Include special check to handle selecting a caption with a blank string value
2451
- if (optionValue == value || (optionValue == "" && value === undefined)) {
2452
- selection = i;
2453
- break;
2454
- }
2455
- }
2456
- if (allowUnset || selection >= 0 || (value === undefined && element.size > 1)) {
2457
- element.selectedIndex = selection;
2458
- }
2459
- break;
2460
- default:
2461
- if ((value === null) || (value === undefined))
2462
- value = "";
2463
- element.value = value;
2464
- break;
2465
- }
2466
- }
2467
- };
2468
- })();
2469
-
2470
- ko.exportSymbol('selectExtensions', ko.selectExtensions);
2471
- ko.exportSymbol('selectExtensions.readValue', ko.selectExtensions.readValue);
2472
- ko.exportSymbol('selectExtensions.writeValue', ko.selectExtensions.writeValue);
2473
- ko.expressionRewriting = (function () {
2474
- var javaScriptReservedWords = ["true", "false", "null", "undefined"];
2475
-
2476
- // Matches something that can be assigned to--either an isolated identifier or something ending with a property accessor
2477
- // This is designed to be simple and avoid false negatives, but could produce false positives (e.g., a+b.c).
2478
- // This also will not properly handle nested brackets (e.g., obj1[obj2['prop']]; see #911).
2479
- var javaScriptAssignmentTarget = /^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i;
2480
-
2481
- function getWriteableValue(expression) {
2482
- if (ko.utils.arrayIndexOf(javaScriptReservedWords, expression) >= 0)
2483
- return false;
2484
- var match = expression.match(javaScriptAssignmentTarget);
2485
- return match === null ? false : match[1] ? ('Object(' + match[1] + ')' + match[2]) : expression;
2486
- }
2487
-
2488
- // The following regular expressions will be used to split an object-literal string into tokens
2489
-
2490
- // These two match strings, either with double quotes or single quotes
2491
- var stringDouble = '"(?:[^"\\\\]|\\\\.)*"',
2492
- stringSingle = "'(?:[^'\\\\]|\\\\.)*'",
2493
- // Matches a regular expression (text enclosed by slashes), but will also match sets of divisions
2494
- // as a regular expression (this is handled by the parsing loop below).
2495
- stringRegexp = '/(?:[^/\\\\]|\\\\.)*/\w*',
2496
- // These characters have special meaning to the parser and must not appear in the middle of a
2497
- // token, except as part of a string.
2498
- specials = ',"\'{}()/:[\\]',
2499
- // Match text (at least two characters) that does not contain any of the above special characters,
2500
- // although some of the special characters are allowed to start it (all but the colon and comma).
2501
- // The text can contain spaces, but leading or trailing spaces are skipped.
2502
- everyThingElse = '[^\\s:,/][^' + specials + ']*[^\\s' + specials + ']',
2503
- // Match any non-space character not matched already. This will match colons and commas, since they're
2504
- // not matched by "everyThingElse", but will also match any other single character that wasn't already
2505
- // matched (for example: in "a: 1, b: 2", each of the non-space characters will be matched by oneNotSpace).
2506
- oneNotSpace = '[^\\s]',
2507
-
2508
- // Create the actual regular expression by or-ing the above strings. The order is important.
2509
- bindingToken = RegExp(stringDouble + '|' + stringSingle + '|' + stringRegexp + '|' + everyThingElse + '|' + oneNotSpace, 'g'),
2510
-
2511
- // Match end of previous token to determine whether a slash is a division or regex.
2512
- divisionLookBehind = /[\])"'A-Za-z0-9_$]+$/,
2513
- keywordRegexLookBehind = {'in':1,'return':1,'typeof':1};
2514
-
2515
- function parseObjectLiteral(objectLiteralString) {
2516
- // Trim leading and trailing spaces from the string
2517
- var str = ko.utils.stringTrim(objectLiteralString);
2518
-
2519
- // Trim braces '{' surrounding the whole object literal
2520
- if (str.charCodeAt(0) === 123) str = str.slice(1, -1);
2521
-
2522
- // Split into tokens
2523
- var result = [], toks = str.match(bindingToken), key, values = [], depth = 0;
2524
-
2525
- if (toks) {
2526
- // Append a comma so that we don't need a separate code block to deal with the last item
2527
- toks.push(',');
2528
-
2529
- for (var i = 0, tok; tok = toks[i]; ++i) {
2530
- var c = tok.charCodeAt(0);
2531
- // A comma signals the end of a key/value pair if depth is zero
2532
- if (c === 44) { // ","
2533
- if (depth <= 0) {
2534
- result.push((key && values.length) ? {key: key, value: values.join('')} : {'unknown': key || values.join('')});
2535
- key = depth = 0;
2536
- values = [];
2537
- continue;
2538
- }
2539
- // Simply skip the colon that separates the name and value
2540
- } else if (c === 58) { // ":"
2541
- if (!depth && !key && values.length === 1) {
2542
- key = values.pop();
2543
- continue;
2544
- }
2545
- // A set of slashes is initially matched as a regular expression, but could be division
2546
- } else if (c === 47 && i && tok.length > 1) { // "/"
2547
- // Look at the end of the previous token to determine if the slash is actually division
2548
- var match = toks[i-1].match(divisionLookBehind);
2549
- if (match && !keywordRegexLookBehind[match[0]]) {
2550
- // The slash is actually a division punctuator; re-parse the remainder of the string (not including the slash)
2551
- str = str.substr(str.indexOf(tok) + 1);
2552
- toks = str.match(bindingToken);
2553
- toks.push(',');
2554
- i = -1;
2555
- // Continue with just the slash
2556
- tok = '/';
2557
- }
2558
- // Increment depth for parentheses, braces, and brackets so that interior commas are ignored
2559
- } else if (c === 40 || c === 123 || c === 91) { // '(', '{', '['
2560
- ++depth;
2561
- } else if (c === 41 || c === 125 || c === 93) { // ')', '}', ']'
2562
- --depth;
2563
- // The key will be the first token; if it's a string, trim the quotes
2564
- } else if (!key && !values.length && (c === 34 || c === 39)) { // '"', "'"
2565
- tok = tok.slice(1, -1);
2566
- }
2567
- values.push(tok);
2568
- }
2569
- }
2570
- return result;
2571
- }
2572
-
2573
- // Two-way bindings include a write function that allow the handler to update the value even if it's not an observable.
2574
- var twoWayBindings = {};
2575
-
2576
- function preProcessBindings(bindingsStringOrKeyValueArray, bindingOptions) {
2577
- bindingOptions = bindingOptions || {};
2578
-
2579
- function processKeyValue(key, val) {
2580
- var writableVal;
2581
- function callPreprocessHook(obj) {
2582
- return (obj && obj['preprocess']) ? (val = obj['preprocess'](val, key, processKeyValue)) : true;
2583
- }
2584
- if (!bindingParams) {
2585
- if (!callPreprocessHook(ko['getBindingHandler'](key)))
2586
- return;
2587
-
2588
- if (twoWayBindings[key] && (writableVal = getWriteableValue(val))) {
2589
- // For two-way bindings, provide a write method in case the value
2590
- // isn't a writable observable.
2591
- propertyAccessorResultStrings.push("'" + key + "':function(_z){" + writableVal + "=_z}");
2592
- }
2593
- }
2594
- // Values are wrapped in a function so that each value can be accessed independently
2595
- if (makeValueAccessors) {
2596
- val = 'function(){return ' + val + ' }';
2597
- }
2598
- resultStrings.push("'" + key + "':" + val);
2599
- }
2600
-
2601
- var resultStrings = [],
2602
- propertyAccessorResultStrings = [],
2603
- makeValueAccessors = bindingOptions['valueAccessors'],
2604
- bindingParams = bindingOptions['bindingParams'],
2605
- keyValueArray = typeof bindingsStringOrKeyValueArray === "string" ?
2606
- parseObjectLiteral(bindingsStringOrKeyValueArray) : bindingsStringOrKeyValueArray;
2607
-
2608
- ko.utils.arrayForEach(keyValueArray, function(keyValue) {
2609
- processKeyValue(keyValue.key || keyValue['unknown'], keyValue.value);
2610
- });
2611
-
2612
- if (propertyAccessorResultStrings.length)
2613
- processKeyValue('_ko_property_writers', "{" + propertyAccessorResultStrings.join(",") + " }");
2614
-
2615
- return resultStrings.join(",");
2616
- }
2617
-
2618
- return {
2619
- bindingRewriteValidators: [],
2620
-
2621
- twoWayBindings: twoWayBindings,
2622
-
2623
- parseObjectLiteral: parseObjectLiteral,
2624
-
2625
- preProcessBindings: preProcessBindings,
2626
-
2627
- keyValueArrayContainsKey: function(keyValueArray, key) {
2628
- for (var i = 0; i < keyValueArray.length; i++)
2629
- if (keyValueArray[i]['key'] == key)
2630
- return true;
2631
- return false;
2632
- },
2633
-
2634
- // Internal, private KO utility for updating model properties from within bindings
2635
- // property: If the property being updated is (or might be) an observable, pass it here
2636
- // If it turns out to be a writable observable, it will be written to directly
2637
- // allBindings: An object with a get method to retrieve bindings in the current execution context.
2638
- // This will be searched for a '_ko_property_writers' property in case you're writing to a non-observable
2639
- // key: The key identifying the property to be written. Example: for { hasFocus: myValue }, write to 'myValue' by specifying the key 'hasFocus'
2640
- // value: The value to be written
2641
- // checkIfDifferent: If true, and if the property being written is a writable observable, the value will only be written if
2642
- // it is !== existing value on that writable observable
2643
- writeValueToProperty: function(property, allBindings, key, value, checkIfDifferent) {
2644
- if (!property || !ko.isObservable(property)) {
2645
- var propWriters = allBindings.get('_ko_property_writers');
2646
- if (propWriters && propWriters[key])
2647
- propWriters[key](value);
2648
- } else if (ko.isWriteableObservable(property) && (!checkIfDifferent || property.peek() !== value)) {
2649
- property(value);
2650
- }
2651
- }
2652
- };
2653
- })();
2654
-
2655
- ko.exportSymbol('expressionRewriting', ko.expressionRewriting);
2656
- ko.exportSymbol('expressionRewriting.bindingRewriteValidators', ko.expressionRewriting.bindingRewriteValidators);
2657
- ko.exportSymbol('expressionRewriting.parseObjectLiteral', ko.expressionRewriting.parseObjectLiteral);
2658
- ko.exportSymbol('expressionRewriting.preProcessBindings', ko.expressionRewriting.preProcessBindings);
2659
-
2660
- // Making bindings explicitly declare themselves as "two way" isn't ideal in the long term (it would be better if
2661
- // all bindings could use an official 'property writer' API without needing to declare that they might). However,
2662
- // since this is not, and has never been, a public API (_ko_property_writers was never documented), it's acceptable
2663
- // as an internal implementation detail in the short term.
2664
- // For those developers who rely on _ko_property_writers in their custom bindings, we expose _twoWayBindings as an
2665
- // undocumented feature that makes it relatively easy to upgrade to KO 3.0. However, this is still not an official
2666
- // public API, and we reserve the right to remove it at any time if we create a real public property writers API.
2667
- ko.exportSymbol('expressionRewriting._twoWayBindings', ko.expressionRewriting.twoWayBindings);
2668
-
2669
- // For backward compatibility, define the following aliases. (Previously, these function names were misleading because
2670
- // they referred to JSON specifically, even though they actually work with arbitrary JavaScript object literal expressions.)
2671
- ko.exportSymbol('jsonExpressionRewriting', ko.expressionRewriting);
2672
- ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.expressionRewriting.preProcessBindings);
2673
- (function() {
2674
- // "Virtual elements" is an abstraction on top of the usual DOM API which understands the notion that comment nodes
2675
- // may be used to represent hierarchy (in addition to the DOM's natural hierarchy).
2676
- // If you call the DOM-manipulating functions on ko.virtualElements, you will be able to read and write the state
2677
- // of that virtual hierarchy
2678
- //
2679
- // The point of all this is to support containerless templates (e.g., <!-- ko foreach:someCollection -->blah<!-- /ko -->)
2680
- // without having to scatter special cases all over the binding and templating code.
2681
-
2682
- // IE 9 cannot reliably read the "nodeValue" property of a comment node (see https://github.com/SteveSanderson/knockout/issues/186)
2683
- // but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property.
2684
- // So, use node.text where available, and node.nodeValue elsewhere
2685
- var commentNodesHaveTextProperty = document && document.createComment("test").text === "<!--test-->";
2686
-
2687
- var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko(?:\s+([\s\S]+))?\s*-->$/ : /^\s*ko(?:\s+([\s\S]+))?\s*$/;
2688
- var endCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/;
2689
- var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true };
2690
-
2691
- function isStartComment(node) {
2692
- return (node.nodeType == 8) && startCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue);
2693
- }
2694
-
2695
- function isEndComment(node) {
2696
- return (node.nodeType == 8) && endCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue);
2697
- }
2698
-
2699
- function getVirtualChildren(startComment, allowUnbalanced) {
2700
- var currentNode = startComment;
2701
- var depth = 1;
2702
- var children = [];
2703
- while (currentNode = currentNode.nextSibling) {
2704
- if (isEndComment(currentNode)) {
2705
- depth--;
2706
- if (depth === 0)
2707
- return children;
2708
- }
2709
-
2710
- children.push(currentNode);
2711
-
2712
- if (isStartComment(currentNode))
2713
- depth++;
2714
- }
2715
- if (!allowUnbalanced)
2716
- throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue);
2717
- return null;
2718
- }
2719
-
2720
- function getMatchingEndComment(startComment, allowUnbalanced) {
2721
- var allVirtualChildren = getVirtualChildren(startComment, allowUnbalanced);
2722
- if (allVirtualChildren) {
2723
- if (allVirtualChildren.length > 0)
2724
- return allVirtualChildren[allVirtualChildren.length - 1].nextSibling;
2725
- return startComment.nextSibling;
2726
- } else
2727
- return null; // Must have no matching end comment, and allowUnbalanced is true
2728
- }
2729
-
2730
- function getUnbalancedChildTags(node) {
2731
- // e.g., from <div>OK</div><!-- ko blah --><span>Another</span>, returns: <!-- ko blah --><span>Another</span>
2732
- // from <div>OK</div><!-- /ko --><!-- /ko -->, returns: <!-- /ko --><!-- /ko -->
2733
- var childNode = node.firstChild, captureRemaining = null;
2734
- if (childNode) {
2735
- do {
2736
- if (captureRemaining) // We already hit an unbalanced node and are now just scooping up all subsequent nodes
2737
- captureRemaining.push(childNode);
2738
- else if (isStartComment(childNode)) {
2739
- var matchingEndComment = getMatchingEndComment(childNode, /* allowUnbalanced: */ true);
2740
- if (matchingEndComment) // It's a balanced tag, so skip immediately to the end of this virtual set
2741
- childNode = matchingEndComment;
2742
- else
2743
- captureRemaining = [childNode]; // It's unbalanced, so start capturing from this point
2744
- } else if (isEndComment(childNode)) {
2745
- captureRemaining = [childNode]; // It's unbalanced (if it wasn't, we'd have skipped over it already), so start capturing
2746
- }
2747
- } while (childNode = childNode.nextSibling);
2748
- }
2749
- return captureRemaining;
2750
- }
2751
-
2752
- ko.virtualElements = {
2753
- allowedBindings: {},
2754
-
2755
- childNodes: function(node) {
2756
- return isStartComment(node) ? getVirtualChildren(node) : node.childNodes;
2757
- },
2758
-
2759
- emptyNode: function(node) {
2760
- if (!isStartComment(node))
2761
- ko.utils.emptyDomNode(node);
2762
- else {
2763
- var virtualChildren = ko.virtualElements.childNodes(node);
2764
- for (var i = 0, j = virtualChildren.length; i < j; i++)
2765
- ko.removeNode(virtualChildren[i]);
2766
- }
2767
- },
2768
-
2769
- setDomNodeChildren: function(node, childNodes) {
2770
- if (!isStartComment(node))
2771
- ko.utils.setDomNodeChildren(node, childNodes);
2772
- else {
2773
- ko.virtualElements.emptyNode(node);
2774
- var endCommentNode = node.nextSibling; // Must be the next sibling, as we just emptied the children
2775
- for (var i = 0, j = childNodes.length; i < j; i++)
2776
- endCommentNode.parentNode.insertBefore(childNodes[i], endCommentNode);
2777
- }
2778
- },
2779
-
2780
- prepend: function(containerNode, nodeToPrepend) {
2781
- if (!isStartComment(containerNode)) {
2782
- if (containerNode.firstChild)
2783
- containerNode.insertBefore(nodeToPrepend, containerNode.firstChild);
2784
- else
2785
- containerNode.appendChild(nodeToPrepend);
2786
- } else {
2787
- // Start comments must always have a parent and at least one following sibling (the end comment)
2788
- containerNode.parentNode.insertBefore(nodeToPrepend, containerNode.nextSibling);
2789
- }
2790
- },
2791
-
2792
- insertAfter: function(containerNode, nodeToInsert, insertAfterNode) {
2793
- if (!insertAfterNode) {
2794
- ko.virtualElements.prepend(containerNode, nodeToInsert);
2795
- } else if (!isStartComment(containerNode)) {
2796
- // Insert after insertion point
2797
- if (insertAfterNode.nextSibling)
2798
- containerNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
2799
- else
2800
- containerNode.appendChild(nodeToInsert);
2801
- } else {
2802
- // Children of start comments must always have a parent and at least one following sibling (the end comment)
2803
- containerNode.parentNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
2804
- }
2805
- },
2806
-
2807
- firstChild: function(node) {
2808
- if (!isStartComment(node))
2809
- return node.firstChild;
2810
- if (!node.nextSibling || isEndComment(node.nextSibling))
2811
- return null;
2812
- return node.nextSibling;
2813
- },
2814
-
2815
- nextSibling: function(node) {
2816
- if (isStartComment(node))
2817
- node = getMatchingEndComment(node);
2818
- if (node.nextSibling && isEndComment(node.nextSibling))
2819
- return null;
2820
- return node.nextSibling;
2821
- },
2822
-
2823
- hasBindingValue: isStartComment,
2824
-
2825
- virtualNodeBindingValue: function(node) {
2826
- var regexMatch = (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex);
2827
- return regexMatch ? regexMatch[1] : null;
2828
- },
2829
-
2830
- normaliseVirtualElementDomStructure: function(elementVerified) {
2831
- // Workaround for https://github.com/SteveSanderson/knockout/issues/155
2832
- // (IE <= 8 or IE 9 quirks mode parses your HTML weirdly, treating closing </li> tags as if they don't exist, thereby moving comment nodes
2833
- // that are direct descendants of <ul> into the preceding <li>)
2834
- if (!htmlTagsWithOptionallyClosingChildren[ko.utils.tagNameLower(elementVerified)])
2835
- return;
2836
-
2837
- // Scan immediate children to see if they contain unbalanced comment tags. If they do, those comment tags
2838
- // must be intended to appear *after* that child, so move them there.
2839
- var childNode = elementVerified.firstChild;
2840
- if (childNode) {
2841
- do {
2842
- if (childNode.nodeType === 1) {
2843
- var unbalancedTags = getUnbalancedChildTags(childNode);
2844
- if (unbalancedTags) {
2845
- // Fix up the DOM by moving the unbalanced tags to where they most likely were intended to be placed - *after* the child
2846
- var nodeToInsertBefore = childNode.nextSibling;
2847
- for (var i = 0; i < unbalancedTags.length; i++) {
2848
- if (nodeToInsertBefore)
2849
- elementVerified.insertBefore(unbalancedTags[i], nodeToInsertBefore);
2850
- else
2851
- elementVerified.appendChild(unbalancedTags[i]);
2852
- }
2853
- }
2854
- }
2855
- } while (childNode = childNode.nextSibling);
2856
- }
2857
- }
2858
- };
2859
- })();
2860
- ko.exportSymbol('virtualElements', ko.virtualElements);
2861
- ko.exportSymbol('virtualElements.allowedBindings', ko.virtualElements.allowedBindings);
2862
- ko.exportSymbol('virtualElements.emptyNode', ko.virtualElements.emptyNode);
2863
- //ko.exportSymbol('virtualElements.firstChild', ko.virtualElements.firstChild); // firstChild is not minified
2864
- ko.exportSymbol('virtualElements.insertAfter', ko.virtualElements.insertAfter);
2865
- //ko.exportSymbol('virtualElements.nextSibling', ko.virtualElements.nextSibling); // nextSibling is not minified
2866
- ko.exportSymbol('virtualElements.prepend', ko.virtualElements.prepend);
2867
- ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomNodeChildren);
2868
- (function() {
2869
- var defaultBindingAttributeName = "data-bind";
2870
-
2871
- ko.bindingProvider = function() {
2872
- this.bindingCache = {};
2873
- };
2874
-
2875
- ko.utils.extend(ko.bindingProvider.prototype, {
2876
- 'nodeHasBindings': function(node) {
2877
- switch (node.nodeType) {
2878
- case 1: // Element
2879
- return node.getAttribute(defaultBindingAttributeName) != null
2880
- || ko.components['getComponentNameForNode'](node);
2881
- case 8: // Comment node
2882
- return ko.virtualElements.hasBindingValue(node);
2883
- default: return false;
2884
- }
2885
- },
2886
-
2887
- 'getBindings': function(node, bindingContext) {
2888
- var bindingsString = this['getBindingsString'](node, bindingContext),
2889
- parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
2890
- return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ false);
2891
- },
2892
-
2893
- 'getBindingAccessors': function(node, bindingContext) {
2894
- var bindingsString = this['getBindingsString'](node, bindingContext),
2895
- parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node, { 'valueAccessors': true }) : null;
2896
- return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ true);
2897
- },
2898
-
2899
- // The following function is only used internally by this default provider.
2900
- // It's not part of the interface definition for a general binding provider.
2901
- 'getBindingsString': function(node, bindingContext) {
2902
- switch (node.nodeType) {
2903
- case 1: return node.getAttribute(defaultBindingAttributeName); // Element
2904
- case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
2905
- default: return null;
2906
- }
2907
- },
2908
-
2909
- // The following function is only used internally by this default provider.
2910
- // It's not part of the interface definition for a general binding provider.
2911
- 'parseBindingsString': function(bindingsString, bindingContext, node, options) {
2912
- try {
2913
- var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache, options);
2914
- return bindingFunction(bindingContext, node);
2915
- } catch (ex) {
2916
- ex.message = "Unable to parse bindings.\nBindings value: " + bindingsString + "\nMessage: " + ex.message;
2917
- throw ex;
2918
- }
2919
- }
2920
- });
2921
-
2922
- ko.bindingProvider['instance'] = new ko.bindingProvider();
2923
-
2924
- function createBindingsStringEvaluatorViaCache(bindingsString, cache, options) {
2925
- var cacheKey = bindingsString + (options && options['valueAccessors'] || '');
2926
- return cache[cacheKey]
2927
- || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, options));
2928
- }
2929
-
2930
- function createBindingsStringEvaluator(bindingsString, options) {
2931
- // Build the source for a function that evaluates "expression"
2932
- // For each scope variable, add an extra level of "with" nesting
2933
- // Example result: with(sc1) { with(sc0) { return (expression) } }
2934
- var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString, options),
2935
- functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
2936
- return new Function("$context", "$element", functionBody);
2937
- }
2938
- })();
2939
-
2940
- ko.exportSymbol('bindingProvider', ko.bindingProvider);
2941
- (function () {
2942
- ko.bindingHandlers = {};
2943
-
2944
- // The following element types will not be recursed into during binding.
2945
- var bindingDoesNotRecurseIntoElementTypes = {
2946
- // Don't want bindings that operate on text nodes to mutate <script> and <textarea> contents,
2947
- // because it's unexpected and a potential XSS issue.
2948
- // Also bindings should not operate on <template> elements since this breaks in Internet Explorer
2949
- // and because such elements' contents are always intended to be bound in a different context
2950
- // from where they appear in the document.
2951
- 'script': true,
2952
- 'textarea': true,
2953
- 'template': true
2954
- };
2955
-
2956
- // Use an overridable method for retrieving binding handlers so that a plugins may support dynamically created handlers
2957
- ko['getBindingHandler'] = function(bindingKey) {
2958
- return ko.bindingHandlers[bindingKey];
2959
- };
2960
-
2961
- // The ko.bindingContext constructor is only called directly to create the root context. For child
2962
- // contexts, use bindingContext.createChildContext or bindingContext.extend.
2963
- ko.bindingContext = function(dataItemOrAccessor, parentContext, dataItemAlias, extendCallback) {
2964
-
2965
- // The binding context object includes static properties for the current, parent, and root view models.
2966
- // If a view model is actually stored in an observable, the corresponding binding context object, and
2967
- // any child contexts, must be updated when the view model is changed.
2968
- function updateContext() {
2969
- // Most of the time, the context will directly get a view model object, but if a function is given,
2970
- // we call the function to retrieve the view model. If the function accesses any observables or returns
2971
- // an observable, the dependency is tracked, and those observables can later cause the binding
2972
- // context to be updated.
2973
- var dataItemOrObservable = isFunc ? dataItemOrAccessor() : dataItemOrAccessor,
2974
- dataItem = ko.utils.unwrapObservable(dataItemOrObservable);
2975
-
2976
- if (parentContext) {
2977
- // When a "parent" context is given, register a dependency on the parent context. Thus whenever the
2978
- // parent context is updated, this context will also be updated.
2979
- if (parentContext._subscribable)
2980
- parentContext._subscribable();
2981
-
2982
- // Copy $root and any custom properties from the parent context
2983
- ko.utils.extend(self, parentContext);
2984
-
2985
- // Because the above copy overwrites our own properties, we need to reset them.
2986
- // During the first execution, "subscribable" isn't set, so don't bother doing the update then.
2987
- if (subscribable) {
2988
- self._subscribable = subscribable;
2989
- }
2990
- } else {
2991
- self['$parents'] = [];
2992
- self['$root'] = dataItem;
2993
-
2994
- // Export 'ko' in the binding context so it will be available in bindings and templates
2995
- // even if 'ko' isn't exported as a global, such as when using an AMD loader.
2996
- // See https://github.com/SteveSanderson/knockout/issues/490
2997
- self['ko'] = ko;
2998
- }
2999
- self['$rawData'] = dataItemOrObservable;
3000
- self['$data'] = dataItem;
3001
- if (dataItemAlias)
3002
- self[dataItemAlias] = dataItem;
3003
-
3004
- // The extendCallback function is provided when creating a child context or extending a context.
3005
- // It handles the specific actions needed to finish setting up the binding context. Actions in this
3006
- // function could also add dependencies to this binding context.
3007
- if (extendCallback)
3008
- extendCallback(self, parentContext, dataItem);
3009
-
3010
- return self['$data'];
3011
- }
3012
- function disposeWhen() {
3013
- return nodes && !ko.utils.anyDomNodeIsAttachedToDocument(nodes);
3014
- }
3015
-
3016
- var self = this,
3017
- isFunc = typeof(dataItemOrAccessor) == "function" && !ko.isObservable(dataItemOrAccessor),
3018
- nodes,
3019
- subscribable = ko.dependentObservable(updateContext, null, { disposeWhen: disposeWhen, disposeWhenNodeIsRemoved: true });
3020
-
3021
- // At this point, the binding context has been initialized, and the "subscribable" computed observable is
3022
- // subscribed to any observables that were accessed in the process. If there is nothing to track, the
3023
- // computed will be inactive, and we can safely throw it away. If it's active, the computed is stored in
3024
- // the context object.
3025
- if (subscribable.isActive()) {
3026
- self._subscribable = subscribable;
3027
-
3028
- // Always notify because even if the model ($data) hasn't changed, other context properties might have changed
3029
- subscribable['equalityComparer'] = null;
3030
-
3031
- // We need to be able to dispose of this computed observable when it's no longer needed. This would be
3032
- // easy if we had a single node to watch, but binding contexts can be used by many different nodes, and
3033
- // we cannot assume that those nodes have any relation to each other. So instead we track any node that
3034
- // the context is attached to, and dispose the computed when all of those nodes have been cleaned.
3035
-
3036
- // Add properties to *subscribable* instead of *self* because any properties added to *self* may be overwritten on updates
3037
- nodes = [];
3038
- subscribable._addNode = function(node) {
3039
- nodes.push(node);
3040
- ko.utils.domNodeDisposal.addDisposeCallback(node, function(node) {
3041
- ko.utils.arrayRemoveItem(nodes, node);
3042
- if (!nodes.length) {
3043
- subscribable.dispose();
3044
- self._subscribable = subscribable = undefined;
3045
- }
3046
- });
3047
- };
3048
- }
3049
- }
3050
-
3051
- // Extend the binding context hierarchy with a new view model object. If the parent context is watching
3052
- // any observables, the new child context will automatically get a dependency on the parent context.
3053
- // But this does not mean that the $data value of the child context will also get updated. If the child
3054
- // view model also depends on the parent view model, you must provide a function that returns the correct
3055
- // view model on each update.
3056
- ko.bindingContext.prototype['createChildContext'] = function (dataItemOrAccessor, dataItemAlias, extendCallback) {
3057
- return new ko.bindingContext(dataItemOrAccessor, this, dataItemAlias, function(self, parentContext) {
3058
- // Extend the context hierarchy by setting the appropriate pointers
3059
- self['$parentContext'] = parentContext;
3060
- self['$parent'] = parentContext['$data'];
3061
- self['$parents'] = (parentContext['$parents'] || []).slice(0);
3062
- self['$parents'].unshift(self['$parent']);
3063
- if (extendCallback)
3064
- extendCallback(self);
3065
- });
3066
- };
3067
-
3068
- // Extend the binding context with new custom properties. This doesn't change the context hierarchy.
3069
- // Similarly to "child" contexts, provide a function here to make sure that the correct values are set
3070
- // when an observable view model is updated.
3071
- ko.bindingContext.prototype['extend'] = function(properties) {
3072
- // If the parent context references an observable view model, "_subscribable" will always be the
3073
- // latest view model object. If not, "_subscribable" isn't set, and we can use the static "$data" value.
3074
- return new ko.bindingContext(this._subscribable || this['$data'], this, null, function(self, parentContext) {
3075
- // This "child" context doesn't directly track a parent observable view model,
3076
- // so we need to manually set the $rawData value to match the parent.
3077
- self['$rawData'] = parentContext['$rawData'];
3078
- ko.utils.extend(self, typeof(properties) == "function" ? properties() : properties);
3079
- });
3080
- };
3081
-
3082
- // Returns the valueAccesor function for a binding value
3083
- function makeValueAccessor(value) {
3084
- return function() {
3085
- return value;
3086
- };
3087
- }
3088
-
3089
- // Returns the value of a valueAccessor function
3090
- function evaluateValueAccessor(valueAccessor) {
3091
- return valueAccessor();
3092
- }
3093
-
3094
- // Given a function that returns bindings, create and return a new object that contains
3095
- // binding value-accessors functions. Each accessor function calls the original function
3096
- // so that it always gets the latest value and all dependencies are captured. This is used
3097
- // by ko.applyBindingsToNode and getBindingsAndMakeAccessors.
3098
- function makeAccessorsFromFunction(callback) {
3099
- return ko.utils.objectMap(ko.dependencyDetection.ignore(callback), function(value, key) {
3100
- return function() {
3101
- return callback()[key];
3102
- };
3103
- });
3104
- }
3105
-
3106
- // Given a bindings function or object, create and return a new object that contains
3107
- // binding value-accessors functions. This is used by ko.applyBindingsToNode.
3108
- function makeBindingAccessors(bindings, context, node) {
3109
- if (typeof bindings === 'function') {
3110
- return makeAccessorsFromFunction(bindings.bind(null, context, node));
3111
- } else {
3112
- return ko.utils.objectMap(bindings, makeValueAccessor);
3113
- }
3114
- }
3115
-
3116
- // This function is used if the binding provider doesn't include a getBindingAccessors function.
3117
- // It must be called with 'this' set to the provider instance.
3118
- function getBindingsAndMakeAccessors(node, context) {
3119
- return makeAccessorsFromFunction(this['getBindings'].bind(this, node, context));
3120
- }
3121
-
3122
- function validateThatBindingIsAllowedForVirtualElements(bindingName) {
3123
- var validator = ko.virtualElements.allowedBindings[bindingName];
3124
- if (!validator)
3125
- throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements")
3126
- }
3127
-
3128
- function applyBindingsToDescendantsInternal (bindingContext, elementOrVirtualElement, bindingContextsMayDifferFromDomParentElement) {
3129
- var currentChild,
3130
- nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement),
3131
- provider = ko.bindingProvider['instance'],
3132
- preprocessNode = provider['preprocessNode'];
3133
-
3134
- // Preprocessing allows a binding provider to mutate a node before bindings are applied to it. For example it's
3135
- // possible to insert new siblings after it, and/or replace the node with a different one. This can be used to
3136
- // implement custom binding syntaxes, such as {{ value }} for string interpolation, or custom element types that
3137
- // trigger insertion of <template> contents at that point in the document.
3138
- if (preprocessNode) {
3139
- while (currentChild = nextInQueue) {
3140
- nextInQueue = ko.virtualElements.nextSibling(currentChild);
3141
- preprocessNode.call(provider, currentChild);
3142
- }
3143
- // Reset nextInQueue for the next loop
3144
- nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
3145
- }
3146
-
3147
- while (currentChild = nextInQueue) {
3148
- // Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
3149
- nextInQueue = ko.virtualElements.nextSibling(currentChild);
3150
- applyBindingsToNodeAndDescendantsInternal(bindingContext, currentChild, bindingContextsMayDifferFromDomParentElement);
3151
- }
3152
- }
3153
-
3154
- function applyBindingsToNodeAndDescendantsInternal (bindingContext, nodeVerified, bindingContextMayDifferFromDomParentElement) {
3155
- var shouldBindDescendants = true;
3156
-
3157
- // Perf optimisation: Apply bindings only if...
3158
- // (1) We need to store the binding context on this node (because it may differ from the DOM parent node's binding context)
3159
- // Note that we can't store binding contexts on non-elements (e.g., text nodes), as IE doesn't allow expando properties for those
3160
- // (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
3161
- var isElement = (nodeVerified.nodeType === 1);
3162
- if (isElement) // Workaround IE <= 8 HTML parsing weirdness
3163
- ko.virtualElements.normaliseVirtualElementDomStructure(nodeVerified);
3164
-
3165
- var shouldApplyBindings = (isElement && bindingContextMayDifferFromDomParentElement) // Case (1)
3166
- || ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified); // Case (2)
3167
- if (shouldApplyBindings)
3168
- shouldBindDescendants = applyBindingsToNodeInternal(nodeVerified, null, bindingContext, bindingContextMayDifferFromDomParentElement)['shouldBindDescendants'];
3169
-
3170
- if (shouldBindDescendants && !bindingDoesNotRecurseIntoElementTypes[ko.utils.tagNameLower(nodeVerified)]) {
3171
- // We're recursing automatically into (real or virtual) child nodes without changing binding contexts. So,
3172
- // * For children of a *real* element, the binding context is certainly the same as on their DOM .parentNode,
3173
- // hence bindingContextsMayDifferFromDomParentElement is false
3174
- // * For children of a *virtual* element, we can't be sure. Evaluating .parentNode on those children may
3175
- // skip over any number of intermediate virtual elements, any of which might define a custom binding context,
3176
- // hence bindingContextsMayDifferFromDomParentElement is true
3177
- applyBindingsToDescendantsInternal(bindingContext, nodeVerified, /* bindingContextsMayDifferFromDomParentElement: */ !isElement);
3178
- }
3179
- }
3180
-
3181
- var boundElementDomDataKey = ko.utils.domData.nextKey();
3182
-
3183
-
3184
- function topologicalSortBindings(bindings) {
3185
- // Depth-first sort
3186
- var result = [], // The list of key/handler pairs that we will return
3187
- bindingsConsidered = {}, // A temporary record of which bindings are already in 'result'
3188
- cyclicDependencyStack = []; // Keeps track of a depth-search so that, if there's a cycle, we know which bindings caused it
3189
- ko.utils.objectForEach(bindings, function pushBinding(bindingKey) {
3190
- if (!bindingsConsidered[bindingKey]) {
3191
- var binding = ko['getBindingHandler'](bindingKey);
3192
- if (binding) {
3193
- // First add dependencies (if any) of the current binding
3194
- if (binding['after']) {
3195
- cyclicDependencyStack.push(bindingKey);
3196
- ko.utils.arrayForEach(binding['after'], function(bindingDependencyKey) {
3197
- if (bindings[bindingDependencyKey]) {
3198
- if (ko.utils.arrayIndexOf(cyclicDependencyStack, bindingDependencyKey) !== -1) {
3199
- throw Error("Cannot combine the following bindings, because they have a cyclic dependency: " + cyclicDependencyStack.join(", "));
3200
- } else {
3201
- pushBinding(bindingDependencyKey);
3202
- }
3203
- }
3204
- });
3205
- cyclicDependencyStack.length--;
3206
- }
3207
- // Next add the current binding
3208
- result.push({ key: bindingKey, handler: binding });
3209
- }
3210
- bindingsConsidered[bindingKey] = true;
3211
- }
3212
- });
3213
-
3214
- return result;
3215
- }
3216
-
3217
- function applyBindingsToNodeInternal(node, sourceBindings, bindingContext, bindingContextMayDifferFromDomParentElement) {
3218
- // Prevent multiple applyBindings calls for the same node, except when a binding value is specified
3219
- var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
3220
- if (!sourceBindings) {
3221
- if (alreadyBound) {
3222
- throw Error("You cannot apply bindings multiple times to the same element.");
3223
- }
3224
- ko.utils.domData.set(node, boundElementDomDataKey, true);
3225
- }
3226
-
3227
- // Optimization: Don't store the binding context on this node if it's definitely the same as on node.parentNode, because
3228
- // we can easily recover it just by scanning up the node's ancestors in the DOM
3229
- // (note: here, parent node means "real DOM parent" not "virtual parent", as there's no O(1) way to find the virtual parent)
3230
- if (!alreadyBound && bindingContextMayDifferFromDomParentElement)
3231
- ko.storedBindingContextForNode(node, bindingContext);
3232
-
3233
- // Use bindings if given, otherwise fall back on asking the bindings provider to give us some bindings
3234
- var bindings;
3235
- if (sourceBindings && typeof sourceBindings !== 'function') {
3236
- bindings = sourceBindings;
3237
- } else {
3238
- var provider = ko.bindingProvider['instance'],
3239
- getBindings = provider['getBindingAccessors'] || getBindingsAndMakeAccessors;
3240
-
3241
- // Get the binding from the provider within a computed observable so that we can update the bindings whenever
3242
- // the binding context is updated or if the binding provider accesses observables.
3243
- var bindingsUpdater = ko.dependentObservable(
3244
- function() {
3245
- bindings = sourceBindings ? sourceBindings(bindingContext, node) : getBindings.call(provider, node, bindingContext);
3246
- // Register a dependency on the binding context to support observable view models.
3247
- if (bindings && bindingContext._subscribable)
3248
- bindingContext._subscribable();
3249
- return bindings;
3250
- },
3251
- null, { disposeWhenNodeIsRemoved: node }
3252
- );
3253
-
3254
- if (!bindings || !bindingsUpdater.isActive())
3255
- bindingsUpdater = null;
3256
- }
3257
-
3258
- var bindingHandlerThatControlsDescendantBindings;
3259
- if (bindings) {
3260
- // Return the value accessor for a given binding. When bindings are static (won't be updated because of a binding
3261
- // context update), just return the value accessor from the binding. Otherwise, return a function that always gets
3262
- // the latest binding value and registers a dependency on the binding updater.
3263
- var getValueAccessor = bindingsUpdater
3264
- ? function(bindingKey) {
3265
- return function() {
3266
- return evaluateValueAccessor(bindingsUpdater()[bindingKey]);
3267
- };
3268
- } : function(bindingKey) {
3269
- return bindings[bindingKey];
3270
- };
3271
-
3272
- // Use of allBindings as a function is maintained for backwards compatibility, but its use is deprecated
3273
- function allBindings() {
3274
- return ko.utils.objectMap(bindingsUpdater ? bindingsUpdater() : bindings, evaluateValueAccessor);
3275
- }
3276
- // The following is the 3.x allBindings API
3277
- allBindings['get'] = function(key) {
3278
- return bindings[key] && evaluateValueAccessor(getValueAccessor(key));
3279
- };
3280
- allBindings['has'] = function(key) {
3281
- return key in bindings;
3282
- };
3283
-
3284
- // First put the bindings into the right order
3285
- var orderedBindings = topologicalSortBindings(bindings);
3286
-
3287
- // Go through the sorted bindings, calling init and update for each
3288
- ko.utils.arrayForEach(orderedBindings, function(bindingKeyAndHandler) {
3289
- // Note that topologicalSortBindings has already filtered out any nonexistent binding handlers,
3290
- // so bindingKeyAndHandler.handler will always be nonnull.
3291
- var handlerInitFn = bindingKeyAndHandler.handler["init"],
3292
- handlerUpdateFn = bindingKeyAndHandler.handler["update"],
3293
- bindingKey = bindingKeyAndHandler.key;
3294
-
3295
- if (node.nodeType === 8) {
3296
- validateThatBindingIsAllowedForVirtualElements(bindingKey);
3297
- }
3298
-
3299
- try {
3300
- // Run init, ignoring any dependencies
3301
- if (typeof handlerInitFn == "function") {
3302
- ko.dependencyDetection.ignore(function() {
3303
- var initResult = handlerInitFn(node, getValueAccessor(bindingKey), allBindings, bindingContext['$data'], bindingContext);
3304
-
3305
- // If this binding handler claims to control descendant bindings, make a note of this
3306
- if (initResult && initResult['controlsDescendantBindings']) {
3307
- if (bindingHandlerThatControlsDescendantBindings !== undefined)
3308
- throw new Error("Multiple bindings (" + bindingHandlerThatControlsDescendantBindings + " and " + bindingKey + ") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");
3309
- bindingHandlerThatControlsDescendantBindings = bindingKey;
3310
- }
3311
- });
3312
- }
3313
-
3314
- // Run update in its own computed wrapper
3315
- if (typeof handlerUpdateFn == "function") {
3316
- ko.dependentObservable(
3317
- function() {
3318
- handlerUpdateFn(node, getValueAccessor(bindingKey), allBindings, bindingContext['$data'], bindingContext);
3319
- },
3320
- null,
3321
- { disposeWhenNodeIsRemoved: node }
3322
- );
3323
- }
3324
- } catch (ex) {
3325
- ex.message = "Unable to process binding \"" + bindingKey + ": " + bindings[bindingKey] + "\"\nMessage: " + ex.message;
3326
- throw ex;
3327
- }
3328
- });
3329
- }
3330
-
3331
- return {
3332
- 'shouldBindDescendants': bindingHandlerThatControlsDescendantBindings === undefined
3333
- };
3334
- };
3335
-
3336
- var storedBindingContextDomDataKey = ko.utils.domData.nextKey();
3337
- ko.storedBindingContextForNode = function (node, bindingContext) {
3338
- if (arguments.length == 2) {
3339
- ko.utils.domData.set(node, storedBindingContextDomDataKey, bindingContext);
3340
- if (bindingContext._subscribable)
3341
- bindingContext._subscribable._addNode(node);
3342
- } else {
3343
- return ko.utils.domData.get(node, storedBindingContextDomDataKey);
3344
- }
3345
- }
3346
-
3347
- function getBindingContext(viewModelOrBindingContext) {
3348
- return viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext)
3349
- ? viewModelOrBindingContext
3350
- : new ko.bindingContext(viewModelOrBindingContext);
3351
- }
3352
-
3353
- ko.applyBindingAccessorsToNode = function (node, bindings, viewModelOrBindingContext) {
3354
- if (node.nodeType === 1) // If it's an element, workaround IE <= 8 HTML parsing weirdness
3355
- ko.virtualElements.normaliseVirtualElementDomStructure(node);
3356
- return applyBindingsToNodeInternal(node, bindings, getBindingContext(viewModelOrBindingContext), true);
3357
- };
3358
-
3359
- ko.applyBindingsToNode = function (node, bindings, viewModelOrBindingContext) {
3360
- var context = getBindingContext(viewModelOrBindingContext);
3361
- return ko.applyBindingAccessorsToNode(node, makeBindingAccessors(bindings, context, node), context);
3362
- };
3363
-
3364
- ko.applyBindingsToDescendants = function(viewModelOrBindingContext, rootNode) {
3365
- if (rootNode.nodeType === 1 || rootNode.nodeType === 8)
3366
- applyBindingsToDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode, true);
3367
- };
3368
-
3369
- ko.applyBindings = function (viewModelOrBindingContext, rootNode) {
3370
- // If jQuery is loaded after Knockout, we won't initially have access to it. So save it here.
3371
- if (!jQueryInstance && window['jQuery']) {
3372
- jQueryInstance = window['jQuery'];
3373
- }
3374
-
3375
- if (rootNode && (rootNode.nodeType !== 1) && (rootNode.nodeType !== 8))
3376
- throw new Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");
3377
- rootNode = rootNode || window.document.body; // Make "rootNode" parameter optional
3378
-
3379
- applyBindingsToNodeAndDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode, true);
3380
- };
3381
-
3382
- // Retrieving binding context from arbitrary nodes
3383
- ko.contextFor = function(node) {
3384
- // We can only do something meaningful for elements and comment nodes (in particular, not text nodes, as IE can't store domdata for them)
3385
- switch (node.nodeType) {
3386
- case 1:
3387
- case 8:
3388
- var context = ko.storedBindingContextForNode(node);
3389
- if (context) return context;
3390
- if (node.parentNode) return ko.contextFor(node.parentNode);
3391
- break;
3392
- }
3393
- return undefined;
3394
- };
3395
- ko.dataFor = function(node) {
3396
- var context = ko.contextFor(node);
3397
- return context ? context['$data'] : undefined;
3398
- };
3399
-
3400
- ko.exportSymbol('bindingHandlers', ko.bindingHandlers);
3401
- ko.exportSymbol('applyBindings', ko.applyBindings);
3402
- ko.exportSymbol('applyBindingsToDescendants', ko.applyBindingsToDescendants);
3403
- ko.exportSymbol('applyBindingAccessorsToNode', ko.applyBindingAccessorsToNode);
3404
- ko.exportSymbol('applyBindingsToNode', ko.applyBindingsToNode);
3405
- ko.exportSymbol('contextFor', ko.contextFor);
3406
- ko.exportSymbol('dataFor', ko.dataFor);
3407
- })();
3408
- (function(undefined) {
3409
- var loadingSubscribablesCache = {}, // Tracks component loads that are currently in flight
3410
- loadedDefinitionsCache = {}; // Tracks component loads that have already completed
3411
-
3412
- ko.components = {
3413
- get: function(componentName, callback) {
3414
- var cachedDefinition = getObjectOwnProperty(loadedDefinitionsCache, componentName);
3415
- if (cachedDefinition) {
3416
- // It's already loaded and cached. Reuse the same definition object.
3417
- // Note that for API consistency, even cache hits complete asynchronously by default.
3418
- // You can bypass this by putting synchronous:true on your component config.
3419
- if (cachedDefinition.isSynchronousComponent) {
3420
- ko.dependencyDetection.ignore(function() { // See comment in loaderRegistryBehaviors.js for reasoning
3421
- callback(cachedDefinition.definition);
3422
- });
3423
- } else {
3424
- ko.tasks.schedule(function() { callback(cachedDefinition.definition); });
3425
- }
3426
- } else {
3427
- // Join the loading process that is already underway, or start a new one.
3428
- loadComponentAndNotify(componentName, callback);
3429
- }
3430
- },
3431
-
3432
- clearCachedDefinition: function(componentName) {
3433
- delete loadedDefinitionsCache[componentName];
3434
- },
3435
-
3436
- _getFirstResultFromLoaders: getFirstResultFromLoaders
3437
- };
3438
-
3439
- function getObjectOwnProperty(obj, propName) {
3440
- return obj.hasOwnProperty(propName) ? obj[propName] : undefined;
3441
- }
3442
-
3443
- function loadComponentAndNotify(componentName, callback) {
3444
- var subscribable = getObjectOwnProperty(loadingSubscribablesCache, componentName),
3445
- completedAsync;
3446
- if (!subscribable) {
3447
- // It's not started loading yet. Start loading, and when it's done, move it to loadedDefinitionsCache.
3448
- subscribable = loadingSubscribablesCache[componentName] = new ko.subscribable();
3449
- subscribable.subscribe(callback);
3450
-
3451
- beginLoadingComponent(componentName, function(definition, config) {
3452
- var isSynchronousComponent = !!(config && config['synchronous']);
3453
- loadedDefinitionsCache[componentName] = { definition: definition, isSynchronousComponent: isSynchronousComponent };
3454
- delete loadingSubscribablesCache[componentName];
3455
-
3456
- // For API consistency, all loads complete asynchronously. However we want to avoid
3457
- // adding an extra task schedule if it's unnecessary (i.e., the completion is already
3458
- // async).
3459
- //
3460
- // You can bypass the 'always asynchronous' feature by putting the synchronous:true
3461
- // flag on your component configuration when you register it.
3462
- if (completedAsync || isSynchronousComponent) {
3463
- // Note that notifySubscribers ignores any dependencies read within the callback.
3464
- // See comment in loaderRegistryBehaviors.js for reasoning
3465
- subscribable['notifySubscribers'](definition);
3466
- } else {
3467
- ko.tasks.schedule(function() {
3468
- subscribable['notifySubscribers'](definition);
3469
- });
3470
- }
3471
- });
3472
- completedAsync = true;
3473
- } else {
3474
- subscribable.subscribe(callback);
3475
- }
3476
- }
3477
-
3478
- function beginLoadingComponent(componentName, callback) {
3479
- getFirstResultFromLoaders('getConfig', [componentName], function(config) {
3480
- if (config) {
3481
- // We have a config, so now load its definition
3482
- getFirstResultFromLoaders('loadComponent', [componentName, config], function(definition) {
3483
- callback(definition, config);
3484
- });
3485
- } else {
3486
- // The component has no config - it's unknown to all the loaders.
3487
- // Note that this is not an error (e.g., a module loading error) - that would abort the
3488
- // process and this callback would not run. For this callback to run, all loaders must
3489
- // have confirmed they don't know about this component.
3490
- callback(null, null);
3491
- }
3492
- });
3493
- }
3494
-
3495
- function getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders) {
3496
- // On the first call in the stack, start with the full set of loaders
3497
- if (!candidateLoaders) {
3498
- candidateLoaders = ko.components['loaders'].slice(0); // Use a copy, because we'll be mutating this array
3499
- }
3500
-
3501
- // Try the next candidate
3502
- var currentCandidateLoader = candidateLoaders.shift();
3503
- if (currentCandidateLoader) {
3504
- var methodInstance = currentCandidateLoader[methodName];
3505
- if (methodInstance) {
3506
- var wasAborted = false,
3507
- synchronousReturnValue = methodInstance.apply(currentCandidateLoader, argsExceptCallback.concat(function(result) {
3508
- if (wasAborted) {
3509
- callback(null);
3510
- } else if (result !== null) {
3511
- // This candidate returned a value. Use it.
3512
- callback(result);
3513
- } else {
3514
- // Try the next candidate
3515
- getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
3516
- }
3517
- }));
3518
-
3519
- // Currently, loaders may not return anything synchronously. This leaves open the possibility
3520
- // that we'll extend the API to support synchronous return values in the future. It won't be
3521
- // a breaking change, because currently no loader is allowed to return anything except undefined.
3522
- if (synchronousReturnValue !== undefined) {
3523
- wasAborted = true;
3524
-
3525
- // Method to suppress exceptions will remain undocumented. This is only to keep
3526
- // KO's specs running tidily, since we can observe the loading got aborted without
3527
- // having exceptions cluttering up the console too.
3528
- if (!currentCandidateLoader['suppressLoaderExceptions']) {
3529
- throw new Error('Component loaders must supply values by invoking the callback, not by returning values synchronously.');
3530
- }
3531
- }
3532
- } else {
3533
- // This candidate doesn't have the relevant handler. Synchronously move on to the next one.
3534
- getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
3535
- }
3536
- } else {
3537
- // No candidates returned a value
3538
- callback(null);
3539
- }
3540
- }
3541
-
3542
- // Reference the loaders via string name so it's possible for developers
3543
- // to replace the whole array by assigning to ko.components.loaders
3544
- ko.components['loaders'] = [];
3545
-
3546
- ko.exportSymbol('components', ko.components);
3547
- ko.exportSymbol('components.get', ko.components.get);
3548
- ko.exportSymbol('components.clearCachedDefinition', ko.components.clearCachedDefinition);
3549
- })();
3550
- (function(undefined) {
3551
-
3552
- // The default loader is responsible for two things:
3553
- // 1. Maintaining the default in-memory registry of component configuration objects
3554
- // (i.e., the thing you're writing to when you call ko.components.register(someName, ...))
3555
- // 2. Answering requests for components by fetching configuration objects
3556
- // from that default in-memory registry and resolving them into standard
3557
- // component definition objects (of the form { createViewModel: ..., template: ... })
3558
- // Custom loaders may override either of these facilities, i.e.,
3559
- // 1. To supply configuration objects from some other source (e.g., conventions)
3560
- // 2. Or, to resolve configuration objects by loading viewmodels/templates via arbitrary logic.
3561
-
3562
- var defaultConfigRegistry = {};
3563
-
3564
- ko.components.register = function(componentName, config) {
3565
- if (!config) {
3566
- throw new Error('Invalid configuration for ' + componentName);
3567
- }
3568
-
3569
- if (ko.components.isRegistered(componentName)) {
3570
- throw new Error('Component ' + componentName + ' is already registered');
3571
- }
3572
-
3573
- defaultConfigRegistry[componentName] = config;
3574
- };
3575
-
3576
- ko.components.isRegistered = function(componentName) {
3577
- return defaultConfigRegistry.hasOwnProperty(componentName);
3578
- };
3579
-
3580
- ko.components.unregister = function(componentName) {
3581
- delete defaultConfigRegistry[componentName];
3582
- ko.components.clearCachedDefinition(componentName);
3583
- };
3584
-
3585
- ko.components.defaultLoader = {
3586
- 'getConfig': function(componentName, callback) {
3587
- var result = defaultConfigRegistry.hasOwnProperty(componentName)
3588
- ? defaultConfigRegistry[componentName]
3589
- : null;
3590
- callback(result);
3591
- },
3592
-
3593
- 'loadComponent': function(componentName, config, callback) {
3594
- var errorCallback = makeErrorCallback(componentName);
3595
- possiblyGetConfigFromAmd(errorCallback, config, function(loadedConfig) {
3596
- resolveConfig(componentName, errorCallback, loadedConfig, callback);
3597
- });
3598
- },
3599
-
3600
- 'loadTemplate': function(componentName, templateConfig, callback) {
3601
- resolveTemplate(makeErrorCallback(componentName), templateConfig, callback);
3602
- },
3603
-
3604
- 'loadViewModel': function(componentName, viewModelConfig, callback) {
3605
- resolveViewModel(makeErrorCallback(componentName), viewModelConfig, callback);
3606
- }
3607
- };
3608
-
3609
- var createViewModelKey = 'createViewModel';
3610
-
3611
- // Takes a config object of the form { template: ..., viewModel: ... }, and asynchronously convert it
3612
- // into the standard component definition format:
3613
- // { template: <ArrayOfDomNodes>, createViewModel: function(params, componentInfo) { ... } }.
3614
- // Since both template and viewModel may need to be resolved asynchronously, both tasks are performed
3615
- // in parallel, and the results joined when both are ready. We don't depend on any promises infrastructure,
3616
- // so this is implemented manually below.
3617
- function resolveConfig(componentName, errorCallback, config, callback) {
3618
- var result = {},
3619
- makeCallBackWhenZero = 2,
3620
- tryIssueCallback = function() {
3621
- if (--makeCallBackWhenZero === 0) {
3622
- callback(result);
3623
- }
3624
- },
3625
- templateConfig = config['template'],
3626
- viewModelConfig = config['viewModel'];
3627
-
3628
- if (templateConfig) {
3629
- possiblyGetConfigFromAmd(errorCallback, templateConfig, function(loadedConfig) {
3630
- ko.components._getFirstResultFromLoaders('loadTemplate', [componentName, loadedConfig], function(resolvedTemplate) {
3631
- result['template'] = resolvedTemplate;
3632
- tryIssueCallback();
3633
- });
3634
- });
3635
- } else {
3636
- tryIssueCallback();
3637
- }
3638
-
3639
- if (viewModelConfig) {
3640
- possiblyGetConfigFromAmd(errorCallback, viewModelConfig, function(loadedConfig) {
3641
- ko.components._getFirstResultFromLoaders('loadViewModel', [componentName, loadedConfig], function(resolvedViewModel) {
3642
- result[createViewModelKey] = resolvedViewModel;
3643
- tryIssueCallback();
3644
- });
3645
- });
3646
- } else {
3647
- tryIssueCallback();
3648
- }
3649
- }
3650
-
3651
- function resolveTemplate(errorCallback, templateConfig, callback) {
3652
- if (typeof templateConfig === 'string') {
3653
- // Markup - parse it
3654
- callback(ko.utils.parseHtmlFragment(templateConfig));
3655
- } else if (templateConfig instanceof Array) {
3656
- // Assume already an array of DOM nodes - pass through unchanged
3657
- callback(templateConfig);
3658
- } else if (isDocumentFragment(templateConfig)) {
3659
- // Document fragment - use its child nodes
3660
- callback(ko.utils.makeArray(templateConfig.childNodes));
3661
- } else if (templateConfig['element']) {
3662
- var element = templateConfig['element'];
3663
- if (isDomElement(element)) {
3664
- // Element instance - copy its child nodes
3665
- callback(cloneNodesFromTemplateSourceElement(element));
3666
- } else if (typeof element === 'string') {
3667
- // Element ID - find it, then copy its child nodes
3668
- var elemInstance = document.getElementById(element);
3669
- if (elemInstance) {
3670
- callback(cloneNodesFromTemplateSourceElement(elemInstance));
3671
- } else {
3672
- errorCallback('Cannot find element with ID ' + element);
3673
- }
3674
- } else {
3675
- errorCallback('Unknown element type: ' + element);
3676
- }
3677
- } else {
3678
- errorCallback('Unknown template value: ' + templateConfig);
3679
- }
3680
- }
3681
-
3682
- function resolveViewModel(errorCallback, viewModelConfig, callback) {
3683
- if (typeof viewModelConfig === 'function') {
3684
- // Constructor - convert to standard factory function format
3685
- // By design, this does *not* supply componentInfo to the constructor, as the intent is that
3686
- // componentInfo contains non-viewmodel data (e.g., the component's element) that should only
3687
- // be used in factory functions, not viewmodel constructors.
3688
- callback(function (params /*, componentInfo */) {
3689
- return new viewModelConfig(params);
3690
- });
3691
- } else if (typeof viewModelConfig[createViewModelKey] === 'function') {
3692
- // Already a factory function - use it as-is
3693
- callback(viewModelConfig[createViewModelKey]);
3694
- } else if ('instance' in viewModelConfig) {
3695
- // Fixed object instance - promote to createViewModel format for API consistency
3696
- var fixedInstance = viewModelConfig['instance'];
3697
- callback(function (params, componentInfo) {
3698
- return fixedInstance;
3699
- });
3700
- } else if ('viewModel' in viewModelConfig) {
3701
- // Resolved AMD module whose value is of the form { viewModel: ... }
3702
- resolveViewModel(errorCallback, viewModelConfig['viewModel'], callback);
3703
- } else {
3704
- errorCallback('Unknown viewModel value: ' + viewModelConfig);
3705
- }
3706
- }
3707
-
3708
- function cloneNodesFromTemplateSourceElement(elemInstance) {
3709
- switch (ko.utils.tagNameLower(elemInstance)) {
3710
- case 'script':
3711
- return ko.utils.parseHtmlFragment(elemInstance.text);
3712
- case 'textarea':
3713
- return ko.utils.parseHtmlFragment(elemInstance.value);
3714
- case 'template':
3715
- // For browsers with proper <template> element support (i.e., where the .content property
3716
- // gives a document fragment), use that document fragment.
3717
- if (isDocumentFragment(elemInstance.content)) {
3718
- return ko.utils.cloneNodes(elemInstance.content.childNodes);
3719
- }
3720
- }
3721
-
3722
- // Regular elements such as <div>, and <template> elements on old browsers that don't really
3723
- // understand <template> and just treat it as a regular container
3724
- return ko.utils.cloneNodes(elemInstance.childNodes);
3725
- }
3726
-
3727
- function isDomElement(obj) {
3728
- if (window['HTMLElement']) {
3729
- return obj instanceof HTMLElement;
3730
- } else {
3731
- return obj && obj.tagName && obj.nodeType === 1;
3732
- }
3733
- }
3734
-
3735
- function isDocumentFragment(obj) {
3736
- if (window['DocumentFragment']) {
3737
- return obj instanceof DocumentFragment;
3738
- } else {
3739
- return obj && obj.nodeType === 11;
3740
- }
3741
- }
3742
-
3743
- function possiblyGetConfigFromAmd(errorCallback, config, callback) {
3744
- if (typeof config['require'] === 'string') {
3745
- // The config is the value of an AMD module
3746
- if (amdRequire || window['require']) {
3747
- (amdRequire || window['require'])([config['require']], callback);
3748
- } else {
3749
- errorCallback('Uses require, but no AMD loader is present');
3750
- }
3751
- } else {
3752
- callback(config);
3753
- }
3754
- }
3755
-
3756
- function makeErrorCallback(componentName) {
3757
- return function (message) {
3758
- throw new Error('Component \'' + componentName + '\': ' + message);
3759
- };
3760
- }
3761
-
3762
- ko.exportSymbol('components.register', ko.components.register);
3763
- ko.exportSymbol('components.isRegistered', ko.components.isRegistered);
3764
- ko.exportSymbol('components.unregister', ko.components.unregister);
3765
-
3766
- // Expose the default loader so that developers can directly ask it for configuration
3767
- // or to resolve configuration
3768
- ko.exportSymbol('components.defaultLoader', ko.components.defaultLoader);
3769
-
3770
- // By default, the default loader is the only registered component loader
3771
- ko.components['loaders'].push(ko.components.defaultLoader);
3772
-
3773
- // Privately expose the underlying config registry for use in old-IE shim
3774
- ko.components._allRegisteredComponents = defaultConfigRegistry;
3775
- })();
3776
- (function (undefined) {
3777
- // Overridable API for determining which component name applies to a given node. By overriding this,
3778
- // you can for example map specific tagNames to components that are not preregistered.
3779
- ko.components['getComponentNameForNode'] = function(node) {
3780
- var tagNameLower = ko.utils.tagNameLower(node);
3781
- if (ko.components.isRegistered(tagNameLower)) {
3782
- // Try to determine that this node can be considered a *custom* element; see https://github.com/knockout/knockout/issues/1603
3783
- if (tagNameLower.indexOf('-') != -1 || ('' + node) == "[object HTMLUnknownElement]" || (ko.utils.ieVersion <= 8 && node.tagName === tagNameLower)) {
3784
- return tagNameLower;
3785
- }
3786
- }
3787
- };
3788
-
3789
- ko.components.addBindingsForCustomElement = function(allBindings, node, bindingContext, valueAccessors) {
3790
- // Determine if it's really a custom element matching a component
3791
- if (node.nodeType === 1) {
3792
- var componentName = ko.components['getComponentNameForNode'](node);
3793
- if (componentName) {
3794
- // It does represent a component, so add a component binding for it
3795
- allBindings = allBindings || {};
3796
-
3797
- if (allBindings['component']) {
3798
- // Avoid silently overwriting some other 'component' binding that may already be on the element
3799
- throw new Error('Cannot use the "component" binding on a custom element matching a component');
3800
- }
3801
-
3802
- var componentBindingValue = { 'name': componentName, 'params': getComponentParamsFromCustomElement(node, bindingContext) };
3803
-
3804
- allBindings['component'] = valueAccessors
3805
- ? function() { return componentBindingValue; }
3806
- : componentBindingValue;
3807
- }
3808
- }
3809
-
3810
- return allBindings;
3811
- }
3812
-
3813
- var nativeBindingProviderInstance = new ko.bindingProvider();
3814
-
3815
- function getComponentParamsFromCustomElement(elem, bindingContext) {
3816
- var paramsAttribute = elem.getAttribute('params');
3817
-
3818
- if (paramsAttribute) {
3819
- var params = nativeBindingProviderInstance['parseBindingsString'](paramsAttribute, bindingContext, elem, { 'valueAccessors': true, 'bindingParams': true }),
3820
- rawParamComputedValues = ko.utils.objectMap(params, function(paramValue, paramName) {
3821
- return ko.computed(paramValue, null, { disposeWhenNodeIsRemoved: elem });
3822
- }),
3823
- result = ko.utils.objectMap(rawParamComputedValues, function(paramValueComputed, paramName) {
3824
- var paramValue = paramValueComputed.peek();
3825
- // Does the evaluation of the parameter value unwrap any observables?
3826
- if (!paramValueComputed.isActive()) {
3827
- // No it doesn't, so there's no need for any computed wrapper. Just pass through the supplied value directly.
3828
- // Example: "someVal: firstName, age: 123" (whether or not firstName is an observable/computed)
3829
- return paramValue;
3830
- } else {
3831
- // Yes it does. Supply a computed property that unwraps both the outer (binding expression)
3832
- // level of observability, and any inner (resulting model value) level of observability.
3833
- // This means the component doesn't have to worry about multiple unwrapping. If the value is a
3834
- // writable observable, the computed will also be writable and pass the value on to the observable.
3835
- return ko.computed({
3836
- 'read': function() {
3837
- return ko.utils.unwrapObservable(paramValueComputed());
3838
- },
3839
- 'write': ko.isWriteableObservable(paramValue) && function(value) {
3840
- paramValueComputed()(value);
3841
- },
3842
- disposeWhenNodeIsRemoved: elem
3843
- });
3844
- }
3845
- });
3846
-
3847
- // Give access to the raw computeds, as long as that wouldn't overwrite any custom param also called '$raw'
3848
- // This is in case the developer wants to react to outer (binding) observability separately from inner
3849
- // (model value) observability, or in case the model value observable has subobservables.
3850
- if (!result.hasOwnProperty('$raw')) {
3851
- result['$raw'] = rawParamComputedValues;
3852
- }
3853
-
3854
- return result;
3855
- } else {
3856
- // For consistency, absence of a "params" attribute is treated the same as the presence of
3857
- // any empty one. Otherwise component viewmodels need special code to check whether or not
3858
- // 'params' or 'params.$raw' is null/undefined before reading subproperties, which is annoying.
3859
- return { '$raw': {} };
3860
- }
3861
- }
3862
-
3863
- // --------------------------------------------------------------------------------
3864
- // Compatibility code for older (pre-HTML5) IE browsers
3865
-
3866
- if (ko.utils.ieVersion < 9) {
3867
- // Whenever you preregister a component, enable it as a custom element in the current document
3868
- ko.components['register'] = (function(originalFunction) {
3869
- return function(componentName) {
3870
- document.createElement(componentName); // Allows IE<9 to parse markup containing the custom element
3871
- return originalFunction.apply(this, arguments);
3872
- }
3873
- })(ko.components['register']);
3874
-
3875
- // Whenever you create a document fragment, enable all preregistered component names as custom elements
3876
- // This is needed to make innerShiv/jQuery HTML parsing correctly handle the custom elements
3877
- document.createDocumentFragment = (function(originalFunction) {
3878
- return function() {
3879
- var newDocFrag = originalFunction(),
3880
- allComponents = ko.components._allRegisteredComponents;
3881
- for (var componentName in allComponents) {
3882
- if (allComponents.hasOwnProperty(componentName)) {
3883
- newDocFrag.createElement(componentName);
3884
- }
3885
- }
3886
- return newDocFrag;
3887
- };
3888
- })(document.createDocumentFragment);
3889
- }
3890
- })();(function(undefined) {
3891
-
3892
- var componentLoadingOperationUniqueId = 0;
3893
-
3894
- ko.bindingHandlers['component'] = {
3895
- 'init': function(element, valueAccessor, ignored1, ignored2, bindingContext) {
3896
- var currentViewModel,
3897
- currentLoadingOperationId,
3898
- disposeAssociatedComponentViewModel = function () {
3899
- var currentViewModelDispose = currentViewModel && currentViewModel['dispose'];
3900
- if (typeof currentViewModelDispose === 'function') {
3901
- currentViewModelDispose.call(currentViewModel);
3902
- }
3903
- currentViewModel = null;
3904
- // Any in-flight loading operation is no longer relevant, so make sure we ignore its completion
3905
- currentLoadingOperationId = null;
3906
- },
3907
- originalChildNodes = ko.utils.makeArray(ko.virtualElements.childNodes(element));
3908
-
3909
- ko.utils.domNodeDisposal.addDisposeCallback(element, disposeAssociatedComponentViewModel);
3910
-
3911
- ko.computed(function () {
3912
- var value = ko.utils.unwrapObservable(valueAccessor()),
3913
- componentName, componentParams;
3914
-
3915
- if (typeof value === 'string') {
3916
- componentName = value;
3917
- } else {
3918
- componentName = ko.utils.unwrapObservable(value['name']);
3919
- componentParams = ko.utils.unwrapObservable(value['params']);
3920
- }
3921
-
3922
- if (!componentName) {
3923
- throw new Error('No component name specified');
3924
- }
3925
-
3926
- var loadingOperationId = currentLoadingOperationId = ++componentLoadingOperationUniqueId;
3927
- ko.components.get(componentName, function(componentDefinition) {
3928
- // If this is not the current load operation for this element, ignore it.
3929
- if (currentLoadingOperationId !== loadingOperationId) {
3930
- return;
3931
- }
3932
-
3933
- // Clean up previous state
3934
- disposeAssociatedComponentViewModel();
3935
-
3936
- // Instantiate and bind new component. Implicitly this cleans any old DOM nodes.
3937
- if (!componentDefinition) {
3938
- throw new Error('Unknown component \'' + componentName + '\'');
3939
- }
3940
- cloneTemplateIntoElement(componentName, componentDefinition, element);
3941
- var componentViewModel = createViewModel(componentDefinition, element, originalChildNodes, componentParams),
3942
- childBindingContext = bindingContext['createChildContext'](componentViewModel, /* dataItemAlias */ undefined, function(ctx) {
3943
- ctx['$component'] = componentViewModel;
3944
- ctx['$componentTemplateNodes'] = originalChildNodes;
3945
- });
3946
- currentViewModel = componentViewModel;
3947
- ko.applyBindingsToDescendants(childBindingContext, element);
3948
- });
3949
- }, null, { disposeWhenNodeIsRemoved: element });
3950
-
3951
- return { 'controlsDescendantBindings': true };
3952
- }
3953
- };
3954
-
3955
- ko.virtualElements.allowedBindings['component'] = true;
3956
-
3957
- function cloneTemplateIntoElement(componentName, componentDefinition, element) {
3958
- var template = componentDefinition['template'];
3959
- if (!template) {
3960
- throw new Error('Component \'' + componentName + '\' has no template');
3961
- }
3962
-
3963
- var clonedNodesArray = ko.utils.cloneNodes(template);
3964
- ko.virtualElements.setDomNodeChildren(element, clonedNodesArray);
3965
- }
3966
-
3967
- function createViewModel(componentDefinition, element, originalChildNodes, componentParams) {
3968
- var componentViewModelFactory = componentDefinition['createViewModel'];
3969
- return componentViewModelFactory
3970
- ? componentViewModelFactory.call(componentDefinition, componentParams, { 'element': element, 'templateNodes': originalChildNodes })
3971
- : componentParams; // Template-only component
3972
- }
3973
-
3974
- })();
3975
- var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
3976
- ko.bindingHandlers['attr'] = {
3977
- 'update': function(element, valueAccessor, allBindings) {
3978
- var value = ko.utils.unwrapObservable(valueAccessor()) || {};
3979
- ko.utils.objectForEach(value, function(attrName, attrValue) {
3980
- attrValue = ko.utils.unwrapObservable(attrValue);
3981
-
3982
- // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
3983
- // when someProp is a "no value"-like value (strictly null, false, or undefined)
3984
- // (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
3985
- var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
3986
- if (toRemove)
3987
- element.removeAttribute(attrName);
3988
-
3989
- // In IE <= 7 and IE8 Quirks Mode, you have to use the Javascript property name instead of the
3990
- // HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior,
3991
- // but instead of figuring out the mode, we'll just set the attribute through the Javascript
3992
- // property for IE <= 8.
3993
- if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavascriptMap) {
3994
- attrName = attrHtmlToJavascriptMap[attrName];
3995
- if (toRemove)
3996
- element.removeAttribute(attrName);
3997
- else
3998
- element[attrName] = attrValue;
3999
- } else if (!toRemove) {
4000
- element.setAttribute(attrName, attrValue.toString());
4001
- }
4002
-
4003
- // Treat "name" specially - although you can think of it as an attribute, it also needs
4004
- // special handling on older versions of IE (https://github.com/SteveSanderson/knockout/pull/333)
4005
- // Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
4006
- // entirely, and there's no strong reason to allow for such casing in HTML.
4007
- if (attrName === "name") {
4008
- ko.utils.setElementName(element, toRemove ? "" : attrValue.toString());
4009
- }
4010
- });
4011
- }
4012
- };
4013
- (function() {
4014
-
4015
- ko.bindingHandlers['checked'] = {
4016
- 'after': ['value', 'attr'],
4017
- 'init': function (element, valueAccessor, allBindings) {
4018
- var checkedValue = ko.pureComputed(function() {
4019
- // Treat "value" like "checkedValue" when it is included with "checked" binding
4020
- if (allBindings['has']('checkedValue')) {
4021
- return ko.utils.unwrapObservable(allBindings.get('checkedValue'));
4022
- } else if (allBindings['has']('value')) {
4023
- return ko.utils.unwrapObservable(allBindings.get('value'));
4024
- }
4025
-
4026
- return element.value;
4027
- });
4028
-
4029
- function updateModel() {
4030
- // This updates the model value from the view value.
4031
- // It runs in response to DOM events (click) and changes in checkedValue.
4032
- var isChecked = element.checked,
4033
- elemValue = useCheckedValue ? checkedValue() : isChecked;
4034
-
4035
- // When we're first setting up this computed, don't change any model state.
4036
- if (ko.computedContext.isInitial()) {
4037
- return;
4038
- }
4039
-
4040
- // We can ignore unchecked radio buttons, because some other radio
4041
- // button will be getting checked, and that one can take care of updating state.
4042
- if (isRadio && !isChecked) {
4043
- return;
4044
- }
4045
-
4046
- var modelValue = ko.dependencyDetection.ignore(valueAccessor);
4047
- if (valueIsArray) {
4048
- var writableValue = rawValueIsNonArrayObservable ? modelValue.peek() : modelValue;
4049
- if (oldElemValue !== elemValue) {
4050
- // When we're responding to the checkedValue changing, and the element is
4051
- // currently checked, replace the old elem value with the new elem value
4052
- // in the model array.
4053
- if (isChecked) {
4054
- ko.utils.addOrRemoveItem(writableValue, elemValue, true);
4055
- ko.utils.addOrRemoveItem(writableValue, oldElemValue, false);
4056
- }
4057
-
4058
- oldElemValue = elemValue;
4059
- } else {
4060
- // When we're responding to the user having checked/unchecked a checkbox,
4061
- // add/remove the element value to the model array.
4062
- ko.utils.addOrRemoveItem(writableValue, elemValue, isChecked);
4063
- }
4064
- if (rawValueIsNonArrayObservable && ko.isWriteableObservable(modelValue)) {
4065
- modelValue(writableValue);
4066
- }
4067
- } else {
4068
- ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'checked', elemValue, true);
4069
- }
4070
- };
4071
-
4072
- function updateView() {
4073
- // This updates the view value from the model value.
4074
- // It runs in response to changes in the bound (checked) value.
4075
- var modelValue = ko.utils.unwrapObservable(valueAccessor());
4076
-
4077
- if (valueIsArray) {
4078
- // When a checkbox is bound to an array, being checked represents its value being present in that array
4079
- element.checked = ko.utils.arrayIndexOf(modelValue, checkedValue()) >= 0;
4080
- } else if (isCheckbox) {
4081
- // When a checkbox is bound to any other value (not an array), being checked represents the value being trueish
4082
- element.checked = modelValue;
4083
- } else {
4084
- // For radio buttons, being checked means that the radio button's value corresponds to the model value
4085
- element.checked = (checkedValue() === modelValue);
4086
- }
4087
- };
4088
-
4089
- var isCheckbox = element.type == "checkbox",
4090
- isRadio = element.type == "radio";
4091
-
4092
- // Only bind to check boxes and radio buttons
4093
- if (!isCheckbox && !isRadio) {
4094
- return;
4095
- }
4096
-
4097
- var rawValue = valueAccessor(),
4098
- valueIsArray = isCheckbox && (ko.utils.unwrapObservable(rawValue) instanceof Array),
4099
- rawValueIsNonArrayObservable = !(valueIsArray && rawValue.push && rawValue.splice),
4100
- oldElemValue = valueIsArray ? checkedValue() : undefined,
4101
- useCheckedValue = isRadio || valueIsArray;
4102
-
4103
- // IE 6 won't allow radio buttons to be selected unless they have a name
4104
- if (isRadio && !element.name)
4105
- ko.bindingHandlers['uniqueName']['init'](element, function() { return true });
4106
-
4107
- // Set up two computeds to update the binding:
4108
-
4109
- // The first responds to changes in the checkedValue value and to element clicks
4110
- ko.computed(updateModel, null, { disposeWhenNodeIsRemoved: element });
4111
- ko.utils.registerEventHandler(element, "click", updateModel);
4112
-
4113
- // The second responds to changes in the model value (the one associated with the checked binding)
4114
- ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element });
4115
-
4116
- rawValue = undefined;
4117
- }
4118
- };
4119
- ko.expressionRewriting.twoWayBindings['checked'] = true;
4120
-
4121
- ko.bindingHandlers['checkedValue'] = {
4122
- 'update': function (element, valueAccessor) {
4123
- element.value = ko.utils.unwrapObservable(valueAccessor());
4124
- }
4125
- };
4126
-
4127
- })();var classesWrittenByBindingKey = '__ko__cssValue';
4128
- ko.bindingHandlers['css'] = {
4129
- 'update': function (element, valueAccessor) {
4130
- var value = ko.utils.unwrapObservable(valueAccessor());
4131
- if (value !== null && typeof value == "object") {
4132
- ko.utils.objectForEach(value, function(className, shouldHaveClass) {
4133
- shouldHaveClass = ko.utils.unwrapObservable(shouldHaveClass);
4134
- ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
4135
- });
4136
- } else {
4137
- value = ko.utils.stringTrim(String(value || '')); // Make sure we don't try to store or set a non-string value
4138
- ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
4139
- element[classesWrittenByBindingKey] = value;
4140
- ko.utils.toggleDomNodeCssClass(element, value, true);
4141
- }
4142
- }
4143
- };
4144
- ko.bindingHandlers['enable'] = {
4145
- 'update': function (element, valueAccessor) {
4146
- var value = ko.utils.unwrapObservable(valueAccessor());
4147
- if (value && element.disabled)
4148
- element.removeAttribute("disabled");
4149
- else if ((!value) && (!element.disabled))
4150
- element.disabled = true;
4151
- }
4152
- };
4153
-
4154
- ko.bindingHandlers['disable'] = {
4155
- 'update': function (element, valueAccessor) {
4156
- ko.bindingHandlers['enable']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) });
4157
- }
4158
- };
4159
- // For certain common events (currently just 'click'), allow a simplified data-binding syntax
4160
- // e.g. click:handler instead of the usual full-length event:{click:handler}
4161
- function makeEventHandlerShortcut(eventName) {
4162
- ko.bindingHandlers[eventName] = {
4163
- 'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
4164
- var newValueAccessor = function () {
4165
- var result = {};
4166
- result[eventName] = valueAccessor();
4167
- return result;
4168
- };
4169
- return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindings, viewModel, bindingContext);
4170
- }
4171
- }
4172
- }
4173
-
4174
- ko.bindingHandlers['event'] = {
4175
- 'init' : function (element, valueAccessor, allBindings, viewModel, bindingContext) {
4176
- var eventsToHandle = valueAccessor() || {};
4177
- ko.utils.objectForEach(eventsToHandle, function(eventName) {
4178
- if (typeof eventName == "string") {
4179
- ko.utils.registerEventHandler(element, eventName, function (event) {
4180
- var handlerReturnValue;
4181
- var handlerFunction = valueAccessor()[eventName];
4182
- if (!handlerFunction)
4183
- return;
4184
-
4185
- try {
4186
- // Take all the event args, and prefix with the viewmodel
4187
- var argsForHandler = ko.utils.makeArray(arguments);
4188
- viewModel = bindingContext['$data'];
4189
- argsForHandler.unshift(viewModel);
4190
- handlerReturnValue = handlerFunction.apply(viewModel, argsForHandler);
4191
- } finally {
4192
- if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
4193
- if (event.preventDefault)
4194
- event.preventDefault();
4195
- else
4196
- event.returnValue = false;
4197
- }
4198
- }
4199
-
4200
- var bubble = allBindings.get(eventName + 'Bubble') !== false;
4201
- if (!bubble) {
4202
- event.cancelBubble = true;
4203
- if (event.stopPropagation)
4204
- event.stopPropagation();
4205
- }
4206
- });
4207
- }
4208
- });
4209
- }
4210
- };
4211
- // "foreach: someExpression" is equivalent to "template: { foreach: someExpression }"
4212
- // "foreach: { data: someExpression, afterAdd: myfn }" is equivalent to "template: { foreach: someExpression, afterAdd: myfn }"
4213
- ko.bindingHandlers['foreach'] = {
4214
- makeTemplateValueAccessor: function(valueAccessor) {
4215
- return function() {
4216
- var modelValue = valueAccessor(),
4217
- unwrappedValue = ko.utils.peekObservable(modelValue); // Unwrap without setting a dependency here
4218
-
4219
- // If unwrappedValue is the array, pass in the wrapped value on its own
4220
- // The value will be unwrapped and tracked within the template binding
4221
- // (See https://github.com/SteveSanderson/knockout/issues/523)
4222
- if ((!unwrappedValue) || typeof unwrappedValue.length == "number")
4223
- return { 'foreach': modelValue, 'templateEngine': ko.nativeTemplateEngine.instance };
4224
-
4225
- // If unwrappedValue.data is the array, preserve all relevant options and unwrap again value so we get updates
4226
- ko.utils.unwrapObservable(modelValue);
4227
- return {
4228
- 'foreach': unwrappedValue['data'],
4229
- 'as': unwrappedValue['as'],
4230
- 'includeDestroyed': unwrappedValue['includeDestroyed'],
4231
- 'afterAdd': unwrappedValue['afterAdd'],
4232
- 'beforeRemove': unwrappedValue['beforeRemove'],
4233
- 'afterRender': unwrappedValue['afterRender'],
4234
- 'beforeMove': unwrappedValue['beforeMove'],
4235
- 'afterMove': unwrappedValue['afterMove'],
4236
- 'templateEngine': ko.nativeTemplateEngine.instance
4237
- };
4238
- };
4239
- },
4240
- 'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
4241
- return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor));
4242
- },
4243
- 'update': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
4244
- return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindings, viewModel, bindingContext);
4245
- }
4246
- };
4247
- ko.expressionRewriting.bindingRewriteValidators['foreach'] = false; // Can't rewrite control flow bindings
4248
- ko.virtualElements.allowedBindings['foreach'] = true;
4249
- var hasfocusUpdatingProperty = '__ko_hasfocusUpdating';
4250
- var hasfocusLastValue = '__ko_hasfocusLastValue';
4251
- ko.bindingHandlers['hasfocus'] = {
4252
- 'init': function(element, valueAccessor, allBindings) {
4253
- var handleElementFocusChange = function(isFocused) {
4254
- // Where possible, ignore which event was raised and determine focus state using activeElement,
4255
- // as this avoids phantom focus/blur events raised when changing tabs in modern browsers.
4256
- // However, not all KO-targeted browsers (Firefox 2) support activeElement. For those browsers,
4257
- // prevent a loss of focus when changing tabs/windows by setting a flag that prevents hasfocus
4258
- // from calling 'blur()' on the element when it loses focus.
4259
- // Discussion at https://github.com/SteveSanderson/knockout/pull/352
4260
- element[hasfocusUpdatingProperty] = true;
4261
- var ownerDoc = element.ownerDocument;
4262
- if ("activeElement" in ownerDoc) {
4263
- var active;
4264
- try {
4265
- active = ownerDoc.activeElement;
4266
- } catch(e) {
4267
- // IE9 throws if you access activeElement during page load (see issue #703)
4268
- active = ownerDoc.body;
4269
- }
4270
- isFocused = (active === element);
4271
- }
4272
- var modelValue = valueAccessor();
4273
- ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'hasfocus', isFocused, true);
4274
-
4275
- //cache the latest value, so we can avoid unnecessarily calling focus/blur in the update function
4276
- element[hasfocusLastValue] = isFocused;
4277
- element[hasfocusUpdatingProperty] = false;
4278
- };
4279
- var handleElementFocusIn = handleElementFocusChange.bind(null, true);
4280
- var handleElementFocusOut = handleElementFocusChange.bind(null, false);
4281
-
4282
- ko.utils.registerEventHandler(element, "focus", handleElementFocusIn);
4283
- ko.utils.registerEventHandler(element, "focusin", handleElementFocusIn); // For IE
4284
- ko.utils.registerEventHandler(element, "blur", handleElementFocusOut);
4285
- ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut); // For IE
4286
- },
4287
- 'update': function(element, valueAccessor) {
4288
- var value = !!ko.utils.unwrapObservable(valueAccessor());
4289
-
4290
- if (!element[hasfocusUpdatingProperty] && element[hasfocusLastValue] !== value) {
4291
- value ? element.focus() : element.blur();
4292
-
4293
- // In IE, the blur method doesn't always cause the element to lose focus (for example, if the window is not in focus).
4294
- // Setting focus to the body element does seem to be reliable in IE, but should only be used if we know that the current
4295
- // element was focused already.
4296
- if (!value && element[hasfocusLastValue]) {
4297
- element.ownerDocument.body.focus();
4298
- }
4299
-
4300
- // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
4301
- ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, value ? "focusin" : "focusout"]);
4302
- }
4303
- }
4304
- };
4305
- ko.expressionRewriting.twoWayBindings['hasfocus'] = true;
4306
-
4307
- ko.bindingHandlers['hasFocus'] = ko.bindingHandlers['hasfocus']; // Make "hasFocus" an alias
4308
- ko.expressionRewriting.twoWayBindings['hasFocus'] = true;
4309
- ko.bindingHandlers['html'] = {
4310
- 'init': function() {
4311
- // Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
4312
- return { 'controlsDescendantBindings': true };
4313
- },
4314
- 'update': function (element, valueAccessor) {
4315
- // setHtml will unwrap the value if needed
4316
- ko.utils.setHtml(element, valueAccessor());
4317
- }
4318
- };
4319
- // Makes a binding like with or if
4320
- function makeWithIfBinding(bindingKey, isWith, isNot, makeContextCallback) {
4321
- ko.bindingHandlers[bindingKey] = {
4322
- 'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
4323
- var didDisplayOnLastUpdate,
4324
- savedNodes;
4325
- ko.computed(function() {
4326
- var dataValue = ko.utils.unwrapObservable(valueAccessor()),
4327
- shouldDisplay = !isNot !== !dataValue, // equivalent to isNot ? !dataValue : !!dataValue
4328
- isFirstRender = !savedNodes,
4329
- needsRefresh = isFirstRender || isWith || (shouldDisplay !== didDisplayOnLastUpdate);
4330
-
4331
- if (needsRefresh) {
4332
- // Save a copy of the inner nodes on the initial update, but only if we have dependencies.
4333
- if (isFirstRender && ko.computedContext.getDependenciesCount()) {
4334
- savedNodes = ko.utils.cloneNodes(ko.virtualElements.childNodes(element), true /* shouldCleanNodes */);
4335
- }
4336
-
4337
- if (shouldDisplay) {
4338
- if (!isFirstRender) {
4339
- ko.virtualElements.setDomNodeChildren(element, ko.utils.cloneNodes(savedNodes));
4340
- }
4341
- ko.applyBindingsToDescendants(makeContextCallback ? makeContextCallback(bindingContext, dataValue) : bindingContext, element);
4342
- } else {
4343
- ko.virtualElements.emptyNode(element);
4344
- }
4345
-
4346
- didDisplayOnLastUpdate = shouldDisplay;
4347
- }
4348
- }, null, { disposeWhenNodeIsRemoved: element });
4349
- return { 'controlsDescendantBindings': true };
4350
- }
4351
- };
4352
- ko.expressionRewriting.bindingRewriteValidators[bindingKey] = false; // Can't rewrite control flow bindings
4353
- ko.virtualElements.allowedBindings[bindingKey] = true;
4354
- }
4355
-
4356
- // Construct the actual binding handlers
4357
- makeWithIfBinding('if');
4358
- makeWithIfBinding('ifnot', false /* isWith */, true /* isNot */);
4359
- makeWithIfBinding('with', true /* isWith */, false /* isNot */,
4360
- function(bindingContext, dataValue) {
4361
- return bindingContext['createChildContext'](dataValue);
4362
- }
4363
- );
4364
- var captionPlaceholder = {};
4365
- ko.bindingHandlers['options'] = {
4366
- 'init': function(element) {
4367
- if (ko.utils.tagNameLower(element) !== "select")
4368
- throw new Error("options binding applies only to SELECT elements");
4369
-
4370
- // Remove all existing <option>s.
4371
- while (element.length > 0) {
4372
- element.remove(0);
4373
- }
4374
-
4375
- // Ensures that the binding processor doesn't try to bind the options
4376
- return { 'controlsDescendantBindings': true };
4377
- },
4378
- 'update': function (element, valueAccessor, allBindings) {
4379
- function selectedOptions() {
4380
- return ko.utils.arrayFilter(element.options, function (node) { return node.selected; });
4381
- }
4382
-
4383
- var selectWasPreviouslyEmpty = element.length == 0,
4384
- multiple = element.multiple,
4385
- previousScrollTop = (!selectWasPreviouslyEmpty && multiple) ? element.scrollTop : null,
4386
- unwrappedArray = ko.utils.unwrapObservable(valueAccessor()),
4387
- valueAllowUnset = allBindings.get('valueAllowUnset') && allBindings['has']('value'),
4388
- includeDestroyed = allBindings.get('optionsIncludeDestroyed'),
4389
- arrayToDomNodeChildrenOptions = {},
4390
- captionValue,
4391
- filteredArray,
4392
- previousSelectedValues = [];
4393
-
4394
- if (!valueAllowUnset) {
4395
- if (multiple) {
4396
- previousSelectedValues = ko.utils.arrayMap(selectedOptions(), ko.selectExtensions.readValue);
4397
- } else if (element.selectedIndex >= 0) {
4398
- previousSelectedValues.push(ko.selectExtensions.readValue(element.options[element.selectedIndex]));
4399
- }
4400
- }
4401
-
4402
- if (unwrappedArray) {
4403
- if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
4404
- unwrappedArray = [unwrappedArray];
4405
-
4406
- // Filter out any entries marked as destroyed
4407
- filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
4408
- return includeDestroyed || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
4409
- });
4410
-
4411
- // If caption is included, add it to the array
4412
- if (allBindings['has']('optionsCaption')) {
4413
- captionValue = ko.utils.unwrapObservable(allBindings.get('optionsCaption'));
4414
- // If caption value is null or undefined, don't show a caption
4415
- if (captionValue !== null && captionValue !== undefined) {
4416
- filteredArray.unshift(captionPlaceholder);
4417
- }
4418
- }
4419
- } else {
4420
- // If a falsy value is provided (e.g. null), we'll simply empty the select element
4421
- }
4422
-
4423
- function applyToObject(object, predicate, defaultValue) {
4424
- var predicateType = typeof predicate;
4425
- if (predicateType == "function") // Given a function; run it against the data value
4426
- return predicate(object);
4427
- else if (predicateType == "string") // Given a string; treat it as a property name on the data value
4428
- return object[predicate];
4429
- else // Given no optionsText arg; use the data value itself
4430
- return defaultValue;
4431
- }
4432
-
4433
- // The following functions can run at two different times:
4434
- // The first is when the whole array is being updated directly from this binding handler.
4435
- // The second is when an observable value for a specific array entry is updated.
4436
- // oldOptions will be empty in the first case, but will be filled with the previously generated option in the second.
4437
- var itemUpdate = false;
4438
- function optionForArrayItem(arrayEntry, index, oldOptions) {
4439
- if (oldOptions.length) {
4440
- previousSelectedValues = !valueAllowUnset && oldOptions[0].selected ? [ ko.selectExtensions.readValue(oldOptions[0]) ] : [];
4441
- itemUpdate = true;
4442
- }
4443
- var option = element.ownerDocument.createElement("option");
4444
- if (arrayEntry === captionPlaceholder) {
4445
- ko.utils.setTextContent(option, allBindings.get('optionsCaption'));
4446
- ko.selectExtensions.writeValue(option, undefined);
4447
- } else {
4448
- // Apply a value to the option element
4449
- var optionValue = applyToObject(arrayEntry, allBindings.get('optionsValue'), arrayEntry);
4450
- ko.selectExtensions.writeValue(option, ko.utils.unwrapObservable(optionValue));
4451
-
4452
- // Apply some text to the option element
4453
- var optionText = applyToObject(arrayEntry, allBindings.get('optionsText'), optionValue);
4454
- ko.utils.setTextContent(option, optionText);
4455
- }
4456
- return [option];
4457
- }
4458
-
4459
- // By using a beforeRemove callback, we delay the removal until after new items are added. This fixes a selection
4460
- // problem in IE<=8 and Firefox. See https://github.com/knockout/knockout/issues/1208
4461
- arrayToDomNodeChildrenOptions['beforeRemove'] =
4462
- function (option) {
4463
- element.removeChild(option);
4464
- };
4465
-
4466
- function setSelectionCallback(arrayEntry, newOptions) {
4467
- if (itemUpdate && valueAllowUnset) {
4468
- // The model value is authoritative, so make sure its value is the one selected
4469
- // There is no need to use dependencyDetection.ignore since setDomNodeChildrenFromArrayMapping does so already.
4470
- ko.selectExtensions.writeValue(element, ko.utils.unwrapObservable(allBindings.get('value')), true /* allowUnset */);
4471
- } else if (previousSelectedValues.length) {
4472
- // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
4473
- // That's why we first added them without selection. Now it's time to set the selection.
4474
- var isSelected = ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[0])) >= 0;
4475
- ko.utils.setOptionNodeSelectionState(newOptions[0], isSelected);
4476
-
4477
- // If this option was changed from being selected during a single-item update, notify the change
4478
- if (itemUpdate && !isSelected) {
4479
- ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
4480
- }
4481
- }
4482
- }
4483
-
4484
- var callback = setSelectionCallback;
4485
- if (allBindings['has']('optionsAfterRender') && typeof allBindings.get('optionsAfterRender') == "function") {
4486
- callback = function(arrayEntry, newOptions) {
4487
- setSelectionCallback(arrayEntry, newOptions);
4488
- ko.dependencyDetection.ignore(allBindings.get('optionsAfterRender'), null, [newOptions[0], arrayEntry !== captionPlaceholder ? arrayEntry : undefined]);
4489
- }
4490
- }
4491
-
4492
- ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, arrayToDomNodeChildrenOptions, callback);
4493
-
4494
- ko.dependencyDetection.ignore(function () {
4495
- if (valueAllowUnset) {
4496
- // The model value is authoritative, so make sure its value is the one selected
4497
- ko.selectExtensions.writeValue(element, ko.utils.unwrapObservable(allBindings.get('value')), true /* allowUnset */);
4498
- } else {
4499
- // Determine if the selection has changed as a result of updating the options list
4500
- var selectionChanged;
4501
- if (multiple) {
4502
- // For a multiple-select box, compare the new selection count to the previous one
4503
- // But if nothing was selected before, the selection can't have changed
4504
- selectionChanged = previousSelectedValues.length && selectedOptions().length < previousSelectedValues.length;
4505
- } else {
4506
- // For a single-select box, compare the current value to the previous value
4507
- // But if nothing was selected before or nothing is selected now, just look for a change in selection
4508
- selectionChanged = (previousSelectedValues.length && element.selectedIndex >= 0)
4509
- ? (ko.selectExtensions.readValue(element.options[element.selectedIndex]) !== previousSelectedValues[0])
4510
- : (previousSelectedValues.length || element.selectedIndex >= 0);
4511
- }
4512
-
4513
- // Ensure consistency between model value and selected option.
4514
- // If the dropdown was changed so that selection is no longer the same,
4515
- // notify the value or selectedOptions binding.
4516
- if (selectionChanged) {
4517
- ko.utils.triggerEvent(element, "change");
4518
- }
4519
- }
4520
- });
4521
-
4522
- // Workaround for IE bug
4523
- ko.utils.ensureSelectElementIsRenderedCorrectly(element);
4524
-
4525
- if (previousScrollTop && Math.abs(previousScrollTop - element.scrollTop) > 20)
4526
- element.scrollTop = previousScrollTop;
4527
- }
4528
- };
4529
- ko.bindingHandlers['options'].optionValueDomDataKey = ko.utils.domData.nextKey();
4530
- ko.bindingHandlers['selectedOptions'] = {
4531
- 'after': ['options', 'foreach'],
4532
- 'init': function (element, valueAccessor, allBindings) {
4533
- ko.utils.registerEventHandler(element, "change", function () {
4534
- var value = valueAccessor(), valueToWrite = [];
4535
- ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
4536
- if (node.selected)
4537
- valueToWrite.push(ko.selectExtensions.readValue(node));
4538
- });
4539
- ko.expressionRewriting.writeValueToProperty(value, allBindings, 'selectedOptions', valueToWrite);
4540
- });
4541
- },
4542
- 'update': function (element, valueAccessor) {
4543
- if (ko.utils.tagNameLower(element) != "select")
4544
- throw new Error("values binding applies only to SELECT elements");
4545
-
4546
- var newValue = ko.utils.unwrapObservable(valueAccessor()),
4547
- previousScrollTop = element.scrollTop;
4548
-
4549
- if (newValue && typeof newValue.length == "number") {
4550
- ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
4551
- var isSelected = ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0;
4552
- if (node.selected != isSelected) { // This check prevents flashing of the select element in IE
4553
- ko.utils.setOptionNodeSelectionState(node, isSelected);
4554
- }
4555
- });
4556
- }
4557
-
4558
- element.scrollTop = previousScrollTop;
4559
- }
4560
- };
4561
- ko.expressionRewriting.twoWayBindings['selectedOptions'] = true;
4562
- ko.bindingHandlers['style'] = {
4563
- 'update': function (element, valueAccessor) {
4564
- var value = ko.utils.unwrapObservable(valueAccessor() || {});
4565
- ko.utils.objectForEach(value, function(styleName, styleValue) {
4566
- styleValue = ko.utils.unwrapObservable(styleValue);
4567
-
4568
- if (styleValue === null || styleValue === undefined || styleValue === false) {
4569
- // Empty string removes the value, whereas null/undefined have no effect
4570
- styleValue = "";
4571
- }
4572
-
4573
- element.style[styleName] = styleValue;
4574
- });
4575
- }
4576
- };
4577
- ko.bindingHandlers['submit'] = {
4578
- 'init': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
4579
- if (typeof valueAccessor() != "function")
4580
- throw new Error("The value for a submit binding must be a function");
4581
- ko.utils.registerEventHandler(element, "submit", function (event) {
4582
- var handlerReturnValue;
4583
- var value = valueAccessor();
4584
- try { handlerReturnValue = value.call(bindingContext['$data'], element); }
4585
- finally {
4586
- if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
4587
- if (event.preventDefault)
4588
- event.preventDefault();
4589
- else
4590
- event.returnValue = false;
4591
- }
4592
- }
4593
- });
4594
- }
4595
- };
4596
- ko.bindingHandlers['text'] = {
4597
- 'init': function() {
4598
- // Prevent binding on the dynamically-injected text node (as developers are unlikely to expect that, and it has security implications).
4599
- // It should also make things faster, as we no longer have to consider whether the text node might be bindable.
4600
- return { 'controlsDescendantBindings': true };
4601
- },
4602
- 'update': function (element, valueAccessor) {
4603
- ko.utils.setTextContent(element, valueAccessor());
4604
- }
4605
- };
4606
- ko.virtualElements.allowedBindings['text'] = true;
4607
- (function () {
4608
-
4609
- if (window && window.navigator) {
4610
- var parseVersion = function (matches) {
4611
- if (matches) {
4612
- return parseFloat(matches[1]);
4613
- }
4614
- };
4615
-
4616
- // Detect various browser versions because some old versions don't fully support the 'input' event
4617
- var operaVersion = window.opera && window.opera.version && parseInt(window.opera.version()),
4618
- userAgent = window.navigator.userAgent,
4619
- safariVersion = parseVersion(userAgent.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),
4620
- firefoxVersion = parseVersion(userAgent.match(/Firefox\/([^ ]*)/));
4621
- }
4622
-
4623
- // IE 8 and 9 have bugs that prevent the normal events from firing when the value changes.
4624
- // But it does fire the 'selectionchange' event on many of those, presumably because the
4625
- // cursor is moving and that counts as the selection changing. The 'selectionchange' event is
4626
- // fired at the document level only and doesn't directly indicate which element changed. We
4627
- // set up just one event handler for the document and use 'activeElement' to determine which
4628
- // element was changed.
4629
- if (ko.utils.ieVersion < 10) {
4630
- var selectionChangeRegisteredName = ko.utils.domData.nextKey(),
4631
- selectionChangeHandlerName = ko.utils.domData.nextKey();
4632
- var selectionChangeHandler = function(event) {
4633
- var target = this.activeElement,
4634
- handler = target && ko.utils.domData.get(target, selectionChangeHandlerName);
4635
- if (handler) {
4636
- handler(event);
4637
- }
4638
- };
4639
- var registerForSelectionChangeEvent = function (element, handler) {
4640
- var ownerDoc = element.ownerDocument;
4641
- if (!ko.utils.domData.get(ownerDoc, selectionChangeRegisteredName)) {
4642
- ko.utils.domData.set(ownerDoc, selectionChangeRegisteredName, true);
4643
- ko.utils.registerEventHandler(ownerDoc, 'selectionchange', selectionChangeHandler);
4644
- }
4645
- ko.utils.domData.set(element, selectionChangeHandlerName, handler);
4646
- };
4647
- }
4648
-
4649
- ko.bindingHandlers['textInput'] = {
4650
- 'init': function (element, valueAccessor, allBindings) {
4651
-
4652
- var previousElementValue = element.value,
4653
- timeoutHandle,
4654
- elementValueBeforeEvent;
4655
-
4656
- var updateModel = function (event) {
4657
- clearTimeout(timeoutHandle);
4658
- elementValueBeforeEvent = timeoutHandle = undefined;
4659
-
4660
- var elementValue = element.value;
4661
- if (previousElementValue !== elementValue) {
4662
- // Provide a way for tests to know exactly which event was processed
4663
- if (DEBUG && event) element['_ko_textInputProcessedEvent'] = event.type;
4664
- previousElementValue = elementValue;
4665
- ko.expressionRewriting.writeValueToProperty(valueAccessor(), allBindings, 'textInput', elementValue);
4666
- }
4667
- };
4668
-
4669
- var deferUpdateModel = function (event) {
4670
- if (!timeoutHandle) {
4671
- // The elementValueBeforeEvent variable is set *only* during the brief gap between an
4672
- // event firing and the updateModel function running. This allows us to ignore model
4673
- // updates that are from the previous state of the element, usually due to techniques
4674
- // such as rateLimit. Such updates, if not ignored, can cause keystrokes to be lost.
4675
- elementValueBeforeEvent = element.value;
4676
- var handler = DEBUG ? updateModel.bind(element, {type: event.type}) : updateModel;
4677
- timeoutHandle = ko.utils.setTimeout(handler, 4);
4678
- }
4679
- };
4680
-
4681
- // IE9 will mess up the DOM if you handle events synchronously which results in DOM changes (such as other bindings);
4682
- // so we'll make sure all updates are asynchronous
4683
- var ieUpdateModel = ko.utils.ieVersion == 9 ? deferUpdateModel : updateModel;
4684
-
4685
- var updateView = function () {
4686
- var modelValue = ko.utils.unwrapObservable(valueAccessor());
4687
-
4688
- if (modelValue === null || modelValue === undefined) {
4689
- modelValue = '';
4690
- }
4691
-
4692
- if (elementValueBeforeEvent !== undefined && modelValue === elementValueBeforeEvent) {
4693
- ko.utils.setTimeout(updateView, 4);
4694
- return;
4695
- }
4696
-
4697
- // Update the element only if the element and model are different. On some browsers, updating the value
4698
- // will move the cursor to the end of the input, which would be bad while the user is typing.
4699
- if (element.value !== modelValue) {
4700
- previousElementValue = modelValue; // Make sure we ignore events (propertychange) that result from updating the value
4701
- element.value = modelValue;
4702
- }
4703
- };
4704
-
4705
- var onEvent = function (event, handler) {
4706
- ko.utils.registerEventHandler(element, event, handler);
4707
- };
4708
-
4709
- if (DEBUG && ko.bindingHandlers['textInput']['_forceUpdateOn']) {
4710
- // Provide a way for tests to specify exactly which events are bound
4711
- ko.utils.arrayForEach(ko.bindingHandlers['textInput']['_forceUpdateOn'], function(eventName) {
4712
- if (eventName.slice(0,5) == 'after') {
4713
- onEvent(eventName.slice(5), deferUpdateModel);
4714
- } else {
4715
- onEvent(eventName, updateModel);
4716
- }
4717
- });
4718
- } else {
4719
- if (ko.utils.ieVersion < 10) {
4720
- // Internet Explorer <= 8 doesn't support the 'input' event, but does include 'propertychange' that fires whenever
4721
- // any property of an element changes. Unlike 'input', it also fires if a property is changed from JavaScript code,
4722
- // but that's an acceptable compromise for this binding. IE 9 does support 'input', but since it doesn't fire it
4723
- // when using autocomplete, we'll use 'propertychange' for it also.
4724
- onEvent('propertychange', function(event) {
4725
- if (event.propertyName === 'value') {
4726
- ieUpdateModel(event);
4727
- }
4728
- });
4729
-
4730
- if (ko.utils.ieVersion == 8) {
4731
- // IE 8 has a bug where it fails to fire 'propertychange' on the first update following a value change from
4732
- // JavaScript code. It also doesn't fire if you clear the entire value. To fix this, we bind to the following
4733
- // events too.
4734
- onEvent('keyup', updateModel); // A single keystoke
4735
- onEvent('keydown', updateModel); // The first character when a key is held down
4736
- }
4737
- if (ko.utils.ieVersion >= 8) {
4738
- // Internet Explorer 9 doesn't fire the 'input' event when deleting text, including using
4739
- // the backspace, delete, or ctrl-x keys, clicking the 'x' to clear the input, dragging text
4740
- // out of the field, and cutting or deleting text using the context menu. 'selectionchange'
4741
- // can detect all of those except dragging text out of the field, for which we use 'dragend'.
4742
- // These are also needed in IE8 because of the bug described above.
4743
- registerForSelectionChangeEvent(element, ieUpdateModel); // 'selectionchange' covers cut, paste, drop, delete, etc.
4744
- onEvent('dragend', deferUpdateModel);
4745
- }
4746
- } else {
4747
- // All other supported browsers support the 'input' event, which fires whenever the content of the element is changed
4748
- // through the user interface.
4749
- onEvent('input', updateModel);
4750
-
4751
- if (safariVersion < 5 && ko.utils.tagNameLower(element) === "textarea") {
4752
- // Safari <5 doesn't fire the 'input' event for <textarea> elements (it does fire 'textInput'
4753
- // but only when typing). So we'll just catch as much as we can with keydown, cut, and paste.
4754
- onEvent('keydown', deferUpdateModel);
4755
- onEvent('paste', deferUpdateModel);
4756
- onEvent('cut', deferUpdateModel);
4757
- } else if (operaVersion < 11) {
4758
- // Opera 10 doesn't always fire the 'input' event for cut, paste, undo & drop operations.
4759
- // We can try to catch some of those using 'keydown'.
4760
- onEvent('keydown', deferUpdateModel);
4761
- } else if (firefoxVersion < 4.0) {
4762
- // Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete
4763
- onEvent('DOMAutoComplete', updateModel);
4764
-
4765
- // Firefox <=3.5 doesn't fire the 'input' event when text is dropped into the input.
4766
- onEvent('dragdrop', updateModel); // <3.5
4767
- onEvent('drop', updateModel); // 3.5
4768
- }
4769
- }
4770
- }
4771
-
4772
- // Bind to the change event so that we can catch programmatic updates of the value that fire this event.
4773
- onEvent('change', updateModel);
4774
-
4775
- ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element });
4776
- }
4777
- };
4778
- ko.expressionRewriting.twoWayBindings['textInput'] = true;
4779
-
4780
- // textinput is an alias for textInput
4781
- ko.bindingHandlers['textinput'] = {
4782
- // preprocess is the only way to set up a full alias
4783
- 'preprocess': function (value, name, addBinding) {
4784
- addBinding('textInput', value);
4785
- }
4786
- };
4787
-
4788
- })();ko.bindingHandlers['uniqueName'] = {
4789
- 'init': function (element, valueAccessor) {
4790
- if (valueAccessor()) {
4791
- var name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex);
4792
- ko.utils.setElementName(element, name);
4793
- }
4794
- }
4795
- };
4796
- ko.bindingHandlers['uniqueName'].currentIndex = 0;
4797
- ko.bindingHandlers['value'] = {
4798
- 'after': ['options', 'foreach'],
4799
- 'init': function (element, valueAccessor, allBindings) {
4800
- // If the value binding is placed on a radio/checkbox, then just pass through to checkedValue and quit
4801
- if (element.tagName.toLowerCase() == "input" && (element.type == "checkbox" || element.type == "radio")) {
4802
- ko.applyBindingAccessorsToNode(element, { 'checkedValue': valueAccessor });
4803
- return;
4804
- }
4805
-
4806
- // Always catch "change" event; possibly other events too if asked
4807
- var eventsToCatch = ["change"];
4808
- var requestedEventsToCatch = allBindings.get("valueUpdate");
4809
- var propertyChangedFired = false;
4810
- var elementValueBeforeEvent = null;
4811
-
4812
- if (requestedEventsToCatch) {
4813
- if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
4814
- requestedEventsToCatch = [requestedEventsToCatch];
4815
- ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
4816
- eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
4817
- }
4818
-
4819
- var valueUpdateHandler = function() {
4820
- elementValueBeforeEvent = null;
4821
- propertyChangedFired = false;
4822
- var modelValue = valueAccessor();
4823
- var elementValue = ko.selectExtensions.readValue(element);
4824
- ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'value', elementValue);
4825
- }
4826
-
4827
- // Workaround for https://github.com/SteveSanderson/knockout/issues/122
4828
- // IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
4829
- var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
4830
- && element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
4831
- if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
4832
- ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
4833
- ko.utils.registerEventHandler(element, "focus", function () { propertyChangedFired = false });
4834
- ko.utils.registerEventHandler(element, "blur", function() {
4835
- if (propertyChangedFired) {
4836
- valueUpdateHandler();
4837
- }
4838
- });
4839
- }
4840
-
4841
- ko.utils.arrayForEach(eventsToCatch, function(eventName) {
4842
- // The syntax "after<eventname>" means "run the handler asynchronously after the event"
4843
- // This is useful, for example, to catch "keydown" events after the browser has updated the control
4844
- // (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
4845
- var handler = valueUpdateHandler;
4846
- if (ko.utils.stringStartsWith(eventName, "after")) {
4847
- handler = function() {
4848
- // The elementValueBeforeEvent variable is non-null *only* during the brief gap between
4849
- // a keyX event firing and the valueUpdateHandler running, which is scheduled to happen
4850
- // at the earliest asynchronous opportunity. We store this temporary information so that
4851
- // if, between keyX and valueUpdateHandler, the underlying model value changes separately,
4852
- // we can overwrite that model value change with the value the user just typed. Otherwise,
4853
- // techniques like rateLimit can trigger model changes at critical moments that will
4854
- // override the user's inputs, causing keystrokes to be lost.
4855
- elementValueBeforeEvent = ko.selectExtensions.readValue(element);
4856
- ko.utils.setTimeout(valueUpdateHandler, 0);
4857
- };
4858
- eventName = eventName.substring("after".length);
4859
- }
4860
- ko.utils.registerEventHandler(element, eventName, handler);
4861
- });
4862
-
4863
- var updateFromModel = function () {
4864
- var newValue = ko.utils.unwrapObservable(valueAccessor());
4865
- var elementValue = ko.selectExtensions.readValue(element);
4866
-
4867
- if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
4868
- ko.utils.setTimeout(updateFromModel, 0);
4869
- return;
4870
- }
4871
-
4872
- var valueHasChanged = (newValue !== elementValue);
4873
-
4874
- if (valueHasChanged) {
4875
- if (ko.utils.tagNameLower(element) === "select") {
4876
- var allowUnset = allBindings.get('valueAllowUnset');
4877
- var applyValueAction = function () {
4878
- ko.selectExtensions.writeValue(element, newValue, allowUnset);
4879
- };
4880
- applyValueAction();
4881
-
4882
- if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
4883
- // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
4884
- // because you're not allowed to have a model value that disagrees with a visible UI selection.
4885
- ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
4886
- } else {
4887
- // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
4888
- // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
4889
- // to apply the value as well.
4890
- ko.utils.setTimeout(applyValueAction, 0);
4891
- }
4892
- } else {
4893
- ko.selectExtensions.writeValue(element, newValue);
4894
- }
4895
- }
4896
- };
4897
-
4898
- ko.computed(updateFromModel, null, { disposeWhenNodeIsRemoved: element });
4899
- },
4900
- 'update': function() {} // Keep for backwards compatibility with code that may have wrapped value binding
4901
- };
4902
- ko.expressionRewriting.twoWayBindings['value'] = true;
4903
- ko.bindingHandlers['visible'] = {
4904
- 'update': function (element, valueAccessor) {
4905
- var value = ko.utils.unwrapObservable(valueAccessor());
4906
- var isCurrentlyVisible = !(element.style.display == "none");
4907
- if (value && !isCurrentlyVisible)
4908
- element.style.display = "";
4909
- else if ((!value) && isCurrentlyVisible)
4910
- element.style.display = "none";
4911
- }
4912
- };
4913
- // 'click' is just a shorthand for the usual full-length event:{click:handler}
4914
- makeEventHandlerShortcut('click');
4915
- // If you want to make a custom template engine,
4916
- //
4917
- // [1] Inherit from this class (like ko.nativeTemplateEngine does)
4918
- // [2] Override 'renderTemplateSource', supplying a function with this signature:
4919
- //
4920
- // function (templateSource, bindingContext, options) {
4921
- // // - templateSource.text() is the text of the template you should render
4922
- // // - bindingContext.$data is the data you should pass into the template
4923
- // // - you might also want to make bindingContext.$parent, bindingContext.$parents,
4924
- // // and bindingContext.$root available in the template too
4925
- // // - options gives you access to any other properties set on "data-bind: { template: options }"
4926
- // // - templateDocument is the document object of the template
4927
- // //
4928
- // // Return value: an array of DOM nodes
4929
- // }
4930
- //
4931
- // [3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
4932
- //
4933
- // function (script) {
4934
- // // Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result"
4935
- // // For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
4936
- // }
4937
- //
4938
- // This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
4939
- // If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
4940
- // and then you don't need to override 'createJavaScriptEvaluatorBlock'.
4941
-
4942
- ko.templateEngine = function () { };
4943
-
4944
- ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options, templateDocument) {
4945
- throw new Error("Override renderTemplateSource");
4946
- };
4947
-
4948
- ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) {
4949
- throw new Error("Override createJavaScriptEvaluatorBlock");
4950
- };
4951
-
4952
- ko.templateEngine.prototype['makeTemplateSource'] = function(template, templateDocument) {
4953
- // Named template
4954
- if (typeof template == "string") {
4955
- templateDocument = templateDocument || document;
4956
- var elem = templateDocument.getElementById(template);
4957
- if (!elem)
4958
- throw new Error("Cannot find template with ID " + template);
4959
- return new ko.templateSources.domElement(elem);
4960
- } else if ((template.nodeType == 1) || (template.nodeType == 8)) {
4961
- // Anonymous template
4962
- return new ko.templateSources.anonymousTemplate(template);
4963
- } else
4964
- throw new Error("Unknown template type: " + template);
4965
- };
4966
-
4967
- ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options, templateDocument) {
4968
- var templateSource = this['makeTemplateSource'](template, templateDocument);
4969
- return this['renderTemplateSource'](templateSource, bindingContext, options, templateDocument);
4970
- };
4971
-
4972
- ko.templateEngine.prototype['isTemplateRewritten'] = function (template, templateDocument) {
4973
- // Skip rewriting if requested
4974
- if (this['allowTemplateRewriting'] === false)
4975
- return true;
4976
- return this['makeTemplateSource'](template, templateDocument)['data']("isRewritten");
4977
- };
4978
-
4979
- ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCallback, templateDocument) {
4980
- var templateSource = this['makeTemplateSource'](template, templateDocument);
4981
- var rewritten = rewriterCallback(templateSource['text']());
4982
- templateSource['text'](rewritten);
4983
- templateSource['data']("isRewritten", true);
4984
- };
4985
-
4986
- ko.exportSymbol('templateEngine', ko.templateEngine);
4987
-
4988
- ko.templateRewriting = (function () {
4989
- var memoizeDataBindingAttributeSyntaxRegex = /(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi;
4990
- var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
4991
-
4992
- function validateDataBindValuesForRewriting(keyValueArray) {
4993
- var allValidators = ko.expressionRewriting.bindingRewriteValidators;
4994
- for (var i = 0; i < keyValueArray.length; i++) {
4995
- var key = keyValueArray[i]['key'];
4996
- if (allValidators.hasOwnProperty(key)) {
4997
- var validator = allValidators[key];
4998
-
4999
- if (typeof validator === "function") {
5000
- var possibleErrorMessage = validator(keyValueArray[i]['value']);
5001
- if (possibleErrorMessage)
5002
- throw new Error(possibleErrorMessage);
5003
- } else if (!validator) {
5004
- throw new Error("This template engine does not support the '" + key + "' binding within its templates");
5005
- }
5006
- }
5007
- }
5008
- }
5009
-
5010
- function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, nodeName, templateEngine) {
5011
- var dataBindKeyValueArray = ko.expressionRewriting.parseObjectLiteral(dataBindAttributeValue);
5012
- validateDataBindValuesForRewriting(dataBindKeyValueArray);
5013
- var rewrittenDataBindAttributeValue = ko.expressionRewriting.preProcessBindings(dataBindKeyValueArray, {'valueAccessors':true});
5014
-
5015
- // For no obvious reason, Opera fails to evaluate rewrittenDataBindAttributeValue unless it's wrapped in an additional
5016
- // anonymous function, even though Opera's built-in debugger can evaluate it anyway. No other browser requires this
5017
- // extra indirection.
5018
- var applyBindingsToNextSiblingScript =
5019
- "ko.__tr_ambtns(function($context,$element){return(function(){return{ " + rewrittenDataBindAttributeValue + " } })()},'" + nodeName.toLowerCase() + "')";
5020
- return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain;
5021
- }
5022
-
5023
- return {
5024
- ensureTemplateIsRewritten: function (template, templateEngine, templateDocument) {
5025
- if (!templateEngine['isTemplateRewritten'](template, templateDocument))
5026
- templateEngine['rewriteTemplate'](template, function (htmlString) {
5027
- return ko.templateRewriting.memoizeBindingAttributeSyntax(htmlString, templateEngine);
5028
- }, templateDocument);
5029
- },
5030
-
5031
- memoizeBindingAttributeSyntax: function (htmlString, templateEngine) {
5032
- return htmlString.replace(memoizeDataBindingAttributeSyntaxRegex, function () {
5033
- return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[4], /* tagToRetain: */ arguments[1], /* nodeName: */ arguments[2], templateEngine);
5034
- }).replace(memoizeVirtualContainerBindingSyntaxRegex, function() {
5035
- return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[1], /* tagToRetain: */ "<!-- ko -->", /* nodeName: */ "#comment", templateEngine);
5036
- });
5037
- },
5038
-
5039
- applyMemoizedBindingsToNextSibling: function (bindings, nodeName) {
5040
- return ko.memoization.memoize(function (domNode, bindingContext) {
5041
- var nodeToBind = domNode.nextSibling;
5042
- if (nodeToBind && nodeToBind.nodeName.toLowerCase() === nodeName) {
5043
- ko.applyBindingAccessorsToNode(nodeToBind, bindings, bindingContext);
5044
- }
5045
- });
5046
- }
5047
- }
5048
- })();
5049
-
5050
-
5051
- // Exported only because it has to be referenced by string lookup from within rewritten template
5052
- ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextSibling);
5053
- (function() {
5054
- // A template source represents a read/write way of accessing a template. This is to eliminate the need for template loading/saving
5055
- // logic to be duplicated in every template engine (and means they can all work with anonymous templates, etc.)
5056
- //
5057
- // Two are provided by default:
5058
- // 1. ko.templateSources.domElement - reads/writes the text content of an arbitrary DOM element
5059
- // 2. ko.templateSources.anonymousElement - uses ko.utils.domData to read/write text *associated* with the DOM element, but
5060
- // without reading/writing the actual element text content, since it will be overwritten
5061
- // with the rendered template output.
5062
- // You can implement your own template source if you want to fetch/store templates somewhere other than in DOM elements.
5063
- // Template sources need to have the following functions:
5064
- // text() - returns the template text from your storage location
5065
- // text(value) - writes the supplied template text to your storage location
5066
- // data(key) - reads values stored using data(key, value) - see below
5067
- // data(key, value) - associates "value" with this template and the key "key". Is used to store information like "isRewritten".
5068
- //
5069
- // Optionally, template sources can also have the following functions:
5070
- // nodes() - returns a DOM element containing the nodes of this template, where available
5071
- // nodes(value) - writes the given DOM element to your storage location
5072
- // If a DOM element is available for a given template source, template engines are encouraged to use it in preference over text()
5073
- // for improved speed. However, all templateSources must supply text() even if they don't supply nodes().
5074
- //
5075
- // Once you've implemented a templateSource, make your template engine use it by subclassing whatever template engine you were
5076
- // using and overriding "makeTemplateSource" to return an instance of your custom template source.
5077
-
5078
- ko.templateSources = {};
5079
-
5080
- // ---- ko.templateSources.domElement -----
5081
-
5082
- // template types
5083
- var templateScript = 1,
5084
- templateTextArea = 2,
5085
- templateTemplate = 3,
5086
- templateElement = 4;
5087
-
5088
- ko.templateSources.domElement = function(element) {
5089
- this.domElement = element;
5090
-
5091
- if (element) {
5092
- var tagNameLower = ko.utils.tagNameLower(element);
5093
- this.templateType =
5094
- tagNameLower === "script" ? templateScript :
5095
- tagNameLower === "textarea" ? templateTextArea :
5096
- // For browsers with proper <template> element support, where the .content property gives a document fragment
5097
- tagNameLower == "template" && element.content && element.content.nodeType === 11 ? templateTemplate :
5098
- templateElement;
5099
- }
5100
- }
5101
-
5102
- ko.templateSources.domElement.prototype['text'] = function(/* valueToWrite */) {
5103
- var elemContentsProperty = this.templateType === templateScript ? "text"
5104
- : this.templateType === templateTextArea ? "value"
5105
- : "innerHTML";
5106
-
5107
- if (arguments.length == 0) {
5108
- return this.domElement[elemContentsProperty];
5109
- } else {
5110
- var valueToWrite = arguments[0];
5111
- if (elemContentsProperty === "innerHTML")
5112
- ko.utils.setHtml(this.domElement, valueToWrite);
5113
- else
5114
- this.domElement[elemContentsProperty] = valueToWrite;
5115
- }
5116
- };
5117
-
5118
- var dataDomDataPrefix = ko.utils.domData.nextKey() + "_";
5119
- ko.templateSources.domElement.prototype['data'] = function(key /*, valueToWrite */) {
5120
- if (arguments.length === 1) {
5121
- return ko.utils.domData.get(this.domElement, dataDomDataPrefix + key);
5122
- } else {
5123
- ko.utils.domData.set(this.domElement, dataDomDataPrefix + key, arguments[1]);
5124
- }
5125
- };
5126
-
5127
- var templatesDomDataKey = ko.utils.domData.nextKey();
5128
- function getTemplateDomData(element) {
5129
- return ko.utils.domData.get(element, templatesDomDataKey) || {};
5130
- }
5131
- function setTemplateDomData(element, data) {
5132
- ko.utils.domData.set(element, templatesDomDataKey, data);
5133
- }
5134
-
5135
- ko.templateSources.domElement.prototype['nodes'] = function(/* valueToWrite */) {
5136
- var element = this.domElement;
5137
- if (arguments.length == 0) {
5138
- var templateData = getTemplateDomData(element),
5139
- containerData = templateData.containerData;
5140
- return containerData || (
5141
- this.templateType === templateTemplate ? element.content :
5142
- this.templateType === templateElement ? element :
5143
- undefined);
5144
- } else {
5145
- var valueToWrite = arguments[0];
5146
- setTemplateDomData(element, {containerData: valueToWrite});
5147
- }
5148
- };
5149
-
5150
- // ---- ko.templateSources.anonymousTemplate -----
5151
- // Anonymous templates are normally saved/retrieved as DOM nodes through "nodes".
5152
- // For compatibility, you can also read "text"; it will be serialized from the nodes on demand.
5153
- // Writing to "text" is still supported, but then the template data will not be available as DOM nodes.
5154
-
5155
- ko.templateSources.anonymousTemplate = function(element) {
5156
- this.domElement = element;
5157
- }
5158
- ko.templateSources.anonymousTemplate.prototype = new ko.templateSources.domElement();
5159
- ko.templateSources.anonymousTemplate.prototype.constructor = ko.templateSources.anonymousTemplate;
5160
- ko.templateSources.anonymousTemplate.prototype['text'] = function(/* valueToWrite */) {
5161
- if (arguments.length == 0) {
5162
- var templateData = getTemplateDomData(this.domElement);
5163
- if (templateData.textData === undefined && templateData.containerData)
5164
- templateData.textData = templateData.containerData.innerHTML;
5165
- return templateData.textData;
5166
- } else {
5167
- var valueToWrite = arguments[0];
5168
- setTemplateDomData(this.domElement, {textData: valueToWrite});
5169
- }
5170
- };
5171
-
5172
- ko.exportSymbol('templateSources', ko.templateSources);
5173
- ko.exportSymbol('templateSources.domElement', ko.templateSources.domElement);
5174
- ko.exportSymbol('templateSources.anonymousTemplate', ko.templateSources.anonymousTemplate);
5175
- })();
5176
- (function () {
5177
- var _templateEngine;
5178
- ko.setTemplateEngine = function (templateEngine) {
5179
- if ((templateEngine != undefined) && !(templateEngine instanceof ko.templateEngine))
5180
- throw new Error("templateEngine must inherit from ko.templateEngine");
5181
- _templateEngine = templateEngine;
5182
- }
5183
-
5184
- function invokeForEachNodeInContinuousRange(firstNode, lastNode, action) {
5185
- var node, nextInQueue = firstNode, firstOutOfRangeNode = ko.virtualElements.nextSibling(lastNode);
5186
- while (nextInQueue && ((node = nextInQueue) !== firstOutOfRangeNode)) {
5187
- nextInQueue = ko.virtualElements.nextSibling(node);
5188
- action(node, nextInQueue);
5189
- }
5190
- }
5191
-
5192
- function activateBindingsOnContinuousNodeArray(continuousNodeArray, bindingContext) {
5193
- // To be used on any nodes that have been rendered by a template and have been inserted into some parent element
5194
- // Walks through continuousNodeArray (which *must* be continuous, i.e., an uninterrupted sequence of sibling nodes, because
5195
- // the algorithm for walking them relies on this), and for each top-level item in the virtual-element sense,
5196
- // (1) Does a regular "applyBindings" to associate bindingContext with this node and to activate any non-memoized bindings
5197
- // (2) Unmemoizes any memos in the DOM subtree (e.g., to activate bindings that had been memoized during template rewriting)
5198
-
5199
- if (continuousNodeArray.length) {
5200
- var firstNode = continuousNodeArray[0],
5201
- lastNode = continuousNodeArray[continuousNodeArray.length - 1],
5202
- parentNode = firstNode.parentNode,
5203
- provider = ko.bindingProvider['instance'],
5204
- preprocessNode = provider['preprocessNode'];
5205
-
5206
- if (preprocessNode) {
5207
- invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node, nextNodeInRange) {
5208
- var nodePreviousSibling = node.previousSibling;
5209
- var newNodes = preprocessNode.call(provider, node);
5210
- if (newNodes) {
5211
- if (node === firstNode)
5212
- firstNode = newNodes[0] || nextNodeInRange;
5213
- if (node === lastNode)
5214
- lastNode = newNodes[newNodes.length - 1] || nodePreviousSibling;
5215
- }
5216
- });
5217
-
5218
- // Because preprocessNode can change the nodes, including the first and last nodes, update continuousNodeArray to match.
5219
- // We need the full set, including inner nodes, because the unmemoize step might remove the first node (and so the real
5220
- // first node needs to be in the array).
5221
- continuousNodeArray.length = 0;
5222
- if (!firstNode) { // preprocessNode might have removed all the nodes, in which case there's nothing left to do
5223
- return;
5224
- }
5225
- if (firstNode === lastNode) {
5226
- continuousNodeArray.push(firstNode);
5227
- } else {
5228
- continuousNodeArray.push(firstNode, lastNode);
5229
- ko.utils.fixUpContinuousNodeArray(continuousNodeArray, parentNode);
5230
- }
5231
- }
5232
-
5233
- // Need to applyBindings *before* unmemoziation, because unmemoization might introduce extra nodes (that we don't want to re-bind)
5234
- // whereas a regular applyBindings won't introduce new memoized nodes
5235
- invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node) {
5236
- if (node.nodeType === 1 || node.nodeType === 8)
5237
- ko.applyBindings(bindingContext, node);
5238
- });
5239
- invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node) {
5240
- if (node.nodeType === 1 || node.nodeType === 8)
5241
- ko.memoization.unmemoizeDomNodeAndDescendants(node, [bindingContext]);
5242
- });
5243
-
5244
- // Make sure any changes done by applyBindings or unmemoize are reflected in the array
5245
- ko.utils.fixUpContinuousNodeArray(continuousNodeArray, parentNode);
5246
- }
5247
- }
5248
-
5249
- function getFirstNodeFromPossibleArray(nodeOrNodeArray) {
5250
- return nodeOrNodeArray.nodeType ? nodeOrNodeArray
5251
- : nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0]
5252
- : null;
5253
- }
5254
-
5255
- function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext, options) {
5256
- options = options || {};
5257
- var firstTargetNode = targetNodeOrNodeArray && getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
5258
- var templateDocument = (firstTargetNode || template || {}).ownerDocument;
5259
- var templateEngineToUse = (options['templateEngine'] || _templateEngine);
5260
- ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse, templateDocument);
5261
- var renderedNodesArray = templateEngineToUse['renderTemplate'](template, bindingContext, options, templateDocument);
5262
-
5263
- // Loosely check result is an array of DOM nodes
5264
- if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number"))
5265
- throw new Error("Template engine must return an array of DOM nodes");
5266
-
5267
- var haveAddedNodesToParent = false;
5268
- switch (renderMode) {
5269
- case "replaceChildren":
5270
- ko.virtualElements.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray);
5271
- haveAddedNodesToParent = true;
5272
- break;
5273
- case "replaceNode":
5274
- ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray);
5275
- haveAddedNodesToParent = true;
5276
- break;
5277
- case "ignoreTargetNode": break;
5278
- default:
5279
- throw new Error("Unknown renderMode: " + renderMode);
5280
- }
5281
-
5282
- if (haveAddedNodesToParent) {
5283
- activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext);
5284
- if (options['afterRender'])
5285
- ko.dependencyDetection.ignore(options['afterRender'], null, [renderedNodesArray, bindingContext['$data']]);
5286
- }
5287
-
5288
- return renderedNodesArray;
5289
- }
5290
-
5291
- function resolveTemplateName(template, data, context) {
5292
- // The template can be specified as:
5293
- if (ko.isObservable(template)) {
5294
- // 1. An observable, with string value
5295
- return template();
5296
- } else if (typeof template === 'function') {
5297
- // 2. A function of (data, context) returning a string
5298
- return template(data, context);
5299
- } else {
5300
- // 3. A string
5301
- return template;
5302
- }
5303
- }
5304
-
5305
- ko.renderTemplate = function (template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode) {
5306
- options = options || {};
5307
- if ((options['templateEngine'] || _templateEngine) == undefined)
5308
- throw new Error("Set a template engine before calling renderTemplate");
5309
- renderMode = renderMode || "replaceChildren";
5310
-
5311
- if (targetNodeOrNodeArray) {
5312
- var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
5313
-
5314
- var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation)
5315
- var activelyDisposeWhenNodeIsRemoved = (firstTargetNode && renderMode == "replaceNode") ? firstTargetNode.parentNode : firstTargetNode;
5316
-
5317
- return ko.dependentObservable( // So the DOM is automatically updated when any dependency changes
5318
- function () {
5319
- // Ensure we've got a proper binding context to work with
5320
- var bindingContext = (dataOrBindingContext && (dataOrBindingContext instanceof ko.bindingContext))
5321
- ? dataOrBindingContext
5322
- : new ko.bindingContext(ko.utils.unwrapObservable(dataOrBindingContext));
5323
-
5324
- var templateName = resolveTemplateName(template, bindingContext['$data'], bindingContext),
5325
- renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
5326
-
5327
- if (renderMode == "replaceNode") {
5328
- targetNodeOrNodeArray = renderedNodesArray;
5329
- firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
5330
- }
5331
- },
5332
- null,
5333
- { disposeWhen: whenToDispose, disposeWhenNodeIsRemoved: activelyDisposeWhenNodeIsRemoved }
5334
- );
5335
- } else {
5336
- // We don't yet have a DOM node to evaluate, so use a memo and render the template later when there is a DOM node
5337
- return ko.memoization.memoize(function (domNode) {
5338
- ko.renderTemplate(template, dataOrBindingContext, options, domNode, "replaceNode");
5339
- });
5340
- }
5341
- };
5342
-
5343
- ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
5344
- // Since setDomNodeChildrenFromArrayMapping always calls executeTemplateForArrayItem and then
5345
- // activateBindingsCallback for added items, we can store the binding context in the former to use in the latter.
5346
- var arrayItemContext;
5347
-
5348
- // This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
5349
- var executeTemplateForArrayItem = function (arrayValue, index) {
5350
- // Support selecting template as a function of the data being rendered
5351
- arrayItemContext = parentBindingContext['createChildContext'](arrayValue, options['as'], function(context) {
5352
- context['$index'] = index;
5353
- });
5354
-
5355
- var templateName = resolveTemplateName(template, arrayValue, arrayItemContext);
5356
- return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
5357
- }
5358
-
5359
- // This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode
5360
- var activateBindingsCallback = function(arrayValue, addedNodesArray, index) {
5361
- activateBindingsOnContinuousNodeArray(addedNodesArray, arrayItemContext);
5362
- if (options['afterRender'])
5363
- options['afterRender'](addedNodesArray, arrayValue);
5364
-
5365
- // release the "cache" variable, so that it can be collected by
5366
- // the GC when its value isn't used from within the bindings anymore.
5367
- arrayItemContext = null;
5368
- };
5369
-
5370
- return ko.dependentObservable(function () {
5371
- var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
5372
- if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
5373
- unwrappedArray = [unwrappedArray];
5374
-
5375
- // Filter out any entries marked as destroyed
5376
- var filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
5377
- return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
5378
- });
5379
-
5380
- // Call setDomNodeChildrenFromArrayMapping, ignoring any observables unwrapped within (most likely from a callback function).
5381
- // If the array items are observables, though, they will be unwrapped in executeTemplateForArrayItem and managed within setDomNodeChildrenFromArrayMapping.
5382
- ko.dependencyDetection.ignore(ko.utils.setDomNodeChildrenFromArrayMapping, null, [targetNode, filteredArray, executeTemplateForArrayItem, options, activateBindingsCallback]);
5383
-
5384
- }, null, { disposeWhenNodeIsRemoved: targetNode });
5385
- };
5386
-
5387
- var templateComputedDomDataKey = ko.utils.domData.nextKey();
5388
- function disposeOldComputedAndStoreNewOne(element, newComputed) {
5389
- var oldComputed = ko.utils.domData.get(element, templateComputedDomDataKey);
5390
- if (oldComputed && (typeof(oldComputed.dispose) == 'function'))
5391
- oldComputed.dispose();
5392
- ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && newComputed.isActive()) ? newComputed : undefined);
5393
- }
5394
-
5395
- ko.bindingHandlers['template'] = {
5396
- 'init': function(element, valueAccessor) {
5397
- // Support anonymous templates
5398
- var bindingValue = ko.utils.unwrapObservable(valueAccessor());
5399
- if (typeof bindingValue == "string" || bindingValue['name']) {
5400
- // It's a named template - clear the element
5401
- ko.virtualElements.emptyNode(element);
5402
- } else if ('nodes' in bindingValue) {
5403
- // We've been given an array of DOM nodes. Save them as the template source.
5404
- // There is no known use case for the node array being an observable array (if the output
5405
- // varies, put that behavior *into* your template - that's what templates are for), and
5406
- // the implementation would be a mess, so assert that it's not observable.
5407
- var nodes = bindingValue['nodes'] || [];
5408
- if (ko.isObservable(nodes)) {
5409
- throw new Error('The "nodes" option must be a plain, non-observable array.');
5410
- }
5411
- var container = ko.utils.moveCleanedNodesToContainerElement(nodes); // This also removes the nodes from their current parent
5412
- new ko.templateSources.anonymousTemplate(element)['nodes'](container);
5413
- } else {
5414
- // It's an anonymous template - store the element contents, then clear the element
5415
- var templateNodes = ko.virtualElements.childNodes(element),
5416
- container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
5417
- new ko.templateSources.anonymousTemplate(element)['nodes'](container);
5418
- }
5419
- return { 'controlsDescendantBindings': true };
5420
- },
5421
- 'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
5422
- var value = valueAccessor(),
5423
- dataValue,
5424
- options = ko.utils.unwrapObservable(value),
5425
- shouldDisplay = true,
5426
- templateComputed = null,
5427
- templateName;
5428
-
5429
- if (typeof options == "string") {
5430
- templateName = value;
5431
- options = {};
5432
- } else {
5433
- templateName = options['name'];
5434
-
5435
- // Support "if"/"ifnot" conditions
5436
- if ('if' in options)
5437
- shouldDisplay = ko.utils.unwrapObservable(options['if']);
5438
- if (shouldDisplay && 'ifnot' in options)
5439
- shouldDisplay = !ko.utils.unwrapObservable(options['ifnot']);
5440
-
5441
- dataValue = ko.utils.unwrapObservable(options['data']);
5442
- }
5443
-
5444
- if ('foreach' in options) {
5445
- // Render once for each data point (treating data set as empty if shouldDisplay==false)
5446
- var dataArray = (shouldDisplay && options['foreach']) || [];
5447
- templateComputed = ko.renderTemplateForEach(templateName || element, dataArray, options, element, bindingContext);
5448
- } else if (!shouldDisplay) {
5449
- ko.virtualElements.emptyNode(element);
5450
- } else {
5451
- // Render once for this single data point (or use the viewModel if no data was provided)
5452
- var innerBindingContext = ('data' in options) ?
5453
- bindingContext['createChildContext'](dataValue, options['as']) : // Given an explitit 'data' value, we create a child binding context for it
5454
- bindingContext; // Given no explicit 'data' value, we retain the same binding context
5455
- templateComputed = ko.renderTemplate(templateName || element, innerBindingContext, options, element);
5456
- }
5457
-
5458
- // It only makes sense to have a single template computed per element (otherwise which one should have its output displayed?)
5459
- disposeOldComputedAndStoreNewOne(element, templateComputed);
5460
- }
5461
- };
5462
-
5463
- // Anonymous templates can't be rewritten. Give a nice error message if you try to do it.
5464
- ko.expressionRewriting.bindingRewriteValidators['template'] = function(bindingValue) {
5465
- var parsedBindingValue = ko.expressionRewriting.parseObjectLiteral(bindingValue);
5466
-
5467
- if ((parsedBindingValue.length == 1) && parsedBindingValue[0]['unknown'])
5468
- return null; // It looks like a string literal, not an object literal, so treat it as a named template (which is allowed for rewriting)
5469
-
5470
- if (ko.expressionRewriting.keyValueArrayContainsKey(parsedBindingValue, "name"))
5471
- return null; // Named templates can be rewritten, so return "no error"
5472
- return "This template engine does not support anonymous templates nested within its templates";
5473
- };
5474
-
5475
- ko.virtualElements.allowedBindings['template'] = true;
5476
- })();
5477
-
5478
- ko.exportSymbol('setTemplateEngine', ko.setTemplateEngine);
5479
- ko.exportSymbol('renderTemplate', ko.renderTemplate);
5480
- // Go through the items that have been added and deleted and try to find matches between them.
5481
- ko.utils.findMovesInArrayComparison = function (left, right, limitFailedCompares) {
5482
- if (left.length && right.length) {
5483
- var failedCompares, l, r, leftItem, rightItem;
5484
- for (failedCompares = l = 0; (!limitFailedCompares || failedCompares < limitFailedCompares) && (leftItem = left[l]); ++l) {
5485
- for (r = 0; rightItem = right[r]; ++r) {
5486
- if (leftItem['value'] === rightItem['value']) {
5487
- leftItem['moved'] = rightItem['index'];
5488
- rightItem['moved'] = leftItem['index'];
5489
- right.splice(r, 1); // This item is marked as moved; so remove it from right list
5490
- failedCompares = r = 0; // Reset failed compares count because we're checking for consecutive failures
5491
- break;
5492
- }
5493
- }
5494
- failedCompares += r;
5495
- }
5496
- }
5497
- };
5498
-
5499
- ko.utils.compareArrays = (function () {
5500
- var statusNotInOld = 'added', statusNotInNew = 'deleted';
5501
-
5502
- // Simple calculation based on Levenshtein distance.
5503
- function compareArrays(oldArray, newArray, options) {
5504
- // For backward compatibility, if the third arg is actually a bool, interpret
5505
- // it as the old parameter 'dontLimitMoves'. Newer code should use { dontLimitMoves: true }.
5506
- options = (typeof options === 'boolean') ? { 'dontLimitMoves': options } : (options || {});
5507
- oldArray = oldArray || [];
5508
- newArray = newArray || [];
5509
-
5510
- if (oldArray.length < newArray.length)
5511
- return compareSmallArrayToBigArray(oldArray, newArray, statusNotInOld, statusNotInNew, options);
5512
- else
5513
- return compareSmallArrayToBigArray(newArray, oldArray, statusNotInNew, statusNotInOld, options);
5514
- }
5515
-
5516
- function compareSmallArrayToBigArray(smlArray, bigArray, statusNotInSml, statusNotInBig, options) {
5517
- var myMin = Math.min,
5518
- myMax = Math.max,
5519
- editDistanceMatrix = [],
5520
- smlIndex, smlIndexMax = smlArray.length,
5521
- bigIndex, bigIndexMax = bigArray.length,
5522
- compareRange = (bigIndexMax - smlIndexMax) || 1,
5523
- maxDistance = smlIndexMax + bigIndexMax + 1,
5524
- thisRow, lastRow,
5525
- bigIndexMaxForRow, bigIndexMinForRow;
5526
-
5527
- for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) {
5528
- lastRow = thisRow;
5529
- editDistanceMatrix.push(thisRow = []);
5530
- bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange);
5531
- bigIndexMinForRow = myMax(0, smlIndex - 1);
5532
- for (bigIndex = bigIndexMinForRow; bigIndex <= bigIndexMaxForRow; bigIndex++) {
5533
- if (!bigIndex)
5534
- thisRow[bigIndex] = smlIndex + 1;
5535
- else if (!smlIndex) // Top row - transform empty array into new array via additions
5536
- thisRow[bigIndex] = bigIndex + 1;
5537
- else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1])
5538
- thisRow[bigIndex] = lastRow[bigIndex - 1]; // copy value (no edit)
5539
- else {
5540
- var northDistance = lastRow[bigIndex] || maxDistance; // not in big (deletion)
5541
- var westDistance = thisRow[bigIndex - 1] || maxDistance; // not in small (addition)
5542
- thisRow[bigIndex] = myMin(northDistance, westDistance) + 1;
5543
- }
5544
- }
5545
- }
5546
-
5547
- var editScript = [], meMinusOne, notInSml = [], notInBig = [];
5548
- for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) {
5549
- meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1;
5550
- if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex-1]) {
5551
- notInSml.push(editScript[editScript.length] = { // added
5552
- 'status': statusNotInSml,
5553
- 'value': bigArray[--bigIndex],
5554
- 'index': bigIndex });
5555
- } else if (smlIndex && meMinusOne === editDistanceMatrix[smlIndex - 1][bigIndex]) {
5556
- notInBig.push(editScript[editScript.length] = { // deleted
5557
- 'status': statusNotInBig,
5558
- 'value': smlArray[--smlIndex],
5559
- 'index': smlIndex });
5560
- } else {
5561
- --bigIndex;
5562
- --smlIndex;
5563
- if (!options['sparse']) {
5564
- editScript.push({
5565
- 'status': "retained",
5566
- 'value': bigArray[bigIndex] });
5567
- }
5568
- }
5569
- }
5570
-
5571
- // Set a limit on the number of consecutive non-matching comparisons; having it a multiple of
5572
- // smlIndexMax keeps the time complexity of this algorithm linear.
5573
- ko.utils.findMovesInArrayComparison(notInBig, notInSml, !options['dontLimitMoves'] && smlIndexMax * 10);
5574
-
5575
- return editScript.reverse();
5576
- }
5577
-
5578
- return compareArrays;
5579
- })();
5580
-
5581
- ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
5582
- (function () {
5583
- // Objective:
5584
- // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
5585
- // map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node
5586
- // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node
5587
- // so that its children is again the concatenation of the mappings of the array elements, but don't re-map any array elements that we
5588
- // previously mapped - retain those nodes, and just insert/delete other ones
5589
-
5590
- // "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
5591
- // You can use this, for example, to activate bindings on those nodes.
5592
-
5593
- function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
5594
- // Map this array value inside a dependentObservable so we re-map when any dependency changes
5595
- var mappedNodes = [];
5596
- var dependentObservable = ko.dependentObservable(function() {
5597
- var newMappedNodes = mapping(valueToMap, index, ko.utils.fixUpContinuousNodeArray(mappedNodes, containerNode)) || [];
5598
-
5599
- // On subsequent evaluations, just replace the previously-inserted DOM nodes
5600
- if (mappedNodes.length > 0) {
5601
- ko.utils.replaceDomNodes(mappedNodes, newMappedNodes);
5602
- if (callbackAfterAddingNodes)
5603
- ko.dependencyDetection.ignore(callbackAfterAddingNodes, null, [valueToMap, newMappedNodes, index]);
5604
- }
5605
-
5606
- // Replace the contents of the mappedNodes array, thereby updating the record
5607
- // of which nodes would be deleted if valueToMap was itself later removed
5608
- mappedNodes.length = 0;
5609
- ko.utils.arrayPushAll(mappedNodes, newMappedNodes);
5610
- }, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function() { return !ko.utils.anyDomNodeIsAttachedToDocument(mappedNodes); } });
5611
- return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) };
5612
- }
5613
-
5614
- var lastMappingResultDomDataKey = ko.utils.domData.nextKey(),
5615
- deletedItemDummyValue = ko.utils.domData.nextKey();
5616
-
5617
- ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) {
5618
- // Compare the provided array against the previous one
5619
- array = array || [];
5620
- options = options || {};
5621
- var isFirstExecution = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) === undefined;
5622
- var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) || [];
5623
- var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
5624
- var editScript = ko.utils.compareArrays(lastArray, array, options['dontLimitMoves']);
5625
-
5626
- // Build the new mapping result
5627
- var newMappingResult = [];
5628
- var lastMappingResultIndex = 0;
5629
- var newMappingResultIndex = 0;
5630
-
5631
- var nodesToDelete = [];
5632
- var itemsToProcess = [];
5633
- var itemsForBeforeRemoveCallbacks = [];
5634
- var itemsForMoveCallbacks = [];
5635
- var itemsForAfterAddCallbacks = [];
5636
- var mapData;
5637
-
5638
- function itemMovedOrRetained(editScriptIndex, oldPosition) {
5639
- mapData = lastMappingResult[oldPosition];
5640
- if (newMappingResultIndex !== oldPosition)
5641
- itemsForMoveCallbacks[editScriptIndex] = mapData;
5642
- // Since updating the index might change the nodes, do so before calling fixUpContinuousNodeArray
5643
- mapData.indexObservable(newMappingResultIndex++);
5644
- ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode);
5645
- newMappingResult.push(mapData);
5646
- itemsToProcess.push(mapData);
5647
- }
5648
-
5649
- function callCallback(callback, items) {
5650
- if (callback) {
5651
- for (var i = 0, n = items.length; i < n; i++) {
5652
- if (items[i]) {
5653
- ko.utils.arrayForEach(items[i].mappedNodes, function(node) {
5654
- callback(node, i, items[i].arrayEntry);
5655
- });
5656
- }
5657
- }
5658
- }
5659
- }
5660
-
5661
- for (var i = 0, editScriptItem, movedIndex; editScriptItem = editScript[i]; i++) {
5662
- movedIndex = editScriptItem['moved'];
5663
- switch (editScriptItem['status']) {
5664
- case "deleted":
5665
- if (movedIndex === undefined) {
5666
- mapData = lastMappingResult[lastMappingResultIndex];
5667
-
5668
- // Stop tracking changes to the mapping for these nodes
5669
- if (mapData.dependentObservable) {
5670
- mapData.dependentObservable.dispose();
5671
- mapData.dependentObservable = undefined;
5672
- }
5673
-
5674
- // Queue these nodes for later removal
5675
- if (ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode).length) {
5676
- if (options['beforeRemove']) {
5677
- newMappingResult.push(mapData);
5678
- itemsToProcess.push(mapData);
5679
- if (mapData.arrayEntry === deletedItemDummyValue) {
5680
- mapData = null;
5681
- } else {
5682
- itemsForBeforeRemoveCallbacks[i] = mapData;
5683
- }
5684
- }
5685
- if (mapData) {
5686
- nodesToDelete.push.apply(nodesToDelete, mapData.mappedNodes);
5687
- }
5688
- }
5689
- }
5690
- lastMappingResultIndex++;
5691
- break;
5692
-
5693
- case "retained":
5694
- itemMovedOrRetained(i, lastMappingResultIndex++);
5695
- break;
5696
-
5697
- case "added":
5698
- if (movedIndex !== undefined) {
5699
- itemMovedOrRetained(i, movedIndex);
5700
- } else {
5701
- mapData = { arrayEntry: editScriptItem['value'], indexObservable: ko.observable(newMappingResultIndex++) };
5702
- newMappingResult.push(mapData);
5703
- itemsToProcess.push(mapData);
5704
- if (!isFirstExecution)
5705
- itemsForAfterAddCallbacks[i] = mapData;
5706
- }
5707
- break;
5708
- }
5709
- }
5710
-
5711
- // Store a copy of the array items we just considered so we can difference it next time
5712
- ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
5713
-
5714
- // Call beforeMove first before any changes have been made to the DOM
5715
- callCallback(options['beforeMove'], itemsForMoveCallbacks);
5716
-
5717
- // Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
5718
- ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
5719
-
5720
- // Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
5721
- for (var i = 0, nextNode = ko.virtualElements.firstChild(domNode), lastNode, node; mapData = itemsToProcess[i]; i++) {
5722
- // Get nodes for newly added items
5723
- if (!mapData.mappedNodes)
5724
- ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable));
5725
-
5726
- // Put nodes in the right place if they aren't there already
5727
- for (var j = 0; node = mapData.mappedNodes[j]; nextNode = node.nextSibling, lastNode = node, j++) {
5728
- if (node !== nextNode)
5729
- ko.virtualElements.insertAfter(domNode, node, lastNode);
5730
- }
5731
-
5732
- // Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
5733
- if (!mapData.initialized && callbackAfterAddingNodes) {
5734
- callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable);
5735
- mapData.initialized = true;
5736
- }
5737
- }
5738
-
5739
- // If there's a beforeRemove callback, call it after reordering.
5740
- // Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
5741
- // some sort of animation, which is why we first reorder the nodes that will be removed. If the
5742
- // callback instead removes the nodes right away, it would be more efficient to skip reordering them.
5743
- // Perhaps we'll make that change in the future if this scenario becomes more common.
5744
- callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks);
5745
-
5746
- // Replace the stored values of deleted items with a dummy value. This provides two benefits: it marks this item
5747
- // as already "removed" so we won't call beforeRemove for it again, and it ensures that the item won't match up
5748
- // with an actual item in the array and appear as "retained" or "moved".
5749
- for (i = 0; i < itemsForBeforeRemoveCallbacks.length; ++i) {
5750
- if (itemsForBeforeRemoveCallbacks[i]) {
5751
- itemsForBeforeRemoveCallbacks[i].arrayEntry = deletedItemDummyValue;
5752
- }
5753
- }
5754
-
5755
- // Finally call afterMove and afterAdd callbacks
5756
- callCallback(options['afterMove'], itemsForMoveCallbacks);
5757
- callCallback(options['afterAdd'], itemsForAfterAddCallbacks);
5758
- }
5759
- })();
5760
-
5761
- ko.exportSymbol('utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping);
5762
- ko.nativeTemplateEngine = function () {
5763
- this['allowTemplateRewriting'] = false;
5764
- }
5765
-
5766
- ko.nativeTemplateEngine.prototype = new ko.templateEngine();
5767
- ko.nativeTemplateEngine.prototype.constructor = ko.nativeTemplateEngine;
5768
- ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options, templateDocument) {
5769
- var useNodesIfAvailable = !(ko.utils.ieVersion < 9), // IE<9 cloneNode doesn't work properly
5770
- templateNodesFunc = useNodesIfAvailable ? templateSource['nodes'] : null,
5771
- templateNodes = templateNodesFunc ? templateSource['nodes']() : null;
5772
-
5773
- if (templateNodes) {
5774
- return ko.utils.makeArray(templateNodes.cloneNode(true).childNodes);
5775
- } else {
5776
- var templateText = templateSource['text']();
5777
- return ko.utils.parseHtmlFragment(templateText, templateDocument);
5778
- }
5779
- };
5780
-
5781
- ko.nativeTemplateEngine.instance = new ko.nativeTemplateEngine();
5782
- ko.setTemplateEngine(ko.nativeTemplateEngine.instance);
5783
-
5784
- ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
5785
- (function() {
5786
- ko.jqueryTmplTemplateEngine = function () {
5787
- // Detect which version of jquery-tmpl you're using. Unfortunately jquery-tmpl
5788
- // doesn't expose a version number, so we have to infer it.
5789
- // Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
5790
- // which KO internally refers to as version "2", so older versions are no longer detected.
5791
- var jQueryTmplVersion = this.jQueryTmplVersion = (function() {
5792
- if (!jQueryInstance || !(jQueryInstance['tmpl']))
5793
- return 0;
5794
- // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
5795
- try {
5796
- if (jQueryInstance['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
5797
- // Since 1.0.0pre, custom tags should append markup to an array called "__"
5798
- return 2; // Final version of jquery.tmpl
5799
- }
5800
- } catch(ex) { /* Apparently not the version we were looking for */ }
5801
-
5802
- return 1; // Any older version that we don't support
5803
- })();
5804
-
5805
- function ensureHasReferencedJQueryTemplates() {
5806
- if (jQueryTmplVersion < 2)
5807
- throw new Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");
5808
- }
5809
-
5810
- function executeTemplate(compiledTemplate, data, jQueryTemplateOptions) {
5811
- return jQueryInstance['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
5812
- }
5813
-
5814
- this['renderTemplateSource'] = function(templateSource, bindingContext, options, templateDocument) {
5815
- templateDocument = templateDocument || document;
5816
- options = options || {};
5817
- ensureHasReferencedJQueryTemplates();
5818
-
5819
- // Ensure we have stored a precompiled version of this template (don't want to reparse on every render)
5820
- var precompiled = templateSource['data']('precompiled');
5821
- if (!precompiled) {
5822
- var templateText = templateSource['text']() || "";
5823
- // Wrap in "with($whatever.koBindingContext) { ... }"
5824
- templateText = "{{ko_with $item.koBindingContext}}" + templateText + "{{/ko_with}}";
5825
-
5826
- precompiled = jQueryInstance['template'](null, templateText);
5827
- templateSource['data']('precompiled', precompiled);
5828
- }
5829
-
5830
- var data = [bindingContext['$data']]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays
5831
- var jQueryTemplateOptions = jQueryInstance['extend']({ 'koBindingContext': bindingContext }, options['templateOptions']);
5832
-
5833
- var resultNodes = executeTemplate(precompiled, data, jQueryTemplateOptions);
5834
- resultNodes['appendTo'](templateDocument.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
5835
-
5836
- jQueryInstance['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
5837
- return resultNodes;
5838
- };
5839
-
5840
- this['createJavaScriptEvaluatorBlock'] = function(script) {
5841
- return "{{ko_code ((function() { return " + script + " })()) }}";
5842
- };
5843
-
5844
- this['addTemplate'] = function(templateName, templateMarkup) {
5845
- document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "<" + "/script>");
5846
- };
5847
-
5848
- if (jQueryTmplVersion > 0) {
5849
- jQueryInstance['tmpl']['tag']['ko_code'] = {
5850
- open: "__.push($1 || '');"
5851
- };
5852
- jQueryInstance['tmpl']['tag']['ko_with'] = {
5853
- open: "with($1) {",
5854
- close: "} "
5855
- };
5856
- }
5857
- };
5858
-
5859
- ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
5860
- ko.jqueryTmplTemplateEngine.prototype.constructor = ko.jqueryTmplTemplateEngine;
5861
-
5862
- // Use this one by default *only if jquery.tmpl is referenced*
5863
- var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine();
5864
- if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0)
5865
- ko.setTemplateEngine(jqueryTmplTemplateEngineInstance);
5866
-
5867
- ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
5868
- })();
5869
- }));
5870
- }());
5871
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/res/lib/knockout/knockout-3.4.0.js DELETED
@@ -1,123 +0,0 @@
1
- /*!
2
- * Knockout JavaScript library v3.4.0
3
- * (c) Steven Sanderson - http://knockoutjs.com/
4
- * License: MIT (http://www.opensource.org/licenses/mit-license.php)
5
- */
6
-
7
- (function() {(function(n){var x=this||(0,eval)("this"),u=x.document,M=x.navigator,v=x.jQuery,F=x.JSON;(function(n){"function"===typeof define&&define.amd?define(["exports","require"],n):"object"===typeof exports&&"object"===typeof module?n(module.exports||exports):n(x.ko={})})(function(N,O){function J(a,c){return null===a||typeof a in T?a===c:!1}function U(b,c){var d;return function(){d||(d=a.a.setTimeout(function(){d=n;b()},c))}}function V(b,c){var d;return function(){clearTimeout(d);d=a.a.setTimeout(b,c)}}function W(a,
8
- c){c&&c!==I?"beforeChange"===c?this.Kb(a):this.Ha(a,c):this.Lb(a)}function X(a,c){null!==c&&c.k&&c.k()}function Y(a,c){var d=this.Hc,e=d[s];e.R||(this.lb&&this.Ma[c]?(d.Pb(c,a,this.Ma[c]),this.Ma[c]=null,--this.lb):e.r[c]||d.Pb(c,a,e.s?{ia:a}:d.uc(a)))}function K(b,c,d,e){a.d[b]={init:function(b,g,k,l,m){var h,r;a.m(function(){var q=a.a.c(g()),p=!d!==!q,A=!r;if(A||c||p!==h)A&&a.va.Aa()&&(r=a.a.ua(a.f.childNodes(b),!0)),p?(A||a.f.da(b,a.a.ua(r)),a.eb(e?e(m,q):m,b)):a.f.xa(b),h=p},null,{i:b});return{controlsDescendantBindings:!0}}};
9
- a.h.ta[b]=!1;a.f.Z[b]=!0}var a="undefined"!==typeof N?N:{};a.b=function(b,c){for(var d=b.split("."),e=a,f=0;f<d.length-1;f++)e=e[d[f]];e[d[d.length-1]]=c};a.G=function(a,c,d){a[c]=d};a.version="3.4.0";a.b("version",a.version);a.options={deferUpdates:!1,useOnlyNativeEvents:!1};a.a=function(){function b(a,b){for(var c in a)a.hasOwnProperty(c)&&b(c,a[c])}function c(a,b){if(b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function d(a,b){a.__proto__=b;return a}function e(b,c,d,e){var h=b[c].match(r)||
10
- [];a.a.q(d.match(r),function(b){a.a.pa(h,b,e)});b[c]=h.join(" ")}var f={__proto__:[]}instanceof Array,g="function"===typeof Symbol,k={},l={};k[M&&/Firefox\/2/i.test(M.userAgent)?"KeyboardEvent":"UIEvents"]=["keyup","keydown","keypress"];k.MouseEvents="click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave".split(" ");b(k,function(a,b){if(b.length)for(var c=0,d=b.length;c<d;c++)l[b[c]]=a});var m={propertychange:!0},h=u&&function(){for(var a=3,b=u.createElement("div"),c=
11
- b.getElementsByTagName("i");b.innerHTML="\x3c!--[if gt IE "+ ++a+"]><i></i><![endif]--\x3e",c[0];);return 4<a?a:n}(),r=/\S+/g;return{cc:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],q:function(a,b){for(var c=0,d=a.length;c<d;c++)b(a[c],c)},o:function(a,b){if("function"==typeof Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b);for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},Sb:function(a,b,c){for(var d=0,e=a.length;d<e;d++)if(b.call(c,a[d],d))return a[d];
12
- return null},La:function(b,c){var d=a.a.o(b,c);0<d?b.splice(d,1):0===d&&b.shift()},Tb:function(b){b=b||[];for(var c=[],d=0,e=b.length;d<e;d++)0>a.a.o(c,b[d])&&c.push(b[d]);return c},fb:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)c.push(b(a[d],d));return c},Ka:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)b(a[d],d)&&c.push(a[d]);return c},ra:function(a,b){if(b instanceof Array)a.push.apply(a,b);else for(var c=0,d=b.length;c<d;c++)a.push(b[c]);return a},pa:function(b,c,d){var e=
13
- a.a.o(a.a.zb(b),c);0>e?d&&b.push(c):d||b.splice(e,1)},ka:f,extend:c,Xa:d,Ya:f?d:c,D:b,Ca:function(a,b){if(!a)return a;var c={},d;for(d in a)a.hasOwnProperty(d)&&(c[d]=b(a[d],d,a));return c},ob:function(b){for(;b.firstChild;)a.removeNode(b.firstChild)},jc:function(b){b=a.a.V(b);for(var c=(b[0]&&b[0].ownerDocument||u).createElement("div"),d=0,e=b.length;d<e;d++)c.appendChild(a.$(b[d]));return c},ua:function(b,c){for(var d=0,e=b.length,h=[];d<e;d++){var m=b[d].cloneNode(!0);h.push(c?a.$(m):m)}return h},
14
- da:function(b,c){a.a.ob(b);if(c)for(var d=0,e=c.length;d<e;d++)b.appendChild(c[d])},qc:function(b,c){var d=b.nodeType?[b]:b;if(0<d.length){for(var e=d[0],h=e.parentNode,m=0,l=c.length;m<l;m++)h.insertBefore(c[m],e);m=0;for(l=d.length;m<l;m++)a.removeNode(d[m])}},za:function(a,b){if(a.length){for(b=8===b.nodeType&&b.parentNode||b;a.length&&a[0].parentNode!==b;)a.splice(0,1);for(;1<a.length&&a[a.length-1].parentNode!==b;)a.length--;if(1<a.length){var c=a[0],d=a[a.length-1];for(a.length=0;c!==d;)a.push(c),
15
- c=c.nextSibling;a.push(d)}}return a},sc:function(a,b){7>h?a.setAttribute("selected",b):a.selected=b},$a:function(a){return null===a||a===n?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},nd:function(a,b){a=a||"";return b.length>a.length?!1:a.substring(0,b.length)===b},Mc:function(a,b){if(a===b)return!0;if(11===a.nodeType)return!1;if(b.contains)return b.contains(3===a.nodeType?a.parentNode:a);if(b.compareDocumentPosition)return 16==(b.compareDocumentPosition(a)&16);for(;a&&a!=
16
- b;)a=a.parentNode;return!!a},nb:function(b){return a.a.Mc(b,b.ownerDocument.documentElement)},Qb:function(b){return!!a.a.Sb(b,a.a.nb)},A:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},Wb:function(b){return a.onError?function(){try{return b.apply(this,arguments)}catch(c){throw a.onError&&a.onError(c),c;}}:b},setTimeout:function(b,c){return setTimeout(a.a.Wb(b),c)},$b:function(b){setTimeout(function(){a.onError&&a.onError(b);throw b;},0)},p:function(b,c,d){var e=a.a.Wb(d);d=h&&m[c];if(a.options.useOnlyNativeEvents||
17
- d||!v)if(d||"function"!=typeof b.addEventListener)if("undefined"!=typeof b.attachEvent){var l=function(a){e.call(b,a)},f="on"+c;b.attachEvent(f,l);a.a.F.oa(b,function(){b.detachEvent(f,l)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(c,e,!1);else v(b).bind(c,e)},Da:function(b,c){if(!b||!b.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var d;"input"===a.a.A(b)&&b.type&&"click"==c.toLowerCase()?(d=b.type,d="checkbox"==
18
- d||"radio"==d):d=!1;if(a.options.useOnlyNativeEvents||!v||d)if("function"==typeof u.createEvent)if("function"==typeof b.dispatchEvent)d=u.createEvent(l[c]||"HTMLEvents"),d.initEvent(c,!0,!0,x,0,0,0,0,0,!1,!1,!1,!1,0,b),b.dispatchEvent(d);else throw Error("The supplied element doesn't support dispatchEvent");else if(d&&b.click)b.click();else if("undefined"!=typeof b.fireEvent)b.fireEvent("on"+c);else throw Error("Browser doesn't support triggering events");else v(b).trigger(c)},c:function(b){return a.H(b)?
19
- b():b},zb:function(b){return a.H(b)?b.t():b},bb:function(b,c,d){var h;c&&("object"===typeof b.classList?(h=b.classList[d?"add":"remove"],a.a.q(c.match(r),function(a){h.call(b.classList,a)})):"string"===typeof b.className.baseVal?e(b.className,"baseVal",c,d):e(b,"className",c,d))},Za:function(b,c){var d=a.a.c(c);if(null===d||d===n)d="";var e=a.f.firstChild(b);!e||3!=e.nodeType||a.f.nextSibling(e)?a.f.da(b,[b.ownerDocument.createTextNode(d)]):e.data=d;a.a.Rc(b)},rc:function(a,b){a.name=b;if(7>=h)try{a.mergeAttributes(u.createElement("<input name='"+
20
- a.name+"'/>"),!1)}catch(c){}},Rc:function(a){9<=h&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},Nc:function(a){if(h){var b=a.style.width;a.style.width=0;a.style.width=b}},hd:function(b,c){b=a.a.c(b);c=a.a.c(c);for(var d=[],e=b;e<=c;e++)d.push(e);return d},V:function(a){for(var b=[],c=0,d=a.length;c<d;c++)b.push(a[c]);return b},Yb:function(a){return g?Symbol(a):a},rd:6===h,sd:7===h,C:h,ec:function(b,c){for(var d=a.a.V(b.getElementsByTagName("input")).concat(a.a.V(b.getElementsByTagName("textarea"))),
21
- e="string"==typeof c?function(a){return a.name===c}:function(a){return c.test(a.name)},h=[],m=d.length-1;0<=m;m--)e(d[m])&&h.push(d[m]);return h},ed:function(b){return"string"==typeof b&&(b=a.a.$a(b))?F&&F.parse?F.parse(b):(new Function("return "+b))():null},Eb:function(b,c,d){if(!F||!F.stringify)throw Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
22
- return F.stringify(a.a.c(b),c,d)},fd:function(c,d,e){e=e||{};var h=e.params||{},m=e.includeFields||this.cc,l=c;if("object"==typeof c&&"form"===a.a.A(c))for(var l=c.action,f=m.length-1;0<=f;f--)for(var g=a.a.ec(c,m[f]),k=g.length-1;0<=k;k--)h[g[k].name]=g[k].value;d=a.a.c(d);var r=u.createElement("form");r.style.display="none";r.action=l;r.method="post";for(var n in d)c=u.createElement("input"),c.type="hidden",c.name=n,c.value=a.a.Eb(a.a.c(d[n])),r.appendChild(c);b(h,function(a,b){var c=u.createElement("input");
23
- c.type="hidden";c.name=a;c.value=b;r.appendChild(c)});u.body.appendChild(r);e.submitter?e.submitter(r):r.submit();setTimeout(function(){r.parentNode.removeChild(r)},0)}}}();a.b("utils",a.a);a.b("utils.arrayForEach",a.a.q);a.b("utils.arrayFirst",a.a.Sb);a.b("utils.arrayFilter",a.a.Ka);a.b("utils.arrayGetDistinctValues",a.a.Tb);a.b("utils.arrayIndexOf",a.a.o);a.b("utils.arrayMap",a.a.fb);a.b("utils.arrayPushAll",a.a.ra);a.b("utils.arrayRemoveItem",a.a.La);a.b("utils.extend",a.a.extend);a.b("utils.fieldsIncludedWithJsonPost",
24
- a.a.cc);a.b("utils.getFormFields",a.a.ec);a.b("utils.peekObservable",a.a.zb);a.b("utils.postJson",a.a.fd);a.b("utils.parseJson",a.a.ed);a.b("utils.registerEventHandler",a.a.p);a.b("utils.stringifyJson",a.a.Eb);a.b("utils.range",a.a.hd);a.b("utils.toggleDomNodeCssClass",a.a.bb);a.b("utils.triggerEvent",a.a.Da);a.b("utils.unwrapObservable",a.a.c);a.b("utils.objectForEach",a.a.D);a.b("utils.addOrRemoveItem",a.a.pa);a.b("utils.setTextContent",a.a.Za);a.b("unwrap",a.a.c);Function.prototype.bind||(Function.prototype.bind=
25
- function(a){var c=this;if(1===arguments.length)return function(){return c.apply(a,arguments)};var d=Array.prototype.slice.call(arguments,1);return function(){var e=d.slice(0);e.push.apply(e,arguments);return c.apply(a,e)}});a.a.e=new function(){function a(b,g){var k=b[d];if(!k||"null"===k||!e[k]){if(!g)return n;k=b[d]="ko"+c++;e[k]={}}return e[k]}var c=0,d="__ko__"+(new Date).getTime(),e={};return{get:function(c,d){var e=a(c,!1);return e===n?n:e[d]},set:function(c,d,e){if(e!==n||a(c,!1)!==n)a(c,!0)[d]=
26
- e},clear:function(a){var b=a[d];return b?(delete e[b],a[d]=null,!0):!1},I:function(){return c++ +d}}};a.b("utils.domData",a.a.e);a.b("utils.domData.clear",a.a.e.clear);a.a.F=new function(){function b(b,c){var e=a.a.e.get(b,d);e===n&&c&&(e=[],a.a.e.set(b,d,e));return e}function c(d){var e=b(d,!1);if(e)for(var e=e.slice(0),l=0;l<e.length;l++)e[l](d);a.a.e.clear(d);a.a.F.cleanExternalData(d);if(f[d.nodeType])for(e=d.firstChild;d=e;)e=d.nextSibling,8===d.nodeType&&c(d)}var d=a.a.e.I(),e={1:!0,8:!0,9:!0},
27
- f={1:!0,9:!0};return{oa:function(a,c){if("function"!=typeof c)throw Error("Callback must be a function");b(a,!0).push(c)},pc:function(c,e){var l=b(c,!1);l&&(a.a.La(l,e),0==l.length&&a.a.e.set(c,d,n))},$:function(b){if(e[b.nodeType]&&(c(b),f[b.nodeType])){var d=[];a.a.ra(d,b.getElementsByTagName("*"));for(var l=0,m=d.length;l<m;l++)c(d[l])}return b},removeNode:function(b){a.$(b);b.parentNode&&b.parentNode.removeChild(b)},cleanExternalData:function(a){v&&"function"==typeof v.cleanData&&v.cleanData([a])}}};
28
- a.$=a.a.F.$;a.removeNode=a.a.F.removeNode;a.b("cleanNode",a.$);a.b("removeNode",a.removeNode);a.b("utils.domNodeDisposal",a.a.F);a.b("utils.domNodeDisposal.addDisposeCallback",a.a.F.oa);a.b("utils.domNodeDisposal.removeDisposeCallback",a.a.F.pc);(function(){var b=[0,"",""],c=[1,"<table>","</table>"],d=[3,"<table><tbody><tr>","</tr></tbody></table>"],e=[1,"<select multiple='multiple'>","</select>"],f={thead:c,tbody:c,tfoot:c,tr:[2,"<table><tbody>","</tbody></table>"],td:d,th:d,option:e,optgroup:e},
29
- g=8>=a.a.C;a.a.ma=function(c,d){var e;if(v)if(v.parseHTML)e=v.parseHTML(c,d)||[];else{if((e=v.clean([c],d))&&e[0]){for(var h=e[0];h.parentNode&&11!==h.parentNode.nodeType;)h=h.parentNode;h.parentNode&&h.parentNode.removeChild(h)}}else{(e=d)||(e=u);var h=e.parentWindow||e.defaultView||x,r=a.a.$a(c).toLowerCase(),q=e.createElement("div"),p;p=(r=r.match(/^<([a-z]+)[ >]/))&&f[r[1]]||b;r=p[0];p="ignored<div>"+p[1]+c+p[2]+"</div>";"function"==typeof h.innerShiv?q.appendChild(h.innerShiv(p)):(g&&e.appendChild(q),
30
- q.innerHTML=p,g&&q.parentNode.removeChild(q));for(;r--;)q=q.lastChild;e=a.a.V(q.lastChild.childNodes)}return e};a.a.Cb=function(b,c){a.a.ob(b);c=a.a.c(c);if(null!==c&&c!==n)if("string"!=typeof c&&(c=c.toString()),v)v(b).html(c);else for(var d=a.a.ma(c,b.ownerDocument),e=0;e<d.length;e++)b.appendChild(d[e])}})();a.b("utils.parseHtmlFragment",a.a.ma);a.b("utils.setHtml",a.a.Cb);a.M=function(){function b(c,e){if(c)if(8==c.nodeType){var f=a.M.lc(c.nodeValue);null!=f&&e.push({Lc:c,cd:f})}else if(1==c.nodeType)for(var f=
31
- 0,g=c.childNodes,k=g.length;f<k;f++)b(g[f],e)}var c={};return{wb:function(a){if("function"!=typeof a)throw Error("You can only pass a function to ko.memoization.memoize()");var b=(4294967296*(1+Math.random())|0).toString(16).substring(1)+(4294967296*(1+Math.random())|0).toString(16).substring(1);c[b]=a;return"\x3c!--[ko_memo:"+b+"]--\x3e"},xc:function(a,b){var f=c[a];if(f===n)throw Error("Couldn't find any memo with ID "+a+". Perhaps it's already been unmemoized.");try{return f.apply(null,b||[]),
32
- !0}finally{delete c[a]}},yc:function(c,e){var f=[];b(c,f);for(var g=0,k=f.length;g<k;g++){var l=f[g].Lc,m=[l];e&&a.a.ra(m,e);a.M.xc(f[g].cd,m);l.nodeValue="";l.parentNode&&l.parentNode.removeChild(l)}},lc:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:null}}}();a.b("memoization",a.M);a.b("memoization.memoize",a.M.wb);a.b("memoization.unmemoize",a.M.xc);a.b("memoization.parseMemoText",a.M.lc);a.b("memoization.unmemoizeDomNodeAndDescendants",a.M.yc);a.Y=function(){function b(){if(e)for(var b=
33
- e,c=0,m;g<e;)if(m=d[g++]){if(g>b){if(5E3<=++c){g=e;a.a.$b(Error("'Too much recursion' after processing "+c+" task groups."));break}b=e}try{m()}catch(h){a.a.$b(h)}}}function c(){b();g=e=d.length=0}var d=[],e=0,f=1,g=0;return{scheduler:x.MutationObserver?function(a){var b=u.createElement("div");(new MutationObserver(a)).observe(b,{attributes:!0});return function(){b.classList.toggle("foo")}}(c):u&&"onreadystatechange"in u.createElement("script")?function(a){var b=u.createElement("script");b.onreadystatechange=
34
- function(){b.onreadystatechange=null;u.documentElement.removeChild(b);b=null;a()};u.documentElement.appendChild(b)}:function(a){setTimeout(a,0)},Wa:function(b){e||a.Y.scheduler(c);d[e++]=b;return f++},cancel:function(a){a-=f-e;a>=g&&a<e&&(d[a]=null)},resetForTesting:function(){var a=e-g;g=e=d.length=0;return a},md:b}}();a.b("tasks",a.Y);a.b("tasks.schedule",a.Y.Wa);a.b("tasks.runEarly",a.Y.md);a.ya={throttle:function(b,c){b.throttleEvaluation=c;var d=null;return a.B({read:b,write:function(e){clearTimeout(d);
35
- d=a.a.setTimeout(function(){b(e)},c)}})},rateLimit:function(a,c){var d,e,f;"number"==typeof c?d=c:(d=c.timeout,e=c.method);a.cb=!1;f="notifyWhenChangesStop"==e?V:U;a.Ta(function(a){return f(a,d)})},deferred:function(b,c){if(!0!==c)throw Error("The 'deferred' extender only accepts the value 'true', because it is not supported to turn deferral off once enabled.");b.cb||(b.cb=!0,b.Ta(function(c){var e;return function(){a.Y.cancel(e);e=a.Y.Wa(c);b.notifySubscribers(n,"dirty")}}))},notify:function(a,c){a.equalityComparer=
36
- "always"==c?null:J}};var T={undefined:1,"boolean":1,number:1,string:1};a.b("extenders",a.ya);a.vc=function(b,c,d){this.ia=b;this.gb=c;this.Kc=d;this.R=!1;a.G(this,"dispose",this.k)};a.vc.prototype.k=function(){this.R=!0;this.Kc()};a.J=function(){a.a.Ya(this,D);D.rb(this)};var I="change",D={rb:function(a){a.K={};a.Nb=1},X:function(b,c,d){var e=this;d=d||I;var f=new a.vc(e,c?b.bind(c):b,function(){a.a.La(e.K[d],f);e.Ia&&e.Ia(d)});e.sa&&e.sa(d);e.K[d]||(e.K[d]=[]);e.K[d].push(f);return f},notifySubscribers:function(b,
37
- c){c=c||I;c===I&&this.zc();if(this.Pa(c))try{a.l.Ub();for(var d=this.K[c].slice(0),e=0,f;f=d[e];++e)f.R||f.gb(b)}finally{a.l.end()}},Na:function(){return this.Nb},Uc:function(a){return this.Na()!==a},zc:function(){++this.Nb},Ta:function(b){var c=this,d=a.H(c),e,f,g;c.Ha||(c.Ha=c.notifySubscribers,c.notifySubscribers=W);var k=b(function(){c.Mb=!1;d&&g===c&&(g=c());e=!1;c.tb(f,g)&&c.Ha(f=g)});c.Lb=function(a){c.Mb=e=!0;g=a;k()};c.Kb=function(a){e||(f=a,c.Ha(a,"beforeChange"))}},Pa:function(a){return this.K[a]&&
38
- this.K[a].length},Sc:function(b){if(b)return this.K[b]&&this.K[b].length||0;var c=0;a.a.D(this.K,function(a,b){"dirty"!==a&&(c+=b.length)});return c},tb:function(a,c){return!this.equalityComparer||!this.equalityComparer(a,c)},extend:function(b){var c=this;b&&a.a.D(b,function(b,e){var f=a.ya[b];"function"==typeof f&&(c=f(c,e)||c)});return c}};a.G(D,"subscribe",D.X);a.G(D,"extend",D.extend);a.G(D,"getSubscriptionsCount",D.Sc);a.a.ka&&a.a.Xa(D,Function.prototype);a.J.fn=D;a.hc=function(a){return null!=
39
- a&&"function"==typeof a.X&&"function"==typeof a.notifySubscribers};a.b("subscribable",a.J);a.b("isSubscribable",a.hc);a.va=a.l=function(){function b(a){d.push(e);e=a}function c(){e=d.pop()}var d=[],e,f=0;return{Ub:b,end:c,oc:function(b){if(e){if(!a.hc(b))throw Error("Only subscribable things can act as dependencies");e.gb.call(e.Gc,b,b.Cc||(b.Cc=++f))}},w:function(a,d,e){try{return b(),a.apply(d,e||[])}finally{c()}},Aa:function(){if(e)return e.m.Aa()},Sa:function(){if(e)return e.Sa}}}();a.b("computedContext",
40
- a.va);a.b("computedContext.getDependenciesCount",a.va.Aa);a.b("computedContext.isInitial",a.va.Sa);a.b("ignoreDependencies",a.qd=a.l.w);var E=a.a.Yb("_latestValue");a.N=function(b){function c(){if(0<arguments.length)return c.tb(c[E],arguments[0])&&(c.ga(),c[E]=arguments[0],c.fa()),this;a.l.oc(c);return c[E]}c[E]=b;a.a.ka||a.a.extend(c,a.J.fn);a.J.fn.rb(c);a.a.Ya(c,B);a.options.deferUpdates&&a.ya.deferred(c,!0);return c};var B={equalityComparer:J,t:function(){return this[E]},fa:function(){this.notifySubscribers(this[E])},
41
- ga:function(){this.notifySubscribers(this[E],"beforeChange")}};a.a.ka&&a.a.Xa(B,a.J.fn);var H=a.N.gd="__ko_proto__";B[H]=a.N;a.Oa=function(b,c){return null===b||b===n||b[H]===n?!1:b[H]===c?!0:a.Oa(b[H],c)};a.H=function(b){return a.Oa(b,a.N)};a.Ba=function(b){return"function"==typeof b&&b[H]===a.N||"function"==typeof b&&b[H]===a.B&&b.Vc?!0:!1};a.b("observable",a.N);a.b("isObservable",a.H);a.b("isWriteableObservable",a.Ba);a.b("isWritableObservable",a.Ba);a.b("observable.fn",B);a.G(B,"peek",B.t);a.G(B,
42
- "valueHasMutated",B.fa);a.G(B,"valueWillMutate",B.ga);a.la=function(b){b=b||[];if("object"!=typeof b||!("length"in b))throw Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");b=a.N(b);a.a.Ya(b,a.la.fn);return b.extend({trackArrayChanges:!0})};a.la.fn={remove:function(b){for(var c=this.t(),d=[],e="function"!=typeof b||a.H(b)?function(a){return a===b}:b,f=0;f<c.length;f++){var g=c[f];e(g)&&(0===d.length&&this.ga(),d.push(g),c.splice(f,1),f--)}d.length&&
43
- this.fa();return d},removeAll:function(b){if(b===n){var c=this.t(),d=c.slice(0);this.ga();c.splice(0,c.length);this.fa();return d}return b?this.remove(function(c){return 0<=a.a.o(b,c)}):[]},destroy:function(b){var c=this.t(),d="function"!=typeof b||a.H(b)?function(a){return a===b}:b;this.ga();for(var e=c.length-1;0<=e;e--)d(c[e])&&(c[e]._destroy=!0);this.fa()},destroyAll:function(b){return b===n?this.destroy(function(){return!0}):b?this.destroy(function(c){return 0<=a.a.o(b,c)}):[]},indexOf:function(b){var c=
44
- this();return a.a.o(c,b)},replace:function(a,c){var d=this.indexOf(a);0<=d&&(this.ga(),this.t()[d]=c,this.fa())}};a.a.ka&&a.a.Xa(a.la.fn,a.N.fn);a.a.q("pop push reverse shift sort splice unshift".split(" "),function(b){a.la.fn[b]=function(){var a=this.t();this.ga();this.Vb(a,b,arguments);var d=a[b].apply(a,arguments);this.fa();return d===a?this:d}});a.a.q(["slice"],function(b){a.la.fn[b]=function(){var a=this();return a[b].apply(a,arguments)}});a.b("observableArray",a.la);a.ya.trackArrayChanges=function(b,
45
- c){function d(){if(!e){e=!0;var c=b.notifySubscribers;b.notifySubscribers=function(a,b){b&&b!==I||++k;return c.apply(this,arguments)};var d=[].concat(b.t()||[]);f=null;g=b.X(function(c){c=[].concat(c||[]);if(b.Pa("arrayChange")){var e;if(!f||1<k)f=a.a.ib(d,c,b.hb);e=f}d=c;f=null;k=0;e&&e.length&&b.notifySubscribers(e,"arrayChange")})}}b.hb={};c&&"object"==typeof c&&a.a.extend(b.hb,c);b.hb.sparse=!0;if(!b.Vb){var e=!1,f=null,g,k=0,l=b.sa,m=b.Ia;b.sa=function(a){l&&l.call(b,a);"arrayChange"===a&&d()};
46
- b.Ia=function(a){m&&m.call(b,a);"arrayChange"!==a||b.Pa("arrayChange")||(g.k(),e=!1)};b.Vb=function(b,c,d){function m(a,b,c){return l[l.length]={status:a,value:b,index:c}}if(e&&!k){var l=[],g=b.length,t=d.length,G=0;switch(c){case "push":G=g;case "unshift":for(c=0;c<t;c++)m("added",d[c],G+c);break;case "pop":G=g-1;case "shift":g&&m("deleted",b[G],G);break;case "splice":c=Math.min(Math.max(0,0>d[0]?g+d[0]:d[0]),g);for(var g=1===t?g:Math.min(c+(d[1]||0),g),t=c+t-2,G=Math.max(g,t),P=[],n=[],Q=2;c<G;++c,
47
- ++Q)c<g&&n.push(m("deleted",b[c],c)),c<t&&P.push(m("added",d[Q],c));a.a.dc(n,P);break;default:return}f=l}}}};var s=a.a.Yb("_state");a.m=a.B=function(b,c,d){function e(){if(0<arguments.length){if("function"===typeof f)f.apply(g.pb,arguments);else throw Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");return this}a.l.oc(e);(g.S||g.s&&e.Qa())&&e.aa();return g.T}"object"===typeof b?d=b:(d=d||{},b&&(d.read=
48
- b));if("function"!=typeof d.read)throw Error("Pass a function that returns the value of the ko.computed");var f=d.write,g={T:n,S:!0,Ra:!1,Fb:!1,R:!1,Va:!1,s:!1,jd:d.read,pb:c||d.owner,i:d.disposeWhenNodeIsRemoved||d.i||null,wa:d.disposeWhen||d.wa,mb:null,r:{},L:0,bc:null};e[s]=g;e.Vc="function"===typeof f;a.a.ka||a.a.extend(e,a.J.fn);a.J.fn.rb(e);a.a.Ya(e,z);d.pure?(g.Va=!0,g.s=!0,a.a.extend(e,$)):d.deferEvaluation&&a.a.extend(e,aa);a.options.deferUpdates&&a.ya.deferred(e,!0);g.i&&(g.Fb=!0,g.i.nodeType||
49
- (g.i=null));g.s||d.deferEvaluation||e.aa();g.i&&e.ba()&&a.a.F.oa(g.i,g.mb=function(){e.k()});return e};var z={equalityComparer:J,Aa:function(){return this[s].L},Pb:function(a,c,d){if(this[s].Va&&c===this)throw Error("A 'pure' computed must not be called recursively");this[s].r[a]=d;d.Ga=this[s].L++;d.na=c.Na()},Qa:function(){var a,c,d=this[s].r;for(a in d)if(d.hasOwnProperty(a)&&(c=d[a],c.ia.Uc(c.na)))return!0},bd:function(){this.Fa&&!this[s].Ra&&this.Fa()},ba:function(){return this[s].S||0<this[s].L},
50
- ld:function(){this.Mb||this.ac()},uc:function(a){if(a.cb&&!this[s].i){var c=a.X(this.bd,this,"dirty"),d=a.X(this.ld,this);return{ia:a,k:function(){c.k();d.k()}}}return a.X(this.ac,this)},ac:function(){var b=this,c=b.throttleEvaluation;c&&0<=c?(clearTimeout(this[s].bc),this[s].bc=a.a.setTimeout(function(){b.aa(!0)},c)):b.Fa?b.Fa():b.aa(!0)},aa:function(b){var c=this[s],d=c.wa;if(!c.Ra&&!c.R){if(c.i&&!a.a.nb(c.i)||d&&d()){if(!c.Fb){this.k();return}}else c.Fb=!1;c.Ra=!0;try{this.Qc(b)}finally{c.Ra=!1}c.L||
51
- this.k()}},Qc:function(b){var c=this[s],d=c.Va?n:!c.L,e={Hc:this,Ma:c.r,lb:c.L};a.l.Ub({Gc:e,gb:Y,m:this,Sa:d});c.r={};c.L=0;e=this.Pc(c,e);this.tb(c.T,e)&&(c.s||this.notifySubscribers(c.T,"beforeChange"),c.T=e,c.s?this.zc():b&&this.notifySubscribers(c.T));d&&this.notifySubscribers(c.T,"awake")},Pc:function(b,c){try{var d=b.jd;return b.pb?d.call(b.pb):d()}finally{a.l.end(),c.lb&&!b.s&&a.a.D(c.Ma,X),b.S=!1}},t:function(){var a=this[s];(a.S&&!a.L||a.s&&this.Qa())&&this.aa();return a.T},Ta:function(b){a.J.fn.Ta.call(this,
52
- b);this.Fa=function(){this.Kb(this[s].T);this[s].S=!0;this.Lb(this)}},k:function(){var b=this[s];!b.s&&b.r&&a.a.D(b.r,function(a,b){b.k&&b.k()});b.i&&b.mb&&a.a.F.pc(b.i,b.mb);b.r=null;b.L=0;b.R=!0;b.S=!1;b.s=!1;b.i=null}},$={sa:function(b){var c=this,d=c[s];if(!d.R&&d.s&&"change"==b){d.s=!1;if(d.S||c.Qa())d.r=null,d.L=0,d.S=!0,c.aa();else{var e=[];a.a.D(d.r,function(a,b){e[b.Ga]=a});a.a.q(e,function(a,b){var e=d.r[a],l=c.uc(e.ia);l.Ga=b;l.na=e.na;d.r[a]=l})}d.R||c.notifySubscribers(d.T,"awake")}},
53
- Ia:function(b){var c=this[s];c.R||"change"!=b||this.Pa("change")||(a.a.D(c.r,function(a,b){b.k&&(c.r[a]={ia:b.ia,Ga:b.Ga,na:b.na},b.k())}),c.s=!0,this.notifySubscribers(n,"asleep"))},Na:function(){var b=this[s];b.s&&(b.S||this.Qa())&&this.aa();return a.J.fn.Na.call(this)}},aa={sa:function(a){"change"!=a&&"beforeChange"!=a||this.t()}};a.a.ka&&a.a.Xa(z,a.J.fn);var R=a.N.gd;a.m[R]=a.N;z[R]=a.m;a.Xc=function(b){return a.Oa(b,a.m)};a.Yc=function(b){return a.Oa(b,a.m)&&b[s]&&b[s].Va};a.b("computed",a.m);
54
- a.b("dependentObservable",a.m);a.b("isComputed",a.Xc);a.b("isPureComputed",a.Yc);a.b("computed.fn",z);a.G(z,"peek",z.t);a.G(z,"dispose",z.k);a.G(z,"isActive",z.ba);a.G(z,"getDependenciesCount",z.Aa);a.nc=function(b,c){if("function"===typeof b)return a.m(b,c,{pure:!0});b=a.a.extend({},b);b.pure=!0;return a.m(b,c)};a.b("pureComputed",a.nc);(function(){function b(a,f,g){g=g||new d;a=f(a);if("object"!=typeof a||null===a||a===n||a instanceof RegExp||a instanceof Date||a instanceof String||a instanceof
55
- Number||a instanceof Boolean)return a;var k=a instanceof Array?[]:{};g.save(a,k);c(a,function(c){var d=f(a[c]);switch(typeof d){case "boolean":case "number":case "string":case "function":k[c]=d;break;case "object":case "undefined":var h=g.get(d);k[c]=h!==n?h:b(d,f,g)}});return k}function c(a,b){if(a instanceof Array){for(var c=0;c<a.length;c++)b(c);"function"==typeof a.toJSON&&b("toJSON")}else for(c in a)b(c)}function d(){this.keys=[];this.Ib=[]}a.wc=function(c){if(0==arguments.length)throw Error("When calling ko.toJS, pass the object you want to convert.");
56
- return b(c,function(b){for(var c=0;a.H(b)&&10>c;c++)b=b();return b})};a.toJSON=function(b,c,d){b=a.wc(b);return a.a.Eb(b,c,d)};d.prototype={save:function(b,c){var d=a.a.o(this.keys,b);0<=d?this.Ib[d]=c:(this.keys.push(b),this.Ib.push(c))},get:function(b){b=a.a.o(this.keys,b);return 0<=b?this.Ib[b]:n}}})();a.b("toJS",a.wc);a.b("toJSON",a.toJSON);(function(){a.j={u:function(b){switch(a.a.A(b)){case "option":return!0===b.__ko__hasDomDataOptionValue__?a.a.e.get(b,a.d.options.xb):7>=a.a.C?b.getAttributeNode("value")&&
57
- b.getAttributeNode("value").specified?b.value:b.text:b.value;case "select":return 0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex]):n;default:return b.value}},ha:function(b,c,d){switch(a.a.A(b)){case "option":switch(typeof c){case "string":a.a.e.set(b,a.d.options.xb,n);"__ko__hasDomDataOptionValue__"in b&&delete b.__ko__hasDomDataOptionValue__;b.value=c;break;default:a.a.e.set(b,a.d.options.xb,c),b.__ko__hasDomDataOptionValue__=!0,b.value="number"===typeof c?c:""}break;case "select":if(""===c||
58
- null===c)c=n;for(var e=-1,f=0,g=b.options.length,k;f<g;++f)if(k=a.j.u(b.options[f]),k==c||""==k&&c===n){e=f;break}if(d||0<=e||c===n&&1<b.size)b.selectedIndex=e;break;default:if(null===c||c===n)c="";b.value=c}}}})();a.b("selectExtensions",a.j);a.b("selectExtensions.readValue",a.j.u);a.b("selectExtensions.writeValue",a.j.ha);a.h=function(){function b(b){b=a.a.$a(b);123===b.charCodeAt(0)&&(b=b.slice(1,-1));var c=[],d=b.match(e),r,k=[],p=0;if(d){d.push(",");for(var A=0,y;y=d[A];++A){var t=y.charCodeAt(0);
59
- if(44===t){if(0>=p){c.push(r&&k.length?{key:r,value:k.join("")}:{unknown:r||k.join("")});r=p=0;k=[];continue}}else if(58===t){if(!p&&!r&&1===k.length){r=k.pop();continue}}else 47===t&&A&&1<y.length?(t=d[A-1].match(f))&&!g[t[0]]&&(b=b.substr(b.indexOf(y)+1),d=b.match(e),d.push(","),A=-1,y="/"):40===t||123===t||91===t?++p:41===t||125===t||93===t?--p:r||k.length||34!==t&&39!==t||(y=y.slice(1,-1));k.push(y)}}return c}var c=["true","false","null","undefined"],d=/^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i,
60
- e=RegExp("\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|/(?:[^/\\\\]|\\\\.)*/w*|[^\\s:,/][^,\"'{}()/:[\\]]*[^\\s,\"'{}()/:[\\]]|[^\\s]","g"),f=/[\])"'A-Za-z0-9_$]+$/,g={"in":1,"return":1,"typeof":1},k={};return{ta:[],ea:k,yb:b,Ua:function(e,m){function h(b,e){var m;if(!A){var l=a.getBindingHandler(b);if(l&&l.preprocess&&!(e=l.preprocess(e,b,h)))return;if(l=k[b])m=e,0<=a.a.o(c,m)?m=!1:(l=m.match(d),m=null===l?!1:l[1]?"Object("+l[1]+")"+l[2]:m),l=m;l&&g.push("'"+b+"':function(_z){"+m+"=_z}")}p&&(e=
61
- "function(){return "+e+" }");f.push("'"+b+"':"+e)}m=m||{};var f=[],g=[],p=m.valueAccessors,A=m.bindingParams,y="string"===typeof e?b(e):e;a.a.q(y,function(a){h(a.key||a.unknown,a.value)});g.length&&h("_ko_property_writers","{"+g.join(",")+" }");return f.join(",")},ad:function(a,b){for(var c=0;c<a.length;c++)if(a[c].key==b)return!0;return!1},Ea:function(b,c,d,e,f){if(b&&a.H(b))!a.Ba(b)||f&&b.t()===e||b(e);else if((b=c.get("_ko_property_writers"))&&b[d])b[d](e)}}}();a.b("expressionRewriting",a.h);a.b("expressionRewriting.bindingRewriteValidators",
62
- a.h.ta);a.b("expressionRewriting.parseObjectLiteral",a.h.yb);a.b("expressionRewriting.preProcessBindings",a.h.Ua);a.b("expressionRewriting._twoWayBindings",a.h.ea);a.b("jsonExpressionRewriting",a.h);a.b("jsonExpressionRewriting.insertPropertyAccessorsIntoJson",a.h.Ua);(function(){function b(a){return 8==a.nodeType&&g.test(f?a.text:a.nodeValue)}function c(a){return 8==a.nodeType&&k.test(f?a.text:a.nodeValue)}function d(a,d){for(var e=a,f=1,l=[];e=e.nextSibling;){if(c(e)&&(f--,0===f))return l;l.push(e);
63
- b(e)&&f++}if(!d)throw Error("Cannot find closing comment tag to match: "+a.nodeValue);return null}function e(a,b){var c=d(a,b);return c?0<c.length?c[c.length-1].nextSibling:a.nextSibling:null}var f=u&&"\x3c!--test--\x3e"===u.createComment("test").text,g=f?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,k=f?/^\x3c!--\s*\/ko\s*--\x3e$/:/^\s*\/ko\s*$/,l={ul:!0,ol:!0};a.f={Z:{},childNodes:function(a){return b(a)?d(a):a.childNodes},xa:function(c){if(b(c)){c=a.f.childNodes(c);for(var d=
64
- 0,e=c.length;d<e;d++)a.removeNode(c[d])}else a.a.ob(c)},da:function(c,d){if(b(c)){a.f.xa(c);for(var e=c.nextSibling,f=0,l=d.length;f<l;f++)e.parentNode.insertBefore(d[f],e)}else a.a.da(c,d)},mc:function(a,c){b(a)?a.parentNode.insertBefore(c,a.nextSibling):a.firstChild?a.insertBefore(c,a.firstChild):a.appendChild(c)},gc:function(c,d,e){e?b(c)?c.parentNode.insertBefore(d,e.nextSibling):e.nextSibling?c.insertBefore(d,e.nextSibling):c.appendChild(d):a.f.mc(c,d)},firstChild:function(a){return b(a)?!a.nextSibling||
65
- c(a.nextSibling)?null:a.nextSibling:a.firstChild},nextSibling:function(a){b(a)&&(a=e(a));return a.nextSibling&&c(a.nextSibling)?null:a.nextSibling},Tc:b,pd:function(a){return(a=(f?a.text:a.nodeValue).match(g))?a[1]:null},kc:function(d){if(l[a.a.A(d)]){var h=d.firstChild;if(h){do if(1===h.nodeType){var f;f=h.firstChild;var g=null;if(f){do if(g)g.push(f);else if(b(f)){var k=e(f,!0);k?f=k:g=[f]}else c(f)&&(g=[f]);while(f=f.nextSibling)}if(f=g)for(g=h.nextSibling,k=0;k<f.length;k++)g?d.insertBefore(f[k],
66
- g):d.appendChild(f[k])}while(h=h.nextSibling)}}}}})();a.b("virtualElements",a.f);a.b("virtualElements.allowedBindings",a.f.Z);a.b("virtualElements.emptyNode",a.f.xa);a.b("virtualElements.insertAfter",a.f.gc);a.b("virtualElements.prepend",a.f.mc);a.b("virtualElements.setDomNodeChildren",a.f.da);(function(){a.Q=function(){this.Fc={}};a.a.extend(a.Q.prototype,{nodeHasBindings:function(b){switch(b.nodeType){case 1:return null!=b.getAttribute("data-bind")||a.g.getComponentNameForNode(b);case 8:return a.f.Tc(b);
67
- default:return!1}},getBindings:function(b,c){var d=this.getBindingsString(b,c),d=d?this.parseBindingsString(d,c,b):null;return a.g.Ob(d,b,c,!1)},getBindingAccessors:function(b,c){var d=this.getBindingsString(b,c),d=d?this.parseBindingsString(d,c,b,{valueAccessors:!0}):null;return a.g.Ob(d,b,c,!0)},getBindingsString:function(b){switch(b.nodeType){case 1:return b.getAttribute("data-bind");case 8:return a.f.pd(b);default:return null}},parseBindingsString:function(b,c,d,e){try{var f=this.Fc,g=b+(e&&e.valueAccessors||
68
- ""),k;if(!(k=f[g])){var l,m="with($context){with($data||{}){return{"+a.h.Ua(b,e)+"}}}";l=new Function("$context","$element",m);k=f[g]=l}return k(c,d)}catch(h){throw h.message="Unable to parse bindings.\nBindings value: "+b+"\nMessage: "+h.message,h;}}});a.Q.instance=new a.Q})();a.b("bindingProvider",a.Q);(function(){function b(a){return function(){return a}}function c(a){return a()}function d(b){return a.a.Ca(a.l.w(b),function(a,c){return function(){return b()[c]}})}function e(c,e,h){return"function"===
69
- typeof c?d(c.bind(null,e,h)):a.a.Ca(c,b)}function f(a,b){return d(this.getBindings.bind(this,a,b))}function g(b,c,d){var e,h=a.f.firstChild(c),f=a.Q.instance,m=f.preprocessNode;if(m){for(;e=h;)h=a.f.nextSibling(e),m.call(f,e);h=a.f.firstChild(c)}for(;e=h;)h=a.f.nextSibling(e),k(b,e,d)}function k(b,c,d){var e=!0,h=1===c.nodeType;h&&a.f.kc(c);if(h&&d||a.Q.instance.nodeHasBindings(c))e=m(c,null,b,d).shouldBindDescendants;e&&!r[a.a.A(c)]&&g(b,c,!h)}function l(b){var c=[],d={},e=[];a.a.D(b,function Z(h){if(!d[h]){var f=
70
- a.getBindingHandler(h);f&&(f.after&&(e.push(h),a.a.q(f.after,function(c){if(b[c]){if(-1!==a.a.o(e,c))throw Error("Cannot combine the following bindings, because they have a cyclic dependency: "+e.join(", "));Z(c)}}),e.length--),c.push({key:h,fc:f}));d[h]=!0}});return c}function m(b,d,e,h){var m=a.a.e.get(b,q);if(!d){if(m)throw Error("You cannot apply bindings multiple times to the same element.");a.a.e.set(b,q,!0)}!m&&h&&a.tc(b,e);var g;if(d&&"function"!==typeof d)g=d;else{var k=a.Q.instance,r=k.getBindingAccessors||
71
- f,p=a.B(function(){(g=d?d(e,b):r.call(k,b,e))&&e.P&&e.P();return g},null,{i:b});g&&p.ba()||(p=null)}var u;if(g){var v=p?function(a){return function(){return c(p()[a])}}:function(a){return g[a]},s=function(){return a.a.Ca(p?p():g,c)};s.get=function(a){return g[a]&&c(v(a))};s.has=function(a){return a in g};h=l(g);a.a.q(h,function(c){var d=c.fc.init,h=c.fc.update,f=c.key;if(8===b.nodeType&&!a.f.Z[f])throw Error("The binding '"+f+"' cannot be used with virtual elements");try{"function"==typeof d&&a.l.w(function(){var a=
72
- d(b,v(f),s,e.$data,e);if(a&&a.controlsDescendantBindings){if(u!==n)throw Error("Multiple bindings ("+u+" and "+f+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");u=f}}),"function"==typeof h&&a.B(function(){h(b,v(f),s,e.$data,e)},null,{i:b})}catch(m){throw m.message='Unable to process binding "'+f+": "+g[f]+'"\nMessage: '+m.message,m;}})}return{shouldBindDescendants:u===n}}function h(b){return b&&b instanceof a.U?b:new a.U(b)}
73
- a.d={};var r={script:!0,textarea:!0,template:!0};a.getBindingHandler=function(b){return a.d[b]};a.U=function(b,c,d,e){var h=this,f="function"==typeof b&&!a.H(b),m,g=a.B(function(){var m=f?b():b,l=a.a.c(m);c?(c.P&&c.P(),a.a.extend(h,c),g&&(h.P=g)):(h.$parents=[],h.$root=l,h.ko=a);h.$rawData=m;h.$data=l;d&&(h[d]=l);e&&e(h,c,l);return h.$data},null,{wa:function(){return m&&!a.a.Qb(m)},i:!0});g.ba()&&(h.P=g,g.equalityComparer=null,m=[],g.Ac=function(b){m.push(b);a.a.F.oa(b,function(b){a.a.La(m,b);m.length||
74
- (g.k(),h.P=g=n)})})};a.U.prototype.createChildContext=function(b,c,d){return new a.U(b,this,c,function(a,b){a.$parentContext=b;a.$parent=b.$data;a.$parents=(b.$parents||[]).slice(0);a.$parents.unshift(a.$parent);d&&d(a)})};a.U.prototype.extend=function(b){return new a.U(this.P||this.$data,this,null,function(c,d){c.$rawData=d.$rawData;a.a.extend(c,"function"==typeof b?b():b)})};var q=a.a.e.I(),p=a.a.e.I();a.tc=function(b,c){if(2==arguments.length)a.a.e.set(b,p,c),c.P&&c.P.Ac(b);else return a.a.e.get(b,
75
- p)};a.Ja=function(b,c,d){1===b.nodeType&&a.f.kc(b);return m(b,c,h(d),!0)};a.Dc=function(b,c,d){d=h(d);return a.Ja(b,e(c,d,b),d)};a.eb=function(a,b){1!==b.nodeType&&8!==b.nodeType||g(h(a),b,!0)};a.Rb=function(a,b){!v&&x.jQuery&&(v=x.jQuery);if(b&&1!==b.nodeType&&8!==b.nodeType)throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");b=b||x.document.body;k(h(a),b,!0)};a.kb=function(b){switch(b.nodeType){case 1:case 8:var c=a.tc(b);if(c)return c;
76
- if(b.parentNode)return a.kb(b.parentNode)}return n};a.Jc=function(b){return(b=a.kb(b))?b.$data:n};a.b("bindingHandlers",a.d);a.b("applyBindings",a.Rb);a.b("applyBindingsToDescendants",a.eb);a.b("applyBindingAccessorsToNode",a.Ja);a.b("applyBindingsToNode",a.Dc);a.b("contextFor",a.kb);a.b("dataFor",a.Jc)})();(function(b){function c(c,e){var m=f.hasOwnProperty(c)?f[c]:b,h;m?m.X(e):(m=f[c]=new a.J,m.X(e),d(c,function(b,d){var e=!(!d||!d.synchronous);g[c]={definition:b,Zc:e};delete f[c];h||e?m.notifySubscribers(b):
77
- a.Y.Wa(function(){m.notifySubscribers(b)})}),h=!0)}function d(a,b){e("getConfig",[a],function(c){c?e("loadComponent",[a,c],function(a){b(a,c)}):b(null,null)})}function e(c,d,f,h){h||(h=a.g.loaders.slice(0));var g=h.shift();if(g){var q=g[c];if(q){var p=!1;if(q.apply(g,d.concat(function(a){p?f(null):null!==a?f(a):e(c,d,f,h)}))!==b&&(p=!0,!g.suppressLoaderExceptions))throw Error("Component loaders must supply values by invoking the callback, not by returning values synchronously.");}else e(c,d,f,h)}else f(null)}
78
- var f={},g={};a.g={get:function(d,e){var f=g.hasOwnProperty(d)?g[d]:b;f?f.Zc?a.l.w(function(){e(f.definition)}):a.Y.Wa(function(){e(f.definition)}):c(d,e)},Xb:function(a){delete g[a]},Jb:e};a.g.loaders=[];a.b("components",a.g);a.b("components.get",a.g.get);a.b("components.clearCachedDefinition",a.g.Xb)})();(function(){function b(b,c,d,e){function g(){0===--y&&e(k)}var k={},y=2,t=d.template;d=d.viewModel;t?f(c,t,function(c){a.g.Jb("loadTemplate",[b,c],function(a){k.template=a;g()})}):g();d?f(c,d,function(c){a.g.Jb("loadViewModel",
79
- [b,c],function(a){k[l]=a;g()})}):g()}function c(a,b,d){if("function"===typeof b)d(function(a){return new b(a)});else if("function"===typeof b[l])d(b[l]);else if("instance"in b){var e=b.instance;d(function(){return e})}else"viewModel"in b?c(a,b.viewModel,d):a("Unknown viewModel value: "+b)}function d(b){switch(a.a.A(b)){case "script":return a.a.ma(b.text);case "textarea":return a.a.ma(b.value);case "template":if(e(b.content))return a.a.ua(b.content.childNodes)}return a.a.ua(b.childNodes)}function e(a){return x.DocumentFragment?
80
- a instanceof DocumentFragment:a&&11===a.nodeType}function f(a,b,c){"string"===typeof b.require?O||x.require?(O||x.require)([b.require],c):a("Uses require, but no AMD loader is present"):c(b)}function g(a){return function(b){throw Error("Component '"+a+"': "+b);}}var k={};a.g.register=function(b,c){if(!c)throw Error("Invalid configuration for "+b);if(a.g.ub(b))throw Error("Component "+b+" is already registered");k[b]=c};a.g.ub=function(a){return k.hasOwnProperty(a)};a.g.od=function(b){delete k[b];
81
- a.g.Xb(b)};a.g.Zb={getConfig:function(a,b){b(k.hasOwnProperty(a)?k[a]:null)},loadComponent:function(a,c,d){var e=g(a);f(e,c,function(c){b(a,e,c,d)})},loadTemplate:function(b,c,f){b=g(b);if("string"===typeof c)f(a.a.ma(c));else if(c instanceof Array)f(c);else if(e(c))f(a.a.V(c.childNodes));else if(c.element)if(c=c.element,x.HTMLElement?c instanceof HTMLElement:c&&c.tagName&&1===c.nodeType)f(d(c));else if("string"===typeof c){var l=u.getElementById(c);l?f(d(l)):b("Cannot find element with ID "+c)}else b("Unknown element type: "+
82
- c);else b("Unknown template value: "+c)},loadViewModel:function(a,b,d){c(g(a),b,d)}};var l="createViewModel";a.b("components.register",a.g.register);a.b("components.isRegistered",a.g.ub);a.b("components.unregister",a.g.od);a.b("components.defaultLoader",a.g.Zb);a.g.loaders.push(a.g.Zb);a.g.Bc=k})();(function(){function b(b,e){var f=b.getAttribute("params");if(f){var f=c.parseBindingsString(f,e,b,{valueAccessors:!0,bindingParams:!0}),f=a.a.Ca(f,function(c){return a.m(c,null,{i:b})}),g=a.a.Ca(f,function(c){var e=
83
- c.t();return c.ba()?a.m({read:function(){return a.a.c(c())},write:a.Ba(e)&&function(a){c()(a)},i:b}):e});g.hasOwnProperty("$raw")||(g.$raw=f);return g}return{$raw:{}}}a.g.getComponentNameForNode=function(b){var c=a.a.A(b);if(a.g.ub(c)&&(-1!=c.indexOf("-")||"[object HTMLUnknownElement]"==""+b||8>=a.a.C&&b.tagName===c))return c};a.g.Ob=function(c,e,f,g){if(1===e.nodeType){var k=a.g.getComponentNameForNode(e);if(k){c=c||{};if(c.component)throw Error('Cannot use the "component" binding on a custom element matching a component');
84
- var l={name:k,params:b(e,f)};c.component=g?function(){return l}:l}}return c};var c=new a.Q;9>a.a.C&&(a.g.register=function(a){return function(b){u.createElement(b);return a.apply(this,arguments)}}(a.g.register),u.createDocumentFragment=function(b){return function(){var c=b(),f=a.g.Bc,g;for(g in f)f.hasOwnProperty(g)&&c.createElement(g);return c}}(u.createDocumentFragment))})();(function(b){function c(b,c,d){c=c.template;if(!c)throw Error("Component '"+b+"' has no template");b=a.a.ua(c);a.f.da(d,b)}
85
- function d(a,b,c,d){var e=a.createViewModel;return e?e.call(a,d,{element:b,templateNodes:c}):d}var e=0;a.d.component={init:function(f,g,k,l,m){function h(){var a=r&&r.dispose;"function"===typeof a&&a.call(r);q=r=null}var r,q,p=a.a.V(a.f.childNodes(f));a.a.F.oa(f,h);a.m(function(){var l=a.a.c(g()),k,t;"string"===typeof l?k=l:(k=a.a.c(l.name),t=a.a.c(l.params));if(!k)throw Error("No component name specified");var n=q=++e;a.g.get(k,function(e){if(q===n){h();if(!e)throw Error("Unknown component '"+k+
86
- "'");c(k,e,f);var g=d(e,f,p,t);e=m.createChildContext(g,b,function(a){a.$component=g;a.$componentTemplateNodes=p});r=g;a.eb(e,f)}})},null,{i:f});return{controlsDescendantBindings:!0}}};a.f.Z.component=!0})();var S={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,c){var d=a.a.c(c())||{};a.a.D(d,function(c,d){d=a.a.c(d);var g=!1===d||null===d||d===n;g&&b.removeAttribute(c);8>=a.a.C&&c in S?(c=S[c],g?b.removeAttribute(c):b[c]=d):g||b.setAttribute(c,d.toString());"name"===c&&a.a.rc(b,
87
- g?"":d.toString())})}};(function(){a.d.checked={after:["value","attr"],init:function(b,c,d){function e(){var e=b.checked,f=p?g():e;if(!a.va.Sa()&&(!l||e)){var m=a.l.w(c);if(h){var k=r?m.t():m;q!==f?(e&&(a.a.pa(k,f,!0),a.a.pa(k,q,!1)),q=f):a.a.pa(k,f,e);r&&a.Ba(m)&&m(k)}else a.h.Ea(m,d,"checked",f,!0)}}function f(){var d=a.a.c(c());b.checked=h?0<=a.a.o(d,g()):k?d:g()===d}var g=a.nc(function(){return d.has("checkedValue")?a.a.c(d.get("checkedValue")):d.has("value")?a.a.c(d.get("value")):b.value}),k=
88
- "checkbox"==b.type,l="radio"==b.type;if(k||l){var m=c(),h=k&&a.a.c(m)instanceof Array,r=!(h&&m.push&&m.splice),q=h?g():n,p=l||h;l&&!b.name&&a.d.uniqueName.init(b,function(){return!0});a.m(e,null,{i:b});a.a.p(b,"click",e);a.m(f,null,{i:b});m=n}}};a.h.ea.checked=!0;a.d.checkedValue={update:function(b,c){b.value=a.a.c(c())}}})();a.d.css={update:function(b,c){var d=a.a.c(c());null!==d&&"object"==typeof d?a.a.D(d,function(c,d){d=a.a.c(d);a.a.bb(b,c,d)}):(d=a.a.$a(String(d||"")),a.a.bb(b,b.__ko__cssValue,
89
- !1),b.__ko__cssValue=d,a.a.bb(b,d,!0))}};a.d.enable={update:function(b,c){var d=a.a.c(c());d&&b.disabled?b.removeAttribute("disabled"):d||b.disabled||(b.disabled=!0)}};a.d.disable={update:function(b,c){a.d.enable.update(b,function(){return!a.a.c(c())})}};a.d.event={init:function(b,c,d,e,f){var g=c()||{};a.a.D(g,function(g){"string"==typeof g&&a.a.p(b,g,function(b){var m,h=c()[g];if(h){try{var r=a.a.V(arguments);e=f.$data;r.unshift(e);m=h.apply(e,r)}finally{!0!==m&&(b.preventDefault?b.preventDefault():
90
- b.returnValue=!1)}!1===d.get(g+"Bubble")&&(b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation())}})})}};a.d.foreach={ic:function(b){return function(){var c=b(),d=a.a.zb(c);if(!d||"number"==typeof d.length)return{foreach:c,templateEngine:a.W.sb};a.a.c(c);return{foreach:d.data,as:d.as,includeDestroyed:d.includeDestroyed,afterAdd:d.afterAdd,beforeRemove:d.beforeRemove,afterRender:d.afterRender,beforeMove:d.beforeMove,afterMove:d.afterMove,templateEngine:a.W.sb}}},init:function(b,c){return a.d.template.init(b,
91
- a.d.foreach.ic(c))},update:function(b,c,d,e,f){return a.d.template.update(b,a.d.foreach.ic(c),d,e,f)}};a.h.ta.foreach=!1;a.f.Z.foreach=!0;a.d.hasfocus={init:function(b,c,d){function e(e){b.__ko_hasfocusUpdating=!0;var f=b.ownerDocument;if("activeElement"in f){var g;try{g=f.activeElement}catch(h){g=f.body}e=g===b}f=c();a.h.Ea(f,d,"hasfocus",e,!0);b.__ko_hasfocusLastValue=e;b.__ko_hasfocusUpdating=!1}var f=e.bind(null,!0),g=e.bind(null,!1);a.a.p(b,"focus",f);a.a.p(b,"focusin",f);a.a.p(b,"blur",g);a.a.p(b,
92
- "focusout",g)},update:function(b,c){var d=!!a.a.c(c());b.__ko_hasfocusUpdating||b.__ko_hasfocusLastValue===d||(d?b.focus():b.blur(),!d&&b.__ko_hasfocusLastValue&&b.ownerDocument.body.focus(),a.l.w(a.a.Da,null,[b,d?"focusin":"focusout"]))}};a.h.ea.hasfocus=!0;a.d.hasFocus=a.d.hasfocus;a.h.ea.hasFocus=!0;a.d.html={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Cb(b,c())}};K("if");K("ifnot",!1,!0);K("with",!0,!1,function(a,c){return a.createChildContext(c)});var L={};
93
- a.d.options={init:function(b){if("select"!==a.a.A(b))throw Error("options binding applies only to SELECT elements");for(;0<b.length;)b.remove(0);return{controlsDescendantBindings:!0}},update:function(b,c,d){function e(){return a.a.Ka(b.options,function(a){return a.selected})}function f(a,b,c){var d=typeof b;return"function"==d?b(a):"string"==d?a[b]:c}function g(c,e){if(A&&h)a.j.ha(b,a.a.c(d.get("value")),!0);else if(p.length){var f=0<=a.a.o(p,a.j.u(e[0]));a.a.sc(e[0],f);A&&!f&&a.l.w(a.a.Da,null,[b,
94
- "change"])}}var k=b.multiple,l=0!=b.length&&k?b.scrollTop:null,m=a.a.c(c()),h=d.get("valueAllowUnset")&&d.has("value"),r=d.get("optionsIncludeDestroyed");c={};var q,p=[];h||(k?p=a.a.fb(e(),a.j.u):0<=b.selectedIndex&&p.push(a.j.u(b.options[b.selectedIndex])));m&&("undefined"==typeof m.length&&(m=[m]),q=a.a.Ka(m,function(b){return r||b===n||null===b||!a.a.c(b._destroy)}),d.has("optionsCaption")&&(m=a.a.c(d.get("optionsCaption")),null!==m&&m!==n&&q.unshift(L)));var A=!1;c.beforeRemove=function(a){b.removeChild(a)};
95
- m=g;d.has("optionsAfterRender")&&"function"==typeof d.get("optionsAfterRender")&&(m=function(b,c){g(0,c);a.l.w(d.get("optionsAfterRender"),null,[c[0],b!==L?b:n])});a.a.Bb(b,q,function(c,e,g){g.length&&(p=!h&&g[0].selected?[a.j.u(g[0])]:[],A=!0);e=b.ownerDocument.createElement("option");c===L?(a.a.Za(e,d.get("optionsCaption")),a.j.ha(e,n)):(g=f(c,d.get("optionsValue"),c),a.j.ha(e,a.a.c(g)),c=f(c,d.get("optionsText"),g),a.a.Za(e,c));return[e]},c,m);a.l.w(function(){h?a.j.ha(b,a.a.c(d.get("value")),
96
- !0):(k?p.length&&e().length<p.length:p.length&&0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex])!==p[0]:p.length||0<=b.selectedIndex)&&a.a.Da(b,"change")});a.a.Nc(b);l&&20<Math.abs(l-b.scrollTop)&&(b.scrollTop=l)}};a.d.options.xb=a.a.e.I();a.d.selectedOptions={after:["options","foreach"],init:function(b,c,d){a.a.p(b,"change",function(){var e=c(),f=[];a.a.q(b.getElementsByTagName("option"),function(b){b.selected&&f.push(a.j.u(b))});a.h.Ea(e,d,"selectedOptions",f)})},update:function(b,c){if("select"!=
97
- a.a.A(b))throw Error("values binding applies only to SELECT elements");var d=a.a.c(c()),e=b.scrollTop;d&&"number"==typeof d.length&&a.a.q(b.getElementsByTagName("option"),function(b){var c=0<=a.a.o(d,a.j.u(b));b.selected!=c&&a.a.sc(b,c)});b.scrollTop=e}};a.h.ea.selectedOptions=!0;a.d.style={update:function(b,c){var d=a.a.c(c()||{});a.a.D(d,function(c,d){d=a.a.c(d);if(null===d||d===n||!1===d)d="";b.style[c]=d})}};a.d.submit={init:function(b,c,d,e,f){if("function"!=typeof c())throw Error("The value for a submit binding must be a function");
98
- a.a.p(b,"submit",function(a){var d,e=c();try{d=e.call(f.$data,b)}finally{!0!==d&&(a.preventDefault?a.preventDefault():a.returnValue=!1)}})}};a.d.text={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Za(b,c())}};a.f.Z.text=!0;(function(){if(x&&x.navigator)var b=function(a){if(a)return parseFloat(a[1])},c=x.opera&&x.opera.version&&parseInt(x.opera.version()),d=x.navigator.userAgent,e=b(d.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),f=b(d.match(/Firefox\/([^ ]*)/));
99
- if(10>a.a.C)var g=a.a.e.I(),k=a.a.e.I(),l=function(b){var c=this.activeElement;(c=c&&a.a.e.get(c,k))&&c(b)},m=function(b,c){var d=b.ownerDocument;a.a.e.get(d,g)||(a.a.e.set(d,g,!0),a.a.p(d,"selectionchange",l));a.a.e.set(b,k,c)};a.d.textInput={init:function(b,d,g){function l(c,d){a.a.p(b,c,d)}function k(){var c=a.a.c(d());if(null===c||c===n)c="";v!==n&&c===v?a.a.setTimeout(k,4):b.value!==c&&(u=c,b.value=c)}function y(){s||(v=b.value,s=a.a.setTimeout(t,4))}function t(){clearTimeout(s);v=s=n;var c=
100
- b.value;u!==c&&(u=c,a.h.Ea(d(),g,"textInput",c))}var u=b.value,s,v,x=9==a.a.C?y:t;10>a.a.C?(l("propertychange",function(a){"value"===a.propertyName&&x(a)}),8==a.a.C&&(l("keyup",t),l("keydown",t)),8<=a.a.C&&(m(b,x),l("dragend",y))):(l("input",t),5>e&&"textarea"===a.a.A(b)?(l("keydown",y),l("paste",y),l("cut",y)):11>c?l("keydown",y):4>f&&(l("DOMAutoComplete",t),l("dragdrop",t),l("drop",t)));l("change",t);a.m(k,null,{i:b})}};a.h.ea.textInput=!0;a.d.textinput={preprocess:function(a,b,c){c("textInput",
101
- a)}}})();a.d.uniqueName={init:function(b,c){if(c()){var d="ko_unique_"+ ++a.d.uniqueName.Ic;a.a.rc(b,d)}}};a.d.uniqueName.Ic=0;a.d.value={after:["options","foreach"],init:function(b,c,d){if("input"!=b.tagName.toLowerCase()||"checkbox"!=b.type&&"radio"!=b.type){var e=["change"],f=d.get("valueUpdate"),g=!1,k=null;f&&("string"==typeof f&&(f=[f]),a.a.ra(e,f),e=a.a.Tb(e));var l=function(){k=null;g=!1;var e=c(),f=a.j.u(b);a.h.Ea(e,d,"value",f)};!a.a.C||"input"!=b.tagName.toLowerCase()||"text"!=b.type||
102
- "off"==b.autocomplete||b.form&&"off"==b.form.autocomplete||-1!=a.a.o(e,"propertychange")||(a.a.p(b,"propertychange",function(){g=!0}),a.a.p(b,"focus",function(){g=!1}),a.a.p(b,"blur",function(){g&&l()}));a.a.q(e,function(c){var d=l;a.a.nd(c,"after")&&(d=function(){k=a.j.u(b);a.a.setTimeout(l,0)},c=c.substring(5));a.a.p(b,c,d)});var m=function(){var e=a.a.c(c()),f=a.j.u(b);if(null!==k&&e===k)a.a.setTimeout(m,0);else if(e!==f)if("select"===a.a.A(b)){var g=d.get("valueAllowUnset"),f=function(){a.j.ha(b,
103
- e,g)};f();g||e===a.j.u(b)?a.a.setTimeout(f,0):a.l.w(a.a.Da,null,[b,"change"])}else a.j.ha(b,e)};a.m(m,null,{i:b})}else a.Ja(b,{checkedValue:c})},update:function(){}};a.h.ea.value=!0;a.d.visible={update:function(b,c){var d=a.a.c(c()),e="none"!=b.style.display;d&&!e?b.style.display="":!d&&e&&(b.style.display="none")}};(function(b){a.d[b]={init:function(c,d,e,f,g){return a.d.event.init.call(this,c,function(){var a={};a[b]=d();return a},e,f,g)}}})("click");a.O=function(){};a.O.prototype.renderTemplateSource=
104
- function(){throw Error("Override renderTemplateSource");};a.O.prototype.createJavaScriptEvaluatorBlock=function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.O.prototype.makeTemplateSource=function(b,c){if("string"==typeof b){c=c||u;var d=c.getElementById(b);if(!d)throw Error("Cannot find template with ID "+b);return new a.v.n(d)}if(1==b.nodeType||8==b.nodeType)return new a.v.qa(b);throw Error("Unknown template type: "+b);};a.O.prototype.renderTemplate=function(a,c,d,e){a=this.makeTemplateSource(a,
105
- e);return this.renderTemplateSource(a,c,d,e)};a.O.prototype.isTemplateRewritten=function(a,c){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,c).data("isRewritten")};a.O.prototype.rewriteTemplate=function(a,c,d){a=this.makeTemplateSource(a,d);c=c(a.text());a.text(c);a.data("isRewritten",!0)};a.b("templateEngine",a.O);a.Gb=function(){function b(b,c,d,k){b=a.h.yb(b);for(var l=a.h.ta,m=0;m<b.length;m++){var h=b[m].key;if(l.hasOwnProperty(h)){var r=l[h];if("function"===typeof r){if(h=
106
- r(b[m].value))throw Error(h);}else if(!r)throw Error("This template engine does not support the '"+h+"' binding within its templates");}}d="ko.__tr_ambtns(function($context,$element){return(function(){return{ "+a.h.Ua(b,{valueAccessors:!0})+" } })()},'"+d.toLowerCase()+"')";return k.createJavaScriptEvaluatorBlock(d)+c}var c=/(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,d=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{Oc:function(b,
107
- c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.Gb.dd(b,c)},d)},dd:function(a,f){return a.replace(c,function(a,c,d,e,h){return b(h,c,d,f)}).replace(d,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",f)})},Ec:function(b,c){return a.M.wb(function(d,k){var l=d.nextSibling;l&&l.nodeName.toLowerCase()===c&&a.Ja(l,b,k)})}}}();a.b("__tr_ambtns",a.Gb.Ec);(function(){a.v={};a.v.n=function(b){if(this.n=b){var c=a.a.A(b);this.ab="script"===c?1:"textarea"===c?2:"template"==c&&
108
- b.content&&11===b.content.nodeType?3:4}};a.v.n.prototype.text=function(){var b=1===this.ab?"text":2===this.ab?"value":"innerHTML";if(0==arguments.length)return this.n[b];var c=arguments[0];"innerHTML"===b?a.a.Cb(this.n,c):this.n[b]=c};var b=a.a.e.I()+"_";a.v.n.prototype.data=function(c){if(1===arguments.length)return a.a.e.get(this.n,b+c);a.a.e.set(this.n,b+c,arguments[1])};var c=a.a.e.I();a.v.n.prototype.nodes=function(){var b=this.n;if(0==arguments.length)return(a.a.e.get(b,c)||{}).jb||(3===this.ab?
109
- b.content:4===this.ab?b:n);a.a.e.set(b,c,{jb:arguments[0]})};a.v.qa=function(a){this.n=a};a.v.qa.prototype=new a.v.n;a.v.qa.prototype.text=function(){if(0==arguments.length){var b=a.a.e.get(this.n,c)||{};b.Hb===n&&b.jb&&(b.Hb=b.jb.innerHTML);return b.Hb}a.a.e.set(this.n,c,{Hb:arguments[0]})};a.b("templateSources",a.v);a.b("templateSources.domElement",a.v.n);a.b("templateSources.anonymousTemplate",a.v.qa)})();(function(){function b(b,c,d){var e;for(c=a.f.nextSibling(c);b&&(e=b)!==c;)b=a.f.nextSibling(e),
110
- d(e,b)}function c(c,d){if(c.length){var e=c[0],f=c[c.length-1],g=e.parentNode,k=a.Q.instance,n=k.preprocessNode;if(n){b(e,f,function(a,b){var c=a.previousSibling,d=n.call(k,a);d&&(a===e&&(e=d[0]||b),a===f&&(f=d[d.length-1]||c))});c.length=0;if(!e)return;e===f?c.push(e):(c.push(e,f),a.a.za(c,g))}b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.Rb(d,b)});b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.M.yc(b,[d])});a.a.za(c,g)}}function d(a){return a.nodeType?a:0<a.length?a[0]:null}function e(b,
111
- e,f,k,q){q=q||{};var p=(b&&d(b)||f||{}).ownerDocument,n=q.templateEngine||g;a.Gb.Oc(f,n,p);f=n.renderTemplate(f,k,q,p);if("number"!=typeof f.length||0<f.length&&"number"!=typeof f[0].nodeType)throw Error("Template engine must return an array of DOM nodes");p=!1;switch(e){case "replaceChildren":a.f.da(b,f);p=!0;break;case "replaceNode":a.a.qc(b,f);p=!0;break;case "ignoreTargetNode":break;default:throw Error("Unknown renderMode: "+e);}p&&(c(f,k),q.afterRender&&a.l.w(q.afterRender,null,[f,k.$data]));
112
- return f}function f(b,c,d){return a.H(b)?b():"function"===typeof b?b(c,d):b}var g;a.Db=function(b){if(b!=n&&!(b instanceof a.O))throw Error("templateEngine must inherit from ko.templateEngine");g=b};a.Ab=function(b,c,h,k,q){h=h||{};if((h.templateEngine||g)==n)throw Error("Set a template engine before calling renderTemplate");q=q||"replaceChildren";if(k){var p=d(k);return a.B(function(){var g=c&&c instanceof a.U?c:new a.U(a.a.c(c)),n=f(b,g.$data,g),g=e(k,q,n,g,h);"replaceNode"==q&&(k=g,p=d(k))},null,
113
- {wa:function(){return!p||!a.a.nb(p)},i:p&&"replaceNode"==q?p.parentNode:p})}return a.M.wb(function(d){a.Ab(b,c,h,d,"replaceNode")})};a.kd=function(b,d,g,k,q){function p(a,b){c(b,s);g.afterRender&&g.afterRender(b,a);s=null}function u(a,c){s=q.createChildContext(a,g.as,function(a){a.$index=c});var d=f(b,a,s);return e(null,"ignoreTargetNode",d,s,g)}var s;return a.B(function(){var b=a.a.c(d)||[];"undefined"==typeof b.length&&(b=[b]);b=a.a.Ka(b,function(b){return g.includeDestroyed||b===n||null===b||!a.a.c(b._destroy)});
114
- a.l.w(a.a.Bb,null,[k,b,u,g,p])},null,{i:k})};var k=a.a.e.I();a.d.template={init:function(b,c){var d=a.a.c(c());if("string"==typeof d||d.name)a.f.xa(b);else{if("nodes"in d){if(d=d.nodes||[],a.H(d))throw Error('The "nodes" option must be a plain, non-observable array.');}else d=a.f.childNodes(b);d=a.a.jc(d);(new a.v.qa(b)).nodes(d)}return{controlsDescendantBindings:!0}},update:function(b,c,d,e,f){var g=c(),s;c=a.a.c(g);d=!0;e=null;"string"==typeof c?c={}:(g=c.name,"if"in c&&(d=a.a.c(c["if"])),d&&"ifnot"in
115
- c&&(d=!a.a.c(c.ifnot)),s=a.a.c(c.data));"foreach"in c?e=a.kd(g||b,d&&c.foreach||[],c,b,f):d?(f="data"in c?f.createChildContext(s,c.as):f,e=a.Ab(g||b,f,c,b)):a.f.xa(b);f=e;(s=a.a.e.get(b,k))&&"function"==typeof s.k&&s.k();a.a.e.set(b,k,f&&f.ba()?f:n)}};a.h.ta.template=function(b){b=a.h.yb(b);return 1==b.length&&b[0].unknown||a.h.ad(b,"name")?null:"This template engine does not support anonymous templates nested within its templates"};a.f.Z.template=!0})();a.b("setTemplateEngine",a.Db);a.b("renderTemplate",
116
- a.Ab);a.a.dc=function(a,c,d){if(a.length&&c.length){var e,f,g,k,l;for(e=f=0;(!d||e<d)&&(k=a[f]);++f){for(g=0;l=c[g];++g)if(k.value===l.value){k.moved=l.index;l.moved=k.index;c.splice(g,1);e=g=0;break}e+=g}}};a.a.ib=function(){function b(b,d,e,f,g){var k=Math.min,l=Math.max,m=[],h,n=b.length,q,p=d.length,s=p-n||1,u=n+p+1,t,v,x;for(h=0;h<=n;h++)for(v=t,m.push(t=[]),x=k(p,h+s),q=l(0,h-1);q<=x;q++)t[q]=q?h?b[h-1]===d[q-1]?v[q-1]:k(v[q]||u,t[q-1]||u)+1:q+1:h+1;k=[];l=[];s=[];h=n;for(q=p;h||q;)p=m[h][q]-
117
- 1,q&&p===m[h][q-1]?l.push(k[k.length]={status:e,value:d[--q],index:q}):h&&p===m[h-1][q]?s.push(k[k.length]={status:f,value:b[--h],index:h}):(--q,--h,g.sparse||k.push({status:"retained",value:d[q]}));a.a.dc(s,l,!g.dontLimitMoves&&10*n);return k.reverse()}return function(a,d,e){e="boolean"===typeof e?{dontLimitMoves:e}:e||{};a=a||[];d=d||[];return a.length<d.length?b(a,d,"added","deleted",e):b(d,a,"deleted","added",e)}}();a.b("utils.compareArrays",a.a.ib);(function(){function b(b,c,d,k,l){var m=[],
118
- h=a.B(function(){var h=c(d,l,a.a.za(m,b))||[];0<m.length&&(a.a.qc(m,h),k&&a.l.w(k,null,[d,h,l]));m.length=0;a.a.ra(m,h)},null,{i:b,wa:function(){return!a.a.Qb(m)}});return{ca:m,B:h.ba()?h:n}}var c=a.a.e.I(),d=a.a.e.I();a.a.Bb=function(e,f,g,k,l){function m(b,c){w=q[c];v!==c&&(D[b]=w);w.qb(v++);a.a.za(w.ca,e);u.push(w);z.push(w)}function h(b,c){if(b)for(var d=0,e=c.length;d<e;d++)c[d]&&a.a.q(c[d].ca,function(a){b(a,d,c[d].ja)})}f=f||[];k=k||{};var r=a.a.e.get(e,c)===n,q=a.a.e.get(e,c)||[],p=a.a.fb(q,
119
- function(a){return a.ja}),s=a.a.ib(p,f,k.dontLimitMoves),u=[],t=0,v=0,x=[],z=[];f=[];for(var D=[],p=[],w,C=0,B,E;B=s[C];C++)switch(E=B.moved,B.status){case "deleted":E===n&&(w=q[t],w.B&&(w.B.k(),w.B=n),a.a.za(w.ca,e).length&&(k.beforeRemove&&(u.push(w),z.push(w),w.ja===d?w=null:f[C]=w),w&&x.push.apply(x,w.ca)));t++;break;case "retained":m(C,t++);break;case "added":E!==n?m(C,E):(w={ja:B.value,qb:a.N(v++)},u.push(w),z.push(w),r||(p[C]=w))}a.a.e.set(e,c,u);h(k.beforeMove,D);a.a.q(x,k.beforeRemove?a.$:
120
- a.removeNode);for(var C=0,r=a.f.firstChild(e),F;w=z[C];C++){w.ca||a.a.extend(w,b(e,g,w.ja,l,w.qb));for(t=0;s=w.ca[t];r=s.nextSibling,F=s,t++)s!==r&&a.f.gc(e,s,F);!w.Wc&&l&&(l(w.ja,w.ca,w.qb),w.Wc=!0)}h(k.beforeRemove,f);for(C=0;C<f.length;++C)f[C]&&(f[C].ja=d);h(k.afterMove,D);h(k.afterAdd,p)}})();a.b("utils.setDomNodeChildrenFromArrayMapping",a.a.Bb);a.W=function(){this.allowTemplateRewriting=!1};a.W.prototype=new a.O;a.W.prototype.renderTemplateSource=function(b,c,d,e){if(c=(9>a.a.C?0:b.nodes)?
121
- b.nodes():null)return a.a.V(c.cloneNode(!0).childNodes);b=b.text();return a.a.ma(b,e)};a.W.sb=new a.W;a.Db(a.W.sb);a.b("nativeTemplateEngine",a.W);(function(){a.vb=function(){var a=this.$c=function(){if(!v||!v.tmpl)return 0;try{if(0<=v.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b,e,f,g){g=g||u;f=f||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var k=b.data("precompiled");
122
- k||(k=b.text()||"",k=v.template(null,"{{ko_with $item.koBindingContext}}"+k+"{{/ko_with}}"),b.data("precompiled",k));b=[e.$data];e=v.extend({koBindingContext:e},f.templateOptions);e=v.tmpl(k,b,e);e.appendTo(g.createElement("div"));v.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+a+" })()) }}"};this.addTemplate=function(a,b){u.write("<script type='text/html' id='"+a+"'>"+b+"\x3c/script>")};0<a&&(v.tmpl.tag.ko_code={open:"__.push($1 || '');"},
123
- v.tmpl.tag.ko_with={open:"with($1) {",close:"} "})};a.vb.prototype=new a.O;var b=new a.vb;0<b.$c&&a.Db(b);a.b("jqueryTmplTemplateEngine",a.vb)})()})})();})();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/.gitattributes DELETED
@@ -1,8 +0,0 @@
1
- *.txt text
2
- *.js text
3
- *.html text
4
- *.md text
5
- *.json text
6
- *.yml text
7
- *.css text
8
- *.svg text
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/.npmignore DELETED
@@ -1,9 +0,0 @@
1
- /node_modules
2
- /demo
3
- /doc
4
- /test
5
- /index.html
6
- /mode/*/*test.js
7
- /mode/*/*.html
8
- /mode/index.html
9
- .*
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/.travis.yml DELETED
@@ -1,4 +0,0 @@
1
- language: node_js
2
- node_js:
3
- - stable
4
- sudo: false
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/comment_test.js DELETED
@@ -1,100 +0,0 @@
1
- namespace = "comment_";
2
-
3
- (function() {
4
- function test(name, mode, run, before, after) {
5
- return testCM(name, function(cm) {
6
- run(cm);
7
- eq(cm.getValue(), after);
8
- }, {value: before, mode: mode});
9
- }
10
-
11
- var simpleProg = "function foo() {\n return bar;\n}";
12
- var inlineBlock = "foo(/* bar */ true);";
13
- var inlineBlocks = "foo(/* bar */ true, /* baz */ false);";
14
- var multiLineInlineBlock = ["above();", "foo(/* bar */ true);", "below();"];
15
-
16
- test("block", "javascript", function(cm) {
17
- cm.blockComment(Pos(0, 3), Pos(3, 0), {blockCommentLead: " *"});
18
- }, simpleProg + "\n", "/* function foo() {\n * return bar;\n * }\n */");
19
-
20
- test("blockToggle", "javascript", function(cm) {
21
- cm.blockComment(Pos(0, 3), Pos(2, 0), {blockCommentLead: " *"});
22
- cm.uncomment(Pos(0, 3), Pos(2, 0), {blockCommentLead: " *"});
23
- }, simpleProg, simpleProg);
24
-
25
- test("blockToggle2", "javascript", function(cm) {
26
- cm.setCursor({line: 0, ch: 7 /* inside the block comment */});
27
- cm.execCommand("toggleComment");
28
- }, inlineBlock, "foo(bar true);");
29
-
30
- // This test should work but currently fails.
31
- // test("blockToggle3", "javascript", function(cm) {
32
- // cm.setCursor({line: 0, ch: 7 /* inside the first block comment */});
33
- // cm.execCommand("toggleComment");
34
- // }, inlineBlocks, "foo(bar true, /* baz */ false);");
35
-
36
- test("line", "javascript", function(cm) {
37
- cm.lineComment(Pos(1, 1), Pos(1, 1));
38
- }, simpleProg, "function foo() {\n// return bar;\n}");
39
-
40
- test("lineToggle", "javascript", function(cm) {
41
- cm.lineComment(Pos(0, 0), Pos(2, 1));
42
- cm.uncomment(Pos(0, 0), Pos(2, 1));
43
- }, simpleProg, simpleProg);
44
-
45
- test("fallbackToBlock", "css", function(cm) {
46
- cm.lineComment(Pos(0, 0), Pos(2, 1));
47
- }, "html {\n border: none;\n}", "/* html {\n border: none;\n} */");
48
-
49
- test("fallbackToLine", "ruby", function(cm) {
50
- cm.blockComment(Pos(0, 0), Pos(1));
51
- }, "def blah()\n return hah\n", "# def blah()\n# return hah\n");
52
-
53
- test("ignoreExternalBlockComments", "javascript", function(cm) {
54
- cm.execCommand("toggleComment");
55
- }, inlineBlocks, "// " + inlineBlocks);
56
-
57
- test("ignoreExternalBlockComments2", "javascript", function(cm) {
58
- cm.setCursor({line: 0, ch: null /* eol */});
59
- cm.execCommand("toggleComment");
60
- }, inlineBlocks, "// " + inlineBlocks);
61
-
62
- test("ignoreExternalBlockCommentsMultiLineAbove", "javascript", function(cm) {
63
- cm.setSelection({line: 0, ch: 0}, {line: 1, ch: 1});
64
- cm.execCommand("toggleComment");
65
- }, multiLineInlineBlock.join("\n"), ["// " + multiLineInlineBlock[0],
66
- "// " + multiLineInlineBlock[1],
67
- multiLineInlineBlock[2]].join("\n"));
68
-
69
- test("ignoreExternalBlockCommentsMultiLineBelow", "javascript", function(cm) {
70
- cm.setSelection({line: 1, ch: 13 /* after end of block comment */}, {line: 2, ch: 1});
71
- cm.execCommand("toggleComment");
72
- }, multiLineInlineBlock.join("\n"), [multiLineInlineBlock[0],
73
- "// " + multiLineInlineBlock[1],
74
- "// " + multiLineInlineBlock[2]].join("\n"));
75
-
76
- test("commentRange", "javascript", function(cm) {
77
- cm.blockComment(Pos(1, 2), Pos(1, 13), {fullLines: false});
78
- }, simpleProg, "function foo() {\n /*return bar;*/\n}");
79
-
80
- test("indented", "javascript", function(cm) {
81
- cm.lineComment(Pos(1, 0), Pos(2), {indent: true});
82
- }, simpleProg, "function foo() {\n // return bar;\n // }");
83
-
84
- test("singleEmptyLine", "javascript", function(cm) {
85
- cm.setCursor(1);
86
- cm.execCommand("toggleComment");
87
- }, "a;\n\nb;", "a;\n// \nb;");
88
-
89
- test("dontMessWithStrings", "javascript", function(cm) {
90
- cm.execCommand("toggleComment");
91
- }, "console.log(\"/*string*/\");", "// console.log(\"/*string*/\");");
92
-
93
- test("dontMessWithStrings2", "javascript", function(cm) {
94
- cm.execCommand("toggleComment");
95
- }, "console.log(\"// string\");", "// console.log(\"// string\");");
96
-
97
- test("dontMessWithStrings3", "javascript", function(cm) {
98
- cm.execCommand("toggleComment");
99
- }, "// console.log(\"// string\");", "console.log(\"// string\");");
100
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/doc_test.js DELETED
@@ -1,371 +0,0 @@
1
- (function() {
2
- // A minilanguage for instantiating linked CodeMirror instances and Docs
3
- function instantiateSpec(spec, place, opts) {
4
- var names = {}, pos = 0, l = spec.length, editors = [];
5
- while (spec) {
6
- var m = spec.match(/^(\w+)(\*?)(?:='([^\']*)'|<(~?)(\w+)(?:\/(\d+)-(\d+))?)\s*/);
7
- var name = m[1], isDoc = m[2], cur;
8
- if (m[3]) {
9
- cur = isDoc ? CodeMirror.Doc(m[3]) : CodeMirror(place, clone(opts, {value: m[3]}));
10
- } else {
11
- var other = m[5];
12
- if (!names.hasOwnProperty(other)) {
13
- names[other] = editors.length;
14
- editors.push(CodeMirror(place, opts));
15
- }
16
- var doc = editors[names[other]].linkedDoc({
17
- sharedHist: !m[4],
18
- from: m[6] ? Number(m[6]) : null,
19
- to: m[7] ? Number(m[7]) : null
20
- });
21
- cur = isDoc ? doc : CodeMirror(place, clone(opts, {value: doc}));
22
- }
23
- names[name] = editors.length;
24
- editors.push(cur);
25
- spec = spec.slice(m[0].length);
26
- }
27
- return editors;
28
- }
29
-
30
- function clone(obj, props) {
31
- if (!obj) return;
32
- clone.prototype = obj;
33
- var inst = new clone();
34
- if (props) for (var n in props) if (props.hasOwnProperty(n))
35
- inst[n] = props[n];
36
- return inst;
37
- }
38
-
39
- function eqAll(val) {
40
- var end = arguments.length, msg = null;
41
- if (typeof arguments[end-1] == "string")
42
- msg = arguments[--end];
43
- if (i == end) throw new Error("No editors provided to eqAll");
44
- for (var i = 1; i < end; ++i)
45
- eq(arguments[i].getValue(), val, msg)
46
- }
47
-
48
- function testDoc(name, spec, run, opts, expectFail) {
49
- if (!opts) opts = {};
50
-
51
- return test("doc_" + name, function() {
52
- var place = document.getElementById("testground");
53
- var editors = instantiateSpec(spec, place, opts);
54
- var successful = false;
55
-
56
- try {
57
- run.apply(null, editors);
58
- successful = true;
59
- } finally {
60
- if (!successful || verbose) {
61
- place.style.visibility = "visible";
62
- } else {
63
- for (var i = 0; i < editors.length; ++i)
64
- if (editors[i] instanceof CodeMirror)
65
- place.removeChild(editors[i].getWrapperElement());
66
- }
67
- }
68
- }, expectFail);
69
- }
70
-
71
- var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
72
-
73
- function testBasic(a, b) {
74
- eqAll("x", a, b);
75
- a.setValue("hey");
76
- eqAll("hey", a, b);
77
- b.setValue("wow");
78
- eqAll("wow", a, b);
79
- a.replaceRange("u\nv\nw", Pos(0, 3));
80
- b.replaceRange("i", Pos(0, 4));
81
- b.replaceRange("j", Pos(2, 1));
82
- eqAll("wowui\nv\nwj", a, b);
83
- }
84
-
85
- testDoc("basic", "A='x' B<A", testBasic);
86
- testDoc("basicSeparate", "A='x' B<~A", testBasic);
87
-
88
- testDoc("sharedHist", "A='ab\ncd\nef' B<A", function(a, b) {
89
- a.replaceRange("x", Pos(0));
90
- b.replaceRange("y", Pos(1));
91
- a.replaceRange("z", Pos(2));
92
- eqAll("abx\ncdy\nefz", a, b);
93
- a.undo();
94
- a.undo();
95
- eqAll("abx\ncd\nef", a, b);
96
- a.redo();
97
- eqAll("abx\ncdy\nef", a, b);
98
- b.redo();
99
- eqAll("abx\ncdy\nefz", a, b);
100
- a.undo(); b.undo(); a.undo(); a.undo();
101
- eqAll("ab\ncd\nef", a, b);
102
- }, null, ie_lt8);
103
-
104
- testDoc("undoIntact", "A='ab\ncd\nef' B<~A", function(a, b) {
105
- a.replaceRange("x", Pos(0));
106
- b.replaceRange("y", Pos(1));
107
- a.replaceRange("z", Pos(2));
108
- a.replaceRange("q", Pos(0));
109
- eqAll("abxq\ncdy\nefz", a, b);
110
- a.undo();
111
- a.undo();
112
- eqAll("abx\ncdy\nef", a, b);
113
- b.undo();
114
- eqAll("abx\ncd\nef", a, b);
115
- a.redo();
116
- eqAll("abx\ncd\nefz", a, b);
117
- a.redo();
118
- eqAll("abxq\ncd\nefz", a, b);
119
- a.undo(); a.undo(); a.undo(); a.undo();
120
- eqAll("ab\ncd\nef", a, b);
121
- b.redo();
122
- eqAll("ab\ncdy\nef", a, b);
123
- });
124
-
125
- testDoc("undoConflict", "A='ab\ncd\nef' B<~A", function(a, b) {
126
- a.replaceRange("x", Pos(0));
127
- a.replaceRange("z", Pos(2));
128
- // This should clear the first undo event in a, but not the second
129
- b.replaceRange("y", Pos(0));
130
- a.undo(); a.undo();
131
- eqAll("abxy\ncd\nef", a, b);
132
- a.replaceRange("u", Pos(2));
133
- a.replaceRange("v", Pos(0));
134
- // This should clear both events in a
135
- b.replaceRange("w", Pos(0));
136
- a.undo(); a.undo();
137
- eqAll("abxyvw\ncd\nefu", a, b);
138
- });
139
-
140
- testDoc("doubleRebase", "A='ab\ncd\nef\ng' B<~A C<B", function(a, b, c) {
141
- c.replaceRange("u", Pos(3));
142
- a.replaceRange("", Pos(0, 0), Pos(1, 0));
143
- c.undo();
144
- eqAll("cd\nef\ng", a, b, c);
145
- });
146
-
147
- testDoc("undoUpdate", "A='ab\ncd\nef' B<~A", function(a, b) {
148
- a.replaceRange("x", Pos(2));
149
- b.replaceRange("u\nv\nw\n", Pos(0, 0));
150
- a.undo();
151
- eqAll("u\nv\nw\nab\ncd\nef", a, b);
152
- a.redo();
153
- eqAll("u\nv\nw\nab\ncd\nefx", a, b);
154
- a.undo();
155
- eqAll("u\nv\nw\nab\ncd\nef", a, b);
156
- b.undo();
157
- a.redo();
158
- eqAll("ab\ncd\nefx", a, b);
159
- a.undo();
160
- eqAll("ab\ncd\nef", a, b);
161
- });
162
-
163
- testDoc("undoKeepRanges", "A='abcdefg' B<A", function(a, b) {
164
- var m = a.markText(Pos(0, 1), Pos(0, 3), {className: "foo"});
165
- b.replaceRange("x", Pos(0, 0));
166
- eqPos(m.find().from, Pos(0, 2));
167
- b.replaceRange("yzzy", Pos(0, 1), Pos(0));
168
- eq(m.find(), null);
169
- b.undo();
170
- eqPos(m.find().from, Pos(0, 2));
171
- b.undo();
172
- eqPos(m.find().from, Pos(0, 1));
173
- });
174
-
175
- testDoc("longChain", "A='uv' B<A C<B D<C", function(a, b, c, d) {
176
- a.replaceSelection("X");
177
- eqAll("Xuv", a, b, c, d);
178
- d.replaceRange("Y", Pos(0));
179
- eqAll("XuvY", a, b, c, d);
180
- });
181
-
182
- testDoc("broadCast", "B<A C<A D<A E<A", function(a, b, c, d, e) {
183
- b.setValue("uu");
184
- eqAll("uu", a, b, c, d, e);
185
- a.replaceRange("v", Pos(0, 1));
186
- eqAll("uvu", a, b, c, d, e);
187
- });
188
-
189
- // A and B share a history, C and D share a separate one
190
- testDoc("islands", "A='x\ny\nz' B<A C<~A D<C", function(a, b, c, d) {
191
- a.replaceRange("u", Pos(0));
192
- d.replaceRange("v", Pos(2));
193
- b.undo();
194
- eqAll("x\ny\nzv", a, b, c, d);
195
- c.undo();
196
- eqAll("x\ny\nz", a, b, c, d);
197
- a.redo();
198
- eqAll("xu\ny\nz", a, b, c, d);
199
- d.redo();
200
- eqAll("xu\ny\nzv", a, b, c, d);
201
- });
202
-
203
- testDoc("unlink", "B<A C<A D<B", function(a, b, c, d) {
204
- a.setValue("hi");
205
- b.unlinkDoc(a);
206
- d.setValue("aye");
207
- eqAll("hi", a, c);
208
- eqAll("aye", b, d);
209
- a.setValue("oo");
210
- eqAll("oo", a, c);
211
- eqAll("aye", b, d);
212
- });
213
-
214
- testDoc("bareDoc", "A*='foo' B*<A C<B", function(a, b, c) {
215
- is(a instanceof CodeMirror.Doc);
216
- is(b instanceof CodeMirror.Doc);
217
- is(c instanceof CodeMirror);
218
- eqAll("foo", a, b, c);
219
- a.replaceRange("hey", Pos(0, 0), Pos(0));
220
- c.replaceRange("!", Pos(0));
221
- eqAll("hey!", a, b, c);
222
- b.unlinkDoc(a);
223
- b.setValue("x");
224
- eqAll("x", b, c);
225
- eqAll("hey!", a);
226
- });
227
-
228
- testDoc("swapDoc", "A='a' B*='b' C<A", function(a, b, c) {
229
- var d = a.swapDoc(b);
230
- d.setValue("x");
231
- eqAll("x", c, d);
232
- eqAll("b", a, b);
233
- });
234
-
235
- testDoc("docKeepsScroll", "A='x' B*='y'", function(a, b) {
236
- addDoc(a, 200, 200);
237
- a.scrollIntoView(Pos(199, 200));
238
- var c = a.swapDoc(b);
239
- a.swapDoc(c);
240
- var pos = a.getScrollInfo();
241
- is(pos.left > 0, "not at left");
242
- is(pos.top > 0, "not at top");
243
- });
244
-
245
- testDoc("copyDoc", "A='u'", function(a) {
246
- var copy = a.getDoc().copy(true);
247
- a.setValue("foo");
248
- copy.setValue("bar");
249
- var old = a.swapDoc(copy);
250
- eq(a.getValue(), "bar");
251
- a.undo();
252
- eq(a.getValue(), "u");
253
- a.swapDoc(old);
254
- eq(a.getValue(), "foo");
255
- eq(old.historySize().undo, 1);
256
- eq(old.copy(false).historySize().undo, 0);
257
- });
258
-
259
- testDoc("docKeepsMode", "A='1+1'", function(a) {
260
- var other = CodeMirror.Doc("hi", "text/x-markdown");
261
- a.setOption("mode", "text/javascript");
262
- var old = a.swapDoc(other);
263
- eq(a.getOption("mode"), "text/x-markdown");
264
- eq(a.getMode().name, "markdown");
265
- a.swapDoc(old);
266
- eq(a.getOption("mode"), "text/javascript");
267
- eq(a.getMode().name, "javascript");
268
- });
269
-
270
- testDoc("subview", "A='1\n2\n3\n4\n5' B<~A/1-3", function(a, b) {
271
- eq(b.getValue(), "2\n3");
272
- eq(b.firstLine(), 1);
273
- b.setCursor(Pos(4));
274
- eqPos(b.getCursor(), Pos(2, 1));
275
- a.replaceRange("-1\n0\n", Pos(0, 0));
276
- eq(b.firstLine(), 3);
277
- eqPos(b.getCursor(), Pos(4, 1));
278
- a.undo();
279
- eqPos(b.getCursor(), Pos(2, 1));
280
- b.replaceRange("oyoy\n", Pos(2, 0));
281
- eq(a.getValue(), "1\n2\noyoy\n3\n4\n5");
282
- b.undo();
283
- eq(a.getValue(), "1\n2\n3\n4\n5");
284
- });
285
-
286
- testDoc("subviewEditOnBoundary", "A='11\n22\n33\n44\n55' B<~A/1-4", function(a, b) {
287
- a.replaceRange("x\nyy\nz", Pos(0, 1), Pos(2, 1));
288
- eq(b.firstLine(), 2);
289
- eq(b.lineCount(), 2);
290
- eq(b.getValue(), "z3\n44");
291
- a.replaceRange("q\nrr\ns", Pos(3, 1), Pos(4, 1));
292
- eq(b.firstLine(), 2);
293
- eq(b.getValue(), "z3\n4q");
294
- eq(a.getValue(), "1x\nyy\nz3\n4q\nrr\ns5");
295
- a.execCommand("selectAll");
296
- a.replaceSelection("!");
297
- eqAll("!", a, b);
298
- });
299
-
300
-
301
- testDoc("sharedMarker", "A='ab\ncd\nef\ngh' B<A C<~A/1-2", function(a, b, c) {
302
- var mark = b.markText(Pos(0, 1), Pos(3, 1),
303
- {className: "cm-searching", shared: true});
304
- var found = a.findMarksAt(Pos(0, 2));
305
- eq(found.length, 1);
306
- eq(found[0], mark);
307
- eq(c.findMarksAt(Pos(1, 1)).length, 1);
308
- eqPos(mark.find().from, Pos(0, 1));
309
- eqPos(mark.find().to, Pos(3, 1));
310
- b.replaceRange("x\ny\n", Pos(0, 0));
311
- eqPos(mark.find().from, Pos(2, 1));
312
- eqPos(mark.find().to, Pos(5, 1));
313
- var cleared = 0;
314
- CodeMirror.on(mark, "clear", function() {++cleared;});
315
- b.operation(function(){mark.clear();});
316
- eq(a.findMarksAt(Pos(3, 1)).length, 0);
317
- eq(b.findMarksAt(Pos(3, 1)).length, 0);
318
- eq(c.findMarksAt(Pos(3, 1)).length, 0);
319
- eq(mark.find(), null);
320
- eq(cleared, 1);
321
- });
322
-
323
- testDoc("sharedMarkerCopy", "A='abcde'", function(a) {
324
- var shared = a.markText(Pos(0, 1), Pos(0, 3), {shared: true});
325
- var b = a.linkedDoc();
326
- var found = b.findMarksAt(Pos(0, 2));
327
- eq(found.length, 1);
328
- eq(found[0], shared);
329
- shared.clear();
330
- eq(b.findMarksAt(Pos(0, 2)), 0);
331
- });
332
-
333
- testDoc("sharedMarkerDetach", "A='abcde' B<A C<B", function(a, b, c) {
334
- var shared = a.markText(Pos(0, 1), Pos(0, 3), {shared: true});
335
- a.unlinkDoc(b);
336
- var inB = b.findMarksAt(Pos(0, 2));
337
- eq(inB.length, 1);
338
- is(inB[0] != shared);
339
- var inC = c.findMarksAt(Pos(0, 2));
340
- eq(inC.length, 1);
341
- is(inC[0] != shared);
342
- inC[0].clear();
343
- is(shared.find());
344
- });
345
-
346
- testDoc("sharedBookmark", "A='ab\ncd\nef\ngh' B<A C<~A/1-2", function(a, b, c) {
347
- var mark = b.setBookmark(Pos(1, 1), {shared: true});
348
- var found = a.findMarksAt(Pos(1, 1));
349
- eq(found.length, 1);
350
- eq(found[0], mark);
351
- eq(c.findMarksAt(Pos(1, 1)).length, 1);
352
- eqPos(mark.find(), Pos(1, 1));
353
- b.replaceRange("x\ny\n", Pos(0, 0));
354
- eqPos(mark.find(), Pos(3, 1));
355
- var cleared = 0;
356
- CodeMirror.on(mark, "clear", function() {++cleared;});
357
- b.operation(function() {mark.clear();});
358
- eq(a.findMarks(Pos(0, 0), Pos(5)).length, 0);
359
- eq(b.findMarks(Pos(0, 0), Pos(5)).length, 0);
360
- eq(c.findMarks(Pos(0, 0), Pos(5)).length, 0);
361
- eq(mark.find(), null);
362
- eq(cleared, 1);
363
- });
364
-
365
- testDoc("undoInSubview", "A='line 0\nline 1\nline 2\nline 3\nline 4' B<A/1-4", function(a, b) {
366
- b.replaceRange("x", Pos(2, 0));
367
- a.undo();
368
- eq(a.getValue(), "line 0\nline 1\nline 2\nline 3\nline 4");
369
- eq(b.getValue(), "line 1\nline 2\nline 3");
370
- });
371
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/driver.js DELETED
@@ -1,138 +0,0 @@
1
- var tests = [], filters = [], allNames = [];
2
-
3
- function Failure(why) {this.message = why;}
4
- Failure.prototype.toString = function() { return this.message; };
5
-
6
- function indexOf(collection, elt) {
7
- if (collection.indexOf) return collection.indexOf(elt);
8
- for (var i = 0, e = collection.length; i < e; ++i)
9
- if (collection[i] == elt) return i;
10
- return -1;
11
- }
12
-
13
- function test(name, run, expectedFail) {
14
- // Force unique names
15
- var originalName = name;
16
- var i = 2; // Second function would be NAME_2
17
- while (indexOf(allNames, name) !== -1){
18
- name = originalName + "_" + i;
19
- i++;
20
- }
21
- allNames.push(name);
22
- // Add test
23
- tests.push({name: name, func: run, expectedFail: expectedFail});
24
- return name;
25
- }
26
- var namespace = "";
27
- function testCM(name, run, opts, expectedFail) {
28
- return test(namespace + name, function() {
29
- var place = document.getElementById("testground"), cm = window.cm = CodeMirror(place, opts);
30
- var successful = false;
31
- try {
32
- run(cm);
33
- successful = true;
34
- } finally {
35
- if (!successful || verbose) {
36
- place.style.visibility = "visible";
37
- } else {
38
- place.removeChild(cm.getWrapperElement());
39
- }
40
- }
41
- }, expectedFail);
42
- }
43
-
44
- function runTests(callback) {
45
- var totalTime = 0;
46
- function step(i) {
47
- for (;;) {
48
- if (i === tests.length) {
49
- running = false;
50
- return callback("done");
51
- }
52
- var test = tests[i], skip = false;
53
- if (filters.length) {
54
- skip = true;
55
- for (var j = 0; j < filters.length; j++)
56
- if (test.name.match(filters[j])) skip = false;
57
- }
58
- if (skip) {
59
- callback("skipped", test.name, message);
60
- i++;
61
- } else {
62
- break;
63
- }
64
- }
65
- var expFail = test.expectedFail, startTime = +new Date, threw = false;
66
- try {
67
- var message = test.func();
68
- } catch(e) {
69
- threw = true;
70
- if (expFail) callback("expected", test.name);
71
- else if (e instanceof Failure) callback("fail", test.name, e.message);
72
- else {
73
- var pos = /(?:\bat |@).*?([^\/:]+):(\d+)/.exec(e.stack);
74
- if (pos) console["log"](e.stack);
75
- callback("error", test.name, e.toString() + (pos ? " (" + pos[1] + ":" + pos[2] + ")" : ""));
76
- }
77
- }
78
- if (!threw) {
79
- if (expFail) callback("fail", test.name, message || "expected failure, but succeeded");
80
- else callback("ok", test.name, message);
81
- }
82
- if (!quit) { // Run next test
83
- var delay = 0;
84
- totalTime += (+new Date) - startTime;
85
- if (totalTime > 500){
86
- totalTime = 0;
87
- delay = 50;
88
- }
89
- setTimeout(function(){step(i + 1);}, delay);
90
- } else { // Quit tests
91
- running = false;
92
- return null;
93
- }
94
- }
95
- step(0);
96
- }
97
-
98
- function label(str, msg) {
99
- if (msg) return str + " (" + msg + ")";
100
- return str;
101
- }
102
- function eq(a, b, msg) {
103
- if (a != b) throw new Failure(label(a + " != " + b, msg));
104
- }
105
- function near(a, b, margin, msg) {
106
- if (Math.abs(a - b) > margin)
107
- throw new Failure(label(a + " is not close to " + b + " (" + margin + ")", msg));
108
- }
109
- function eqPos(a, b, msg) {
110
- function str(p) { return "{line:" + p.line + ",ch:" + p.ch + "}"; }
111
- if (a == b) return;
112
- if (a == null) throw new Failure(label("comparing null to " + str(b), msg));
113
- if (b == null) throw new Failure(label("comparing " + str(a) + " to null", msg));
114
- if (a.line != b.line || a.ch != b.ch) throw new Failure(label(str(a) + " != " + str(b), msg));
115
- }
116
- function is(a, msg) {
117
- if (!a) throw new Failure(label("assertion failed", msg));
118
- }
119
-
120
- function countTests() {
121
- if (!filters.length) return tests.length;
122
- var sum = 0;
123
- for (var i = 0; i < tests.length; ++i) {
124
- var name = tests[i].name;
125
- for (var j = 0; j < filters.length; j++) {
126
- if (name.match(filters[j])) {
127
- ++sum;
128
- break;
129
- }
130
- }
131
- }
132
- return sum;
133
- }
134
-
135
- function parseTestFilter(s) {
136
- if (/_\*$/.test(s)) return new RegExp("^" + s.slice(0, s.length - 2), "i");
137
- else return new RegExp(s, "i");
138
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/emacs_test.js DELETED
@@ -1,147 +0,0 @@
1
- (function() {
2
- "use strict";
3
-
4
- var Pos = CodeMirror.Pos;
5
- namespace = "emacs_";
6
-
7
- var eventCache = {};
8
- function fakeEvent(keyName) {
9
- var event = eventCache[key];
10
- if (event) return event;
11
-
12
- var ctrl, shift, alt;
13
- var key = keyName.replace(/\w+-/g, function(type) {
14
- if (type == "Ctrl-") ctrl = true;
15
- else if (type == "Alt-") alt = true;
16
- else if (type == "Shift-") shift = true;
17
- return "";
18
- });
19
- var code;
20
- for (var c in CodeMirror.keyNames)
21
- if (CodeMirror.keyNames[c] == key) { code = c; break; }
22
- if (c == null) throw new Error("Unknown key: " + key);
23
-
24
- return eventCache[keyName] = {
25
- type: "keydown", keyCode: code, ctrlKey: ctrl, shiftKey: shift, altKey: alt,
26
- preventDefault: function(){}, stopPropagation: function(){}
27
- };
28
- }
29
-
30
- function sim(name, start /*, actions... */) {
31
- var keys = Array.prototype.slice.call(arguments, 2);
32
- testCM(name, function(cm) {
33
- for (var i = 0; i < keys.length; ++i) {
34
- var cur = keys[i];
35
- if (cur instanceof Pos) cm.setCursor(cur);
36
- else if (cur.call) cur(cm);
37
- else cm.triggerOnKeyDown(fakeEvent(cur));
38
- }
39
- }, {keyMap: "emacs", value: start, mode: "javascript"});
40
- }
41
-
42
- function at(line, ch) { return function(cm) { eqPos(cm.getCursor(), Pos(line, ch)); }; }
43
- function txt(str) { return function(cm) { eq(cm.getValue(), str); }; }
44
-
45
- sim("motionHSimple", "abc", "Ctrl-F", "Ctrl-F", "Ctrl-B", at(0, 1));
46
- sim("motionHMulti", "abcde",
47
- "Ctrl-4", "Ctrl-F", at(0, 4), "Ctrl--", "Ctrl-2", "Ctrl-F", at(0, 2),
48
- "Ctrl-5", "Ctrl-B", at(0, 0));
49
-
50
- sim("motionHWord", "abc. def ghi",
51
- "Alt-F", at(0, 3), "Alt-F", at(0, 8),
52
- "Ctrl-B", "Alt-B", at(0, 5), "Alt-B", at(0, 0));
53
- sim("motionHWordMulti", "abc. def ghi ",
54
- "Ctrl-3", "Alt-F", at(0, 12), "Ctrl-2", "Alt-B", at(0, 5),
55
- "Ctrl--", "Alt-B", at(0, 8));
56
-
57
- sim("motionVSimple", "a\nb\nc\n", "Ctrl-N", "Ctrl-N", "Ctrl-P", at(1, 0));
58
- sim("motionVMulti", "a\nb\nc\nd\ne\n",
59
- "Ctrl-2", "Ctrl-N", at(2, 0), "Ctrl-F", "Ctrl--", "Ctrl-N", at(1, 1),
60
- "Ctrl--", "Ctrl-3", "Ctrl-P", at(4, 1));
61
-
62
- sim("killYank", "abc\ndef\nghi",
63
- "Ctrl-F", "Ctrl-Space", "Ctrl-N", "Ctrl-N", "Ctrl-W", "Ctrl-E", "Ctrl-Y",
64
- txt("ahibc\ndef\ng"));
65
- sim("killRing", "abcdef",
66
- "Ctrl-Space", "Ctrl-F", "Ctrl-W", "Ctrl-Space", "Ctrl-F", "Ctrl-W",
67
- "Ctrl-Y", "Alt-Y",
68
- txt("acdef"));
69
- sim("copyYank", "abcd",
70
- "Ctrl-Space", "Ctrl-E", "Alt-W", "Ctrl-Y",
71
- txt("abcdabcd"));
72
-
73
- sim("killLineSimple", "foo\nbar", "Ctrl-F", "Ctrl-K", txt("f\nbar"));
74
- sim("killLineEmptyLine", "foo\n \nbar", "Ctrl-N", "Ctrl-K", txt("foo\nbar"));
75
- sim("killLineMulti", "foo\nbar\nbaz",
76
- "Ctrl-F", "Ctrl-F", "Ctrl-K", "Ctrl-K", "Ctrl-K", "Ctrl-A", "Ctrl-Y",
77
- txt("o\nbarfo\nbaz"));
78
-
79
- sim("moveByParagraph", "abc\ndef\n\n\nhij\nklm\n\n",
80
- "Ctrl-F", "Ctrl-Down", at(2, 0), "Ctrl-Down", at(6, 0),
81
- "Ctrl-N", "Ctrl-Up", at(3, 0), "Ctrl-Up", at(0, 0),
82
- Pos(1, 2), "Ctrl-Down", at(2, 0), Pos(4, 2), "Ctrl-Up", at(3, 0));
83
- sim("moveByParagraphMulti", "abc\n\ndef\n\nhij\n\nklm",
84
- "Ctrl-U", "2", "Ctrl-Down", at(3, 0),
85
- "Shift-Alt-.", "Ctrl-3", "Ctrl-Up", at(1, 0));
86
-
87
- sim("moveBySentence", "sentence one! sentence\ntwo\n\nparagraph two",
88
- "Alt-E", at(0, 13), "Alt-E", at(1, 3), "Ctrl-F", "Alt-A", at(0, 13));
89
-
90
- sim("moveByExpr", "function foo(a, b) {}",
91
- "Ctrl-Alt-F", at(0, 8), "Ctrl-Alt-F", at(0, 12), "Ctrl-Alt-F", at(0, 18),
92
- "Ctrl-Alt-B", at(0, 12), "Ctrl-Alt-B", at(0, 9));
93
- sim("moveByExprMulti", "foo bar baz bug",
94
- "Ctrl-2", "Ctrl-Alt-F", at(0, 7),
95
- "Ctrl--", "Ctrl-Alt-F", at(0, 4),
96
- "Ctrl--", "Ctrl-2", "Ctrl-Alt-B", at(0, 11));
97
- sim("delExpr", "var x = [\n a,\n b\n c\n];",
98
- Pos(0, 8), "Ctrl-Alt-K", txt("var x = ;"), "Ctrl-/",
99
- Pos(4, 1), "Ctrl-Alt-Backspace", txt("var x = ;"));
100
- sim("delExprMulti", "foo bar baz",
101
- "Ctrl-2", "Ctrl-Alt-K", txt(" baz"),
102
- "Ctrl-/", "Ctrl-E", "Ctrl-2", "Ctrl-Alt-Backspace", txt("foo "));
103
-
104
- sim("justOneSpace", "hi bye ",
105
- Pos(0, 4), "Alt-Space", txt("hi bye "),
106
- Pos(0, 4), "Alt-Space", txt("hi b ye "),
107
- "Ctrl-A", "Alt-Space", "Ctrl-E", "Alt-Space", txt(" hi b ye "));
108
-
109
- sim("openLine", "foo bar", "Alt-F", "Ctrl-O", txt("foo\n bar"))
110
-
111
- sim("transposeChar", "abcd\ne",
112
- "Ctrl-F", "Ctrl-T", "Ctrl-T", txt("bcad\ne"), at(0, 3),
113
- "Ctrl-F", "Ctrl-T", "Ctrl-T", "Ctrl-T", txt("bcda\ne"), at(0, 4),
114
- "Ctrl-F", "Ctrl-T", txt("bcde\na"), at(1, 0));
115
-
116
- sim("manipWordCase", "foo BAR bAZ",
117
- "Alt-C", "Alt-L", "Alt-U", txt("Foo bar BAZ"),
118
- "Ctrl-A", "Alt-U", "Alt-L", "Alt-C", txt("FOO bar Baz"));
119
- sim("manipWordCaseMulti", "foo Bar bAz",
120
- "Ctrl-2", "Alt-U", txt("FOO BAR bAz"),
121
- "Ctrl-A", "Ctrl-3", "Alt-C", txt("Foo Bar Baz"));
122
-
123
- sim("upExpr", "foo {\n bar[];\n baz(blah);\n}",
124
- Pos(2, 7), "Ctrl-Alt-U", at(2, 5), "Ctrl-Alt-U", at(0, 4));
125
- sim("transposeExpr", "do foo[bar] dah",
126
- Pos(0, 6), "Ctrl-Alt-T", txt("do [bar]foo dah"));
127
-
128
- sim("clearMark", "abcde", Pos(0, 2), "Ctrl-Space", "Ctrl-F", "Ctrl-F",
129
- "Ctrl-G", "Ctrl-W", txt("abcde"));
130
-
131
- sim("delRegion", "abcde", "Ctrl-Space", "Ctrl-F", "Ctrl-F", "Delete", txt("cde"));
132
- sim("backspaceRegion", "abcde", "Ctrl-Space", "Ctrl-F", "Ctrl-F", "Backspace", txt("cde"));
133
-
134
- testCM("save", function(cm) {
135
- var saved = false;
136
- CodeMirror.commands.save = function(cm) { saved = cm.getValue(); };
137
- cm.triggerOnKeyDown(fakeEvent("Ctrl-X"));
138
- cm.triggerOnKeyDown(fakeEvent("Ctrl-S"));
139
- is(saved, "hi");
140
- }, {value: "hi", keyMap: "emacs"});
141
-
142
- testCM("gotoInvalidLineFloat", function(cm) {
143
- cm.openDialog = function(_, cb) { cb("2.2"); };
144
- cm.triggerOnKeyDown(fakeEvent("Alt-G"));
145
- cm.triggerOnKeyDown(fakeEvent("G"));
146
- }, {value: "1\n2\n3\n4", keyMap: "emacs"});
147
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/index.html DELETED
@@ -1,241 +0,0 @@
1
- <!doctype html>
2
-
3
- <meta charset="utf-8"/>
4
- <title>CodeMirror: Test Suite</title>
5
- <link rel=stylesheet href="../doc/docs.css">
6
-
7
- <link rel="stylesheet" href="../lib/codemirror.css">
8
- <link rel="stylesheet" href="mode_test.css">
9
- <script src="../doc/activebookmark.js"></script>
10
- <script src="../lib/codemirror.js"></script>
11
- <script src="../addon/mode/overlay.js"></script>
12
- <script src="../addon/mode/multiplex.js"></script>
13
- <script src="../addon/search/searchcursor.js"></script>
14
- <script src="../addon/dialog/dialog.js"></script>
15
- <script src="../addon/edit/matchbrackets.js"></script>
16
- <script src="../addon/hint/sql-hint.js"></script>
17
- <script src="../addon/comment/comment.js"></script>
18
- <script src="../mode/css/css.js"></script>
19
- <script src="../mode/clike/clike.js"></script>
20
- <!-- clike must be after css or vim and sublime tests will fail -->
21
- <script src="../mode/gfm/gfm.js"></script>
22
- <script src="../mode/haml/haml.js"></script>
23
- <script src="../mode/htmlmixed/htmlmixed.js"></script>
24
- <script src="../mode/javascript/javascript.js"></script>
25
- <script src="../mode/markdown/markdown.js"></script>
26
- <script src="../mode/php/php.js"></script>
27
- <script src="../mode/ruby/ruby.js"></script>
28
- <script src="../mode/shell/shell.js"></script>
29
- <script src="../mode/slim/slim.js"></script>
30
- <script src="../mode/sql/sql.js"></script>
31
- <script src="../mode/stex/stex.js"></script>
32
- <script src="../mode/textile/textile.js"></script>
33
- <script src="../mode/verilog/verilog.js"></script>
34
- <script src="../mode/xml/xml.js"></script>
35
- <script src="../mode/xquery/xquery.js"></script>
36
- <script src="../keymap/emacs.js"></script>
37
- <script src="../keymap/sublime.js"></script>
38
- <script src="../keymap/vim.js"></script>
39
-
40
- <style type="text/css">
41
- .ok {color: #090;}
42
- .fail {color: #e00;}
43
- .error {color: #c90;}
44
- .done {font-weight: bold;}
45
- #progress {
46
- background: #45d;
47
- color: white;
48
- text-shadow: 0 0 1px #45d, 0 0 2px #45d, 0 0 3px #45d;
49
- font-weight: bold;
50
- white-space: pre;
51
- }
52
- #testground {
53
- visibility: hidden;
54
- }
55
- #testground.offscreen {
56
- visibility: visible;
57
- position: absolute;
58
- left: -10000px;
59
- top: -10000px;
60
- }
61
- .CodeMirror { border: 1px solid black; }
62
- </style>
63
-
64
- <div id=nav>
65
- <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a>
66
-
67
- <ul>
68
- <li><a href="../index.html">Home</a>
69
- <li><a href="../doc/manual.html">Manual</a>
70
- <li><a href="https://github.com/codemirror/codemirror">Code</a>
71
- </ul>
72
- <ul>
73
- <li><a class=active href="#">Test suite</a>
74
- </ul>
75
- </div>
76
-
77
- <article>
78
- <h2>Test Suite</h2>
79
-
80
- <p>A limited set of programmatic sanity tests for CodeMirror.</p>
81
-
82
- <div style="border: 1px solid black; padding: 1px; max-width: 700px;">
83
- <div style="width: 0px;" id=progress><div style="padding: 3px;">Ran <span id="progress_ran">0</span><span id="progress_total"> of 0</span> tests</div></div>
84
- </div>
85
- <p id=status>Please enable JavaScript...</p>
86
- <div id=output></div>
87
-
88
- <div id=testground></div>
89
-
90
- <script src="driver.js"></script>
91
- <script src="test.js"></script>
92
- <script src="doc_test.js"></script>
93
- <script src="multi_test.js"></script>
94
- <script src="scroll_test.js"></script>
95
- <script src="comment_test.js"></script>
96
- <script src="search_test.js"></script>
97
- <script src="mode_test.js"></script>
98
-
99
- <script src="../mode/clike/test.js"></script>
100
- <script src="../mode/css/test.js"></script>
101
- <script src="../mode/css/scss_test.js"></script>
102
- <script src="../mode/css/less_test.js"></script>
103
- <script src="../mode/gfm/test.js"></script>
104
- <script src="../mode/haml/test.js"></script>
105
- <script src="../mode/javascript/test.js"></script>
106
- <script src="../mode/markdown/test.js"></script>
107
- <script src="../mode/php/test.js"></script>
108
- <script src="../mode/ruby/test.js"></script>
109
- <script src="../mode/shell/test.js"></script>
110
- <script src="../mode/slim/test.js"></script>
111
- <script src="../mode/stex/test.js"></script>
112
- <script src="../mode/textile/test.js"></script>
113
- <script src="../mode/verilog/test.js"></script>
114
- <script src="../mode/xml/test.js"></script>
115
- <script src="../mode/xquery/test.js"></script>
116
- <script src="../addon/mode/multiplex_test.js"></script>
117
- <script src="emacs_test.js"></script>
118
- <script src="sql-hint-test.js"></script>
119
- <script src="sublime_test.js"></script>
120
- <script src="vim_test.js"></script>
121
- <script>
122
- window.onload = runHarness;
123
- CodeMirror.on(window, 'hashchange', runHarness);
124
-
125
- function esc(str) {
126
- return str.replace(/[<&]/, function(ch) { return ch == "<" ? "&lt;" : "&amp;"; });
127
- }
128
-
129
- var output = document.getElementById("output"),
130
- progress = document.getElementById("progress"),
131
- progressRan = document.getElementById("progress_ran").childNodes[0],
132
- progressTotal = document.getElementById("progress_total").childNodes[0];
133
- var count = 0,
134
- failed = 0,
135
- skipped = 0,
136
- bad = "",
137
- running = false, // Flag that states tests are running
138
- quit = false, // Flag to quit tests ASAP
139
- verbose = false; // Adds message for *every* test to output
140
-
141
- function runHarness(){
142
- if (running) {
143
- quit = true;
144
- setStatus("Restarting tests...", '', true);
145
- setTimeout(function(){runHarness();}, 500);
146
- return;
147
- }
148
- filters = [];
149
- verbose = false;
150
- if (window.location.hash.substr(1)){
151
- var strings = window.location.hash.substr(1).split(",");
152
- while (strings.length) {
153
- var s = strings.shift();
154
- if (s === "verbose")
155
- verbose = true;
156
- else
157
- filters.push(parseTestFilter(decodeURIComponent(s)));
158
- }
159
- }
160
- quit = false;
161
- running = true;
162
- setStatus("Loading tests...");
163
- count = 0;
164
- failed = 0;
165
- skipped = 0;
166
- bad = "";
167
- totalTests = countTests();
168
- progressTotal.nodeValue = " of " + totalTests;
169
- progressRan.nodeValue = count;
170
- output.innerHTML = '';
171
- document.getElementById("testground").innerHTML = "<form>" +
172
- "<textarea id=\"code\" name=\"code\"></textarea>" +
173
- "<input type=submit value=ok name=submit>" +
174
- "</form>";
175
- runTests(displayTest);
176
- }
177
-
178
- function setStatus(message, className, force){
179
- if (quit && !force) return;
180
- if (!message) throw("must provide message");
181
- var status = document.getElementById("status").childNodes[0];
182
- status.nodeValue = message;
183
- status.parentNode.className = className;
184
- }
185
- function addOutput(name, className, code){
186
- var newOutput = document.createElement("dl");
187
- var newTitle = document.createElement("dt");
188
- newTitle.className = className;
189
- newTitle.appendChild(document.createTextNode(name));
190
- newOutput.appendChild(newTitle);
191
- var newMessage = document.createElement("dd");
192
- newMessage.innerHTML = code;
193
- newOutput.appendChild(newTitle);
194
- newOutput.appendChild(newMessage);
195
- output.appendChild(newOutput);
196
- }
197
- function displayTest(type, name, customMessage) {
198
- var message = "???";
199
- if (type != "done" && type != "skipped") ++count;
200
- progress.style.width = (count * (progress.parentNode.clientWidth - 2) / totalTests) + "px";
201
- progressRan.nodeValue = count;
202
- if (type == "ok") {
203
- message = "Test '" + name + "' succeeded";
204
- if (!verbose) customMessage = false;
205
- } else if (type == "skipped") {
206
- message = "Test '" + name + "' skipped";
207
- ++skipped;
208
- if (!verbose) customMessage = false;
209
- } else if (type == "expected") {
210
- message = "Test '" + name + "' failed as expected";
211
- if (!verbose) customMessage = false;
212
- } else if (type == "error" || type == "fail") {
213
- ++failed;
214
- message = "Test '" + name + "' failed";
215
- } else if (type == "done") {
216
- if (failed) {
217
- type += " fail";
218
- message = failed + " failure" + (failed > 1 ? "s" : "");
219
- } else if (count < totalTests) {
220
- failed = totalTests - count;
221
- type += " fail";
222
- message = failed + " failure" + (failed > 1 ? "s" : "");
223
- } else {
224
- type += " ok";
225
- message = "All passed";
226
- if (skipped) {
227
- message += " (" + skipped + " skipped)";
228
- }
229
- }
230
- progressTotal.nodeValue = '';
231
- customMessage = true; // Hack to avoid adding to output
232
- }
233
- if (verbose && !customMessage) customMessage = message;
234
- setStatus(message, type);
235
- if (customMessage && customMessage.length > 0) {
236
- addOutput(name, type, customMessage);
237
- }
238
- }
239
- </script>
240
-
241
- </article>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint.js DELETED
@@ -1,11 +0,0 @@
1
- var blint = require("blint");
2
-
3
- ["mode", "lib", "addon", "keymap"].forEach(function(dir) {
4
- blint.checkDir(dir, {
5
- browser: true,
6
- allowedGlobals: ["CodeMirror", "define", "test", "requirejs"],
7
- blob: "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http:\/\/codemirror.net\/LICENSE\n\n"
8
- });
9
- });
10
-
11
- module.exports = {ok: blint.success()};
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint/acorn.js DELETED
@@ -1,1782 +0,0 @@
1
- // Acorn is a tiny, fast JavaScript parser written in JavaScript.
2
- //
3
- // Acorn was written by Marijn Haverbeke and released under an MIT
4
- // license. The Unicode regexps (for identifiers and whitespace) were
5
- // taken from [Esprima](http://esprima.org) by Ariya Hidayat.
6
- //
7
- // Git repositories for Acorn are available at
8
- //
9
- // http://marijnhaverbeke.nl/git/acorn
10
- // https://github.com/marijnh/acorn.git
11
- //
12
- // Please use the [github bug tracker][ghbt] to report issues.
13
- //
14
- // [ghbt]: https://github.com/marijnh/acorn/issues
15
- //
16
- // This file defines the main parser interface. The library also comes
17
- // with a [error-tolerant parser][dammit] and an
18
- // [abstract syntax tree walker][walk], defined in other files.
19
- //
20
- // [dammit]: acorn_loose.js
21
- // [walk]: util/walk.js
22
-
23
- (function(root, mod) {
24
- if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS
25
- if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD
26
- mod(root.acorn || (root.acorn = {})); // Plain browser env
27
- })(this, function(exports) {
28
- "use strict";
29
-
30
- exports.version = "0.4.1";
31
-
32
- // The main exported interface (under `self.acorn` when in the
33
- // browser) is a `parse` function that takes a code string and
34
- // returns an abstract syntax tree as specified by [Mozilla parser
35
- // API][api], with the caveat that the SpiderMonkey-specific syntax
36
- // (`let`, `yield`, inline XML, etc) is not recognized.
37
- //
38
- // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
39
-
40
- var options, input, inputLen, sourceFile;
41
-
42
- exports.parse = function(inpt, opts) {
43
- input = String(inpt); inputLen = input.length;
44
- setOptions(opts);
45
- initTokenState();
46
- return parseTopLevel(options.program);
47
- };
48
-
49
- // A second optional argument can be given to further configure
50
- // the parser process. These options are recognized:
51
-
52
- var defaultOptions = exports.defaultOptions = {
53
- // `ecmaVersion` indicates the ECMAScript version to parse. Must
54
- // be either 3 or 5. This
55
- // influences support for strict mode, the set of reserved words, and
56
- // support for getters and setter.
57
- ecmaVersion: 5,
58
- // Turn on `strictSemicolons` to prevent the parser from doing
59
- // automatic semicolon insertion.
60
- strictSemicolons: false,
61
- // When `allowTrailingCommas` is false, the parser will not allow
62
- // trailing commas in array and object literals.
63
- allowTrailingCommas: true,
64
- // By default, reserved words are not enforced. Enable
65
- // `forbidReserved` to enforce them. When this option has the
66
- // value "everywhere", reserved words and keywords can also not be
67
- // used as property names.
68
- forbidReserved: false,
69
- // When enabled, a return at the top level is not considered an
70
- // error.
71
- allowReturnOutsideFunction: false,
72
- // When `locations` is on, `loc` properties holding objects with
73
- // `start` and `end` properties in `{line, column}` form (with
74
- // line being 1-based and column 0-based) will be attached to the
75
- // nodes.
76
- locations: false,
77
- // A function can be passed as `onComment` option, which will
78
- // cause Acorn to call that function with `(block, text, start,
79
- // end)` parameters whenever a comment is skipped. `block` is a
80
- // boolean indicating whether this is a block (`/* */`) comment,
81
- // `text` is the content of the comment, and `start` and `end` are
82
- // character offsets that denote the start and end of the comment.
83
- // When the `locations` option is on, two more parameters are
84
- // passed, the full `{line, column}` locations of the start and
85
- // end of the comments. Note that you are not allowed to call the
86
- // parser from the callback—that will corrupt its internal state.
87
- onComment: null,
88
- // Nodes have their start and end characters offsets recorded in
89
- // `start` and `end` properties (directly on the node, rather than
90
- // the `loc` object, which holds line/column data. To also add a
91
- // [semi-standardized][range] `range` property holding a `[start,
92
- // end]` array with the same numbers, set the `ranges` option to
93
- // `true`.
94
- //
95
- // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678
96
- ranges: false,
97
- // It is possible to parse multiple files into a single AST by
98
- // passing the tree produced by parsing the first file as
99
- // `program` option in subsequent parses. This will add the
100
- // toplevel forms of the parsed file to the `Program` (top) node
101
- // of an existing parse tree.
102
- program: null,
103
- // When `locations` is on, you can pass this to record the source
104
- // file in every node's `loc` object.
105
- sourceFile: null,
106
- // This value, if given, is stored in every node, whether
107
- // `locations` is on or off.
108
- directSourceFile: null
109
- };
110
-
111
- function setOptions(opts) {
112
- options = opts || {};
113
- for (var opt in defaultOptions) if (!Object.prototype.hasOwnProperty.call(options, opt))
114
- options[opt] = defaultOptions[opt];
115
- sourceFile = options.sourceFile || null;
116
- }
117
-
118
- // The `getLineInfo` function is mostly useful when the
119
- // `locations` option is off (for performance reasons) and you
120
- // want to find the line/column position for a given character
121
- // offset. `input` should be the code string that the offset refers
122
- // into.
123
-
124
- var getLineInfo = exports.getLineInfo = function(input, offset) {
125
- for (var line = 1, cur = 0;;) {
126
- lineBreak.lastIndex = cur;
127
- var match = lineBreak.exec(input);
128
- if (match && match.index < offset) {
129
- ++line;
130
- cur = match.index + match[0].length;
131
- } else break;
132
- }
133
- return {line: line, column: offset - cur};
134
- };
135
-
136
- // Acorn is organized as a tokenizer and a recursive-descent parser.
137
- // The `tokenize` export provides an interface to the tokenizer.
138
- // Because the tokenizer is optimized for being efficiently used by
139
- // the Acorn parser itself, this interface is somewhat crude and not
140
- // very modular. Performing another parse or call to `tokenize` will
141
- // reset the internal state, and invalidate existing tokenizers.
142
-
143
- exports.tokenize = function(inpt, opts) {
144
- input = String(inpt); inputLen = input.length;
145
- setOptions(opts);
146
- initTokenState();
147
-
148
- var t = {};
149
- function getToken(forceRegexp) {
150
- lastEnd = tokEnd;
151
- readToken(forceRegexp);
152
- t.start = tokStart; t.end = tokEnd;
153
- t.startLoc = tokStartLoc; t.endLoc = tokEndLoc;
154
- t.type = tokType; t.value = tokVal;
155
- return t;
156
- }
157
- getToken.jumpTo = function(pos, reAllowed) {
158
- tokPos = pos;
159
- if (options.locations) {
160
- tokCurLine = 1;
161
- tokLineStart = lineBreak.lastIndex = 0;
162
- var match;
163
- while ((match = lineBreak.exec(input)) && match.index < pos) {
164
- ++tokCurLine;
165
- tokLineStart = match.index + match[0].length;
166
- }
167
- }
168
- tokRegexpAllowed = reAllowed;
169
- skipSpace();
170
- };
171
- return getToken;
172
- };
173
-
174
- // State is kept in (closure-)global variables. We already saw the
175
- // `options`, `input`, and `inputLen` variables above.
176
-
177
- // The current position of the tokenizer in the input.
178
-
179
- var tokPos;
180
-
181
- // The start and end offsets of the current token.
182
-
183
- var tokStart, tokEnd;
184
-
185
- // When `options.locations` is true, these hold objects
186
- // containing the tokens start and end line/column pairs.
187
-
188
- var tokStartLoc, tokEndLoc;
189
-
190
- // The type and value of the current token. Token types are objects,
191
- // named by variables against which they can be compared, and
192
- // holding properties that describe them (indicating, for example,
193
- // the precedence of an infix operator, and the original name of a
194
- // keyword token). The kind of value that's held in `tokVal` depends
195
- // on the type of the token. For literals, it is the literal value,
196
- // for operators, the operator name, and so on.
197
-
198
- var tokType, tokVal;
199
-
200
- // Interal state for the tokenizer. To distinguish between division
201
- // operators and regular expressions, it remembers whether the last
202
- // token was one that is allowed to be followed by an expression.
203
- // (If it is, a slash is probably a regexp, if it isn't it's a
204
- // division operator. See the `parseStatement` function for a
205
- // caveat.)
206
-
207
- var tokRegexpAllowed;
208
-
209
- // When `options.locations` is true, these are used to keep
210
- // track of the current line, and know when a new line has been
211
- // entered.
212
-
213
- var tokCurLine, tokLineStart;
214
-
215
- // These store the position of the previous token, which is useful
216
- // when finishing a node and assigning its `end` position.
217
-
218
- var lastStart, lastEnd, lastEndLoc;
219
-
220
- // This is the parser's state. `inFunction` is used to reject
221
- // `return` statements outside of functions, `labels` to verify that
222
- // `break` and `continue` have somewhere to jump to, and `strict`
223
- // indicates whether strict mode is on.
224
-
225
- var inFunction, labels, strict;
226
-
227
- // This function is used to raise exceptions on parse errors. It
228
- // takes an offset integer (into the current `input`) to indicate
229
- // the location of the error, attaches the position to the end
230
- // of the error message, and then raises a `SyntaxError` with that
231
- // message.
232
-
233
- function raise(pos, message) {
234
- var loc = getLineInfo(input, pos);
235
- message += " (" + loc.line + ":" + loc.column + ")";
236
- var err = new SyntaxError(message);
237
- err.pos = pos; err.loc = loc; err.raisedAt = tokPos;
238
- throw err;
239
- }
240
-
241
- // Reused empty array added for node fields that are always empty.
242
-
243
- var empty = [];
244
-
245
- // ## Token types
246
-
247
- // The assignment of fine-grained, information-carrying type objects
248
- // allows the tokenizer to store the information it has about a
249
- // token in a way that is very cheap for the parser to look up.
250
-
251
- // All token type variables start with an underscore, to make them
252
- // easy to recognize.
253
-
254
- // These are the general types. The `type` property is only used to
255
- // make them recognizeable when debugging.
256
-
257
- var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"};
258
- var _name = {type: "name"}, _eof = {type: "eof"};
259
-
260
- // Keyword tokens. The `keyword` property (also used in keyword-like
261
- // operators) indicates that the token originated from an
262
- // identifier-like word, which is used when parsing property names.
263
- //
264
- // The `beforeExpr` property is used to disambiguate between regular
265
- // expressions and divisions. It is set on all token types that can
266
- // be followed by an expression (thus, a slash after them would be a
267
- // regular expression).
268
- //
269
- // `isLoop` marks a keyword as starting a loop, which is important
270
- // to know when parsing a label, in order to allow or disallow
271
- // continue jumps to that label.
272
-
273
- var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"};
274
- var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"};
275
- var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true};
276
- var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"};
277
- var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"};
278
- var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"};
279
- var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true};
280
- var _this = {keyword: "this"};
281
-
282
- // The keywords that denote values.
283
-
284
- var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true};
285
- var _false = {keyword: "false", atomValue: false};
286
-
287
- // Some keywords are treated as regular operators. `in` sometimes
288
- // (when parsing `for`) needs to be tested against specifically, so
289
- // we assign a variable name to it for quick comparing.
290
-
291
- var _in = {keyword: "in", binop: 7, beforeExpr: true};
292
-
293
- // Map keyword names to token types.
294
-
295
- var keywordTypes = {"break": _break, "case": _case, "catch": _catch,
296
- "continue": _continue, "debugger": _debugger, "default": _default,
297
- "do": _do, "else": _else, "finally": _finally, "for": _for,
298
- "function": _function, "if": _if, "return": _return, "switch": _switch,
299
- "throw": _throw, "try": _try, "var": _var, "while": _while, "with": _with,
300
- "null": _null, "true": _true, "false": _false, "new": _new, "in": _in,
301
- "instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this,
302
- "typeof": {keyword: "typeof", prefix: true, beforeExpr: true},
303
- "void": {keyword: "void", prefix: true, beforeExpr: true},
304
- "delete": {keyword: "delete", prefix: true, beforeExpr: true}};
305
-
306
- // Punctuation token types. Again, the `type` property is purely for debugging.
307
-
308
- var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true};
309
- var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"};
310
- var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true};
311
- var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true};
312
-
313
- // Operators. These carry several kinds of properties to help the
314
- // parser use them properly (the presence of these properties is
315
- // what categorizes them as operators).
316
- //
317
- // `binop`, when present, specifies that this operator is a binary
318
- // operator, and will refer to its precedence.
319
- //
320
- // `prefix` and `postfix` mark the operator as a prefix or postfix
321
- // unary operator. `isUpdate` specifies that the node produced by
322
- // the operator should be of type UpdateExpression rather than
323
- // simply UnaryExpression (`++` and `--`).
324
- //
325
- // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as
326
- // binary operators with a very low precedence, that should result
327
- // in AssignmentExpression nodes.
328
-
329
- var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true};
330
- var _assign = {isAssign: true, beforeExpr: true};
331
- var _incDec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true};
332
- var _logicalOR = {binop: 1, beforeExpr: true};
333
- var _logicalAND = {binop: 2, beforeExpr: true};
334
- var _bitwiseOR = {binop: 3, beforeExpr: true};
335
- var _bitwiseXOR = {binop: 4, beforeExpr: true};
336
- var _bitwiseAND = {binop: 5, beforeExpr: true};
337
- var _equality = {binop: 6, beforeExpr: true};
338
- var _relational = {binop: 7, beforeExpr: true};
339
- var _bitShift = {binop: 8, beforeExpr: true};
340
- var _plusMin = {binop: 9, prefix: true, beforeExpr: true};
341
- var _multiplyModulo = {binop: 10, beforeExpr: true};
342
-
343
- // Provide access to the token types for external users of the
344
- // tokenizer.
345
-
346
- exports.tokTypes = {bracketL: _bracketL, bracketR: _bracketR, braceL: _braceL, braceR: _braceR,
347
- parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon,
348
- dot: _dot, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof,
349
- num: _num, regexp: _regexp, string: _string};
350
- for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw];
351
-
352
- // This is a trick taken from Esprima. It turns out that, on
353
- // non-Chrome browsers, to check whether a string is in a set, a
354
- // predicate containing a big ugly `switch` statement is faster than
355
- // a regular expression, and on Chrome the two are about on par.
356
- // This function uses `eval` (non-lexical) to produce such a
357
- // predicate from a space-separated string of words.
358
- //
359
- // It starts by sorting the words by length.
360
-
361
- function makePredicate(words) {
362
- words = words.split(" ");
363
- var f = "", cats = [];
364
- out: for (var i = 0; i < words.length; ++i) {
365
- for (var j = 0; j < cats.length; ++j)
366
- if (cats[j][0].length == words[i].length) {
367
- cats[j].push(words[i]);
368
- continue out;
369
- }
370
- cats.push([words[i]]);
371
- }
372
- function compareTo(arr) {
373
- if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";";
374
- f += "switch(str){";
375
- for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":";
376
- f += "return true}return false;";
377
- }
378
-
379
- // When there are more than three length categories, an outer
380
- // switch first dispatches on the lengths, to save on comparisons.
381
-
382
- if (cats.length > 3) {
383
- cats.sort(function(a, b) {return b.length - a.length;});
384
- f += "switch(str.length){";
385
- for (var i = 0; i < cats.length; ++i) {
386
- var cat = cats[i];
387
- f += "case " + cat[0].length + ":";
388
- compareTo(cat);
389
- }
390
- f += "}";
391
-
392
- // Otherwise, simply generate a flat `switch` statement.
393
-
394
- } else {
395
- compareTo(words);
396
- }
397
- return new Function("str", f);
398
- }
399
-
400
- // The ECMAScript 3 reserved word list.
401
-
402
- var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile");
403
-
404
- // ECMAScript 5 reserved words.
405
-
406
- var isReservedWord5 = makePredicate("class enum extends super const export import");
407
-
408
- // The additional reserved words in strict mode.
409
-
410
- var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield");
411
-
412
- // The forbidden variable names in strict mode.
413
-
414
- var isStrictBadIdWord = makePredicate("eval arguments");
415
-
416
- // And the keywords.
417
-
418
- var isKeyword = makePredicate("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this");
419
-
420
- // ## Character categories
421
-
422
- // Big ugly regular expressions that match characters in the
423
- // whitespace, identifier, and identifier-start categories. These
424
- // are only applied when a character is found to actually have a
425
- // code point above 128.
426
-
427
- var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;
428
- var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
429
- var nonASCIIidentifierChars = "\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f";
430
- var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
431
- var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
432
-
433
- // Whether a single character denotes a newline.
434
-
435
- var newline = /[\n\r\u2028\u2029]/;
436
-
437
- // Matches a whole line break (where CRLF is considered a single
438
- // line break). Used to count lines.
439
-
440
- var lineBreak = /\r\n|[\n\r\u2028\u2029]/g;
441
-
442
- // Test whether a given character code starts an identifier.
443
-
444
- var isIdentifierStart = exports.isIdentifierStart = function(code) {
445
- if (code < 65) return code === 36;
446
- if (code < 91) return true;
447
- if (code < 97) return code === 95;
448
- if (code < 123)return true;
449
- return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
450
- };
451
-
452
- // Test whether a given character is part of an identifier.
453
-
454
- var isIdentifierChar = exports.isIdentifierChar = function(code) {
455
- if (code < 48) return code === 36;
456
- if (code < 58) return true;
457
- if (code < 65) return false;
458
- if (code < 91) return true;
459
- if (code < 97) return code === 95;
460
- if (code < 123)return true;
461
- return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
462
- };
463
-
464
- // ## Tokenizer
465
-
466
- // These are used when `options.locations` is on, for the
467
- // `tokStartLoc` and `tokEndLoc` properties.
468
-
469
- function line_loc_t() {
470
- this.line = tokCurLine;
471
- this.column = tokPos - tokLineStart;
472
- }
473
-
474
- // Reset the token state. Used at the start of a parse.
475
-
476
- function initTokenState() {
477
- tokCurLine = 1;
478
- tokPos = tokLineStart = 0;
479
- tokRegexpAllowed = true;
480
- skipSpace();
481
- }
482
-
483
- // Called at the end of every token. Sets `tokEnd`, `tokVal`, and
484
- // `tokRegexpAllowed`, and skips the space after the token, so that
485
- // the next one's `tokStart` will point at the right position.
486
-
487
- function finishToken(type, val) {
488
- tokEnd = tokPos;
489
- if (options.locations) tokEndLoc = new line_loc_t;
490
- tokType = type;
491
- skipSpace();
492
- tokVal = val;
493
- tokRegexpAllowed = type.beforeExpr;
494
- }
495
-
496
- function skipBlockComment() {
497
- var startLoc = options.onComment && options.locations && new line_loc_t;
498
- var start = tokPos, end = input.indexOf("*/", tokPos += 2);
499
- if (end === -1) raise(tokPos - 2, "Unterminated comment");
500
- tokPos = end + 2;
501
- if (options.locations) {
502
- lineBreak.lastIndex = start;
503
- var match;
504
- while ((match = lineBreak.exec(input)) && match.index < tokPos) {
505
- ++tokCurLine;
506
- tokLineStart = match.index + match[0].length;
507
- }
508
- }
509
- if (options.onComment)
510
- options.onComment(true, input.slice(start + 2, end), start, tokPos,
511
- startLoc, options.locations && new line_loc_t);
512
- }
513
-
514
- function skipLineComment() {
515
- var start = tokPos;
516
- var startLoc = options.onComment && options.locations && new line_loc_t;
517
- var ch = input.charCodeAt(tokPos+=2);
518
- while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {
519
- ++tokPos;
520
- ch = input.charCodeAt(tokPos);
521
- }
522
- if (options.onComment)
523
- options.onComment(false, input.slice(start + 2, tokPos), start, tokPos,
524
- startLoc, options.locations && new line_loc_t);
525
- }
526
-
527
- // Called at the start of the parse and after every token. Skips
528
- // whitespace and comments, and.
529
-
530
- function skipSpace() {
531
- while (tokPos < inputLen) {
532
- var ch = input.charCodeAt(tokPos);
533
- if (ch === 32) { // ' '
534
- ++tokPos;
535
- } else if (ch === 13) {
536
- ++tokPos;
537
- var next = input.charCodeAt(tokPos);
538
- if (next === 10) {
539
- ++tokPos;
540
- }
541
- if (options.locations) {
542
- ++tokCurLine;
543
- tokLineStart = tokPos;
544
- }
545
- } else if (ch === 10 || ch === 8232 || ch === 8233) {
546
- ++tokPos;
547
- if (options.locations) {
548
- ++tokCurLine;
549
- tokLineStart = tokPos;
550
- }
551
- } else if (ch > 8 && ch < 14) {
552
- ++tokPos;
553
- } else if (ch === 47) { // '/'
554
- var next = input.charCodeAt(tokPos + 1);
555
- if (next === 42) { // '*'
556
- skipBlockComment();
557
- } else if (next === 47) { // '/'
558
- skipLineComment();
559
- } else break;
560
- } else if (ch === 160) { // '\xa0'
561
- ++tokPos;
562
- } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
563
- ++tokPos;
564
- } else {
565
- break;
566
- }
567
- }
568
- }
569
-
570
- // ### Token reading
571
-
572
- // This is the function that is called to fetch the next token. It
573
- // is somewhat obscure, because it works in character codes rather
574
- // than characters, and because operator parsing has been inlined
575
- // into it.
576
- //
577
- // All in the name of speed.
578
- //
579
- // The `forceRegexp` parameter is used in the one case where the
580
- // `tokRegexpAllowed` trick does not work. See `parseStatement`.
581
-
582
- function readToken_dot() {
583
- var next = input.charCodeAt(tokPos + 1);
584
- if (next >= 48 && next <= 57) return readNumber(true);
585
- ++tokPos;
586
- return finishToken(_dot);
587
- }
588
-
589
- function readToken_slash() { // '/'
590
- var next = input.charCodeAt(tokPos + 1);
591
- if (tokRegexpAllowed) {++tokPos; return readRegexp();}
592
- if (next === 61) return finishOp(_assign, 2);
593
- return finishOp(_slash, 1);
594
- }
595
-
596
- function readToken_mult_modulo() { // '%*'
597
- var next = input.charCodeAt(tokPos + 1);
598
- if (next === 61) return finishOp(_assign, 2);
599
- return finishOp(_multiplyModulo, 1);
600
- }
601
-
602
- function readToken_pipe_amp(code) { // '|&'
603
- var next = input.charCodeAt(tokPos + 1);
604
- if (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2);
605
- if (next === 61) return finishOp(_assign, 2);
606
- return finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1);
607
- }
608
-
609
- function readToken_caret() { // '^'
610
- var next = input.charCodeAt(tokPos + 1);
611
- if (next === 61) return finishOp(_assign, 2);
612
- return finishOp(_bitwiseXOR, 1);
613
- }
614
-
615
- function readToken_plus_min(code) { // '+-'
616
- var next = input.charCodeAt(tokPos + 1);
617
- if (next === code) {
618
- if (next == 45 && input.charCodeAt(tokPos + 2) == 62 &&
619
- newline.test(input.slice(lastEnd, tokPos))) {
620
- // A `-->` line comment
621
- tokPos += 3;
622
- skipLineComment();
623
- skipSpace();
624
- return readToken();
625
- }
626
- return finishOp(_incDec, 2);
627
- }
628
- if (next === 61) return finishOp(_assign, 2);
629
- return finishOp(_plusMin, 1);
630
- }
631
-
632
- function readToken_lt_gt(code) { // '<>'
633
- var next = input.charCodeAt(tokPos + 1);
634
- var size = 1;
635
- if (next === code) {
636
- size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2;
637
- if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1);
638
- return finishOp(_bitShift, size);
639
- }
640
- if (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 &&
641
- input.charCodeAt(tokPos + 3) == 45) {
642
- // `<!--`, an XML-style comment that should be interpreted as a line comment
643
- tokPos += 4;
644
- skipLineComment();
645
- skipSpace();
646
- return readToken();
647
- }
648
- if (next === 61)
649
- size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2;
650
- return finishOp(_relational, size);
651
- }
652
-
653
- function readToken_eq_excl(code) { // '=!'
654
- var next = input.charCodeAt(tokPos + 1);
655
- if (next === 61) return finishOp(_equality, input.charCodeAt(tokPos + 2) === 61 ? 3 : 2);
656
- return finishOp(code === 61 ? _eq : _prefix, 1);
657
- }
658
-
659
- function getTokenFromCode(code) {
660
- switch(code) {
661
- // The interpretation of a dot depends on whether it is followed
662
- // by a digit.
663
- case 46: // '.'
664
- return readToken_dot();
665
-
666
- // Punctuation tokens.
667
- case 40: ++tokPos; return finishToken(_parenL);
668
- case 41: ++tokPos; return finishToken(_parenR);
669
- case 59: ++tokPos; return finishToken(_semi);
670
- case 44: ++tokPos; return finishToken(_comma);
671
- case 91: ++tokPos; return finishToken(_bracketL);
672
- case 93: ++tokPos; return finishToken(_bracketR);
673
- case 123: ++tokPos; return finishToken(_braceL);
674
- case 125: ++tokPos; return finishToken(_braceR);
675
- case 58: ++tokPos; return finishToken(_colon);
676
- case 63: ++tokPos; return finishToken(_question);
677
-
678
- // '0x' is a hexadecimal number.
679
- case 48: // '0'
680
- var next = input.charCodeAt(tokPos + 1);
681
- if (next === 120 || next === 88) return readHexNumber();
682
- // Anything else beginning with a digit is an integer, octal
683
- // number, or float.
684
- case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9
685
- return readNumber(false);
686
-
687
- // Quotes produce strings.
688
- case 34: case 39: // '"', "'"
689
- return readString(code);
690
-
691
- // Operators are parsed inline in tiny state machines. '=' (61) is
692
- // often referred to. `finishOp` simply skips the amount of
693
- // characters it is given as second argument, and returns a token
694
- // of the type given by its first argument.
695
-
696
- case 47: // '/'
697
- return readToken_slash(code);
698
-
699
- case 37: case 42: // '%*'
700
- return readToken_mult_modulo();
701
-
702
- case 124: case 38: // '|&'
703
- return readToken_pipe_amp(code);
704
-
705
- case 94: // '^'
706
- return readToken_caret();
707
-
708
- case 43: case 45: // '+-'
709
- return readToken_plus_min(code);
710
-
711
- case 60: case 62: // '<>'
712
- return readToken_lt_gt(code);
713
-
714
- case 61: case 33: // '=!'
715
- return readToken_eq_excl(code);
716
-
717
- case 126: // '~'
718
- return finishOp(_prefix, 1);
719
- }
720
-
721
- return false;
722
- }
723
-
724
- function readToken(forceRegexp) {
725
- if (!forceRegexp) tokStart = tokPos;
726
- else tokPos = tokStart + 1;
727
- if (options.locations) tokStartLoc = new line_loc_t;
728
- if (forceRegexp) return readRegexp();
729
- if (tokPos >= inputLen) return finishToken(_eof);
730
-
731
- var code = input.charCodeAt(tokPos);
732
- // Identifier or keyword. '\uXXXX' sequences are allowed in
733
- // identifiers, so '\' also dispatches to that.
734
- if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord();
735
-
736
- var tok = getTokenFromCode(code);
737
-
738
- if (tok === false) {
739
- // If we are here, we either found a non-ASCII identifier
740
- // character, or something that's entirely disallowed.
741
- var ch = String.fromCharCode(code);
742
- if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord();
743
- raise(tokPos, "Unexpected character '" + ch + "'");
744
- }
745
- return tok;
746
- }
747
-
748
- function finishOp(type, size) {
749
- var str = input.slice(tokPos, tokPos + size);
750
- tokPos += size;
751
- finishToken(type, str);
752
- }
753
-
754
- // Parse a regular expression. Some context-awareness is necessary,
755
- // since a '/' inside a '[]' set does not end the expression.
756
-
757
- function readRegexp() {
758
- var content = "", escaped, inClass, start = tokPos;
759
- for (;;) {
760
- if (tokPos >= inputLen) raise(start, "Unterminated regular expression");
761
- var ch = input.charAt(tokPos);
762
- if (newline.test(ch)) raise(start, "Unterminated regular expression");
763
- if (!escaped) {
764
- if (ch === "[") inClass = true;
765
- else if (ch === "]" && inClass) inClass = false;
766
- else if (ch === "/" && !inClass) break;
767
- escaped = ch === "\\";
768
- } else escaped = false;
769
- ++tokPos;
770
- }
771
- var content = input.slice(start, tokPos);
772
- ++tokPos;
773
- // Need to use `readWord1` because '\uXXXX' sequences are allowed
774
- // here (don't ask).
775
- var mods = readWord1();
776
- if (mods && !/^[gmsiy]*$/.test(mods)) raise(start, "Invalid regexp flag");
777
- try {
778
- var value = new RegExp(content, mods);
779
- } catch (e) {
780
- if (e instanceof SyntaxError) raise(start, e.message);
781
- raise(e);
782
- }
783
- return finishToken(_regexp, value);
784
- }
785
-
786
- // Read an integer in the given radix. Return null if zero digits
787
- // were read, the integer value otherwise. When `len` is given, this
788
- // will return `null` unless the integer has exactly `len` digits.
789
-
790
- function readInt(radix, len) {
791
- var start = tokPos, total = 0;
792
- for (var i = 0, e = len == null ? Infinity : len; i < e; ++i) {
793
- var code = input.charCodeAt(tokPos), val;
794
- if (code >= 97) val = code - 97 + 10; // a
795
- else if (code >= 65) val = code - 65 + 10; // A
796
- else if (code >= 48 && code <= 57) val = code - 48; // 0-9
797
- else val = Infinity;
798
- if (val >= radix) break;
799
- ++tokPos;
800
- total = total * radix + val;
801
- }
802
- if (tokPos === start || len != null && tokPos - start !== len) return null;
803
-
804
- return total;
805
- }
806
-
807
- function readHexNumber() {
808
- tokPos += 2; // 0x
809
- var val = readInt(16);
810
- if (val == null) raise(tokStart + 2, "Expected hexadecimal number");
811
- if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
812
- return finishToken(_num, val);
813
- }
814
-
815
- // Read an integer, octal integer, or floating-point number.
816
-
817
- function readNumber(startsWithDot) {
818
- var start = tokPos, isFloat = false, octal = input.charCodeAt(tokPos) === 48;
819
- if (!startsWithDot && readInt(10) === null) raise(start, "Invalid number");
820
- if (input.charCodeAt(tokPos) === 46) {
821
- ++tokPos;
822
- readInt(10);
823
- isFloat = true;
824
- }
825
- var next = input.charCodeAt(tokPos);
826
- if (next === 69 || next === 101) { // 'eE'
827
- next = input.charCodeAt(++tokPos);
828
- if (next === 43 || next === 45) ++tokPos; // '+-'
829
- if (readInt(10) === null) raise(start, "Invalid number");
830
- isFloat = true;
831
- }
832
- if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
833
-
834
- var str = input.slice(start, tokPos), val;
835
- if (isFloat) val = parseFloat(str);
836
- else if (!octal || str.length === 1) val = parseInt(str, 10);
837
- else if (/[89]/.test(str) || strict) raise(start, "Invalid number");
838
- else val = parseInt(str, 8);
839
- return finishToken(_num, val);
840
- }
841
-
842
- // Read a string value, interpreting backslash-escapes.
843
-
844
- function readString(quote) {
845
- tokPos++;
846
- var out = "";
847
- for (;;) {
848
- if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant");
849
- var ch = input.charCodeAt(tokPos);
850
- if (ch === quote) {
851
- ++tokPos;
852
- return finishToken(_string, out);
853
- }
854
- if (ch === 92) { // '\'
855
- ch = input.charCodeAt(++tokPos);
856
- var octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3));
857
- if (octal) octal = octal[0];
858
- while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, -1);
859
- if (octal === "0") octal = null;
860
- ++tokPos;
861
- if (octal) {
862
- if (strict) raise(tokPos - 2, "Octal literal in strict mode");
863
- out += String.fromCharCode(parseInt(octal, 8));
864
- tokPos += octal.length - 1;
865
- } else {
866
- switch (ch) {
867
- case 110: out += "\n"; break; // 'n' -> '\n'
868
- case 114: out += "\r"; break; // 'r' -> '\r'
869
- case 120: out += String.fromCharCode(readHexChar(2)); break; // 'x'
870
- case 117: out += String.fromCharCode(readHexChar(4)); break; // 'u'
871
- case 85: out += String.fromCharCode(readHexChar(8)); break; // 'U'
872
- case 116: out += "\t"; break; // 't' -> '\t'
873
- case 98: out += "\b"; break; // 'b' -> '\b'
874
- case 118: out += "\u000b"; break; // 'v' -> '\u000b'
875
- case 102: out += "\f"; break; // 'f' -> '\f'
876
- case 48: out += "\0"; break; // 0 -> '\0'
877
- case 13: if (input.charCodeAt(tokPos) === 10) ++tokPos; // '\r\n'
878
- case 10: // ' \n'
879
- if (options.locations) { tokLineStart = tokPos; ++tokCurLine; }
880
- break;
881
- default: out += String.fromCharCode(ch); break;
882
- }
883
- }
884
- } else {
885
- if (ch === 13 || ch === 10 || ch === 8232 || ch === 8233) raise(tokStart, "Unterminated string constant");
886
- out += String.fromCharCode(ch); // '\'
887
- ++tokPos;
888
- }
889
- }
890
- }
891
-
892
- // Used to read character escape sequences ('\x', '\u', '\U').
893
-
894
- function readHexChar(len) {
895
- var n = readInt(16, len);
896
- if (n === null) raise(tokStart, "Bad character escape sequence");
897
- return n;
898
- }
899
-
900
- // Used to signal to callers of `readWord1` whether the word
901
- // contained any escape sequences. This is needed because words with
902
- // escape sequences must not be interpreted as keywords.
903
-
904
- var containsEsc;
905
-
906
- // Read an identifier, and return it as a string. Sets `containsEsc`
907
- // to whether the word contained a '\u' escape.
908
- //
909
- // Only builds up the word character-by-character when it actually
910
- // containeds an escape, as a micro-optimization.
911
-
912
- function readWord1() {
913
- containsEsc = false;
914
- var word, first = true, start = tokPos;
915
- for (;;) {
916
- var ch = input.charCodeAt(tokPos);
917
- if (isIdentifierChar(ch)) {
918
- if (containsEsc) word += input.charAt(tokPos);
919
- ++tokPos;
920
- } else if (ch === 92) { // "\"
921
- if (!containsEsc) word = input.slice(start, tokPos);
922
- containsEsc = true;
923
- if (input.charCodeAt(++tokPos) != 117) // "u"
924
- raise(tokPos, "Expecting Unicode escape sequence \\uXXXX");
925
- ++tokPos;
926
- var esc = readHexChar(4);
927
- var escStr = String.fromCharCode(esc);
928
- if (!escStr) raise(tokPos - 1, "Invalid Unicode escape");
929
- if (!(first ? isIdentifierStart(esc) : isIdentifierChar(esc)))
930
- raise(tokPos - 4, "Invalid Unicode escape");
931
- word += escStr;
932
- } else {
933
- break;
934
- }
935
- first = false;
936
- }
937
- return containsEsc ? word : input.slice(start, tokPos);
938
- }
939
-
940
- // Read an identifier or keyword token. Will check for reserved
941
- // words when necessary.
942
-
943
- function readWord() {
944
- var word = readWord1();
945
- var type = _name;
946
- if (!containsEsc && isKeyword(word))
947
- type = keywordTypes[word];
948
- return finishToken(type, word);
949
- }
950
-
951
- // ## Parser
952
-
953
- // A recursive descent parser operates by defining functions for all
954
- // syntactic elements, and recursively calling those, each function
955
- // advancing the input stream and returning an AST node. Precedence
956
- // of constructs (for example, the fact that `!x[1]` means `!(x[1])`
957
- // instead of `(!x)[1]` is handled by the fact that the parser
958
- // function that parses unary prefix operators is called first, and
959
- // in turn calls the function that parses `[]` subscripts — that
960
- // way, it'll receive the node for `x[1]` already parsed, and wraps
961
- // *that* in the unary operator node.
962
- //
963
- // Acorn uses an [operator precedence parser][opp] to handle binary
964
- // operator precedence, because it is much more compact than using
965
- // the technique outlined above, which uses different, nesting
966
- // functions to specify precedence, for all of the ten binary
967
- // precedence levels that JavaScript defines.
968
- //
969
- // [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser
970
-
971
- // ### Parser utilities
972
-
973
- // Continue to the next token.
974
-
975
- function next() {
976
- lastStart = tokStart;
977
- lastEnd = tokEnd;
978
- lastEndLoc = tokEndLoc;
979
- readToken();
980
- }
981
-
982
- // Enter strict mode. Re-reads the next token to please pedantic
983
- // tests ("use strict"; 010; -- should fail).
984
-
985
- function setStrict(strct) {
986
- strict = strct;
987
- tokPos = tokStart;
988
- if (options.locations) {
989
- while (tokPos < tokLineStart) {
990
- tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1;
991
- --tokCurLine;
992
- }
993
- }
994
- skipSpace();
995
- readToken();
996
- }
997
-
998
- // Start an AST node, attaching a start offset.
999
-
1000
- function node_t() {
1001
- this.type = null;
1002
- this.start = tokStart;
1003
- this.end = null;
1004
- }
1005
-
1006
- function node_loc_t() {
1007
- this.start = tokStartLoc;
1008
- this.end = null;
1009
- if (sourceFile !== null) this.source = sourceFile;
1010
- }
1011
-
1012
- function startNode() {
1013
- var node = new node_t();
1014
- if (options.locations)
1015
- node.loc = new node_loc_t();
1016
- if (options.directSourceFile)
1017
- node.sourceFile = options.directSourceFile;
1018
- if (options.ranges)
1019
- node.range = [tokStart, 0];
1020
- return node;
1021
- }
1022
-
1023
- // Start a node whose start offset information should be based on
1024
- // the start of another node. For example, a binary operator node is
1025
- // only started after its left-hand side has already been parsed.
1026
-
1027
- function startNodeFrom(other) {
1028
- var node = new node_t();
1029
- node.start = other.start;
1030
- if (options.locations) {
1031
- node.loc = new node_loc_t();
1032
- node.loc.start = other.loc.start;
1033
- }
1034
- if (options.ranges)
1035
- node.range = [other.range[0], 0];
1036
-
1037
- return node;
1038
- }
1039
-
1040
- // Finish an AST node, adding `type` and `end` properties.
1041
-
1042
- function finishNode(node, type) {
1043
- node.type = type;
1044
- node.end = lastEnd;
1045
- if (options.locations)
1046
- node.loc.end = lastEndLoc;
1047
- if (options.ranges)
1048
- node.range[1] = lastEnd;
1049
- return node;
1050
- }
1051
-
1052
- // Test whether a statement node is the string literal `"use strict"`.
1053
-
1054
- function isUseStrict(stmt) {
1055
- return options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" &&
1056
- stmt.expression.type === "Literal" && stmt.expression.value === "use strict";
1057
- }
1058
-
1059
- // Predicate that tests whether the next token is of the given
1060
- // type, and if yes, consumes it as a side effect.
1061
-
1062
- function eat(type) {
1063
- if (tokType === type) {
1064
- next();
1065
- return true;
1066
- }
1067
- }
1068
-
1069
- // Test whether a semicolon can be inserted at the current position.
1070
-
1071
- function canInsertSemicolon() {
1072
- return !options.strictSemicolons &&
1073
- (tokType === _eof || tokType === _braceR || newline.test(input.slice(lastEnd, tokStart)));
1074
- }
1075
-
1076
- // Consume a semicolon, or, failing that, see if we are allowed to
1077
- // pretend that there is a semicolon at this position.
1078
-
1079
- function semicolon() {
1080
- if (!eat(_semi) && !canInsertSemicolon()) unexpected();
1081
- }
1082
-
1083
- // Expect a token of a given type. If found, consume it, otherwise,
1084
- // raise an unexpected token error.
1085
-
1086
- function expect(type) {
1087
- if (tokType === type) next();
1088
- else unexpected();
1089
- }
1090
-
1091
- // Raise an unexpected token error.
1092
-
1093
- function unexpected() {
1094
- raise(tokStart, "Unexpected token");
1095
- }
1096
-
1097
- // Verify that a node is an lval — something that can be assigned
1098
- // to.
1099
-
1100
- function checkLVal(expr) {
1101
- if (expr.type !== "Identifier" && expr.type !== "MemberExpression")
1102
- raise(expr.start, "Assigning to rvalue");
1103
- if (strict && expr.type === "Identifier" && isStrictBadIdWord(expr.name))
1104
- raise(expr.start, "Assigning to " + expr.name + " in strict mode");
1105
- }
1106
-
1107
- // ### Statement parsing
1108
-
1109
- // Parse a program. Initializes the parser, reads any number of
1110
- // statements, and wraps them in a Program node. Optionally takes a
1111
- // `program` argument. If present, the statements will be appended
1112
- // to its body instead of creating a new node.
1113
-
1114
- function parseTopLevel(program) {
1115
- lastStart = lastEnd = tokPos;
1116
- if (options.locations) lastEndLoc = new line_loc_t;
1117
- inFunction = strict = null;
1118
- labels = [];
1119
- readToken();
1120
-
1121
- var node = program || startNode(), first = true;
1122
- if (!program) node.body = [];
1123
- while (tokType !== _eof) {
1124
- var stmt = parseStatement();
1125
- node.body.push(stmt);
1126
- if (first && isUseStrict(stmt)) setStrict(true);
1127
- first = false;
1128
- }
1129
- return finishNode(node, "Program");
1130
- }
1131
-
1132
- var loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"};
1133
-
1134
- // Parse a single statement.
1135
- //
1136
- // If expecting a statement and finding a slash operator, parse a
1137
- // regular expression literal. This is to handle cases like
1138
- // `if (foo) /blah/.exec(foo);`, where looking at the previous token
1139
- // does not help.
1140
-
1141
- function parseStatement() {
1142
- if (tokType === _slash || tokType === _assign && tokVal == "/=")
1143
- readToken(true);
1144
-
1145
- var starttype = tokType, node = startNode();
1146
-
1147
- // Most types of statements are recognized by the keyword they
1148
- // start with. Many are trivial to parse, some require a bit of
1149
- // complexity.
1150
-
1151
- switch (starttype) {
1152
- case _break: case _continue:
1153
- next();
1154
- var isBreak = starttype === _break;
1155
- if (eat(_semi) || canInsertSemicolon()) node.label = null;
1156
- else if (tokType !== _name) unexpected();
1157
- else {
1158
- node.label = parseIdent();
1159
- semicolon();
1160
- }
1161
-
1162
- // Verify that there is an actual destination to break or
1163
- // continue to.
1164
- for (var i = 0; i < labels.length; ++i) {
1165
- var lab = labels[i];
1166
- if (node.label == null || lab.name === node.label.name) {
1167
- if (lab.kind != null && (isBreak || lab.kind === "loop")) break;
1168
- if (node.label && isBreak) break;
1169
- }
1170
- }
1171
- if (i === labels.length) raise(node.start, "Unsyntactic " + starttype.keyword);
1172
- return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
1173
-
1174
- case _debugger:
1175
- next();
1176
- semicolon();
1177
- return finishNode(node, "DebuggerStatement");
1178
-
1179
- case _do:
1180
- next();
1181
- labels.push(loopLabel);
1182
- node.body = parseStatement();
1183
- labels.pop();
1184
- expect(_while);
1185
- node.test = parseParenExpression();
1186
- semicolon();
1187
- return finishNode(node, "DoWhileStatement");
1188
-
1189
- // Disambiguating between a `for` and a `for`/`in` loop is
1190
- // non-trivial. Basically, we have to parse the init `var`
1191
- // statement or expression, disallowing the `in` operator (see
1192
- // the second parameter to `parseExpression`), and then check
1193
- // whether the next token is `in`. When there is no init part
1194
- // (semicolon immediately after the opening parenthesis), it is
1195
- // a regular `for` loop.
1196
-
1197
- case _for:
1198
- next();
1199
- labels.push(loopLabel);
1200
- expect(_parenL);
1201
- if (tokType === _semi) return parseFor(node, null);
1202
- if (tokType === _var) {
1203
- var init = startNode();
1204
- next();
1205
- parseVar(init, true);
1206
- finishNode(init, "VariableDeclaration");
1207
- if (init.declarations.length === 1 && eat(_in))
1208
- return parseForIn(node, init);
1209
- return parseFor(node, init);
1210
- }
1211
- var init = parseExpression(false, true);
1212
- if (eat(_in)) {checkLVal(init); return parseForIn(node, init);}
1213
- return parseFor(node, init);
1214
-
1215
- case _function:
1216
- next();
1217
- return parseFunction(node, true);
1218
-
1219
- case _if:
1220
- next();
1221
- node.test = parseParenExpression();
1222
- node.consequent = parseStatement();
1223
- node.alternate = eat(_else) ? parseStatement() : null;
1224
- return finishNode(node, "IfStatement");
1225
-
1226
- case _return:
1227
- if (!inFunction && !options.allowReturnOutsideFunction)
1228
- raise(tokStart, "'return' outside of function");
1229
- next();
1230
-
1231
- // In `return` (and `break`/`continue`), the keywords with
1232
- // optional arguments, we eagerly look for a semicolon or the
1233
- // possibility to insert one.
1234
-
1235
- if (eat(_semi) || canInsertSemicolon()) node.argument = null;
1236
- else { node.argument = parseExpression(); semicolon(); }
1237
- return finishNode(node, "ReturnStatement");
1238
-
1239
- case _switch:
1240
- next();
1241
- node.discriminant = parseParenExpression();
1242
- node.cases = [];
1243
- expect(_braceL);
1244
- labels.push(switchLabel);
1245
-
1246
- // Statements under must be grouped (by label) in SwitchCase
1247
- // nodes. `cur` is used to keep the node that we are currently
1248
- // adding statements to.
1249
-
1250
- for (var cur, sawDefault; tokType != _braceR;) {
1251
- if (tokType === _case || tokType === _default) {
1252
- var isCase = tokType === _case;
1253
- if (cur) finishNode(cur, "SwitchCase");
1254
- node.cases.push(cur = startNode());
1255
- cur.consequent = [];
1256
- next();
1257
- if (isCase) cur.test = parseExpression();
1258
- else {
1259
- if (sawDefault) raise(lastStart, "Multiple default clauses"); sawDefault = true;
1260
- cur.test = null;
1261
- }
1262
- expect(_colon);
1263
- } else {
1264
- if (!cur) unexpected();
1265
- cur.consequent.push(parseStatement());
1266
- }
1267
- }
1268
- if (cur) finishNode(cur, "SwitchCase");
1269
- next(); // Closing brace
1270
- labels.pop();
1271
- return finishNode(node, "SwitchStatement");
1272
-
1273
- case _throw:
1274
- next();
1275
- if (newline.test(input.slice(lastEnd, tokStart)))
1276
- raise(lastEnd, "Illegal newline after throw");
1277
- node.argument = parseExpression();
1278
- semicolon();
1279
- return finishNode(node, "ThrowStatement");
1280
-
1281
- case _try:
1282
- next();
1283
- node.block = parseBlock();
1284
- node.handler = null;
1285
- if (tokType === _catch) {
1286
- var clause = startNode();
1287
- next();
1288
- expect(_parenL);
1289
- clause.param = parseIdent();
1290
- if (strict && isStrictBadIdWord(clause.param.name))
1291
- raise(clause.param.start, "Binding " + clause.param.name + " in strict mode");
1292
- expect(_parenR);
1293
- clause.guard = null;
1294
- clause.body = parseBlock();
1295
- node.handler = finishNode(clause, "CatchClause");
1296
- }
1297
- node.guardedHandlers = empty;
1298
- node.finalizer = eat(_finally) ? parseBlock() : null;
1299
- if (!node.handler && !node.finalizer)
1300
- raise(node.start, "Missing catch or finally clause");
1301
- return finishNode(node, "TryStatement");
1302
-
1303
- case _var:
1304
- next();
1305
- parseVar(node);
1306
- semicolon();
1307
- return finishNode(node, "VariableDeclaration");
1308
-
1309
- case _while:
1310
- next();
1311
- node.test = parseParenExpression();
1312
- labels.push(loopLabel);
1313
- node.body = parseStatement();
1314
- labels.pop();
1315
- return finishNode(node, "WhileStatement");
1316
-
1317
- case _with:
1318
- if (strict) raise(tokStart, "'with' in strict mode");
1319
- next();
1320
- node.object = parseParenExpression();
1321
- node.body = parseStatement();
1322
- return finishNode(node, "WithStatement");
1323
-
1324
- case _braceL:
1325
- return parseBlock();
1326
-
1327
- case _semi:
1328
- next();
1329
- return finishNode(node, "EmptyStatement");
1330
-
1331
- // If the statement does not start with a statement keyword or a
1332
- // brace, it's an ExpressionStatement or LabeledStatement. We
1333
- // simply start parsing an expression, and afterwards, if the
1334
- // next token is a colon and the expression was a simple
1335
- // Identifier node, we switch to interpreting it as a label.
1336
-
1337
- default:
1338
- var maybeName = tokVal, expr = parseExpression();
1339
- if (starttype === _name && expr.type === "Identifier" && eat(_colon)) {
1340
- for (var i = 0; i < labels.length; ++i)
1341
- if (labels[i].name === maybeName) raise(expr.start, "Label '" + maybeName + "' is already declared");
1342
- var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null;
1343
- labels.push({name: maybeName, kind: kind});
1344
- node.body = parseStatement();
1345
- labels.pop();
1346
- node.label = expr;
1347
- return finishNode(node, "LabeledStatement");
1348
- } else {
1349
- node.expression = expr;
1350
- semicolon();
1351
- return finishNode(node, "ExpressionStatement");
1352
- }
1353
- }
1354
- }
1355
-
1356
- // Used for constructs like `switch` and `if` that insist on
1357
- // parentheses around their expression.
1358
-
1359
- function parseParenExpression() {
1360
- expect(_parenL);
1361
- var val = parseExpression();
1362
- expect(_parenR);
1363
- return val;
1364
- }
1365
-
1366
- // Parse a semicolon-enclosed block of statements, handling `"use
1367
- // strict"` declarations when `allowStrict` is true (used for
1368
- // function bodies).
1369
-
1370
- function parseBlock(allowStrict) {
1371
- var node = startNode(), first = true, strict = false, oldStrict;
1372
- node.body = [];
1373
- expect(_braceL);
1374
- while (!eat(_braceR)) {
1375
- var stmt = parseStatement();
1376
- node.body.push(stmt);
1377
- if (first && allowStrict && isUseStrict(stmt)) {
1378
- oldStrict = strict;
1379
- setStrict(strict = true);
1380
- }
1381
- first = false;
1382
- }
1383
- if (strict && !oldStrict) setStrict(false);
1384
- return finishNode(node, "BlockStatement");
1385
- }
1386
-
1387
- // Parse a regular `for` loop. The disambiguation code in
1388
- // `parseStatement` will already have parsed the init statement or
1389
- // expression.
1390
-
1391
- function parseFor(node, init) {
1392
- node.init = init;
1393
- expect(_semi);
1394
- node.test = tokType === _semi ? null : parseExpression();
1395
- expect(_semi);
1396
- node.update = tokType === _parenR ? null : parseExpression();
1397
- expect(_parenR);
1398
- node.body = parseStatement();
1399
- labels.pop();
1400
- return finishNode(node, "ForStatement");
1401
- }
1402
-
1403
- // Parse a `for`/`in` loop.
1404
-
1405
- function parseForIn(node, init) {
1406
- node.left = init;
1407
- node.right = parseExpression();
1408
- expect(_parenR);
1409
- node.body = parseStatement();
1410
- labels.pop();
1411
- return finishNode(node, "ForInStatement");
1412
- }
1413
-
1414
- // Parse a list of variable declarations.
1415
-
1416
- function parseVar(node, noIn) {
1417
- node.declarations = [];
1418
- node.kind = "var";
1419
- for (;;) {
1420
- var decl = startNode();
1421
- decl.id = parseIdent();
1422
- if (strict && isStrictBadIdWord(decl.id.name))
1423
- raise(decl.id.start, "Binding " + decl.id.name + " in strict mode");
1424
- decl.init = eat(_eq) ? parseExpression(true, noIn) : null;
1425
- node.declarations.push(finishNode(decl, "VariableDeclarator"));
1426
- if (!eat(_comma)) break;
1427
- }
1428
- return node;
1429
- }
1430
-
1431
- // ### Expression parsing
1432
-
1433
- // These nest, from the most general expression type at the top to
1434
- // 'atomic', nondivisible expression types at the bottom. Most of
1435
- // the functions will simply let the function(s) below them parse,
1436
- // and, *if* the syntactic construct they handle is present, wrap
1437
- // the AST node that the inner parser gave them in another node.
1438
-
1439
- // Parse a full expression. The arguments are used to forbid comma
1440
- // sequences (in argument lists, array literals, or object literals)
1441
- // or the `in` operator (in for loops initalization expressions).
1442
-
1443
- function parseExpression(noComma, noIn) {
1444
- var expr = parseMaybeAssign(noIn);
1445
- if (!noComma && tokType === _comma) {
1446
- var node = startNodeFrom(expr);
1447
- node.expressions = [expr];
1448
- while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn));
1449
- return finishNode(node, "SequenceExpression");
1450
- }
1451
- return expr;
1452
- }
1453
-
1454
- // Parse an assignment expression. This includes applications of
1455
- // operators like `+=`.
1456
-
1457
- function parseMaybeAssign(noIn) {
1458
- var left = parseMaybeConditional(noIn);
1459
- if (tokType.isAssign) {
1460
- var node = startNodeFrom(left);
1461
- node.operator = tokVal;
1462
- node.left = left;
1463
- next();
1464
- node.right = parseMaybeAssign(noIn);
1465
- checkLVal(left);
1466
- return finishNode(node, "AssignmentExpression");
1467
- }
1468
- return left;
1469
- }
1470
-
1471
- // Parse a ternary conditional (`?:`) operator.
1472
-
1473
- function parseMaybeConditional(noIn) {
1474
- var expr = parseExprOps(noIn);
1475
- if (eat(_question)) {
1476
- var node = startNodeFrom(expr);
1477
- node.test = expr;
1478
- node.consequent = parseExpression(true);
1479
- expect(_colon);
1480
- node.alternate = parseExpression(true, noIn);
1481
- return finishNode(node, "ConditionalExpression");
1482
- }
1483
- return expr;
1484
- }
1485
-
1486
- // Start the precedence parser.
1487
-
1488
- function parseExprOps(noIn) {
1489
- return parseExprOp(parseMaybeUnary(), -1, noIn);
1490
- }
1491
-
1492
- // Parse binary operators with the operator precedence parsing
1493
- // algorithm. `left` is the left-hand side of the operator.
1494
- // `minPrec` provides context that allows the function to stop and
1495
- // defer further parser to one of its callers when it encounters an
1496
- // operator that has a lower precedence than the set it is parsing.
1497
-
1498
- function parseExprOp(left, minPrec, noIn) {
1499
- var prec = tokType.binop;
1500
- if (prec != null && (!noIn || tokType !== _in)) {
1501
- if (prec > minPrec) {
1502
- var node = startNodeFrom(left);
1503
- node.left = left;
1504
- node.operator = tokVal;
1505
- var op = tokType;
1506
- next();
1507
- node.right = parseExprOp(parseMaybeUnary(), prec, noIn);
1508
- var exprNode = finishNode(node, (op === _logicalOR || op === _logicalAND) ? "LogicalExpression" : "BinaryExpression");
1509
- return parseExprOp(exprNode, minPrec, noIn);
1510
- }
1511
- }
1512
- return left;
1513
- }
1514
-
1515
- // Parse unary operators, both prefix and postfix.
1516
-
1517
- function parseMaybeUnary() {
1518
- if (tokType.prefix) {
1519
- var node = startNode(), update = tokType.isUpdate;
1520
- node.operator = tokVal;
1521
- node.prefix = true;
1522
- tokRegexpAllowed = true;
1523
- next();
1524
- node.argument = parseMaybeUnary();
1525
- if (update) checkLVal(node.argument);
1526
- else if (strict && node.operator === "delete" &&
1527
- node.argument.type === "Identifier")
1528
- raise(node.start, "Deleting local variable in strict mode");
1529
- return finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
1530
- }
1531
- var expr = parseExprSubscripts();
1532
- while (tokType.postfix && !canInsertSemicolon()) {
1533
- var node = startNodeFrom(expr);
1534
- node.operator = tokVal;
1535
- node.prefix = false;
1536
- node.argument = expr;
1537
- checkLVal(expr);
1538
- next();
1539
- expr = finishNode(node, "UpdateExpression");
1540
- }
1541
- return expr;
1542
- }
1543
-
1544
- // Parse call, dot, and `[]`-subscript expressions.
1545
-
1546
- function parseExprSubscripts() {
1547
- return parseSubscripts(parseExprAtom());
1548
- }
1549
-
1550
- function parseSubscripts(base, noCalls) {
1551
- if (eat(_dot)) {
1552
- var node = startNodeFrom(base);
1553
- node.object = base;
1554
- node.property = parseIdent(true);
1555
- node.computed = false;
1556
- return parseSubscripts(finishNode(node, "MemberExpression"), noCalls);
1557
- } else if (eat(_bracketL)) {
1558
- var node = startNodeFrom(base);
1559
- node.object = base;
1560
- node.property = parseExpression();
1561
- node.computed = true;
1562
- expect(_bracketR);
1563
- return parseSubscripts(finishNode(node, "MemberExpression"), noCalls);
1564
- } else if (!noCalls && eat(_parenL)) {
1565
- var node = startNodeFrom(base);
1566
- node.callee = base;
1567
- node.arguments = parseExprList(_parenR, false);
1568
- return parseSubscripts(finishNode(node, "CallExpression"), noCalls);
1569
- } else return base;
1570
- }
1571
-
1572
- // Parse an atomic expression — either a single token that is an
1573
- // expression, an expression started by a keyword like `function` or
1574
- // `new`, or an expression wrapped in punctuation like `()`, `[]`,
1575
- // or `{}`.
1576
-
1577
- function parseExprAtom() {
1578
- switch (tokType) {
1579
- case _this:
1580
- var node = startNode();
1581
- next();
1582
- return finishNode(node, "ThisExpression");
1583
- case _name:
1584
- return parseIdent();
1585
- case _num: case _string: case _regexp:
1586
- var node = startNode();
1587
- node.value = tokVal;
1588
- node.raw = input.slice(tokStart, tokEnd);
1589
- next();
1590
- return finishNode(node, "Literal");
1591
-
1592
- case _null: case _true: case _false:
1593
- var node = startNode();
1594
- node.value = tokType.atomValue;
1595
- node.raw = tokType.keyword;
1596
- next();
1597
- return finishNode(node, "Literal");
1598
-
1599
- case _parenL:
1600
- var tokStartLoc1 = tokStartLoc, tokStart1 = tokStart;
1601
- next();
1602
- var val = parseExpression();
1603
- val.start = tokStart1;
1604
- val.end = tokEnd;
1605
- if (options.locations) {
1606
- val.loc.start = tokStartLoc1;
1607
- val.loc.end = tokEndLoc;
1608
- }
1609
- if (options.ranges)
1610
- val.range = [tokStart1, tokEnd];
1611
- expect(_parenR);
1612
- return val;
1613
-
1614
- case _bracketL:
1615
- var node = startNode();
1616
- next();
1617
- node.elements = parseExprList(_bracketR, true, true);
1618
- return finishNode(node, "ArrayExpression");
1619
-
1620
- case _braceL:
1621
- return parseObj();
1622
-
1623
- case _function:
1624
- var node = startNode();
1625
- next();
1626
- return parseFunction(node, false);
1627
-
1628
- case _new:
1629
- return parseNew();
1630
-
1631
- default:
1632
- unexpected();
1633
- }
1634
- }
1635
-
1636
- // New's precedence is slightly tricky. It must allow its argument
1637
- // to be a `[]` or dot subscript expression, but not a call — at
1638
- // least, not without wrapping it in parentheses. Thus, it uses the
1639
-
1640
- function parseNew() {
1641
- var node = startNode();
1642
- next();
1643
- node.callee = parseSubscripts(parseExprAtom(), true);
1644
- if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
1645
- else node.arguments = empty;
1646
- return finishNode(node, "NewExpression");
1647
- }
1648
-
1649
- // Parse an object literal.
1650
-
1651
- function parseObj() {
1652
- var node = startNode(), first = true, sawGetSet = false;
1653
- node.properties = [];
1654
- next();
1655
- while (!eat(_braceR)) {
1656
- if (!first) {
1657
- expect(_comma);
1658
- if (options.allowTrailingCommas && eat(_braceR)) break;
1659
- } else first = false;
1660
-
1661
- var prop = {key: parsePropertyName()}, isGetSet = false, kind;
1662
- if (eat(_colon)) {
1663
- prop.value = parseExpression(true);
1664
- kind = prop.kind = "init";
1665
- } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" &&
1666
- (prop.key.name === "get" || prop.key.name === "set")) {
1667
- isGetSet = sawGetSet = true;
1668
- kind = prop.kind = prop.key.name;
1669
- prop.key = parsePropertyName();
1670
- if (tokType !== _parenL) unexpected();
1671
- prop.value = parseFunction(startNode(), false);
1672
- } else unexpected();
1673
-
1674
- // getters and setters are not allowed to clash — either with
1675
- // each other or with an init property — and in strict mode,
1676
- // init properties are also not allowed to be repeated.
1677
-
1678
- if (prop.key.type === "Identifier" && (strict || sawGetSet)) {
1679
- for (var i = 0; i < node.properties.length; ++i) {
1680
- var other = node.properties[i];
1681
- if (other.key.name === prop.key.name) {
1682
- var conflict = kind == other.kind || isGetSet && other.kind === "init" ||
1683
- kind === "init" && (other.kind === "get" || other.kind === "set");
1684
- if (conflict && !strict && kind === "init" && other.kind === "init") conflict = false;
1685
- if (conflict) raise(prop.key.start, "Redefinition of property");
1686
- }
1687
- }
1688
- }
1689
- node.properties.push(prop);
1690
- }
1691
- return finishNode(node, "ObjectExpression");
1692
- }
1693
-
1694
- function parsePropertyName() {
1695
- if (tokType === _num || tokType === _string) return parseExprAtom();
1696
- return parseIdent(true);
1697
- }
1698
-
1699
- // Parse a function declaration or literal (depending on the
1700
- // `isStatement` parameter).
1701
-
1702
- function parseFunction(node, isStatement) {
1703
- if (tokType === _name) node.id = parseIdent();
1704
- else if (isStatement) unexpected();
1705
- else node.id = null;
1706
- node.params = [];
1707
- var first = true;
1708
- expect(_parenL);
1709
- while (!eat(_parenR)) {
1710
- if (!first) expect(_comma); else first = false;
1711
- node.params.push(parseIdent());
1712
- }
1713
-
1714
- // Start a new scope with regard to labels and the `inFunction`
1715
- // flag (restore them to their old value afterwards).
1716
- var oldInFunc = inFunction, oldLabels = labels;
1717
- inFunction = true; labels = [];
1718
- node.body = parseBlock(true);
1719
- inFunction = oldInFunc; labels = oldLabels;
1720
-
1721
- // If this is a strict mode function, verify that argument names
1722
- // are not repeated, and it does not try to bind the words `eval`
1723
- // or `arguments`.
1724
- if (strict || node.body.body.length && isUseStrict(node.body.body[0])) {
1725
- for (var i = node.id ? -1 : 0; i < node.params.length; ++i) {
1726
- var id = i < 0 ? node.id : node.params[i];
1727
- if (isStrictReservedWord(id.name) || isStrictBadIdWord(id.name))
1728
- raise(id.start, "Defining '" + id.name + "' in strict mode");
1729
- if (i >= 0) for (var j = 0; j < i; ++j) if (id.name === node.params[j].name)
1730
- raise(id.start, "Argument name clash in strict mode");
1731
- }
1732
- }
1733
-
1734
- return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
1735
- }
1736
-
1737
- // Parses a comma-separated list of expressions, and returns them as
1738
- // an array. `close` is the token type that ends the list, and
1739
- // `allowEmpty` can be turned on to allow subsequent commas with
1740
- // nothing in between them to be parsed as `null` (which is needed
1741
- // for array literals).
1742
-
1743
- function parseExprList(close, allowTrailingComma, allowEmpty) {
1744
- var elts = [], first = true;
1745
- while (!eat(close)) {
1746
- if (!first) {
1747
- expect(_comma);
1748
- if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break;
1749
- } else first = false;
1750
-
1751
- if (allowEmpty && tokType === _comma) elts.push(null);
1752
- else elts.push(parseExpression(true));
1753
- }
1754
- return elts;
1755
- }
1756
-
1757
- // Parse the next token as an identifier. If `liberal` is true (used
1758
- // when parsing properties), it will also convert keywords into
1759
- // identifiers.
1760
-
1761
- function parseIdent(liberal) {
1762
- var node = startNode();
1763
- if (liberal && options.forbidReserved == "everywhere") liberal = false;
1764
- if (tokType === _name) {
1765
- if (!liberal &&
1766
- (options.forbidReserved &&
1767
- (options.ecmaVersion === 3 ? isReservedWord3 : isReservedWord5)(tokVal) ||
1768
- strict && isStrictReservedWord(tokVal)) &&
1769
- input.slice(tokStart, tokEnd).indexOf("\\") == -1)
1770
- raise(tokStart, "The keyword '" + tokVal + "' is reserved");
1771
- node.name = tokVal;
1772
- } else if (liberal && tokType.keyword) {
1773
- node.name = tokType.keyword;
1774
- } else {
1775
- unexpected();
1776
- }
1777
- tokRegexpAllowed = false;
1778
- next();
1779
- return finishNode(node, "Identifier");
1780
- }
1781
-
1782
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint/lint.js DELETED
@@ -1,166 +0,0 @@
1
- /*
2
- Simple linter, based on the Acorn [1] parser module
3
-
4
- All of the existing linters either cramp my style or have huge
5
- dependencies (Closure). So here's a very simple, non-invasive one
6
- that only spots
7
-
8
- - missing semicolons and trailing commas
9
- - variables or properties that are reserved words
10
- - assigning to a variable you didn't declare
11
- - access to non-whitelisted globals
12
- (use a '// declare global: foo, bar' comment to declare extra
13
- globals in a file)
14
-
15
- [1]: https://github.com/marijnh/acorn/
16
- */
17
-
18
- var topAllowedGlobals = Object.create(null);
19
- ("Error RegExp Number String Array Function Object Math Date undefined " +
20
- "parseInt parseFloat Infinity NaN isNaN " +
21
- "window document navigator prompt alert confirm console " +
22
- "screen FileReader Worker postMessage importScripts " +
23
- "setInterval clearInterval setTimeout clearTimeout " +
24
- "CodeMirror " +
25
- "test exports require module define requirejs")
26
- .split(" ").forEach(function(n) { topAllowedGlobals[n] = true; });
27
-
28
- var fs = require("fs"), acorn = require("./acorn.js"), walk = require("./walk.js");
29
-
30
- var scopePasser = walk.make({
31
- ScopeBody: function(node, prev, c) { c(node, node.scope); }
32
- });
33
-
34
- var cBlob = /^\/\/ CodeMirror, copyright \(c\) by Marijn Haverbeke and others\n\/\/ Distributed under an MIT license: http:\/\/codemirror.net\/LICENSE\n\n/;
35
-
36
- function checkFile(fileName) {
37
- var file = fs.readFileSync(fileName, "utf8"), notAllowed;
38
- if (notAllowed = file.match(/[\x00-\x08\x0b\x0c\x0e-\x19\uFEFF\t]|[ \t]\n/)) {
39
- var msg;
40
- if (notAllowed[0] == "\t") msg = "Found tab character";
41
- else if (notAllowed[0].indexOf("\n") > -1) msg = "Trailing whitespace";
42
- else msg = "Undesirable character " + notAllowed[0].charCodeAt(0);
43
- var info = acorn.getLineInfo(file, notAllowed.index);
44
- fail(msg + " at line " + info.line + ", column " + info.column, {source: fileName});
45
- }
46
-
47
- if (!cBlob.test(file))
48
- fail("Missing license blob", {source: fileName});
49
-
50
- var globalsSeen = Object.create(null);
51
-
52
- try {
53
- var parsed = acorn.parse(file, {
54
- locations: true,
55
- ecmaVersion: 3,
56
- strictSemicolons: true,
57
- allowTrailingCommas: false,
58
- forbidReserved: "everywhere",
59
- sourceFile: fileName
60
- });
61
- } catch (e) {
62
- fail(e.message, {source: fileName});
63
- return;
64
- }
65
-
66
- var scopes = [];
67
-
68
- walk.simple(parsed, {
69
- ScopeBody: function(node, scope) {
70
- node.scope = scope;
71
- scopes.push(scope);
72
- }
73
- }, walk.scopeVisitor, {vars: Object.create(null)});
74
-
75
- var ignoredGlobals = Object.create(null);
76
-
77
- function inScope(name, scope) {
78
- for (var cur = scope; cur; cur = cur.prev)
79
- if (name in cur.vars) return true;
80
- }
81
- function checkLHS(node, scope) {
82
- if (node.type == "Identifier" && !(node.name in ignoredGlobals) &&
83
- !inScope(node.name, scope)) {
84
- ignoredGlobals[node.name] = true;
85
- fail("Assignment to global variable", node.loc);
86
- }
87
- }
88
-
89
- walk.simple(parsed, {
90
- UpdateExpression: function(node, scope) {checkLHS(node.argument, scope);},
91
- AssignmentExpression: function(node, scope) {checkLHS(node.left, scope);},
92
- Identifier: function(node, scope) {
93
- if (node.name == "arguments") return;
94
- // Mark used identifiers
95
- for (var cur = scope; cur; cur = cur.prev)
96
- if (node.name in cur.vars) {
97
- cur.vars[node.name].used = true;
98
- return;
99
- }
100
- globalsSeen[node.name] = node.loc;
101
- },
102
- FunctionExpression: function(node) {
103
- if (node.id) fail("Named function expression", node.loc);
104
- },
105
- ForStatement: function(node) {
106
- checkReusedIndex(node);
107
- },
108
- MemberExpression: function(node) {
109
- if (node.object.type == "Identifier" && node.object.name == "console" && !node.computed)
110
- fail("Found console." + node.property.name, node.loc);
111
- },
112
- DebuggerStatement: function(node) {
113
- fail("Found debugger statement", node.loc);
114
- }
115
- }, scopePasser);
116
-
117
- function checkReusedIndex(node) {
118
- if (!node.init || node.init.type != "VariableDeclaration") return;
119
- var name = node.init.declarations[0].id.name;
120
- walk.recursive(node.body, null, {
121
- Function: function() {},
122
- VariableDeclaration: function(node, st, c) {
123
- for (var i = 0; i < node.declarations.length; i++)
124
- if (node.declarations[i].id.name == name)
125
- fail("redefined loop variable", node.declarations[i].id.loc);
126
- walk.base.VariableDeclaration(node, st, c);
127
- }
128
- });
129
- }
130
-
131
- var allowedGlobals = Object.create(topAllowedGlobals), m;
132
- if (m = file.match(/\/\/ declare global:\s+(.*)/))
133
- m[1].split(/,\s*/g).forEach(function(n) { allowedGlobals[n] = true; });
134
- for (var glob in globalsSeen)
135
- if (!(glob in allowedGlobals))
136
- fail("Access to global variable " + glob + ". Add a '// declare global: " + glob +
137
- "' comment or add this variable in test/lint/lint.js.", globalsSeen[glob]);
138
-
139
- for (var i = 0; i < scopes.length; ++i) {
140
- var scope = scopes[i];
141
- for (var name in scope.vars) {
142
- var info = scope.vars[name];
143
- if (!info.used && info.type != "catch clause" && info.type != "function name" && name.charAt(0) != "_")
144
- fail("Unused " + info.type + " " + name, info.node.loc);
145
- }
146
- }
147
- }
148
-
149
- var failed = false;
150
- function fail(msg, pos) {
151
- if (pos.start) msg += " (" + pos.start.line + ":" + pos.start.column + ")";
152
- console.log(pos.source + ": " + msg);
153
- failed = true;
154
- }
155
-
156
- function checkDir(dir) {
157
- fs.readdirSync(dir).forEach(function(file) {
158
- var fname = dir + "/" + file;
159
- if (/\.js$/.test(file)) checkFile(fname);
160
- else if (fs.lstatSync(fname).isDirectory()) checkDir(fname);
161
- });
162
- }
163
-
164
- exports.checkDir = checkDir;
165
- exports.checkFile = checkFile;
166
- exports.success = function() { return !failed; };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint/walk.js DELETED
@@ -1,313 +0,0 @@
1
- // AST walker module for Mozilla Parser API compatible trees
2
-
3
- (function(mod) {
4
- if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS
5
- if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD
6
- mod((this.acorn || (this.acorn = {})).walk = {}); // Plain browser env
7
- })(function(exports) {
8
- "use strict";
9
-
10
- // A simple walk is one where you simply specify callbacks to be
11
- // called on specific nodes. The last two arguments are optional. A
12
- // simple use would be
13
- //
14
- // walk.simple(myTree, {
15
- // Expression: function(node) { ... }
16
- // });
17
- //
18
- // to do something with all expressions. All Parser API node types
19
- // can be used to identify node types, as well as Expression,
20
- // Statement, and ScopeBody, which denote categories of nodes.
21
- //
22
- // The base argument can be used to pass a custom (recursive)
23
- // walker, and state can be used to give this walked an initial
24
- // state.
25
- exports.simple = function(node, visitors, base, state) {
26
- if (!base) base = exports.base;
27
- function c(node, st, override) {
28
- var type = override || node.type, found = visitors[type];
29
- base[type](node, st, c);
30
- if (found) found(node, st);
31
- }
32
- c(node, state);
33
- };
34
-
35
- // A recursive walk is one where your functions override the default
36
- // walkers. They can modify and replace the state parameter that's
37
- // threaded through the walk, and can opt how and whether to walk
38
- // their child nodes (by calling their third argument on these
39
- // nodes).
40
- exports.recursive = function(node, state, funcs, base) {
41
- var visitor = funcs ? exports.make(funcs, base) : base;
42
- function c(node, st, override) {
43
- visitor[override || node.type](node, st, c);
44
- }
45
- c(node, state);
46
- };
47
-
48
- function makeTest(test) {
49
- if (typeof test == "string")
50
- return function(type) { return type == test; };
51
- else if (!test)
52
- return function() { return true; };
53
- else
54
- return test;
55
- }
56
-
57
- function Found(node, state) { this.node = node; this.state = state; }
58
-
59
- // Find a node with a given start, end, and type (all are optional,
60
- // null can be used as wildcard). Returns a {node, state} object, or
61
- // undefined when it doesn't find a matching node.
62
- exports.findNodeAt = function(node, start, end, test, base, state) {
63
- test = makeTest(test);
64
- try {
65
- if (!base) base = exports.base;
66
- var c = function(node, st, override) {
67
- var type = override || node.type;
68
- if ((start == null || node.start <= start) &&
69
- (end == null || node.end >= end))
70
- base[type](node, st, c);
71
- if (test(type, node) &&
72
- (start == null || node.start == start) &&
73
- (end == null || node.end == end))
74
- throw new Found(node, st);
75
- };
76
- c(node, state);
77
- } catch (e) {
78
- if (e instanceof Found) return e;
79
- throw e;
80
- }
81
- };
82
-
83
- // Find the innermost node of a given type that contains the given
84
- // position. Interface similar to findNodeAt.
85
- exports.findNodeAround = function(node, pos, test, base, state) {
86
- test = makeTest(test);
87
- try {
88
- if (!base) base = exports.base;
89
- var c = function(node, st, override) {
90
- var type = override || node.type;
91
- if (node.start > pos || node.end < pos) return;
92
- base[type](node, st, c);
93
- if (test(type, node)) throw new Found(node, st);
94
- };
95
- c(node, state);
96
- } catch (e) {
97
- if (e instanceof Found) return e;
98
- throw e;
99
- }
100
- };
101
-
102
- // Find the outermost matching node after a given position.
103
- exports.findNodeAfter = function(node, pos, test, base, state) {
104
- test = makeTest(test);
105
- try {
106
- if (!base) base = exports.base;
107
- var c = function(node, st, override) {
108
- if (node.end < pos) return;
109
- var type = override || node.type;
110
- if (node.start >= pos && test(type, node)) throw new Found(node, st);
111
- base[type](node, st, c);
112
- };
113
- c(node, state);
114
- } catch (e) {
115
- if (e instanceof Found) return e;
116
- throw e;
117
- }
118
- };
119
-
120
- // Find the outermost matching node before a given position.
121
- exports.findNodeBefore = function(node, pos, test, base, state) {
122
- test = makeTest(test);
123
- if (!base) base = exports.base;
124
- var max;
125
- var c = function(node, st, override) {
126
- if (node.start > pos) return;
127
- var type = override || node.type;
128
- if (node.end <= pos && (!max || max.node.end < node.end) && test(type, node))
129
- max = new Found(node, st);
130
- base[type](node, st, c);
131
- };
132
- c(node, state);
133
- return max;
134
- };
135
-
136
- // Used to create a custom walker. Will fill in all missing node
137
- // type properties with the defaults.
138
- exports.make = function(funcs, base) {
139
- if (!base) base = exports.base;
140
- var visitor = {};
141
- for (var type in base) visitor[type] = base[type];
142
- for (var type in funcs) visitor[type] = funcs[type];
143
- return visitor;
144
- };
145
-
146
- function skipThrough(node, st, c) { c(node, st); }
147
- function ignore(_node, _st, _c) {}
148
-
149
- // Node walkers.
150
-
151
- var base = exports.base = {};
152
- base.Program = base.BlockStatement = function(node, st, c) {
153
- for (var i = 0; i < node.body.length; ++i)
154
- c(node.body[i], st, "Statement");
155
- };
156
- base.Statement = skipThrough;
157
- base.EmptyStatement = ignore;
158
- base.ExpressionStatement = function(node, st, c) {
159
- c(node.expression, st, "Expression");
160
- };
161
- base.IfStatement = function(node, st, c) {
162
- c(node.test, st, "Expression");
163
- c(node.consequent, st, "Statement");
164
- if (node.alternate) c(node.alternate, st, "Statement");
165
- };
166
- base.LabeledStatement = function(node, st, c) {
167
- c(node.body, st, "Statement");
168
- };
169
- base.BreakStatement = base.ContinueStatement = ignore;
170
- base.WithStatement = function(node, st, c) {
171
- c(node.object, st, "Expression");
172
- c(node.body, st, "Statement");
173
- };
174
- base.SwitchStatement = function(node, st, c) {
175
- c(node.discriminant, st, "Expression");
176
- for (var i = 0; i < node.cases.length; ++i) {
177
- var cs = node.cases[i];
178
- if (cs.test) c(cs.test, st, "Expression");
179
- for (var j = 0; j < cs.consequent.length; ++j)
180
- c(cs.consequent[j], st, "Statement");
181
- }
182
- };
183
- base.ReturnStatement = function(node, st, c) {
184
- if (node.argument) c(node.argument, st, "Expression");
185
- };
186
- base.ThrowStatement = function(node, st, c) {
187
- c(node.argument, st, "Expression");
188
- };
189
- base.TryStatement = function(node, st, c) {
190
- c(node.block, st, "Statement");
191
- if (node.handler) c(node.handler.body, st, "ScopeBody");
192
- if (node.finalizer) c(node.finalizer, st, "Statement");
193
- };
194
- base.WhileStatement = function(node, st, c) {
195
- c(node.test, st, "Expression");
196
- c(node.body, st, "Statement");
197
- };
198
- base.DoWhileStatement = base.WhileStatement;
199
- base.ForStatement = function(node, st, c) {
200
- if (node.init) c(node.init, st, "ForInit");
201
- if (node.test) c(node.test, st, "Expression");
202
- if (node.update) c(node.update, st, "Expression");
203
- c(node.body, st, "Statement");
204
- };
205
- base.ForInStatement = function(node, st, c) {
206
- c(node.left, st, "ForInit");
207
- c(node.right, st, "Expression");
208
- c(node.body, st, "Statement");
209
- };
210
- base.ForInit = function(node, st, c) {
211
- if (node.type == "VariableDeclaration") c(node, st);
212
- else c(node, st, "Expression");
213
- };
214
- base.DebuggerStatement = ignore;
215
-
216
- base.FunctionDeclaration = function(node, st, c) {
217
- c(node, st, "Function");
218
- };
219
- base.VariableDeclaration = function(node, st, c) {
220
- for (var i = 0; i < node.declarations.length; ++i) {
221
- var decl = node.declarations[i];
222
- if (decl.init) c(decl.init, st, "Expression");
223
- }
224
- };
225
-
226
- base.Function = function(node, st, c) {
227
- c(node.body, st, "ScopeBody");
228
- };
229
- base.ScopeBody = function(node, st, c) {
230
- c(node, st, "Statement");
231
- };
232
-
233
- base.Expression = skipThrough;
234
- base.ThisExpression = ignore;
235
- base.ArrayExpression = function(node, st, c) {
236
- for (var i = 0; i < node.elements.length; ++i) {
237
- var elt = node.elements[i];
238
- if (elt) c(elt, st, "Expression");
239
- }
240
- };
241
- base.ObjectExpression = function(node, st, c) {
242
- for (var i = 0; i < node.properties.length; ++i)
243
- c(node.properties[i].value, st, "Expression");
244
- };
245
- base.FunctionExpression = base.FunctionDeclaration;
246
- base.SequenceExpression = function(node, st, c) {
247
- for (var i = 0; i < node.expressions.length; ++i)
248
- c(node.expressions[i], st, "Expression");
249
- };
250
- base.UnaryExpression = base.UpdateExpression = function(node, st, c) {
251
- c(node.argument, st, "Expression");
252
- };
253
- base.BinaryExpression = base.AssignmentExpression = base.LogicalExpression = function(node, st, c) {
254
- c(node.left, st, "Expression");
255
- c(node.right, st, "Expression");
256
- };
257
- base.ConditionalExpression = function(node, st, c) {
258
- c(node.test, st, "Expression");
259
- c(node.consequent, st, "Expression");
260
- c(node.alternate, st, "Expression");
261
- };
262
- base.NewExpression = base.CallExpression = function(node, st, c) {
263
- c(node.callee, st, "Expression");
264
- if (node.arguments) for (var i = 0; i < node.arguments.length; ++i)
265
- c(node.arguments[i], st, "Expression");
266
- };
267
- base.MemberExpression = function(node, st, c) {
268
- c(node.object, st, "Expression");
269
- if (node.computed) c(node.property, st, "Expression");
270
- };
271
- base.Identifier = base.Literal = ignore;
272
-
273
- // A custom walker that keeps track of the scope chain and the
274
- // variables defined in it.
275
- function makeScope(prev, isCatch) {
276
- return {vars: Object.create(null), prev: prev, isCatch: isCatch};
277
- }
278
- function normalScope(scope) {
279
- while (scope.isCatch) scope = scope.prev;
280
- return scope;
281
- }
282
- exports.scopeVisitor = exports.make({
283
- Function: function(node, scope, c) {
284
- var inner = makeScope(scope);
285
- for (var i = 0; i < node.params.length; ++i)
286
- inner.vars[node.params[i].name] = {type: "argument", node: node.params[i]};
287
- if (node.id) {
288
- var decl = node.type == "FunctionDeclaration";
289
- (decl ? normalScope(scope) : inner).vars[node.id.name] =
290
- {type: decl ? "function" : "function name", node: node.id};
291
- }
292
- c(node.body, inner, "ScopeBody");
293
- },
294
- TryStatement: function(node, scope, c) {
295
- c(node.block, scope, "Statement");
296
- if (node.handler) {
297
- var inner = makeScope(scope, true);
298
- inner.vars[node.handler.param.name] = {type: "catch clause", node: node.handler.param};
299
- c(node.handler.body, inner, "ScopeBody");
300
- }
301
- if (node.finalizer) c(node.finalizer, scope, "Statement");
302
- },
303
- VariableDeclaration: function(node, scope, c) {
304
- var target = normalScope(scope);
305
- for (var i = 0; i < node.declarations.length; ++i) {
306
- var decl = node.declarations[i];
307
- target.vars[decl.id.name] = {type: "var", node: decl.id};
308
- if (decl.init) c(decl.init, scope, "Expression");
309
- }
310
- }
311
- });
312
-
313
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/mode_test.css DELETED
@@ -1,23 +0,0 @@
1
- .mt-output .mt-token {
2
- border: 1px solid #ddd;
3
- white-space: pre;
4
- font-family: "Consolas", monospace;
5
- text-align: center;
6
- }
7
-
8
- .mt-output .mt-style {
9
- font-size: x-small;
10
- }
11
-
12
- .mt-output .mt-state {
13
- font-size: x-small;
14
- vertical-align: top;
15
- }
16
-
17
- .mt-output .mt-state-row {
18
- display: none;
19
- }
20
-
21
- .mt-state-unhide .mt-output .mt-state-row {
22
- display: table-row;
23
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/mode_test.js DELETED
@@ -1,192 +0,0 @@
1
- /**
2
- * Helper to test CodeMirror highlighting modes. It pretty prints output of the
3
- * highlighter and can check against expected styles.
4
- *
5
- * Mode tests are registered by calling test.mode(testName, mode,
6
- * tokens), where mode is a mode object as returned by
7
- * CodeMirror.getMode, and tokens is an array of lines that make up
8
- * the test.
9
- *
10
- * These lines are strings, in which styled stretches of code are
11
- * enclosed in brackets `[]`, and prefixed by their style. For
12
- * example, `[keyword if]`. Brackets in the code itself must be
13
- * duplicated to prevent them from being interpreted as token
14
- * boundaries. For example `a[[i]]` for `a[i]`. If a token has
15
- * multiple styles, the styles must be separated by ampersands, for
16
- * example `[tag&error </hmtl>]`.
17
- *
18
- * See the test.js files in the css, markdown, gfm, and stex mode
19
- * directories for examples.
20
- */
21
- (function() {
22
- function findSingle(str, pos, ch) {
23
- for (;;) {
24
- var found = str.indexOf(ch, pos);
25
- if (found == -1) return null;
26
- if (str.charAt(found + 1) != ch) return found;
27
- pos = found + 2;
28
- }
29
- }
30
-
31
- var styleName = /[\w&-_]+/g;
32
- function parseTokens(strs) {
33
- var tokens = [], plain = "";
34
- for (var i = 0; i < strs.length; ++i) {
35
- if (i) plain += "\n";
36
- var str = strs[i], pos = 0;
37
- while (pos < str.length) {
38
- var style = null, text;
39
- if (str.charAt(pos) == "[" && str.charAt(pos+1) != "[") {
40
- styleName.lastIndex = pos + 1;
41
- var m = styleName.exec(str);
42
- style = m[0].replace(/&/g, " ");
43
- var textStart = pos + style.length + 2;
44
- var end = findSingle(str, textStart, "]");
45
- if (end == null) throw new Error("Unterminated token at " + pos + " in '" + str + "'" + style);
46
- text = str.slice(textStart, end);
47
- pos = end + 1;
48
- } else {
49
- var end = findSingle(str, pos, "[");
50
- if (end == null) end = str.length;
51
- text = str.slice(pos, end);
52
- pos = end;
53
- }
54
- text = text.replace(/\[\[|\]\]/g, function(s) {return s.charAt(0);});
55
- tokens.push({style: style, text: text});
56
- plain += text;
57
- }
58
- }
59
- return {tokens: tokens, plain: plain};
60
- }
61
-
62
- test.mode = function(name, mode, tokens, modeName) {
63
- var data = parseTokens(tokens);
64
- return test((modeName || mode.name) + "_" + name, function() {
65
- return compare(data.plain, data.tokens, mode);
66
- });
67
- };
68
-
69
- function esc(str) {
70
- return str.replace('&', '&amp;').replace('<', '&lt;').replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
71
- ;
72
- }
73
-
74
- function compare(text, expected, mode) {
75
-
76
- var expectedOutput = [];
77
- for (var i = 0; i < expected.length; ++i) {
78
- var sty = expected[i].style;
79
- if (sty && sty.indexOf(" ")) sty = sty.split(' ').sort().join(' ');
80
- expectedOutput.push({style: sty, text: expected[i].text});
81
- }
82
-
83
- var observedOutput = highlight(text, mode);
84
-
85
- var s = "";
86
- var diff = highlightOutputsDifferent(expectedOutput, observedOutput);
87
- if (diff != null) {
88
- s += '<div class="mt-test mt-fail">';
89
- s += '<pre>' + esc(text) + '</pre>';
90
- s += '<div class="cm-s-default">';
91
- s += 'expected:';
92
- s += prettyPrintOutputTable(expectedOutput, diff);
93
- s += 'observed: [<a onclick="this.parentElement.className+=\' mt-state-unhide\'">display states</a>]';
94
- s += prettyPrintOutputTable(observedOutput, diff);
95
- s += '</div>';
96
- s += '</div>';
97
- }
98
- if (observedOutput.indentFailures) {
99
- for (var i = 0; i < observedOutput.indentFailures.length; i++)
100
- s += "<div class='mt-test mt-fail'>" + esc(observedOutput.indentFailures[i]) + "</div>";
101
- }
102
- if (s) throw new Failure(s);
103
- }
104
-
105
- function stringify(obj) {
106
- function replacer(key, obj) {
107
- if (typeof obj == "function") {
108
- var m = obj.toString().match(/function\s*[^\s(]*/);
109
- return m ? m[0] : "function";
110
- }
111
- return obj;
112
- }
113
- if (window.JSON && JSON.stringify)
114
- return JSON.stringify(obj, replacer, 2);
115
- return "[unsupported]"; // Fail safely if no native JSON.
116
- }
117
-
118
- function highlight(string, mode) {
119
- var state = mode.startState();
120
-
121
- var lines = string.replace(/\r\n/g,'\n').split('\n');
122
- var st = [], pos = 0;
123
- for (var i = 0; i < lines.length; ++i) {
124
- var line = lines[i], newLine = true;
125
- if (mode.indent) {
126
- var ws = line.match(/^\s*/)[0];
127
- var indent = mode.indent(state, line.slice(ws.length));
128
- if (indent != CodeMirror.Pass && indent != ws.length)
129
- (st.indentFailures || (st.indentFailures = [])).push(
130
- "Indentation of line " + (i + 1) + " is " + indent + " (expected " + ws.length + ")");
131
- }
132
- var stream = new CodeMirror.StringStream(line);
133
- if (line == "" && mode.blankLine) mode.blankLine(state);
134
- /* Start copied code from CodeMirror.highlight */
135
- while (!stream.eol()) {
136
- for (var j = 0; j < 10 && stream.start >= stream.pos; j++)
137
- var compare = mode.token(stream, state);
138
- if (j == 10)
139
- throw new Failure("Failed to advance the stream." + stream.string + " " + stream.pos);
140
- var substr = stream.current();
141
- if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' ');
142
- stream.start = stream.pos;
143
- if (pos && st[pos-1].style == compare && !newLine) {
144
- st[pos-1].text += substr;
145
- } else if (substr) {
146
- st[pos++] = {style: compare, text: substr, state: stringify(state)};
147
- }
148
- // Give up when line is ridiculously long
149
- if (stream.pos > 5000) {
150
- st[pos++] = {style: null, text: this.text.slice(stream.pos)};
151
- break;
152
- }
153
- newLine = false;
154
- }
155
- }
156
-
157
- return st;
158
- }
159
-
160
- function highlightOutputsDifferent(o1, o2) {
161
- var minLen = Math.min(o1.length, o2.length);
162
- for (var i = 0; i < minLen; ++i)
163
- if (o1[i].style != o2[i].style || o1[i].text != o2[i].text) return i;
164
- if (o1.length > minLen || o2.length > minLen) return minLen;
165
- }
166
-
167
- function prettyPrintOutputTable(output, diffAt) {
168
- var s = '<table class="mt-output">';
169
- s += '<tr>';
170
- for (var i = 0; i < output.length; ++i) {
171
- var style = output[i].style, val = output[i].text;
172
- s +=
173
- '<td class="mt-token"' + (i == diffAt * 2 ? " style='background: pink'" : "") + '>' +
174
- '<span class="cm-' + esc(String(style)) + '">' +
175
- esc(val.replace(/ /g,'\xb7')) + // · MIDDLE DOT
176
- '</span>' +
177
- '</td>';
178
- }
179
- s += '</tr><tr>';
180
- for (var i = 0; i < output.length; ++i) {
181
- s += '<td class="mt-style"><span>' + (output[i].style || null) + '</span></td>';
182
- }
183
- if(output[0].state) {
184
- s += '</tr><tr class="mt-state-row" title="State AFTER each token">';
185
- for (var i = 0; i < output.length; ++i) {
186
- s += '<td class="mt-state"><pre>' + esc(output[i].state) + '</pre></td>';
187
- }
188
- }
189
- s += '</tr></table>';
190
- return s;
191
- }
192
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/multi_test.js DELETED
@@ -1,285 +0,0 @@
1
- (function() {
2
- namespace = "multi_";
3
-
4
- function hasSelections(cm) {
5
- var sels = cm.listSelections();
6
- var given = (arguments.length - 1) / 4;
7
- if (sels.length != given)
8
- throw new Failure("expected " + given + " selections, found " + sels.length);
9
- for (var i = 0, p = 1; i < given; i++, p += 4) {
10
- var anchor = Pos(arguments[p], arguments[p + 1]);
11
- var head = Pos(arguments[p + 2], arguments[p + 3]);
12
- eqPos(sels[i].anchor, anchor, "anchor of selection " + i);
13
- eqPos(sels[i].head, head, "head of selection " + i);
14
- }
15
- }
16
- function hasCursors(cm) {
17
- var sels = cm.listSelections();
18
- var given = (arguments.length - 1) / 2;
19
- if (sels.length != given)
20
- throw new Failure("expected " + given + " selections, found " + sels.length);
21
- for (var i = 0, p = 1; i < given; i++, p += 2) {
22
- eqPos(sels[i].anchor, sels[i].head, "something selected for " + i);
23
- var head = Pos(arguments[p], arguments[p + 1]);
24
- eqPos(sels[i].head, head, "selection " + i);
25
- }
26
- }
27
-
28
- testCM("getSelection", function(cm) {
29
- select(cm, {anchor: Pos(0, 0), head: Pos(1, 2)}, {anchor: Pos(2, 2), head: Pos(2, 0)});
30
- eq(cm.getSelection(), "1234\n56\n90");
31
- eq(cm.getSelection(false).join("|"), "1234|56|90");
32
- eq(cm.getSelections().join("|"), "1234\n56|90");
33
- }, {value: "1234\n5678\n90"});
34
-
35
- testCM("setSelection", function(cm) {
36
- select(cm, Pos(3, 0), Pos(0, 0), {anchor: Pos(2, 5), head: Pos(1, 0)});
37
- hasSelections(cm, 0, 0, 0, 0,
38
- 2, 5, 1, 0,
39
- 3, 0, 3, 0);
40
- cm.setSelection(Pos(1, 2), Pos(1, 1));
41
- hasSelections(cm, 1, 2, 1, 1);
42
- select(cm, {anchor: Pos(1, 1), head: Pos(2, 4)},
43
- {anchor: Pos(0, 0), head: Pos(1, 3)},
44
- Pos(3, 0), Pos(2, 2));
45
- hasSelections(cm, 0, 0, 2, 4,
46
- 3, 0, 3, 0);
47
- cm.setSelections([{anchor: Pos(0, 1), head: Pos(0, 2)},
48
- {anchor: Pos(1, 1), head: Pos(1, 2)},
49
- {anchor: Pos(2, 1), head: Pos(2, 2)}], 1);
50
- eqPos(cm.getCursor("head"), Pos(1, 2));
51
- eqPos(cm.getCursor("anchor"), Pos(1, 1));
52
- eqPos(cm.getCursor("from"), Pos(1, 1));
53
- eqPos(cm.getCursor("to"), Pos(1, 2));
54
- cm.setCursor(Pos(1, 1));
55
- hasCursors(cm, 1, 1);
56
- }, {value: "abcde\nabcde\nabcde\n"});
57
-
58
- testCM("somethingSelected", function(cm) {
59
- select(cm, Pos(0, 1), {anchor: Pos(0, 3), head: Pos(0, 5)});
60
- eq(cm.somethingSelected(), true);
61
- select(cm, Pos(0, 1), Pos(0, 3), Pos(0, 5));
62
- eq(cm.somethingSelected(), false);
63
- }, {value: "123456789"});
64
-
65
- testCM("extendSelection", function(cm) {
66
- select(cm, Pos(0, 1), Pos(1, 1), Pos(2, 1));
67
- cm.setExtending(true);
68
- cm.extendSelections([Pos(0, 2), Pos(1, 0), Pos(2, 3)]);
69
- hasSelections(cm, 0, 1, 0, 2,
70
- 1, 1, 1, 0,
71
- 2, 1, 2, 3);
72
- cm.extendSelection(Pos(2, 4), Pos(2, 0));
73
- hasSelections(cm, 2, 4, 2, 0);
74
- }, {value: "1234\n1234\n1234"});
75
-
76
- testCM("addSelection", function(cm) {
77
- select(cm, Pos(0, 1), Pos(1, 1));
78
- cm.addSelection(Pos(0, 0), Pos(0, 4));
79
- hasSelections(cm, 0, 0, 0, 4,
80
- 1, 1, 1, 1);
81
- cm.addSelection(Pos(2, 2));
82
- hasSelections(cm, 0, 0, 0, 4,
83
- 1, 1, 1, 1,
84
- 2, 2, 2, 2);
85
- }, {value: "1234\n1234\n1234"});
86
-
87
- testCM("replaceSelection", function(cm) {
88
- var selections = [{anchor: Pos(0, 0), head: Pos(0, 1)},
89
- {anchor: Pos(0, 2), head: Pos(0, 3)},
90
- {anchor: Pos(0, 4), head: Pos(0, 5)},
91
- {anchor: Pos(2, 1), head: Pos(2, 4)},
92
- {anchor: Pos(2, 5), head: Pos(2, 6)}];
93
- var val = "123456\n123456\n123456";
94
- cm.setValue(val);
95
- cm.setSelections(selections);
96
- cm.replaceSelection("ab", "around");
97
- eq(cm.getValue(), "ab2ab4ab6\n123456\n1ab5ab");
98
- hasSelections(cm, 0, 0, 0, 2,
99
- 0, 3, 0, 5,
100
- 0, 6, 0, 8,
101
- 2, 1, 2, 3,
102
- 2, 4, 2, 6);
103
- cm.setValue(val);
104
- cm.setSelections(selections);
105
- cm.replaceSelection("", "around");
106
- eq(cm.getValue(), "246\n123456\n15");
107
- hasSelections(cm, 0, 0, 0, 0,
108
- 0, 1, 0, 1,
109
- 0, 2, 0, 2,
110
- 2, 1, 2, 1,
111
- 2, 2, 2, 2);
112
- cm.setValue(val);
113
- cm.setSelections(selections);
114
- cm.replaceSelection("X\nY\nZ", "around");
115
- hasSelections(cm, 0, 0, 2, 1,
116
- 2, 2, 4, 1,
117
- 4, 2, 6, 1,
118
- 8, 1, 10, 1,
119
- 10, 2, 12, 1);
120
- cm.replaceSelection("a", "around");
121
- hasSelections(cm, 0, 0, 0, 1,
122
- 0, 2, 0, 3,
123
- 0, 4, 0, 5,
124
- 2, 1, 2, 2,
125
- 2, 3, 2, 4);
126
- cm.replaceSelection("xy", "start");
127
- hasSelections(cm, 0, 0, 0, 0,
128
- 0, 3, 0, 3,
129
- 0, 6, 0, 6,
130
- 2, 1, 2, 1,
131
- 2, 4, 2, 4);
132
- cm.replaceSelection("z\nf");
133
- hasSelections(cm, 1, 1, 1, 1,
134
- 2, 1, 2, 1,
135
- 3, 1, 3, 1,
136
- 6, 1, 6, 1,
137
- 7, 1, 7, 1);
138
- eq(cm.getValue(), "z\nfxy2z\nfxy4z\nfxy6\n123456\n1z\nfxy5z\nfxy");
139
- });
140
-
141
- function select(cm) {
142
- var sels = [];
143
- for (var i = 1; i < arguments.length; i++) {
144
- var arg = arguments[i];
145
- if (arg.head) sels.push(arg);
146
- else sels.push({head: arg, anchor: arg});
147
- }
148
- cm.setSelections(sels, sels.length - 1);
149
- }
150
-
151
- testCM("indentSelection", function(cm) {
152
- select(cm, Pos(0, 1), Pos(1, 1));
153
- cm.indentSelection(4);
154
- eq(cm.getValue(), " foo\n bar\nbaz");
155
-
156
- select(cm, Pos(0, 2), Pos(0, 3), Pos(0, 4));
157
- cm.indentSelection(-2);
158
- eq(cm.getValue(), " foo\n bar\nbaz");
159
-
160
- select(cm, {anchor: Pos(0, 0), head: Pos(1, 2)},
161
- {anchor: Pos(1, 3), head: Pos(2, 0)});
162
- cm.indentSelection(-2);
163
- eq(cm.getValue(), "foo\n bar\nbaz");
164
- }, {value: "foo\nbar\nbaz"});
165
-
166
- testCM("killLine", function(cm) {
167
- select(cm, Pos(0, 1), Pos(0, 2), Pos(1, 1));
168
- cm.execCommand("killLine");
169
- eq(cm.getValue(), "f\nb\nbaz");
170
- cm.execCommand("killLine");
171
- eq(cm.getValue(), "fbbaz");
172
- cm.setValue("foo\nbar\nbaz");
173
- select(cm, Pos(0, 1), {anchor: Pos(0, 2), head: Pos(2, 1)});
174
- cm.execCommand("killLine");
175
- eq(cm.getValue(), "faz");
176
- }, {value: "foo\nbar\nbaz"});
177
-
178
- testCM("deleteLine", function(cm) {
179
- select(cm, Pos(0, 0),
180
- {head: Pos(0, 1), anchor: Pos(2, 0)},
181
- Pos(4, 0));
182
- cm.execCommand("deleteLine");
183
- eq(cm.getValue(), "4\n6\n7");
184
- select(cm, Pos(2, 1));
185
- cm.execCommand("deleteLine");
186
- eq(cm.getValue(), "4\n6\n");
187
- }, {value: "1\n2\n3\n4\n5\n6\n7"});
188
-
189
- testCM("deleteH", function(cm) {
190
- select(cm, Pos(0, 4), {anchor: Pos(1, 4), head: Pos(1, 5)});
191
- cm.execCommand("delWordAfter");
192
- eq(cm.getValue(), "foo bar baz\nabc ef ghi\n");
193
- cm.execCommand("delWordAfter");
194
- eq(cm.getValue(), "foo baz\nabc ghi\n");
195
- cm.execCommand("delCharBefore");
196
- cm.execCommand("delCharBefore");
197
- eq(cm.getValue(), "fo baz\nab ghi\n");
198
- select(cm, Pos(0, 3), Pos(0, 4), Pos(0, 5));
199
- cm.execCommand("delWordAfter");
200
- eq(cm.getValue(), "fo \nab ghi\n");
201
- }, {value: "foo bar baz\nabc def ghi\n"});
202
-
203
- testCM("goLineStart", function(cm) {
204
- select(cm, Pos(0, 2), Pos(0, 3), Pos(1, 1));
205
- cm.execCommand("goLineStart");
206
- hasCursors(cm, 0, 0, 1, 0);
207
- select(cm, Pos(1, 1), Pos(0, 1));
208
- cm.setExtending(true);
209
- cm.execCommand("goLineStart");
210
- hasSelections(cm, 0, 1, 0, 0,
211
- 1, 1, 1, 0);
212
- }, {value: "foo\nbar\nbaz"});
213
-
214
- testCM("moveV", function(cm) {
215
- select(cm, Pos(0, 2), Pos(1, 2));
216
- cm.execCommand("goLineDown");
217
- hasCursors(cm, 1, 2, 2, 2);
218
- cm.execCommand("goLineUp");
219
- hasCursors(cm, 0, 2, 1, 2);
220
- cm.execCommand("goLineUp");
221
- hasCursors(cm, 0, 0, 0, 2);
222
- cm.execCommand("goLineUp");
223
- hasCursors(cm, 0, 0);
224
- select(cm, Pos(0, 2), Pos(1, 2));
225
- cm.setExtending(true);
226
- cm.execCommand("goLineDown");
227
- hasSelections(cm, 0, 2, 2, 2);
228
- }, {value: "12345\n12345\n12345"});
229
-
230
- testCM("moveH", function(cm) {
231
- select(cm, Pos(0, 1), Pos(0, 3), Pos(0, 5), Pos(2, 3));
232
- cm.execCommand("goCharRight");
233
- hasCursors(cm, 0, 2, 0, 4, 1, 0, 2, 4);
234
- cm.execCommand("goCharLeft");
235
- hasCursors(cm, 0, 1, 0, 3, 0, 5, 2, 3);
236
- for (var i = 0; i < 15; i++)
237
- cm.execCommand("goCharRight");
238
- hasCursors(cm, 2, 4, 2, 5);
239
- }, {value: "12345\n12345\n12345"});
240
-
241
- testCM("newlineAndIndent", function(cm) {
242
- select(cm, Pos(0, 5), Pos(1, 5));
243
- cm.execCommand("newlineAndIndent");
244
- hasCursors(cm, 1, 2, 3, 2);
245
- eq(cm.getValue(), "x = [\n 1];\ny = [\n 2];");
246
- cm.undo();
247
- eq(cm.getValue(), "x = [1];\ny = [2];");
248
- hasCursors(cm, 0, 5, 1, 5);
249
- select(cm, Pos(0, 5), Pos(0, 6));
250
- cm.execCommand("newlineAndIndent");
251
- hasCursors(cm, 1, 2, 2, 0);
252
- eq(cm.getValue(), "x = [\n 1\n];\ny = [2];");
253
- }, {value: "x = [1];\ny = [2];", mode: "javascript"});
254
-
255
- testCM("goDocStartEnd", function(cm) {
256
- select(cm, Pos(0, 1), Pos(1, 1));
257
- cm.execCommand("goDocStart");
258
- hasCursors(cm, 0, 0);
259
- select(cm, Pos(0, 1), Pos(1, 1));
260
- cm.execCommand("goDocEnd");
261
- hasCursors(cm, 1, 3);
262
- select(cm, Pos(0, 1), Pos(1, 1));
263
- cm.setExtending(true);
264
- cm.execCommand("goDocEnd");
265
- hasSelections(cm, 1, 1, 1, 3);
266
- }, {value: "abc\ndef"});
267
-
268
- testCM("selectionHistory", function(cm) {
269
- for (var i = 0; i < 3; ++i)
270
- cm.addSelection(Pos(0, i * 2), Pos(0, i * 2 + 1));
271
- cm.execCommand("undoSelection");
272
- eq(cm.getSelection(), "1\n2");
273
- cm.execCommand("undoSelection");
274
- eq(cm.getSelection(), "1");
275
- cm.execCommand("undoSelection");
276
- eq(cm.getSelection(), "");
277
- eqPos(cm.getCursor(), Pos(0, 0));
278
- cm.execCommand("redoSelection");
279
- eq(cm.getSelection(), "1");
280
- cm.execCommand("redoSelection");
281
- eq(cm.getSelection(), "1\n2");
282
- cm.execCommand("redoSelection");
283
- eq(cm.getSelection(), "1\n2\n3");
284
- }, {value: "1 2 3"});
285
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/phantom_driver.js DELETED
@@ -1,31 +0,0 @@
1
- var page = require('webpage').create();
2
-
3
- page.open("http://localhost:3000/test/index.html", function (status) {
4
- if (status != "success") {
5
- console.log("page couldn't be loaded successfully");
6
- phantom.exit(1);
7
- }
8
- waitFor(function () {
9
- return page.evaluate(function () {
10
- var output = document.getElementById('status');
11
- if (!output) { return false; }
12
- return (/^(\d+ failures?|all passed)/i).test(output.innerText);
13
- });
14
- }, function () {
15
- var failed = page.evaluate(function () { return window.failed; });
16
- var output = page.evaluate(function () {
17
- return document.getElementById('output').innerText + "\n" +
18
- document.getElementById('status').innerText;
19
- });
20
- console.log(output);
21
- phantom.exit(failed > 0 ? 1 : 0);
22
- });
23
- });
24
-
25
- function waitFor (test, cb) {
26
- if (test()) {
27
- cb();
28
- } else {
29
- setTimeout(function () { waitFor(test, cb); }, 250);
30
- }
31
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/run.js DELETED
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- var ok = require("./lint").ok;
4
-
5
- var files = new (require('node-static').Server)();
6
-
7
- var server = require('http').createServer(function (req, res) {
8
- req.addListener('end', function () {
9
- files.serve(req, res, function (err/*, result */) {
10
- if (err) {
11
- console.error(err);
12
- process.exit(1);
13
- }
14
- });
15
- }).resume();
16
- }).addListener('error', function (err) {
17
- throw err;
18
- }).listen(3000, function () {
19
- var childProcess = require('child_process');
20
- var phantomjs = require("phantomjs");
21
- var childArgs = [
22
- require("path").join(__dirname, 'phantom_driver.js')
23
- ];
24
- childProcess.execFile(phantomjs.path, childArgs, function (err, stdout, stderr) {
25
- server.close();
26
- console.log(stdout);
27
- if (err) console.error(err);
28
- if (stderr) console.error(stderr);
29
- process.exit(err || stderr || !ok ? 1 : 0);
30
- });
31
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/scroll_test.js DELETED
@@ -1,115 +0,0 @@
1
- (function() {
2
- "use strict";
3
-
4
- namespace = "scroll_";
5
-
6
- testCM("bars_hidden", function(cm) {
7
- for (var i = 0;; i++) {
8
- var wrapBox = cm.getWrapperElement().getBoundingClientRect();
9
- var scrollBox = cm.getScrollerElement().getBoundingClientRect();
10
- is(wrapBox.bottom < scrollBox.bottom - 10);
11
- is(wrapBox.right < scrollBox.right - 10);
12
- if (i == 1) break;
13
- cm.getWrapperElement().style.height = "auto";
14
- cm.refresh();
15
- }
16
- });
17
-
18
- function barH(cm) { return byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0]; }
19
- function barV(cm) { return byClassName(cm.getWrapperElement(), "CodeMirror-vscrollbar")[0]; }
20
-
21
- function displayBottom(cm, scrollbar) {
22
- if (scrollbar)
23
- return barH(cm).getBoundingClientRect().top;
24
- else
25
- return cm.getWrapperElement().getBoundingClientRect().bottom - 1;
26
- }
27
-
28
- function displayRight(cm, scrollbar) {
29
- if (scrollbar)
30
- return barV(cm).getBoundingClientRect().left;
31
- else
32
- return cm.getWrapperElement().getBoundingClientRect().right - 1;
33
- }
34
-
35
- function testMovedownFixed(cm, hScroll) {
36
- cm.setSize("100px", "100px");
37
- if (hScroll) cm.setValue(new Array(100).join("x"));
38
- var bottom = displayBottom(cm, hScroll);
39
- for (var i = 0; i < 30; i++) {
40
- cm.replaceSelection("x\n");
41
- var cursorBottom = cm.cursorCoords(null, "window").bottom;
42
- is(cursorBottom <= bottom);
43
- }
44
- is(cursorBottom >= bottom - 5);
45
- }
46
-
47
- testCM("movedown_fixed", function(cm) {testMovedownFixed(cm, false);});
48
- testCM("movedown_hscroll_fixed", function(cm) {testMovedownFixed(cm, true);});
49
-
50
- function testMovedownResize(cm, hScroll) {
51
- cm.getWrapperElement().style.height = "auto";
52
- if (hScroll) cm.setValue(new Array(100).join("x"));
53
- cm.refresh();
54
- for (var i = 0; i < 30; i++) {
55
- cm.replaceSelection("x\n");
56
- var bottom = displayBottom(cm, hScroll);
57
- var cursorBottom = cm.cursorCoords(null, "window").bottom;
58
- is(cursorBottom <= bottom);
59
- is(cursorBottom >= bottom - 5);
60
- }
61
- }
62
-
63
- testCM("movedown_resize", function(cm) {testMovedownResize(cm, false);});
64
- testCM("movedown_hscroll_resize", function(cm) {testMovedownResize(cm, true);});
65
-
66
- function testMoveright(cm, wrap, scroll) {
67
- cm.setSize("100px", "100px");
68
- if (wrap) cm.setOption("lineWrapping", true);
69
- if (scroll) {
70
- cm.setValue("\n" + new Array(100).join("x\n"));
71
- cm.setCursor(Pos(0, 0));
72
- }
73
- var right = displayRight(cm, scroll);
74
- for (var i = 0; i < 10; i++) {
75
- cm.replaceSelection("xxxxxxxxxx");
76
- var cursorRight = cm.cursorCoords(null, "window").right;
77
- is(cursorRight < right);
78
- }
79
- if (!wrap) is(cursorRight > right - 20);
80
- }
81
-
82
- testCM("moveright", function(cm) {testMoveright(cm, false, false);});
83
- testCM("moveright_wrap", function(cm) {testMoveright(cm, true, false);});
84
- testCM("moveright_scroll", function(cm) {testMoveright(cm, false, true);});
85
- testCM("moveright_scroll_wrap", function(cm) {testMoveright(cm, true, true);});
86
-
87
- testCM("suddenly_wide", function(cm) {
88
- addDoc(cm, 100, 100);
89
- cm.replaceSelection(new Array(600).join("l ") + "\n");
90
- cm.execCommand("goLineUp");
91
- cm.execCommand("goLineEnd");
92
- is(barH(cm).scrollLeft > cm.getScrollerElement().scrollLeft - 1);
93
- });
94
-
95
- testCM("wrap_changes_height", function(cm) {
96
- var line = new Array(20).join("a ") + "\n";
97
- cm.setValue(new Array(20).join(line));
98
- var box = cm.getWrapperElement().getBoundingClientRect();
99
- cm.setSize(cm.cursorCoords(Pos(0), "window").right - box.left + 2,
100
- cm.cursorCoords(Pos(19, 0), "window").bottom - box.top + 2);
101
- cm.setCursor(Pos(19, 0));
102
- cm.replaceSelection("\n");
103
- is(cm.cursorCoords(null, "window").bottom < displayBottom(cm, false));
104
- }, {lineWrapping: true});
105
-
106
- testCM("height_auto_with_gutter_expect_no_scroll_after_line_delete", function(cm) {
107
- cm.setSize(null, "auto");
108
- cm.setValue("x\n");
109
- cm.execCommand("goDocEnd");
110
- cm.execCommand("delCharBefore");
111
- eq(cm.getScrollInfo().top, 0);
112
- cm.scrollTo(null, 10);
113
- is(cm.getScrollInfo().top < 5);
114
- }, {lineNumbers: true});
115
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/search_test.js DELETED
@@ -1,62 +0,0 @@
1
- (function() {
2
- "use strict";
3
-
4
- function test(name) {
5
- var text = Array.prototype.slice.call(arguments, 1, arguments.length - 1).join("\n");
6
- var body = arguments[arguments.length - 1];
7
- return window.test("search_" + name, function() {
8
- body(new CodeMirror.Doc(text));
9
- });
10
- }
11
-
12
- function run(doc, query, insensitive) {
13
- var cursor = doc.getSearchCursor(query, null, insensitive);
14
- for (var i = 3; i < arguments.length; i += 4) {
15
- var found = cursor.findNext();
16
- is(found, "not enough results (forward)");
17
- eqPos(Pos(arguments[i], arguments[i + 1]), cursor.from(), "from, forward, " + (i - 3) / 4);
18
- eqPos(Pos(arguments[i + 2], arguments[i + 3]), cursor.to(), "to, forward, " + (i - 3) / 4);
19
- }
20
- is(!cursor.findNext(), "too many matches (forward)");
21
- for (var i = arguments.length - 4; i >= 3; i -= 4) {
22
- var found = cursor.findPrevious();
23
- is(found, "not enough results (backwards)");
24
- eqPos(Pos(arguments[i], arguments[i + 1]), cursor.from(), "from, backwards, " + (i - 3) / 4);
25
- eqPos(Pos(arguments[i + 2], arguments[i + 3]), cursor.to(), "to, backwards, " + (i - 3) / 4);
26
- }
27
- is(!cursor.findPrevious(), "too many matches (backwards)");
28
- }
29
-
30
- test("simple", "abcdefg", "abcdefg", function(doc) {
31
- run(doc, "cde", false, 0, 2, 0, 5, 1, 2, 1, 5);
32
- });
33
-
34
- test("multiline", "hallo", "goodbye", function(doc) {
35
- run(doc, "llo\ngoo", false, 0, 2, 1, 3);
36
- run(doc, "blah\nhall", false);
37
- run(doc, "bye\neye", false);
38
- });
39
-
40
- test("regexp", "abcde", "abcde", function(doc) {
41
- run(doc, /bcd/, false, 0, 1, 0, 4, 1, 1, 1, 4);
42
- run(doc, /BCD/, false);
43
- run(doc, /BCD/i, false, 0, 1, 0, 4, 1, 1, 1, 4);
44
- });
45
-
46
- test("insensitive", "hallo", "HALLO", "oink", "hAllO", function(doc) {
47
- run(doc, "All", false, 3, 1, 3, 4);
48
- run(doc, "All", true, 0, 1, 0, 4, 1, 1, 1, 4, 3, 1, 3, 4);
49
- });
50
-
51
- test("multilineInsensitive", "zie ginds komT", "De Stoomboot", "uit Spanje weer aan", function(doc) {
52
- run(doc, "komt\nde stoomboot\nuit", false);
53
- run(doc, "komt\nde stoomboot\nuit", true, 0, 10, 2, 3);
54
- run(doc, "kOMt\ndE stOOmboot\nuiT", true, 0, 10, 2, 3);
55
- });
56
-
57
- test("expandingCaseFold", "<b>İİ İİ</b>", "<b>uu uu</b>", function(doc) {
58
- if (phantom) return; // A Phantom bug makes this hang
59
- run(doc, "</b>", true, 0, 8, 0, 12, 1, 8, 1, 12);
60
- run(doc, "İİ", true, 0, 3, 0, 5, 0, 6, 0, 8);
61
- });
62
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/sql-hint-test.js DELETED
@@ -1,189 +0,0 @@
1
- // CodeMirror, copyright (c) by Marijn Haverbeke and others
2
- // Distributed under an MIT license: http://codemirror.net/LICENSE
3
-
4
- (function() {
5
- var Pos = CodeMirror.Pos;
6
-
7
- var simpleTables = {
8
- "users": ["name", "score", "birthDate"],
9
- "xcountries": ["name", "population", "size"]
10
- };
11
-
12
- var schemaTables = {
13
- "schema.users": ["name", "score", "birthDate"],
14
- "schema.countries": ["name", "population", "size"]
15
- };
16
-
17
- var displayTextTables = [{
18
- text: "mytable",
19
- displayText: "mytable | The main table",
20
- columns: [{text: "id", displayText: "id | Unique ID"},
21
- {text: "name", displayText: "name | The name"}]
22
- }];
23
-
24
- namespace = "sql-hint_";
25
-
26
- function test(name, spec) {
27
- testCM(name, function(cm) {
28
- cm.setValue(spec.value);
29
- cm.setCursor(spec.cursor);
30
- var completion = CodeMirror.hint.sql(cm, {tables: spec.tables});
31
- if (!deepCompare(completion.list, spec.list))
32
- throw new Failure("Wrong completion results " + JSON.stringify(completion.list) + " vs " + JSON.stringify(spec.list));
33
- eqPos(completion.from, spec.from);
34
- eqPos(completion.to, spec.to);
35
- }, {
36
- value: spec.value,
37
- mode: "text/x-mysql"
38
- });
39
- }
40
-
41
- test("keywords", {
42
- value: "SEL",
43
- cursor: Pos(0, 3),
44
- list: ["SELECT"],
45
- from: Pos(0, 0),
46
- to: Pos(0, 3)
47
- });
48
-
49
- test("from", {
50
- value: "SELECT * fr",
51
- cursor: Pos(0, 11),
52
- list: ["FROM"],
53
- from: Pos(0, 9),
54
- to: Pos(0, 11)
55
- });
56
-
57
- test("table", {
58
- value: "SELECT xc",
59
- cursor: Pos(0, 9),
60
- tables: simpleTables,
61
- list: ["xcountries"],
62
- from: Pos(0, 7),
63
- to: Pos(0, 9)
64
- });
65
-
66
- test("columns", {
67
- value: "SELECT users.",
68
- cursor: Pos(0, 13),
69
- tables: simpleTables,
70
- list: ["users.name", "users.score", "users.birthDate"],
71
- from: Pos(0, 7),
72
- to: Pos(0, 13)
73
- });
74
-
75
- test("singlecolumn", {
76
- value: "SELECT users.na",
77
- cursor: Pos(0, 15),
78
- tables: simpleTables,
79
- list: ["users.name"],
80
- from: Pos(0, 7),
81
- to: Pos(0, 15)
82
- });
83
-
84
- test("quoted", {
85
- value: "SELECT `users`.`na",
86
- cursor: Pos(0, 18),
87
- tables: simpleTables,
88
- list: ["`users`.`name`"],
89
- from: Pos(0, 7),
90
- to: Pos(0, 18)
91
- });
92
-
93
- test("quotedcolumn", {
94
- value: "SELECT users.`na",
95
- cursor: Pos(0, 16),
96
- tables: simpleTables,
97
- list: ["`users`.`name`"],
98
- from: Pos(0, 7),
99
- to: Pos(0, 16)
100
- });
101
-
102
- test("schema", {
103
- value: "SELECT schem",
104
- cursor: Pos(0, 12),
105
- tables: schemaTables,
106
- list: ["schema.users", "schema.countries",
107
- "SCHEMA", "SCHEMA_NAME", "SCHEMAS"],
108
- from: Pos(0, 7),
109
- to: Pos(0, 12)
110
- });
111
-
112
- test("schemaquoted", {
113
- value: "SELECT `sch",
114
- cursor: Pos(0, 11),
115
- tables: schemaTables,
116
- list: ["`schema`.`users`", "`schema`.`countries`"],
117
- from: Pos(0, 7),
118
- to: Pos(0, 11)
119
- });
120
-
121
- test("schemacolumn", {
122
- value: "SELECT schema.users.",
123
- cursor: Pos(0, 20),
124
- tables: schemaTables,
125
- list: ["schema.users.name",
126
- "schema.users.score",
127
- "schema.users.birthDate"],
128
- from: Pos(0, 7),
129
- to: Pos(0, 20)
130
- });
131
-
132
- test("schemacolumnquoted", {
133
- value: "SELECT `schema`.`users`.",
134
- cursor: Pos(0, 24),
135
- tables: schemaTables,
136
- list: ["`schema`.`users`.`name`",
137
- "`schema`.`users`.`score`",
138
- "`schema`.`users`.`birthDate`"],
139
- from: Pos(0, 7),
140
- to: Pos(0, 24)
141
- });
142
-
143
- test("displayText_table", {
144
- value: "SELECT myt",
145
- cursor: Pos(0, 10),
146
- tables: displayTextTables,
147
- list: displayTextTables,
148
- from: Pos(0, 7),
149
- to: Pos(0, 10)
150
- });
151
-
152
- test("displayText_column", {
153
- value: "SELECT mytable.",
154
- cursor: Pos(0, 15),
155
- tables: displayTextTables,
156
- list: [{text: "mytable.id", displayText: "id | Unique ID"},
157
- {text: "mytable.name", displayText: "name | The name"}],
158
- from: Pos(0, 7),
159
- to: Pos(0, 15)
160
- });
161
-
162
- test("alias_complete", {
163
- value: "SELECT t. FROM users t",
164
- cursor: Pos(0, 9),
165
- tables: simpleTables,
166
- list: ["t.name", "t.score", "t.birthDate"],
167
- from: Pos(0, 7),
168
- to: Pos(0, 9)
169
- });
170
-
171
- test("alias_complete_with_displayText", {
172
- value: "SELECT t. FROM mytable t",
173
- cursor: Pos(0, 9),
174
- tables: displayTextTables,
175
- list: [{text: "t.id", displayText: "id | Unique ID"},
176
- {text: "t.name", displayText: "name | The name"}],
177
- from: Pos(0, 7),
178
- to: Pos(0, 9)
179
- })
180
-
181
- function deepCompare(a, b) {
182
- if (!a || typeof a != "object")
183
- return a === b;
184
- if (!b || typeof b != "object")
185
- return false;
186
- for (var prop in a) if (!deepCompare(a[prop], b[prop])) return false;
187
- return true;
188
- }
189
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/sublime_test.js DELETED
@@ -1,303 +0,0 @@
1
- (function() {
2
- "use strict";
3
-
4
- var Pos = CodeMirror.Pos;
5
- namespace = "sublime_";
6
-
7
- function stTest(name) {
8
- var actions = Array.prototype.slice.call(arguments, 1);
9
- testCM(name, function(cm) {
10
- for (var i = 0; i < actions.length; i++) {
11
- var action = actions[i];
12
- if (typeof action == "string" && i == 0)
13
- cm.setValue(action);
14
- else if (typeof action == "string")
15
- cm.execCommand(action);
16
- else if (action instanceof Pos)
17
- cm.setCursor(action);
18
- else
19
- action(cm);
20
- }
21
- });
22
- }
23
-
24
- function at(line, ch, msg) {
25
- return function(cm) {
26
- eq(cm.listSelections().length, 1);
27
- eqPos(cm.getCursor("head"), Pos(line, ch), msg);
28
- eqPos(cm.getCursor("anchor"), Pos(line, ch), msg);
29
- };
30
- }
31
-
32
- function val(content, msg) {
33
- return function(cm) { eq(cm.getValue(), content, msg); };
34
- }
35
-
36
- function argsToRanges(args) {
37
- if (args.length % 4) throw new Error("Wrong number of arguments for ranges.");
38
- var ranges = [];
39
- for (var i = 0; i < args.length; i += 4)
40
- ranges.push({anchor: Pos(args[i], args[i + 1]),
41
- head: Pos(args[i + 2], args[i + 3])});
42
- return ranges;
43
- }
44
-
45
- function setSel() {
46
- var ranges = argsToRanges(arguments);
47
- return function(cm) { cm.setSelections(ranges, 0); };
48
- }
49
-
50
- function hasSel() {
51
- var ranges = argsToRanges(arguments);
52
- return function(cm) {
53
- var sels = cm.listSelections();
54
- if (sels.length != ranges.length)
55
- throw new Failure("Expected " + ranges.length + " selections, but found " + sels.length);
56
- for (var i = 0; i < sels.length; i++) {
57
- eqPos(sels[i].anchor, ranges[i].anchor, "anchor " + i);
58
- eqPos(sels[i].head, ranges[i].head, "head " + i);
59
- }
60
- };
61
- }
62
-
63
- stTest("bySubword", "the foo_bar DooDahBah \n a",
64
- "goSubwordLeft", at(0, 0),
65
- "goSubwordRight", at(0, 3),
66
- "goSubwordRight", at(0, 7),
67
- "goSubwordRight", at(0, 11),
68
- "goSubwordRight", at(0, 15),
69
- "goSubwordRight", at(0, 18),
70
- "goSubwordRight", at(0, 21),
71
- "goSubwordRight", at(0, 22),
72
- "goSubwordRight", at(1, 0),
73
- "goSubwordRight", at(1, 2),
74
- "goSubwordRight", at(1, 2),
75
- "goSubwordLeft", at(1, 1),
76
- "goSubwordLeft", at(1, 0),
77
- "goSubwordLeft", at(0, 22),
78
- "goSubwordLeft", at(0, 18),
79
- "goSubwordLeft", at(0, 15),
80
- "goSubwordLeft", at(0, 12),
81
- "goSubwordLeft", at(0, 8),
82
- "goSubwordLeft", at(0, 4),
83
- "goSubwordLeft", at(0, 0));
84
-
85
- stTest("splitSelectionByLine", "abc\ndef\nghi",
86
- setSel(0, 1, 2, 2),
87
- "splitSelectionByLine",
88
- hasSel(0, 1, 0, 3,
89
- 1, 0, 1, 3,
90
- 2, 0, 2, 2));
91
-
92
- stTest("splitSelectionByLineMulti", "abc\ndef\nghi\njkl",
93
- setSel(0, 1, 1, 1,
94
- 1, 2, 3, 2,
95
- 3, 3, 3, 3),
96
- "splitSelectionByLine",
97
- hasSel(0, 1, 0, 3,
98
- 1, 0, 1, 1,
99
- 1, 2, 1, 3,
100
- 2, 0, 2, 3,
101
- 3, 0, 3, 2,
102
- 3, 3, 3, 3));
103
-
104
- stTest("selectLine", "abc\ndef\nghi",
105
- setSel(0, 1, 0, 1,
106
- 2, 0, 2, 1),
107
- "selectLine",
108
- hasSel(0, 0, 1, 0,
109
- 2, 0, 2, 3),
110
- setSel(0, 1, 1, 0),
111
- "selectLine",
112
- hasSel(0, 0, 2, 0));
113
-
114
- stTest("insertLineAfter", "abcde\nfghijkl\nmn",
115
- setSel(0, 1, 0, 1,
116
- 0, 3, 0, 3,
117
- 1, 2, 1, 2,
118
- 1, 3, 1, 5), "insertLineAfter",
119
- hasSel(1, 0, 1, 0,
120
- 3, 0, 3, 0), val("abcde\n\nfghijkl\n\nmn"));
121
-
122
- stTest("insertLineBefore", "abcde\nfghijkl\nmn",
123
- setSel(0, 1, 0, 1,
124
- 0, 3, 0, 3,
125
- 1, 2, 1, 2,
126
- 1, 3, 1, 5), "insertLineBefore",
127
- hasSel(0, 0, 0, 0,
128
- 2, 0, 2, 0), val("\nabcde\n\nfghijkl\nmn"));
129
-
130
- stTest("selectNextOccurrence", "a foo bar\nfoobar foo",
131
- setSel(0, 2, 0, 5),
132
- "selectNextOccurrence", hasSel(0, 2, 0, 5,
133
- 1, 0, 1, 3),
134
- "selectNextOccurrence", hasSel(0, 2, 0, 5,
135
- 1, 0, 1, 3,
136
- 1, 7, 1, 10),
137
- "selectNextOccurrence", hasSel(0, 2, 0, 5,
138
- 1, 0, 1, 3,
139
- 1, 7, 1, 10),
140
- Pos(0, 3), "selectNextOccurrence", hasSel(0, 2, 0, 5),
141
- "selectNextOccurrence", hasSel(0, 2, 0, 5,
142
- 1, 7, 1, 10),
143
- setSel(0, 6, 0, 9),
144
- "selectNextOccurrence", hasSel(0, 6, 0, 9,
145
- 1, 3, 1, 6));
146
-
147
- stTest("selectScope", "foo(a) {\n bar[1, 2];\n}",
148
- "selectScope", hasSel(0, 0, 2, 1),
149
- Pos(0, 4), "selectScope", hasSel(0, 4, 0, 5),
150
- Pos(0, 5), "selectScope", hasSel(0, 4, 0, 5),
151
- Pos(0, 6), "selectScope", hasSel(0, 0, 2, 1),
152
- Pos(0, 8), "selectScope", hasSel(0, 8, 2, 0),
153
- Pos(1, 2), "selectScope", hasSel(0, 8, 2, 0),
154
- Pos(1, 6), "selectScope", hasSel(1, 6, 1, 10),
155
- Pos(1, 9), "selectScope", hasSel(1, 6, 1, 10));
156
-
157
- stTest("goToBracket", "foo(a) {\n bar[1, 2];\n}",
158
- Pos(0, 0), "goToBracket", at(0, 0),
159
- Pos(0, 4), "goToBracket", at(0, 5), "goToBracket", at(0, 4),
160
- Pos(0, 8), "goToBracket", at(2, 0), "goToBracket", at(0, 8),
161
- Pos(1, 2), "goToBracket", at(2, 0),
162
- Pos(1, 7), "goToBracket", at(1, 10), "goToBracket", at(1, 6));
163
-
164
- stTest("swapLine", "1\n2\n3---\n4\n5",
165
- "swapLineDown", val("2\n1\n3---\n4\n5"),
166
- "swapLineUp", val("1\n2\n3---\n4\n5"),
167
- "swapLineUp", val("1\n2\n3---\n4\n5"),
168
- Pos(4, 1), "swapLineDown", val("1\n2\n3---\n4\n5"),
169
- setSel(0, 1, 0, 1,
170
- 1, 0, 2, 0,
171
- 2, 2, 2, 2),
172
- "swapLineDown", val("4\n1\n2\n3---\n5"),
173
- hasSel(1, 1, 1, 1,
174
- 2, 0, 3, 0,
175
- 3, 2, 3, 2),
176
- "swapLineUp", val("1\n2\n3---\n4\n5"),
177
- hasSel(0, 1, 0, 1,
178
- 1, 0, 2, 0,
179
- 2, 2, 2, 2));
180
-
181
- stTest("swapLineEmptyBottomSel", "1\n2\n3",
182
- setSel(0, 1, 1, 0),
183
- "swapLineDown", val("2\n1\n3"), hasSel(1, 1, 2, 0),
184
- "swapLineUp", val("1\n2\n3"), hasSel(0, 1, 1, 0),
185
- "swapLineUp", val("1\n2\n3"), hasSel(0, 0, 0, 0));
186
-
187
- stTest("swapLineUpFromEnd", "a\nb\nc",
188
- Pos(2, 1), "swapLineUp",
189
- hasSel(1, 1, 1, 1), val("a\nc\nb"));
190
-
191
- stTest("joinLines", "abc\ndef\nghi\njkl",
192
- "joinLines", val("abc def\nghi\njkl"), at(0, 4),
193
- "undo",
194
- setSel(0, 2, 1, 1), "joinLines",
195
- val("abc def ghi\njkl"), hasSel(0, 2, 0, 8),
196
- "undo",
197
- setSel(0, 1, 0, 1,
198
- 1, 1, 1, 1,
199
- 3, 1, 3, 1), "joinLines",
200
- val("abc def ghi\njkl"), hasSel(0, 4, 0, 4,
201
- 0, 8, 0, 8,
202
- 1, 3, 1, 3));
203
-
204
- stTest("duplicateLine", "abc\ndef\nghi",
205
- Pos(1, 0), "duplicateLine", val("abc\ndef\ndef\nghi"), at(2, 0),
206
- "undo",
207
- setSel(0, 1, 0, 1,
208
- 1, 1, 1, 1,
209
- 2, 1, 2, 1), "duplicateLine",
210
- val("abc\nabc\ndef\ndef\nghi\nghi"), hasSel(1, 1, 1, 1,
211
- 3, 1, 3, 1,
212
- 5, 1, 5, 1));
213
- stTest("duplicateLineSelection", "abcdef",
214
- setSel(0, 1, 0, 1,
215
- 0, 2, 0, 4,
216
- 0, 5, 0, 5),
217
- "duplicateLine",
218
- val("abcdef\nabcdcdef\nabcdcdef"), hasSel(2, 1, 2, 1,
219
- 2, 4, 2, 6,
220
- 2, 7, 2, 7));
221
-
222
- stTest("selectLinesUpward", "123\n345\n789\n012",
223
- setSel(0, 1, 0, 1,
224
- 1, 1, 1, 3,
225
- 2, 0, 2, 0,
226
- 3, 0, 3, 0),
227
- "selectLinesUpward",
228
- hasSel(0, 1, 0, 1,
229
- 0, 3, 0, 3,
230
- 1, 0, 1, 0,
231
- 1, 1, 1, 3,
232
- 2, 0, 2, 0,
233
- 3, 0, 3, 0));
234
-
235
- stTest("selectLinesDownward", "123\n345\n789\n012",
236
- setSel(0, 1, 0, 1,
237
- 1, 1, 1, 3,
238
- 2, 0, 2, 0,
239
- 3, 0, 3, 0),
240
- "selectLinesDownward",
241
- hasSel(0, 1, 0, 1,
242
- 1, 1, 1, 3,
243
- 2, 0, 2, 0,
244
- 2, 3, 2, 3,
245
- 3, 0, 3, 0));
246
-
247
- stTest("sortLines", "c\nb\na\nC\nB\nA",
248
- "sortLines", val("A\nB\nC\na\nb\nc"),
249
- "undo",
250
- setSel(0, 0, 2, 0,
251
- 3, 0, 5, 0),
252
- "sortLines", val("a\nb\nc\nA\nB\nC"),
253
- hasSel(0, 0, 2, 1,
254
- 3, 0, 5, 1),
255
- "undo",
256
- setSel(1, 0, 4, 0), "sortLinesInsensitive", val("c\na\nB\nb\nC\nA"));
257
-
258
- stTest("bookmarks", "abc\ndef\nghi\njkl",
259
- Pos(0, 1), "toggleBookmark",
260
- setSel(1, 1, 1, 2), "toggleBookmark",
261
- setSel(2, 1, 2, 2), "toggleBookmark",
262
- "nextBookmark", hasSel(0, 1, 0, 1),
263
- "nextBookmark", hasSel(1, 1, 1, 2),
264
- "nextBookmark", hasSel(2, 1, 2, 2),
265
- "prevBookmark", hasSel(1, 1, 1, 2),
266
- "prevBookmark", hasSel(0, 1, 0, 1),
267
- "prevBookmark", hasSel(2, 1, 2, 2),
268
- "prevBookmark", hasSel(1, 1, 1, 2),
269
- "toggleBookmark",
270
- "prevBookmark", hasSel(2, 1, 2, 2),
271
- "prevBookmark", hasSel(0, 1, 0, 1),
272
- "selectBookmarks", hasSel(0, 1, 0, 1,
273
- 2, 1, 2, 2),
274
- "clearBookmarks",
275
- Pos(0, 0), "selectBookmarks", at(0, 0));
276
-
277
- stTest("upAndDowncaseAtCursor", "abc\ndef x\nghI",
278
- setSel(0, 1, 0, 3,
279
- 1, 1, 1, 1,
280
- 1, 4, 1, 4), "upcaseAtCursor",
281
- val("aBC\nDEF x\nghI"), hasSel(0, 1, 0, 3,
282
- 1, 3, 1, 3,
283
- 1, 4, 1, 4),
284
- "downcaseAtCursor",
285
- val("abc\ndef x\nghI"), hasSel(0, 1, 0, 3,
286
- 1, 3, 1, 3,
287
- 1, 4, 1, 4));
288
-
289
- stTest("mark", "abc\ndef\nghi",
290
- Pos(1, 1), "setSublimeMark",
291
- Pos(2, 1), "selectToSublimeMark", hasSel(2, 1, 1, 1),
292
- Pos(0, 1), "swapWithSublimeMark", at(1, 1), "swapWithSublimeMark", at(0, 1),
293
- "deleteToSublimeMark", val("aef\nghi"),
294
- "sublimeYank", val("abc\ndef\nghi"), at(1, 1));
295
-
296
- stTest("findUnder", "foo foobar a",
297
- "findUnder", hasSel(0, 4, 0, 7),
298
- "findUnder", hasSel(0, 0, 0, 3),
299
- "findUnderPrevious", hasSel(0, 4, 0, 7),
300
- "findUnderPrevious", hasSel(0, 0, 0, 3),
301
- Pos(0, 4), "findUnder", hasSel(0, 4, 0, 10),
302
- Pos(0, 11), "findUnder", hasSel(0, 11, 0, 11));
303
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/test.js DELETED
@@ -1,2142 +0,0 @@
1
- var Pos = CodeMirror.Pos;
2
-
3
- CodeMirror.defaults.rtlMoveVisually = true;
4
-
5
- function forEach(arr, f) {
6
- for (var i = 0, e = arr.length; i < e; ++i) f(arr[i], i);
7
- }
8
-
9
- function addDoc(cm, width, height) {
10
- var content = [], line = "";
11
- for (var i = 0; i < width; ++i) line += "x";
12
- for (var i = 0; i < height; ++i) content.push(line);
13
- cm.setValue(content.join("\n"));
14
- }
15
-
16
- function byClassName(elt, cls) {
17
- if (elt.getElementsByClassName) return elt.getElementsByClassName(cls);
18
- var found = [], re = new RegExp("\\b" + cls + "\\b");
19
- function search(elt) {
20
- if (elt.nodeType == 3) return;
21
- if (re.test(elt.className)) found.push(elt);
22
- for (var i = 0, e = elt.childNodes.length; i < e; ++i)
23
- search(elt.childNodes[i]);
24
- }
25
- search(elt);
26
- return found;
27
- }
28
-
29
- var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
30
- var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
31
- var mac = /Mac/.test(navigator.platform);
32
- var phantom = /PhantomJS/.test(navigator.userAgent);
33
- var opera = /Opera\/\./.test(navigator.userAgent);
34
- var opera_version = opera && navigator.userAgent.match(/Version\/(\d+\.\d+)/);
35
- if (opera_version) opera_version = Number(opera_version);
36
- var opera_lt10 = opera && (!opera_version || opera_version < 10);
37
-
38
- namespace = "core_";
39
-
40
- test("core_fromTextArea", function() {
41
- var te = document.getElementById("code");
42
- te.value = "CONTENT";
43
- var cm = CodeMirror.fromTextArea(te);
44
- is(!te.offsetHeight);
45
- eq(cm.getValue(), "CONTENT");
46
- cm.setValue("foo\nbar");
47
- eq(cm.getValue(), "foo\nbar");
48
- cm.save();
49
- is(/^foo\r?\nbar$/.test(te.value));
50
- cm.setValue("xxx");
51
- cm.toTextArea();
52
- is(te.offsetHeight);
53
- eq(te.value, "xxx");
54
- });
55
-
56
- testCM("getRange", function(cm) {
57
- eq(cm.getLine(0), "1234");
58
- eq(cm.getLine(1), "5678");
59
- eq(cm.getLine(2), null);
60
- eq(cm.getLine(-1), null);
61
- eq(cm.getRange(Pos(0, 0), Pos(0, 3)), "123");
62
- eq(cm.getRange(Pos(0, -1), Pos(0, 200)), "1234");
63
- eq(cm.getRange(Pos(0, 2), Pos(1, 2)), "34\n56");
64
- eq(cm.getRange(Pos(1, 2), Pos(100, 0)), "78");
65
- }, {value: "1234\n5678"});
66
-
67
- testCM("replaceRange", function(cm) {
68
- eq(cm.getValue(), "");
69
- cm.replaceRange("foo\n", Pos(0, 0));
70
- eq(cm.getValue(), "foo\n");
71
- cm.replaceRange("a\nb", Pos(0, 1));
72
- eq(cm.getValue(), "fa\nboo\n");
73
- eq(cm.lineCount(), 3);
74
- cm.replaceRange("xyzzy", Pos(0, 0), Pos(1, 1));
75
- eq(cm.getValue(), "xyzzyoo\n");
76
- cm.replaceRange("abc", Pos(0, 0), Pos(10, 0));
77
- eq(cm.getValue(), "abc");
78
- eq(cm.lineCount(), 1);
79
- });
80
-
81
- testCM("selection", function(cm) {
82
- cm.setSelection(Pos(0, 4), Pos(2, 2));
83
- is(cm.somethingSelected());
84
- eq(cm.getSelection(), "11\n222222\n33");
85
- eqPos(cm.getCursor(false), Pos(2, 2));
86
- eqPos(cm.getCursor(true), Pos(0, 4));
87
- cm.setSelection(Pos(1, 0));
88
- is(!cm.somethingSelected());
89
- eq(cm.getSelection(), "");
90
- eqPos(cm.getCursor(true), Pos(1, 0));
91
- cm.replaceSelection("abc", "around");
92
- eq(cm.getSelection(), "abc");
93
- eq(cm.getValue(), "111111\nabc222222\n333333");
94
- cm.replaceSelection("def", "end");
95
- eq(cm.getSelection(), "");
96
- eqPos(cm.getCursor(true), Pos(1, 3));
97
- cm.setCursor(Pos(2, 1));
98
- eqPos(cm.getCursor(true), Pos(2, 1));
99
- cm.setCursor(1, 2);
100
- eqPos(cm.getCursor(true), Pos(1, 2));
101
- }, {value: "111111\n222222\n333333"});
102
-
103
- testCM("extendSelection", function(cm) {
104
- cm.setExtending(true);
105
- addDoc(cm, 10, 10);
106
- cm.setSelection(Pos(3, 5));
107
- eqPos(cm.getCursor("head"), Pos(3, 5));
108
- eqPos(cm.getCursor("anchor"), Pos(3, 5));
109
- cm.setSelection(Pos(2, 5), Pos(5, 5));
110
- eqPos(cm.getCursor("head"), Pos(5, 5));
111
- eqPos(cm.getCursor("anchor"), Pos(2, 5));
112
- eqPos(cm.getCursor("start"), Pos(2, 5));
113
- eqPos(cm.getCursor("end"), Pos(5, 5));
114
- cm.setSelection(Pos(5, 5), Pos(2, 5));
115
- eqPos(cm.getCursor("head"), Pos(2, 5));
116
- eqPos(cm.getCursor("anchor"), Pos(5, 5));
117
- eqPos(cm.getCursor("start"), Pos(2, 5));
118
- eqPos(cm.getCursor("end"), Pos(5, 5));
119
- cm.extendSelection(Pos(3, 2));
120
- eqPos(cm.getCursor("head"), Pos(3, 2));
121
- eqPos(cm.getCursor("anchor"), Pos(5, 5));
122
- cm.extendSelection(Pos(6, 2));
123
- eqPos(cm.getCursor("head"), Pos(6, 2));
124
- eqPos(cm.getCursor("anchor"), Pos(5, 5));
125
- cm.extendSelection(Pos(6, 3), Pos(6, 4));
126
- eqPos(cm.getCursor("head"), Pos(6, 4));
127
- eqPos(cm.getCursor("anchor"), Pos(5, 5));
128
- cm.extendSelection(Pos(0, 3), Pos(0, 4));
129
- eqPos(cm.getCursor("head"), Pos(0, 3));
130
- eqPos(cm.getCursor("anchor"), Pos(5, 5));
131
- cm.extendSelection(Pos(4, 5), Pos(6, 5));
132
- eqPos(cm.getCursor("head"), Pos(6, 5));
133
- eqPos(cm.getCursor("anchor"), Pos(4, 5));
134
- cm.setExtending(false);
135
- cm.extendSelection(Pos(0, 3), Pos(0, 4));
136
- eqPos(cm.getCursor("head"), Pos(0, 3));
137
- eqPos(cm.getCursor("anchor"), Pos(0, 4));
138
- });
139
-
140
- testCM("lines", function(cm) {
141
- eq(cm.getLine(0), "111111");
142
- eq(cm.getLine(1), "222222");
143
- eq(cm.getLine(-1), null);
144
- cm.replaceRange("", Pos(1, 0), Pos(2, 0))
145
- cm.replaceRange("abc", Pos(1, 0), Pos(1));
146
- eq(cm.getValue(), "111111\nabc");
147
- }, {value: "111111\n222222\n333333"});
148
-
149
- testCM("indent", function(cm) {
150
- cm.indentLine(1);
151
- eq(cm.getLine(1), " blah();");
152
- cm.setOption("indentUnit", 8);
153
- cm.indentLine(1);
154
- eq(cm.getLine(1), "\tblah();");
155
- cm.setOption("indentUnit", 10);
156
- cm.setOption("tabSize", 4);
157
- cm.indentLine(1);
158
- eq(cm.getLine(1), "\t\t blah();");
159
- }, {value: "if (x) {\nblah();\n}", indentUnit: 3, indentWithTabs: true, tabSize: 8});
160
-
161
- testCM("indentByNumber", function(cm) {
162
- cm.indentLine(0, 2);
163
- eq(cm.getLine(0), " foo");
164
- cm.indentLine(0, -200);
165
- eq(cm.getLine(0), "foo");
166
- cm.setSelection(Pos(0, 0), Pos(1, 2));
167
- cm.indentSelection(3);
168
- eq(cm.getValue(), " foo\n bar\nbaz");
169
- }, {value: "foo\nbar\nbaz"});
170
-
171
- test("core_defaults", function() {
172
- var defsCopy = {}, defs = CodeMirror.defaults;
173
- for (var opt in defs) defsCopy[opt] = defs[opt];
174
- defs.indentUnit = 5;
175
- defs.value = "uu";
176
- defs.indentWithTabs = true;
177
- defs.tabindex = 55;
178
- var place = document.getElementById("testground"), cm = CodeMirror(place);
179
- try {
180
- eq(cm.getOption("indentUnit"), 5);
181
- cm.setOption("indentUnit", 10);
182
- eq(defs.indentUnit, 5);
183
- eq(cm.getValue(), "uu");
184
- eq(cm.getOption("indentWithTabs"), true);
185
- eq(cm.getInputField().tabIndex, 55);
186
- }
187
- finally {
188
- for (var opt in defsCopy) defs[opt] = defsCopy[opt];
189
- place.removeChild(cm.getWrapperElement());
190
- }
191
- });
192
-
193
- testCM("lineInfo", function(cm) {
194
- eq(cm.lineInfo(-1), null);
195
- var mark = document.createElement("span");
196
- var lh = cm.setGutterMarker(1, "FOO", mark);
197
- var info = cm.lineInfo(1);
198
- eq(info.text, "222222");
199
- eq(info.gutterMarkers.FOO, mark);
200
- eq(info.line, 1);
201
- eq(cm.lineInfo(2).gutterMarkers, null);
202
- cm.setGutterMarker(lh, "FOO", null);
203
- eq(cm.lineInfo(1).gutterMarkers, null);
204
- cm.setGutterMarker(1, "FOO", mark);
205
- cm.setGutterMarker(0, "FOO", mark);
206
- cm.clearGutter("FOO");
207
- eq(cm.lineInfo(0).gutterMarkers, null);
208
- eq(cm.lineInfo(1).gutterMarkers, null);
209
- }, {value: "111111\n222222\n333333"});
210
-
211
- testCM("coords", function(cm) {
212
- cm.setSize(null, 100);
213
- addDoc(cm, 32, 200);
214
- var top = cm.charCoords(Pos(0, 0));
215
- var bot = cm.charCoords(Pos(200, 30));
216
- is(top.left < bot.left);
217
- is(top.top < bot.top);
218
- is(top.top < top.bottom);
219
- cm.scrollTo(null, 100);
220
- var top2 = cm.charCoords(Pos(0, 0));
221
- is(top.top > top2.top);
222
- eq(top.left, top2.left);
223
- });
224
-
225
- testCM("coordsChar", function(cm) {
226
- addDoc(cm, 35, 70);
227
- for (var i = 0; i < 2; ++i) {
228
- var sys = i ? "local" : "page";
229
- for (var ch = 0; ch <= 35; ch += 5) {
230
- for (var line = 0; line < 70; line += 5) {
231
- cm.setCursor(line, ch);
232
- var coords = cm.charCoords(Pos(line, ch), sys);
233
- var pos = cm.coordsChar({left: coords.left + 1, top: coords.top + 1}, sys);
234
- eqPos(pos, Pos(line, ch));
235
- }
236
- }
237
- }
238
- }, {lineNumbers: true});
239
-
240
- testCM("posFromIndex", function(cm) {
241
- cm.setValue(
242
- "This function should\n" +
243
- "convert a zero based index\n" +
244
- "to line and ch."
245
- );
246
-
247
- var examples = [
248
- { index: -1, line: 0, ch: 0 }, // <- Tests clipping
249
- { index: 0, line: 0, ch: 0 },
250
- { index: 10, line: 0, ch: 10 },
251
- { index: 39, line: 1, ch: 18 },
252
- { index: 55, line: 2, ch: 7 },
253
- { index: 63, line: 2, ch: 15 },
254
- { index: 64, line: 2, ch: 15 } // <- Tests clipping
255
- ];
256
-
257
- for (var i = 0; i < examples.length; i++) {
258
- var example = examples[i];
259
- var pos = cm.posFromIndex(example.index);
260
- eq(pos.line, example.line);
261
- eq(pos.ch, example.ch);
262
- if (example.index >= 0 && example.index < 64)
263
- eq(cm.indexFromPos(pos), example.index);
264
- }
265
- });
266
-
267
- testCM("undo", function(cm) {
268
- cm.replaceRange("def", Pos(0, 0), Pos(0));
269
- eq(cm.historySize().undo, 1);
270
- cm.undo();
271
- eq(cm.getValue(), "abc");
272
- eq(cm.historySize().undo, 0);
273
- eq(cm.historySize().redo, 1);
274
- cm.redo();
275
- eq(cm.getValue(), "def");
276
- eq(cm.historySize().undo, 1);
277
- eq(cm.historySize().redo, 0);
278
- cm.setValue("1\n\n\n2");
279
- cm.clearHistory();
280
- eq(cm.historySize().undo, 0);
281
- for (var i = 0; i < 20; ++i) {
282
- cm.replaceRange("a", Pos(0, 0));
283
- cm.replaceRange("b", Pos(3, 0));
284
- }
285
- eq(cm.historySize().undo, 40);
286
- for (var i = 0; i < 40; ++i)
287
- cm.undo();
288
- eq(cm.historySize().redo, 40);
289
- eq(cm.getValue(), "1\n\n\n2");
290
- }, {value: "abc"});
291
-
292
- testCM("undoDepth", function(cm) {
293
- cm.replaceRange("d", Pos(0));
294
- cm.replaceRange("e", Pos(0));
295
- cm.replaceRange("f", Pos(0));
296
- cm.undo(); cm.undo(); cm.undo();
297
- eq(cm.getValue(), "abcd");
298
- }, {value: "abc", undoDepth: 4});
299
-
300
- testCM("undoDoesntClearValue", function(cm) {
301
- cm.undo();
302
- eq(cm.getValue(), "x");
303
- }, {value: "x"});
304
-
305
- testCM("undoMultiLine", function(cm) {
306
- cm.operation(function() {
307
- cm.replaceRange("x", Pos(0, 0));
308
- cm.replaceRange("y", Pos(1, 0));
309
- });
310
- cm.undo();
311
- eq(cm.getValue(), "abc\ndef\nghi");
312
- cm.operation(function() {
313
- cm.replaceRange("y", Pos(1, 0));
314
- cm.replaceRange("x", Pos(0, 0));
315
- });
316
- cm.undo();
317
- eq(cm.getValue(), "abc\ndef\nghi");
318
- cm.operation(function() {
319
- cm.replaceRange("y", Pos(2, 0));
320
- cm.replaceRange("x", Pos(1, 0));
321
- cm.replaceRange("z", Pos(2, 0));
322
- });
323
- cm.undo();
324
- eq(cm.getValue(), "abc\ndef\nghi", 3);
325
- }, {value: "abc\ndef\nghi"});
326
-
327
- testCM("undoComposite", function(cm) {
328
- cm.replaceRange("y", Pos(1));
329
- cm.operation(function() {
330
- cm.replaceRange("x", Pos(0));
331
- cm.replaceRange("z", Pos(2));
332
- });
333
- eq(cm.getValue(), "ax\nby\ncz\n");
334
- cm.undo();
335
- eq(cm.getValue(), "a\nby\nc\n");
336
- cm.undo();
337
- eq(cm.getValue(), "a\nb\nc\n");
338
- cm.redo(); cm.redo();
339
- eq(cm.getValue(), "ax\nby\ncz\n");
340
- }, {value: "a\nb\nc\n"});
341
-
342
- testCM("undoSelection", function(cm) {
343
- cm.setSelection(Pos(0, 2), Pos(0, 4));
344
- cm.replaceSelection("");
345
- cm.setCursor(Pos(1, 0));
346
- cm.undo();
347
- eqPos(cm.getCursor(true), Pos(0, 2));
348
- eqPos(cm.getCursor(false), Pos(0, 4));
349
- cm.setCursor(Pos(1, 0));
350
- cm.redo();
351
- eqPos(cm.getCursor(true), Pos(0, 2));
352
- eqPos(cm.getCursor(false), Pos(0, 2));
353
- }, {value: "abcdefgh\n"});
354
-
355
- testCM("undoSelectionAsBefore", function(cm) {
356
- cm.replaceSelection("abc", "around");
357
- cm.undo();
358
- cm.redo();
359
- eq(cm.getSelection(), "abc");
360
- });
361
-
362
- testCM("selectionChangeConfusesHistory", function(cm) {
363
- cm.replaceSelection("abc", null, "dontmerge");
364
- cm.operation(function() {
365
- cm.setCursor(Pos(0, 0));
366
- cm.replaceSelection("abc", null, "dontmerge");
367
- });
368
- eq(cm.historySize().undo, 2);
369
- });
370
-
371
- testCM("markTextSingleLine", function(cm) {
372
- forEach([{a: 0, b: 1, c: "", f: 2, t: 5},
373
- {a: 0, b: 4, c: "", f: 0, t: 2},
374
- {a: 1, b: 2, c: "x", f: 3, t: 6},
375
- {a: 4, b: 5, c: "", f: 3, t: 5},
376
- {a: 4, b: 5, c: "xx", f: 3, t: 7},
377
- {a: 2, b: 5, c: "", f: 2, t: 3},
378
- {a: 2, b: 5, c: "abcd", f: 6, t: 7},
379
- {a: 2, b: 6, c: "x", f: null, t: null},
380
- {a: 3, b: 6, c: "", f: null, t: null},
381
- {a: 0, b: 9, c: "hallo", f: null, t: null},
382
- {a: 4, b: 6, c: "x", f: 3, t: 4},
383
- {a: 4, b: 8, c: "", f: 3, t: 4},
384
- {a: 6, b: 6, c: "a", f: 3, t: 6},
385
- {a: 8, b: 9, c: "", f: 3, t: 6}], function(test) {
386
- cm.setValue("1234567890");
387
- var r = cm.markText(Pos(0, 3), Pos(0, 6), {className: "foo"});
388
- cm.replaceRange(test.c, Pos(0, test.a), Pos(0, test.b));
389
- var f = r.find();
390
- eq(f && f.from.ch, test.f); eq(f && f.to.ch, test.t);
391
- });
392
- });
393
-
394
- testCM("markTextMultiLine", function(cm) {
395
- function p(v) { return v && Pos(v[0], v[1]); }
396
- forEach([{a: [0, 0], b: [0, 5], c: "", f: [0, 0], t: [2, 5]},
397
- {a: [0, 0], b: [0, 5], c: "foo\n", f: [1, 0], t: [3, 5]},
398
- {a: [0, 1], b: [0, 10], c: "", f: [0, 1], t: [2, 5]},
399
- {a: [0, 5], b: [0, 6], c: "x", f: [0, 6], t: [2, 5]},
400
- {a: [0, 0], b: [1, 0], c: "", f: [0, 0], t: [1, 5]},
401
- {a: [0, 6], b: [2, 4], c: "", f: [0, 5], t: [0, 7]},
402
- {a: [0, 6], b: [2, 4], c: "aa", f: [0, 5], t: [0, 9]},
403
- {a: [1, 2], b: [1, 8], c: "", f: [0, 5], t: [2, 5]},
404
- {a: [0, 5], b: [2, 5], c: "xx", f: null, t: null},
405
- {a: [0, 0], b: [2, 10], c: "x", f: null, t: null},
406
- {a: [1, 5], b: [2, 5], c: "", f: [0, 5], t: [1, 5]},
407
- {a: [2, 0], b: [2, 3], c: "", f: [0, 5], t: [2, 2]},
408
- {a: [2, 5], b: [3, 0], c: "a\nb", f: [0, 5], t: [2, 5]},
409
- {a: [2, 3], b: [3, 0], c: "x", f: [0, 5], t: [2, 3]},
410
- {a: [1, 1], b: [1, 9], c: "1\n2\n3", f: [0, 5], t: [4, 5]}], function(test) {
411
- cm.setValue("aaaaaaaaaa\nbbbbbbbbbb\ncccccccccc\ndddddddd\n");
412
- var r = cm.markText(Pos(0, 5), Pos(2, 5),
413
- {className: "CodeMirror-matchingbracket"});
414
- cm.replaceRange(test.c, p(test.a), p(test.b));
415
- var f = r.find();
416
- eqPos(f && f.from, p(test.f)); eqPos(f && f.to, p(test.t));
417
- });
418
- });
419
-
420
- testCM("markTextUndo", function(cm) {
421
- var marker1, marker2, bookmark;
422
- marker1 = cm.markText(Pos(0, 1), Pos(0, 3),
423
- {className: "CodeMirror-matchingbracket"});
424
- marker2 = cm.markText(Pos(0, 0), Pos(2, 1),
425
- {className: "CodeMirror-matchingbracket"});
426
- bookmark = cm.setBookmark(Pos(1, 5));
427
- cm.operation(function(){
428
- cm.replaceRange("foo", Pos(0, 2));
429
- cm.replaceRange("bar\nbaz\nbug\n", Pos(2, 0), Pos(3, 0));
430
- });
431
- var v1 = cm.getValue();
432
- cm.setValue("");
433
- eq(marker1.find(), null); eq(marker2.find(), null); eq(bookmark.find(), null);
434
- cm.undo();
435
- eqPos(bookmark.find(), Pos(1, 5), "still there");
436
- cm.undo();
437
- var m1Pos = marker1.find(), m2Pos = marker2.find();
438
- eqPos(m1Pos.from, Pos(0, 1)); eqPos(m1Pos.to, Pos(0, 3));
439
- eqPos(m2Pos.from, Pos(0, 0)); eqPos(m2Pos.to, Pos(2, 1));
440
- eqPos(bookmark.find(), Pos(1, 5));
441
- cm.redo(); cm.redo();
442
- eq(bookmark.find(), null);
443
- cm.undo();
444
- eqPos(bookmark.find(), Pos(1, 5));
445
- eq(cm.getValue(), v1);
446
- }, {value: "1234\n56789\n00\n"});
447
-
448
- testCM("markTextStayGone", function(cm) {
449
- var m1 = cm.markText(Pos(0, 0), Pos(0, 1));
450
- cm.replaceRange("hi", Pos(0, 2));
451
- m1.clear();
452
- cm.undo();
453
- eq(m1.find(), null);
454
- }, {value: "hello"});
455
-
456
- testCM("markTextAllowEmpty", function(cm) {
457
- var m1 = cm.markText(Pos(0, 1), Pos(0, 2), {clearWhenEmpty: false});
458
- is(m1.find());
459
- cm.replaceRange("x", Pos(0, 0));
460
- is(m1.find());
461
- cm.replaceRange("y", Pos(0, 2));
462
- is(m1.find());
463
- cm.replaceRange("z", Pos(0, 3), Pos(0, 4));
464
- is(!m1.find());
465
- var m2 = cm.markText(Pos(0, 1), Pos(0, 2), {clearWhenEmpty: false,
466
- inclusiveLeft: true,
467
- inclusiveRight: true});
468
- cm.replaceRange("q", Pos(0, 1), Pos(0, 2));
469
- is(m2.find());
470
- cm.replaceRange("", Pos(0, 0), Pos(0, 3));
471
- is(!m2.find());
472
- var m3 = cm.markText(Pos(0, 1), Pos(0, 1), {clearWhenEmpty: false});
473
- cm.replaceRange("a", Pos(0, 3));
474
- is(m3.find());
475
- cm.replaceRange("b", Pos(0, 1));
476
- is(!m3.find());
477
- }, {value: "abcde"});
478
-
479
- testCM("markTextStacked", function(cm) {
480
- var m1 = cm.markText(Pos(0, 0), Pos(0, 0), {clearWhenEmpty: false});
481
- var m2 = cm.markText(Pos(0, 0), Pos(0, 0), {clearWhenEmpty: false});
482
- cm.replaceRange("B", Pos(0, 1));
483
- is(m1.find() && m2.find());
484
- }, {value: "A"});
485
-
486
- testCM("undoPreservesNewMarks", function(cm) {
487
- cm.markText(Pos(0, 3), Pos(0, 4));
488
- cm.markText(Pos(1, 1), Pos(1, 3));
489
- cm.replaceRange("", Pos(0, 3), Pos(3, 1));
490
- var mBefore = cm.markText(Pos(0, 0), Pos(0, 1));
491
- var mAfter = cm.markText(Pos(0, 5), Pos(0, 6));
492
- var mAround = cm.markText(Pos(0, 2), Pos(0, 4));
493
- cm.undo();
494
- eqPos(mBefore.find().from, Pos(0, 0));
495
- eqPos(mBefore.find().to, Pos(0, 1));
496
- eqPos(mAfter.find().from, Pos(3, 3));
497
- eqPos(mAfter.find().to, Pos(3, 4));
498
- eqPos(mAround.find().from, Pos(0, 2));
499
- eqPos(mAround.find().to, Pos(3, 2));
500
- var found = cm.findMarksAt(Pos(2, 2));
501
- eq(found.length, 1);
502
- eq(found[0], mAround);
503
- }, {value: "aaaa\nbbbb\ncccc\ndddd"});
504
-
505
- testCM("markClearBetween", function(cm) {
506
- cm.setValue("aaa\nbbb\nccc\nddd\n");
507
- cm.markText(Pos(0, 0), Pos(2));
508
- cm.replaceRange("aaa\nbbb\nccc", Pos(0, 0), Pos(2));
509
- eq(cm.findMarksAt(Pos(1, 1)).length, 0);
510
- });
511
-
512
- testCM("deleteSpanCollapsedInclusiveLeft", function(cm) {
513
- var from = Pos(1, 0), to = Pos(1, 1);
514
- var m = cm.markText(from, to, {collapsed: true, inclusiveLeft: true});
515
- // Delete collapsed span.
516
- cm.replaceRange("", from, to);
517
- }, {value: "abc\nX\ndef"});
518
-
519
- testCM("markTextCSS", function(cm) {
520
- function present() {
521
- var spans = cm.display.lineDiv.getElementsByTagName("span");
522
- for (var i = 0; i < spans.length; i++)
523
- if (spans[i].style.color == "cyan" && span[i].textContent == "cdefg") return true;
524
- }
525
- var m = cm.markText(Pos(0, 2), Pos(0, 6), {css: "color: cyan"});
526
- m.clear();
527
- is(!present());
528
- }, {value: "abcdefgh"});
529
-
530
- testCM("bookmark", function(cm) {
531
- function p(v) { return v && Pos(v[0], v[1]); }
532
- forEach([{a: [1, 0], b: [1, 1], c: "", d: [1, 4]},
533
- {a: [1, 1], b: [1, 1], c: "xx", d: [1, 7]},
534
- {a: [1, 4], b: [1, 5], c: "ab", d: [1, 6]},
535
- {a: [1, 4], b: [1, 6], c: "", d: null},
536
- {a: [1, 5], b: [1, 6], c: "abc", d: [1, 5]},
537
- {a: [1, 6], b: [1, 8], c: "", d: [1, 5]},
538
- {a: [1, 4], b: [1, 4], c: "\n\n", d: [3, 1]},
539
- {bm: [1, 9], a: [1, 1], b: [1, 1], c: "\n", d: [2, 8]}], function(test) {
540
- cm.setValue("1234567890\n1234567890\n1234567890");
541
- var b = cm.setBookmark(p(test.bm) || Pos(1, 5));
542
- cm.replaceRange(test.c, p(test.a), p(test.b));
543
- eqPos(b.find(), p(test.d));
544
- });
545
- });
546
-
547
- testCM("bookmarkInsertLeft", function(cm) {
548
- var br = cm.setBookmark(Pos(0, 2), {insertLeft: false});
549
- var bl = cm.setBookmark(Pos(0, 2), {insertLeft: true});
550
- cm.setCursor(Pos(0, 2));
551
- cm.replaceSelection("hi");
552
- eqPos(br.find(), Pos(0, 2));
553
- eqPos(bl.find(), Pos(0, 4));
554
- cm.replaceRange("", Pos(0, 4), Pos(0, 5));
555
- cm.replaceRange("", Pos(0, 2), Pos(0, 4));
556
- cm.replaceRange("", Pos(0, 1), Pos(0, 2));
557
- // Verify that deleting next to bookmarks doesn't kill them
558
- eqPos(br.find(), Pos(0, 1));
559
- eqPos(bl.find(), Pos(0, 1));
560
- }, {value: "abcdef"});
561
-
562
- testCM("bookmarkCursor", function(cm) {
563
- var pos01 = cm.cursorCoords(Pos(0, 1)), pos11 = cm.cursorCoords(Pos(1, 1)),
564
- pos20 = cm.cursorCoords(Pos(2, 0)), pos30 = cm.cursorCoords(Pos(3, 0)),
565
- pos41 = cm.cursorCoords(Pos(4, 1));
566
- cm.setBookmark(Pos(0, 1), {widget: document.createTextNode("←"), insertLeft: true});
567
- cm.setBookmark(Pos(2, 0), {widget: document.createTextNode("←"), insertLeft: true});
568
- cm.setBookmark(Pos(1, 1), {widget: document.createTextNode("→")});
569
- cm.setBookmark(Pos(3, 0), {widget: document.createTextNode("→")});
570
- var new01 = cm.cursorCoords(Pos(0, 1)), new11 = cm.cursorCoords(Pos(1, 1)),
571
- new20 = cm.cursorCoords(Pos(2, 0)), new30 = cm.cursorCoords(Pos(3, 0));
572
- near(new01.left, pos01.left, 1);
573
- near(new01.top, pos01.top, 1);
574
- is(new11.left > pos11.left, "at right, middle of line");
575
- near(new11.top == pos11.top, 1);
576
- near(new20.left, pos20.left, 1);
577
- near(new20.top, pos20.top, 1);
578
- is(new30.left > pos30.left, "at right, empty line");
579
- near(new30.top, pos30, 1);
580
- cm.setBookmark(Pos(4, 0), {widget: document.createTextNode("→")});
581
- is(cm.cursorCoords(Pos(4, 1)).left > pos41.left, "single-char bug");
582
- }, {value: "foo\nbar\n\n\nx\ny"});
583
-
584
- testCM("multiBookmarkCursor", function(cm) {
585
- if (phantom) return;
586
- var ms = [], m;
587
- function add(insertLeft) {
588
- for (var i = 0; i < 3; ++i) {
589
- var node = document.createElement("span");
590
- node.innerHTML = "X";
591
- ms.push(cm.setBookmark(Pos(0, 1), {widget: node, insertLeft: insertLeft}));
592
- }
593
- }
594
- var base1 = cm.cursorCoords(Pos(0, 1)).left, base4 = cm.cursorCoords(Pos(0, 4)).left;
595
- add(true);
596
- near(base1, cm.cursorCoords(Pos(0, 1)).left, 1);
597
- while (m = ms.pop()) m.clear();
598
- add(false);
599
- near(base4, cm.cursorCoords(Pos(0, 1)).left, 1);
600
- }, {value: "abcdefg"});
601
-
602
- testCM("getAllMarks", function(cm) {
603
- addDoc(cm, 10, 10);
604
- var m1 = cm.setBookmark(Pos(0, 2));
605
- var m2 = cm.markText(Pos(0, 2), Pos(3, 2));
606
- var m3 = cm.markText(Pos(1, 2), Pos(1, 8));
607
- var m4 = cm.markText(Pos(8, 0), Pos(9, 0));
608
- eq(cm.getAllMarks().length, 4);
609
- m1.clear();
610
- m3.clear();
611
- eq(cm.getAllMarks().length, 2);
612
- });
613
-
614
- testCM("setValueClears", function(cm) {
615
- cm.addLineClass(0, "wrap", "foo");
616
- var mark = cm.markText(Pos(0, 0), Pos(1, 1), {inclusiveLeft: true, inclusiveRight: true});
617
- cm.setValue("foo");
618
- is(!cm.lineInfo(0).wrapClass);
619
- is(!mark.find());
620
- }, {value: "a\nb"});
621
-
622
- testCM("bug577", function(cm) {
623
- cm.setValue("a\nb");
624
- cm.clearHistory();
625
- cm.setValue("fooooo");
626
- cm.undo();
627
- });
628
-
629
- testCM("scrollSnap", function(cm) {
630
- cm.setSize(100, 100);
631
- addDoc(cm, 200, 200);
632
- cm.setCursor(Pos(100, 180));
633
- var info = cm.getScrollInfo();
634
- is(info.left > 0 && info.top > 0);
635
- cm.setCursor(Pos(0, 0));
636
- info = cm.getScrollInfo();
637
- is(info.left == 0 && info.top == 0, "scrolled clean to top");
638
- cm.setCursor(Pos(100, 180));
639
- cm.setCursor(Pos(199, 0));
640
- info = cm.getScrollInfo();
641
- is(info.left == 0 && info.top + 2 > info.height - cm.getScrollerElement().clientHeight, "scrolled clean to bottom");
642
- });
643
-
644
- testCM("scrollIntoView", function(cm) {
645
- if (phantom) return;
646
- var outer = cm.getWrapperElement().getBoundingClientRect();
647
- function test(line, ch, msg) {
648
- var pos = Pos(line, ch);
649
- cm.scrollIntoView(pos);
650
- var box = cm.charCoords(pos, "window");
651
- is(box.left >= outer.left, msg + " (left)");
652
- is(box.right <= outer.right, msg + " (right)");
653
- is(box.top >= outer.top, msg + " (top)");
654
- is(box.bottom <= outer.bottom, msg + " (bottom)");
655
- }
656
- addDoc(cm, 200, 200);
657
- test(199, 199, "bottom right");
658
- test(0, 0, "top left");
659
- test(100, 100, "center");
660
- test(199, 0, "bottom left");
661
- test(0, 199, "top right");
662
- test(100, 100, "center again");
663
- });
664
-
665
- testCM("scrollBackAndForth", function(cm) {
666
- addDoc(cm, 1, 200);
667
- cm.operation(function() {
668
- cm.scrollIntoView(Pos(199, 0));
669
- cm.scrollIntoView(Pos(4, 0));
670
- });
671
- is(cm.getScrollInfo().top > 0);
672
- });
673
-
674
- testCM("selectAllNoScroll", function(cm) {
675
- addDoc(cm, 1, 200);
676
- cm.execCommand("selectAll");
677
- eq(cm.getScrollInfo().top, 0);
678
- cm.setCursor(199);
679
- cm.execCommand("selectAll");
680
- is(cm.getScrollInfo().top > 0);
681
- });
682
-
683
- testCM("selectionPos", function(cm) {
684
- if (phantom || cm.getOption("inputStyle") != "textarea") return;
685
- cm.setSize(100, 100);
686
- addDoc(cm, 200, 100);
687
- cm.setSelection(Pos(1, 100), Pos(98, 100));
688
- var lineWidth = cm.charCoords(Pos(0, 200), "local").left;
689
- var lineHeight = (cm.charCoords(Pos(99)).top - cm.charCoords(Pos(0)).top) / 100;
690
- cm.scrollTo(0, 0);
691
- var selElt = byClassName(cm.getWrapperElement(), "CodeMirror-selected");
692
- var outer = cm.getWrapperElement().getBoundingClientRect();
693
- var sawMiddle, sawTop, sawBottom;
694
- for (var i = 0, e = selElt.length; i < e; ++i) {
695
- var box = selElt[i].getBoundingClientRect();
696
- var atLeft = box.left - outer.left < 30;
697
- var width = box.right - box.left;
698
- var atRight = box.right - outer.left > .8 * lineWidth;
699
- if (atLeft && atRight) {
700
- sawMiddle = true;
701
- is(box.bottom - box.top > 90 * lineHeight, "middle high");
702
- is(width > .9 * lineWidth, "middle wide");
703
- } else {
704
- is(width > .4 * lineWidth, "top/bot wide enough");
705
- is(width < .6 * lineWidth, "top/bot slim enough");
706
- if (atLeft) {
707
- sawBottom = true;
708
- is(box.top - outer.top > 96 * lineHeight, "bot below");
709
- } else if (atRight) {
710
- sawTop = true;
711
- is(box.top - outer.top < 2.1 * lineHeight, "top above");
712
- }
713
- }
714
- }
715
- is(sawTop && sawBottom && sawMiddle, "all parts");
716
- }, null);
717
-
718
- testCM("restoreHistory", function(cm) {
719
- cm.setValue("abc\ndef");
720
- cm.replaceRange("hello", Pos(1, 0), Pos(1));
721
- cm.replaceRange("goop", Pos(0, 0), Pos(0));
722
- cm.undo();
723
- var storedVal = cm.getValue(), storedHist = cm.getHistory();
724
- if (window.JSON) storedHist = JSON.parse(JSON.stringify(storedHist));
725
- eq(storedVal, "abc\nhello");
726
- cm.setValue("");
727
- cm.clearHistory();
728
- eq(cm.historySize().undo, 0);
729
- cm.setValue(storedVal);
730
- cm.setHistory(storedHist);
731
- cm.redo();
732
- eq(cm.getValue(), "goop\nhello");
733
- cm.undo(); cm.undo();
734
- eq(cm.getValue(), "abc\ndef");
735
- });
736
-
737
- testCM("doubleScrollbar", function(cm) {
738
- var dummy = document.body.appendChild(document.createElement("p"));
739
- dummy.style.cssText = "height: 50px; overflow: scroll; width: 50px";
740
- var scrollbarWidth = dummy.offsetWidth + 1 - dummy.clientWidth;
741
- document.body.removeChild(dummy);
742
- if (scrollbarWidth < 2) return;
743
- cm.setSize(null, 100);
744
- addDoc(cm, 1, 300);
745
- var wrap = cm.getWrapperElement();
746
- is(wrap.offsetWidth - byClassName(wrap, "CodeMirror-lines")[0].offsetWidth <= scrollbarWidth * 1.5);
747
- });
748
-
749
- testCM("weirdLinebreaks", function(cm) {
750
- cm.setValue("foo\nbar\rbaz\r\nquux\n\rplop");
751
- is(cm.getValue(), "foo\nbar\nbaz\nquux\n\nplop");
752
- is(cm.lineCount(), 6);
753
- cm.setValue("\n\n");
754
- is(cm.lineCount(), 3);
755
- });
756
-
757
- testCM("setSize", function(cm) {
758
- cm.setSize(100, 100);
759
- var wrap = cm.getWrapperElement();
760
- is(wrap.offsetWidth, 100);
761
- is(wrap.offsetHeight, 100);
762
- cm.setSize("100%", "3em");
763
- is(wrap.style.width, "100%");
764
- is(wrap.style.height, "3em");
765
- cm.setSize(null, 40);
766
- is(wrap.style.width, "100%");
767
- is(wrap.style.height, "40px");
768
- });
769
-
770
- function foldLines(cm, start, end, autoClear) {
771
- return cm.markText(Pos(start, 0), Pos(end - 1), {
772
- inclusiveLeft: true,
773
- inclusiveRight: true,
774
- collapsed: true,
775
- clearOnEnter: autoClear
776
- });
777
- }
778
-
779
- testCM("collapsedLines", function(cm) {
780
- addDoc(cm, 4, 10);
781
- var range = foldLines(cm, 4, 5), cleared = 0;
782
- CodeMirror.on(range, "clear", function() {cleared++;});
783
- cm.setCursor(Pos(3, 0));
784
- CodeMirror.commands.goLineDown(cm);
785
- eqPos(cm.getCursor(), Pos(5, 0));
786
- cm.replaceRange("abcdefg", Pos(3, 0), Pos(3));
787
- cm.setCursor(Pos(3, 6));
788
- CodeMirror.commands.goLineDown(cm);
789
- eqPos(cm.getCursor(), Pos(5, 4));
790
- cm.replaceRange("ab", Pos(3, 0), Pos(3));
791
- cm.setCursor(Pos(3, 2));
792
- CodeMirror.commands.goLineDown(cm);
793
- eqPos(cm.getCursor(), Pos(5, 2));
794
- cm.operation(function() {range.clear(); range.clear();});
795
- eq(cleared, 1);
796
- });
797
-
798
- testCM("collapsedRangeCoordsChar", function(cm) {
799
- var pos_1_3 = cm.charCoords(Pos(1, 3));
800
- pos_1_3.left += 2; pos_1_3.top += 2;
801
- var opts = {collapsed: true, inclusiveLeft: true, inclusiveRight: true};
802
- var m1 = cm.markText(Pos(0, 0), Pos(2, 0), opts);
803
- eqPos(cm.coordsChar(pos_1_3), Pos(3, 3));
804
- m1.clear();
805
- var m1 = cm.markText(Pos(0, 0), Pos(1, 1), {collapsed: true, inclusiveLeft: true});
806
- var m2 = cm.markText(Pos(1, 1), Pos(2, 0), {collapsed: true, inclusiveRight: true});
807
- eqPos(cm.coordsChar(pos_1_3), Pos(3, 3));
808
- m1.clear(); m2.clear();
809
- var m1 = cm.markText(Pos(0, 0), Pos(1, 6), opts);
810
- eqPos(cm.coordsChar(pos_1_3), Pos(3, 3));
811
- }, {value: "123456\nabcdef\nghijkl\nmnopqr\n"});
812
-
813
- testCM("collapsedRangeBetweenLinesSelected", function(cm) {
814
- if (cm.getOption("inputStyle") != "textarea") return;
815
- var widget = document.createElement("span");
816
- widget.textContent = "\u2194";
817
- cm.markText(Pos(0, 3), Pos(1, 0), {replacedWith: widget});
818
- cm.setSelection(Pos(0, 3), Pos(1, 0));
819
- var selElts = byClassName(cm.getWrapperElement(), "CodeMirror-selected");
820
- for (var i = 0, w = 0; i < selElts.length; i++)
821
- w += selElts[i].offsetWidth;
822
- is(w > 0);
823
- }, {value: "one\ntwo"});
824
-
825
- testCM("randomCollapsedRanges", function(cm) {
826
- addDoc(cm, 20, 500);
827
- cm.operation(function() {
828
- for (var i = 0; i < 200; i++) {
829
- var start = Pos(Math.floor(Math.random() * 500), Math.floor(Math.random() * 20));
830
- if (i % 4)
831
- try { cm.markText(start, Pos(start.line + 2, 1), {collapsed: true}); }
832
- catch(e) { if (!/overlapping/.test(String(e))) throw e; }
833
- else
834
- cm.markText(start, Pos(start.line, start.ch + 4), {"className": "foo"});
835
- }
836
- });
837
- });
838
-
839
- testCM("hiddenLinesAutoUnfold", function(cm) {
840
- var range = foldLines(cm, 1, 3, true), cleared = 0;
841
- CodeMirror.on(range, "clear", function() {cleared++;});
842
- cm.setCursor(Pos(3, 0));
843
- eq(cleared, 0);
844
- cm.execCommand("goCharLeft");
845
- eq(cleared, 1);
846
- range = foldLines(cm, 1, 3, true);
847
- CodeMirror.on(range, "clear", function() {cleared++;});
848
- eqPos(cm.getCursor(), Pos(3, 0));
849
- cm.setCursor(Pos(0, 3));
850
- cm.execCommand("goCharRight");
851
- eq(cleared, 2);
852
- }, {value: "abc\ndef\nghi\njkl"});
853
-
854
- testCM("hiddenLinesSelectAll", function(cm) { // Issue #484
855
- addDoc(cm, 4, 20);
856
- foldLines(cm, 0, 10);
857
- foldLines(cm, 11, 20);
858
- CodeMirror.commands.selectAll(cm);
859
- eqPos(cm.getCursor(true), Pos(10, 0));
860
- eqPos(cm.getCursor(false), Pos(10, 4));
861
- });
862
-
863
-
864
- testCM("everythingFolded", function(cm) {
865
- addDoc(cm, 2, 2);
866
- function enterPress() {
867
- cm.triggerOnKeyDown({type: "keydown", keyCode: 13, preventDefault: function(){}, stopPropagation: function(){}});
868
- }
869
- var fold = foldLines(cm, 0, 2);
870
- enterPress();
871
- eq(cm.getValue(), "xx\nxx");
872
- fold.clear();
873
- fold = foldLines(cm, 0, 2, true);
874
- eq(fold.find(), null);
875
- enterPress();
876
- eq(cm.getValue(), "\nxx\nxx");
877
- });
878
-
879
- testCM("structuredFold", function(cm) {
880
- if (phantom) return;
881
- addDoc(cm, 4, 8);
882
- var range = cm.markText(Pos(1, 2), Pos(6, 2), {
883
- replacedWith: document.createTextNode("Q")
884
- });
885
- cm.setCursor(0, 3);
886
- CodeMirror.commands.goLineDown(cm);
887
- eqPos(cm.getCursor(), Pos(6, 2));
888
- CodeMirror.commands.goCharLeft(cm);
889
- eqPos(cm.getCursor(), Pos(1, 2));
890
- CodeMirror.commands.delCharAfter(cm);
891
- eq(cm.getValue(), "xxxx\nxxxx\nxxxx");
892
- addDoc(cm, 4, 8);
893
- range = cm.markText(Pos(1, 2), Pos(6, 2), {
894
- replacedWith: document.createTextNode("M"),
895
- clearOnEnter: true
896
- });
897
- var cleared = 0;
898
- CodeMirror.on(range, "clear", function(){++cleared;});
899
- cm.setCursor(0, 3);
900
- CodeMirror.commands.goLineDown(cm);
901
- eqPos(cm.getCursor(), Pos(6, 2));
902
- CodeMirror.commands.goCharLeft(cm);
903
- eqPos(cm.getCursor(), Pos(6, 1));
904
- eq(cleared, 1);
905
- range.clear();
906
- eq(cleared, 1);
907
- range = cm.markText(Pos(1, 2), Pos(6, 2), {
908
- replacedWith: document.createTextNode("Q"),
909
- clearOnEnter: true
910
- });
911
- range.clear();
912
- cm.setCursor(1, 2);
913
- CodeMirror.commands.goCharRight(cm);
914
- eqPos(cm.getCursor(), Pos(1, 3));
915
- range = cm.markText(Pos(2, 0), Pos(4, 4), {
916
- replacedWith: document.createTextNode("M")
917
- });
918
- cm.setCursor(1, 0);
919
- CodeMirror.commands.goLineDown(cm);
920
- eqPos(cm.getCursor(), Pos(2, 0));
921
- }, null);
922
-
923
- testCM("nestedFold", function(cm) {
924
- addDoc(cm, 10, 3);
925
- function fold(ll, cl, lr, cr) {
926
- return cm.markText(Pos(ll, cl), Pos(lr, cr), {collapsed: true});
927
- }
928
- var inner1 = fold(0, 6, 1, 3), inner2 = fold(0, 2, 1, 8), outer = fold(0, 1, 2, 3), inner0 = fold(0, 5, 0, 6);
929
- cm.setCursor(0, 1);
930
- CodeMirror.commands.goCharRight(cm);
931
- eqPos(cm.getCursor(), Pos(2, 3));
932
- inner0.clear();
933
- CodeMirror.commands.goCharLeft(cm);
934
- eqPos(cm.getCursor(), Pos(0, 1));
935
- outer.clear();
936
- CodeMirror.commands.goCharRight(cm);
937
- eqPos(cm.getCursor(), Pos(0, 2));
938
- CodeMirror.commands.goCharRight(cm);
939
- eqPos(cm.getCursor(), Pos(1, 8));
940
- inner2.clear();
941
- CodeMirror.commands.goCharLeft(cm);
942
- eqPos(cm.getCursor(), Pos(1, 7));
943
- cm.setCursor(0, 5);
944
- CodeMirror.commands.goCharRight(cm);
945
- eqPos(cm.getCursor(), Pos(0, 6));
946
- CodeMirror.commands.goCharRight(cm);
947
- eqPos(cm.getCursor(), Pos(1, 3));
948
- });
949
-
950
- testCM("badNestedFold", function(cm) {
951
- addDoc(cm, 4, 4);
952
- cm.markText(Pos(0, 2), Pos(3, 2), {collapsed: true});
953
- var caught;
954
- try {cm.markText(Pos(0, 1), Pos(0, 3), {collapsed: true});}
955
- catch(e) {caught = e;}
956
- is(caught instanceof Error, "no error");
957
- is(/overlap/i.test(caught.message), "wrong error");
958
- });
959
-
960
- testCM("nestedFoldOnSide", function(cm) {
961
- var m1 = cm.markText(Pos(0, 1), Pos(2, 1), {collapsed: true, inclusiveRight: true});
962
- var m2 = cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true});
963
- cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true}).clear();
964
- try { cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true, inclusiveLeft: true}); }
965
- catch(e) { var caught = e; }
966
- is(caught && /overlap/i.test(caught.message));
967
- var m3 = cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true});
968
- var m4 = cm.markText(Pos(2, 0), Pos(2, 1), {collapse: true, inclusiveRight: true});
969
- m1.clear(); m4.clear();
970
- m1 = cm.markText(Pos(0, 1), Pos(2, 1), {collapsed: true});
971
- cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true}).clear();
972
- try { cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true, inclusiveRight: true}); }
973
- catch(e) { var caught = e; }
974
- is(caught && /overlap/i.test(caught.message));
975
- }, {value: "ab\ncd\ef"});
976
-
977
- testCM("editInFold", function(cm) {
978
- addDoc(cm, 4, 6);
979
- var m = cm.markText(Pos(1, 2), Pos(3, 2), {collapsed: true});
980
- cm.replaceRange("", Pos(0, 0), Pos(1, 3));
981
- cm.replaceRange("", Pos(2, 1), Pos(3, 3));
982
- cm.replaceRange("a\nb\nc\nd", Pos(0, 1), Pos(1, 0));
983
- cm.cursorCoords(Pos(0, 0));
984
- });
985
-
986
- testCM("wrappingInlineWidget", function(cm) {
987
- cm.setSize("11em");
988
- var w = document.createElement("span");
989
- w.style.color = "red";
990
- w.innerHTML = "one two three four";
991
- cm.markText(Pos(0, 6), Pos(0, 9), {replacedWith: w});
992
- var cur0 = cm.cursorCoords(Pos(0, 0)), cur1 = cm.cursorCoords(Pos(0, 10));
993
- is(cur0.top < cur1.top);
994
- is(cur0.bottom < cur1.bottom);
995
- var curL = cm.cursorCoords(Pos(0, 6)), curR = cm.cursorCoords(Pos(0, 9));
996
- eq(curL.top, cur0.top);
997
- eq(curL.bottom, cur0.bottom);
998
- eq(curR.top, cur1.top);
999
- eq(curR.bottom, cur1.bottom);
1000
- cm.replaceRange("", Pos(0, 9), Pos(0));
1001
- curR = cm.cursorCoords(Pos(0, 9));
1002
- if (phantom) return;
1003
- eq(curR.top, cur1.top);
1004
- eq(curR.bottom, cur1.bottom);
1005
- }, {value: "1 2 3 xxx 4", lineWrapping: true});
1006
-
1007
- testCM("showEmptyWidgetSpan", function(cm) {
1008
- var marker = cm.markText(Pos(0, 2), Pos(0, 2), {
1009
- clearWhenEmpty: false,
1010
- replacedWith: document.createTextNode("X")
1011
- });
1012
- eq(cm.display.view[0].text.textContent, "abXc");
1013
- }, {value: "abc"});
1014
-
1015
- testCM("changedInlineWidget", function(cm) {
1016
- cm.setSize("10em");
1017
- var w = document.createElement("span");
1018
- w.innerHTML = "x";
1019
- var m = cm.markText(Pos(0, 4), Pos(0, 5), {replacedWith: w});
1020
- w.innerHTML = "and now the widget is really really long all of a sudden and a scrollbar is needed";
1021
- m.changed();
1022
- var hScroll = byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0];
1023
- is(hScroll.scrollWidth > hScroll.clientWidth);
1024
- }, {value: "hello there"});
1025
-
1026
- testCM("changedBookmark", function(cm) {
1027
- cm.setSize("10em");
1028
- var w = document.createElement("span");
1029
- w.innerHTML = "x";
1030
- var m = cm.setBookmark(Pos(0, 4), {widget: w});
1031
- w.innerHTML = "and now the widget is really really long all of a sudden and a scrollbar is needed";
1032
- m.changed();
1033
- var hScroll = byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0];
1034
- is(hScroll.scrollWidth > hScroll.clientWidth);
1035
- }, {value: "abcdefg"});
1036
-
1037
- testCM("inlineWidget", function(cm) {
1038
- var w = cm.setBookmark(Pos(0, 2), {widget: document.createTextNode("uu")});
1039
- cm.setCursor(0, 2);
1040
- CodeMirror.commands.goLineDown(cm);
1041
- eqPos(cm.getCursor(), Pos(1, 4));
1042
- cm.setCursor(0, 2);
1043
- cm.replaceSelection("hi");
1044
- eqPos(w.find(), Pos(0, 2));
1045
- cm.setCursor(0, 1);
1046
- cm.replaceSelection("ay");
1047
- eqPos(w.find(), Pos(0, 4));
1048
- eq(cm.getLine(0), "uayuhiuu");
1049
- }, {value: "uuuu\nuuuuuu"});
1050
-
1051
- testCM("wrappingAndResizing", function(cm) {
1052
- cm.setSize(null, "auto");
1053
- cm.setOption("lineWrapping", true);
1054
- var wrap = cm.getWrapperElement(), h0 = wrap.offsetHeight;
1055
- var doc = "xxx xxx xxx xxx xxx";
1056
- cm.setValue(doc);
1057
- for (var step = 10, w = cm.charCoords(Pos(0, 18), "div").right;; w += step) {
1058
- cm.setSize(w);
1059
- if (wrap.offsetHeight <= h0 * (opera_lt10 ? 1.2 : 1.5)) {
1060
- if (step == 10) { w -= 10; step = 1; }
1061
- else break;
1062
- }
1063
- }
1064
- // Ensure that putting the cursor at the end of the maximally long
1065
- // line doesn't cause wrapping to happen.
1066
- cm.setCursor(Pos(0, doc.length));
1067
- eq(wrap.offsetHeight, h0);
1068
- cm.replaceSelection("x");
1069
- is(wrap.offsetHeight > h0, "wrapping happens");
1070
- // Now add a max-height and, in a document consisting of
1071
- // almost-wrapped lines, go over it so that a scrollbar appears.
1072
- cm.setValue(doc + "\n" + doc + "\n");
1073
- cm.getScrollerElement().style.maxHeight = "100px";
1074
- cm.replaceRange("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n!\n", Pos(2, 0));
1075
- forEach([Pos(0, doc.length), Pos(0, doc.length - 1),
1076
- Pos(0, 0), Pos(1, doc.length), Pos(1, doc.length - 1)],
1077
- function(pos) {
1078
- var coords = cm.charCoords(pos);
1079
- eqPos(pos, cm.coordsChar({left: coords.left + 2, top: coords.top + 5}));
1080
- });
1081
- }, null, ie_lt8);
1082
-
1083
- testCM("measureEndOfLine", function(cm) {
1084
- cm.setSize(null, "auto");
1085
- var inner = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild;
1086
- var lh = inner.offsetHeight;
1087
- for (var step = 10, w = cm.charCoords(Pos(0, 7), "div").right;; w += step) {
1088
- cm.setSize(w);
1089
- if (inner.offsetHeight < 2.5 * lh) {
1090
- if (step == 10) { w -= 10; step = 1; }
1091
- else break;
1092
- }
1093
- }
1094
- cm.setValue(cm.getValue() + "\n\n");
1095
- var endPos = cm.charCoords(Pos(0, 18), "local");
1096
- is(endPos.top > lh * .8, "not at top");
1097
- is(endPos.left > w - 20, "not at right");
1098
- endPos = cm.charCoords(Pos(0, 18));
1099
- eqPos(cm.coordsChar({left: endPos.left, top: endPos.top + 5}), Pos(0, 18));
1100
- }, {mode: "text/html", value: "<!-- foo barrr -->", lineWrapping: true}, ie_lt8 || opera_lt10);
1101
-
1102
- testCM("scrollVerticallyAndHorizontally", function(cm) {
1103
- if (cm.getOption("inputStyle") != "textarea") return;
1104
- cm.setSize(100, 100);
1105
- addDoc(cm, 40, 40);
1106
- cm.setCursor(39);
1107
- var wrap = cm.getWrapperElement(), bar = byClassName(wrap, "CodeMirror-vscrollbar")[0];
1108
- is(bar.offsetHeight < wrap.offsetHeight, "vertical scrollbar limited by horizontal one");
1109
- var cursorBox = byClassName(wrap, "CodeMirror-cursor")[0].getBoundingClientRect();
1110
- var editorBox = wrap.getBoundingClientRect();
1111
- is(cursorBox.bottom < editorBox.top + cm.getScrollerElement().clientHeight,
1112
- "bottom line visible");
1113
- }, {lineNumbers: true});
1114
-
1115
- testCM("moveVstuck", function(cm) {
1116
- var lines = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild, h0 = lines.offsetHeight;
1117
- var val = "fooooooooooooooooooooooooo baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar\n";
1118
- cm.setValue(val);
1119
- for (var w = cm.charCoords(Pos(0, 26), "div").right * 2.8;; w += 5) {
1120
- cm.setSize(w);
1121
- if (lines.offsetHeight <= 3.5 * h0) break;
1122
- }
1123
- cm.setCursor(Pos(0, val.length - 1));
1124
- cm.moveV(-1, "line");
1125
- eqPos(cm.getCursor(), Pos(0, 26));
1126
- }, {lineWrapping: true}, ie_lt8 || opera_lt10);
1127
-
1128
- testCM("collapseOnMove", function(cm) {
1129
- cm.setSelection(Pos(0, 1), Pos(2, 4));
1130
- cm.execCommand("goLineUp");
1131
- is(!cm.somethingSelected());
1132
- eqPos(cm.getCursor(), Pos(0, 1));
1133
- cm.setSelection(Pos(0, 1), Pos(2, 4));
1134
- cm.execCommand("goPageDown");
1135
- is(!cm.somethingSelected());
1136
- eqPos(cm.getCursor(), Pos(2, 4));
1137
- cm.execCommand("goLineUp");
1138
- cm.execCommand("goLineUp");
1139
- eqPos(cm.getCursor(), Pos(0, 4));
1140
- cm.setSelection(Pos(0, 1), Pos(2, 4));
1141
- cm.execCommand("goCharLeft");
1142
- is(!cm.somethingSelected());
1143
- eqPos(cm.getCursor(), Pos(0, 1));
1144
- }, {value: "aaaaa\nb\nccccc"});
1145
-
1146
- testCM("clickTab", function(cm) {
1147
- var p0 = cm.charCoords(Pos(0, 0));
1148
- eqPos(cm.coordsChar({left: p0.left + 5, top: p0.top + 5}), Pos(0, 0));
1149
- eqPos(cm.coordsChar({left: p0.right - 5, top: p0.top + 5}), Pos(0, 1));
1150
- }, {value: "\t\n\n", lineWrapping: true, tabSize: 8});
1151
-
1152
- testCM("verticalScroll", function(cm) {
1153
- cm.setSize(100, 200);
1154
- cm.setValue("foo\nbar\nbaz\n");
1155
- var sc = cm.getScrollerElement(), baseWidth = sc.scrollWidth;
1156
- cm.replaceRange("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah", Pos(0, 0), Pos(0));
1157
- is(sc.scrollWidth > baseWidth, "scrollbar present");
1158
- cm.replaceRange("foo", Pos(0, 0), Pos(0));
1159
- if (!phantom) eq(sc.scrollWidth, baseWidth, "scrollbar gone");
1160
- cm.replaceRange("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah", Pos(0, 0), Pos(0));
1161
- cm.replaceRange("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbh", Pos(1, 0), Pos(1));
1162
- is(sc.scrollWidth > baseWidth, "present again");
1163
- var curWidth = sc.scrollWidth;
1164
- cm.replaceRange("foo", Pos(0, 0), Pos(0));
1165
- is(sc.scrollWidth < curWidth, "scrollbar smaller");
1166
- is(sc.scrollWidth > baseWidth, "but still present");
1167
- });
1168
-
1169
- testCM("extraKeys", function(cm) {
1170
- var outcome;
1171
- function fakeKey(expected, code, props) {
1172
- if (typeof code == "string") code = code.charCodeAt(0);
1173
- var e = {type: "keydown", keyCode: code, preventDefault: function(){}, stopPropagation: function(){}};
1174
- if (props) for (var n in props) e[n] = props[n];
1175
- outcome = null;
1176
- cm.triggerOnKeyDown(e);
1177
- eq(outcome, expected);
1178
- }
1179
- CodeMirror.commands.testCommand = function() {outcome = "tc";};
1180
- CodeMirror.commands.goTestCommand = function() {outcome = "gtc";};
1181
- cm.setOption("extraKeys", {"Shift-X": function() {outcome = "sx";},
1182
- "X": function() {outcome = "x";},
1183
- "Ctrl-Alt-U": function() {outcome = "cau";},
1184
- "End": "testCommand",
1185
- "Home": "goTestCommand",
1186
- "Tab": false});
1187
- fakeKey(null, "U");
1188
- fakeKey("cau", "U", {ctrlKey: true, altKey: true});
1189
- fakeKey(null, "U", {shiftKey: true, ctrlKey: true, altKey: true});
1190
- fakeKey("x", "X");
1191
- fakeKey("sx", "X", {shiftKey: true});
1192
- fakeKey("tc", 35);
1193
- fakeKey(null, 35, {shiftKey: true});
1194
- fakeKey("gtc", 36);
1195
- fakeKey("gtc", 36, {shiftKey: true});
1196
- fakeKey(null, 9);
1197
- }, null, window.opera && mac);
1198
-
1199
- testCM("wordMovementCommands", function(cm) {
1200
- cm.execCommand("goWordLeft");
1201
- eqPos(cm.getCursor(), Pos(0, 0));
1202
- cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
1203
- eqPos(cm.getCursor(), Pos(0, 7));
1204
- cm.execCommand("goWordLeft");
1205
- eqPos(cm.getCursor(), Pos(0, 5));
1206
- cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
1207
- eqPos(cm.getCursor(), Pos(0, 12));
1208
- cm.execCommand("goWordLeft");
1209
- eqPos(cm.getCursor(), Pos(0, 9));
1210
- cm.execCommand("goWordRight"); cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
1211
- eqPos(cm.getCursor(), Pos(0, 24));
1212
- cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
1213
- eqPos(cm.getCursor(), Pos(1, 9));
1214
- cm.execCommand("goWordRight");
1215
- eqPos(cm.getCursor(), Pos(1, 13));
1216
- cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
1217
- eqPos(cm.getCursor(), Pos(2, 0));
1218
- }, {value: "this is (the) firstline.\na foo12\u00e9\u00f8\u00d7bar\n"});
1219
-
1220
- testCM("groupMovementCommands", function(cm) {
1221
- cm.execCommand("goGroupLeft");
1222
- eqPos(cm.getCursor(), Pos(0, 0));
1223
- cm.execCommand("goGroupRight");
1224
- eqPos(cm.getCursor(), Pos(0, 4));
1225
- cm.execCommand("goGroupRight");
1226
- eqPos(cm.getCursor(), Pos(0, 7));
1227
- cm.execCommand("goGroupRight");
1228
- eqPos(cm.getCursor(), Pos(0, 10));
1229
- cm.execCommand("goGroupLeft");
1230
- eqPos(cm.getCursor(), Pos(0, 7));
1231
- cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight");
1232
- eqPos(cm.getCursor(), Pos(0, 15));
1233
- cm.setCursor(Pos(0, 17));
1234
- cm.execCommand("goGroupLeft");
1235
- eqPos(cm.getCursor(), Pos(0, 16));
1236
- cm.execCommand("goGroupLeft");
1237
- eqPos(cm.getCursor(), Pos(0, 14));
1238
- cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight");
1239
- eqPos(cm.getCursor(), Pos(0, 20));
1240
- cm.execCommand("goGroupRight");
1241
- eqPos(cm.getCursor(), Pos(1, 0));
1242
- cm.execCommand("goGroupRight");
1243
- eqPos(cm.getCursor(), Pos(1, 2));
1244
- cm.execCommand("goGroupRight");
1245
- eqPos(cm.getCursor(), Pos(1, 5));
1246
- cm.execCommand("goGroupLeft"); cm.execCommand("goGroupLeft");
1247
- eqPos(cm.getCursor(), Pos(1, 0));
1248
- cm.execCommand("goGroupLeft");
1249
- eqPos(cm.getCursor(), Pos(0, 20));
1250
- cm.execCommand("goGroupLeft");
1251
- eqPos(cm.getCursor(), Pos(0, 16));
1252
- }, {value: "booo ba---quux. ffff\n abc d"});
1253
-
1254
- testCM("groupsAndWhitespace", function(cm) {
1255
- var positions = [Pos(0, 0), Pos(0, 2), Pos(0, 5), Pos(0, 9), Pos(0, 11),
1256
- Pos(1, 0), Pos(1, 2), Pos(1, 5)];
1257
- for (var i = 1; i < positions.length; i++) {
1258
- cm.execCommand("goGroupRight");
1259
- eqPos(cm.getCursor(), positions[i]);
1260
- }
1261
- for (var i = positions.length - 2; i >= 0; i--) {
1262
- cm.execCommand("goGroupLeft");
1263
- eqPos(cm.getCursor(), i == 2 ? Pos(0, 6) : positions[i]);
1264
- }
1265
- }, {value: " foo +++ \n bar"});
1266
-
1267
- testCM("charMovementCommands", function(cm) {
1268
- cm.execCommand("goCharLeft"); cm.execCommand("goColumnLeft");
1269
- eqPos(cm.getCursor(), Pos(0, 0));
1270
- cm.execCommand("goCharRight"); cm.execCommand("goCharRight");
1271
- eqPos(cm.getCursor(), Pos(0, 2));
1272
- cm.setCursor(Pos(1, 0));
1273
- cm.execCommand("goColumnLeft");
1274
- eqPos(cm.getCursor(), Pos(1, 0));
1275
- cm.execCommand("goCharLeft");
1276
- eqPos(cm.getCursor(), Pos(0, 5));
1277
- cm.execCommand("goColumnRight");
1278
- eqPos(cm.getCursor(), Pos(0, 5));
1279
- cm.execCommand("goCharRight");
1280
- eqPos(cm.getCursor(), Pos(1, 0));
1281
- cm.execCommand("goLineEnd");
1282
- eqPos(cm.getCursor(), Pos(1, 5));
1283
- cm.execCommand("goLineStartSmart");
1284
- eqPos(cm.getCursor(), Pos(1, 1));
1285
- cm.execCommand("goLineStartSmart");
1286
- eqPos(cm.getCursor(), Pos(1, 0));
1287
- cm.setCursor(Pos(2, 0));
1288
- cm.execCommand("goCharRight"); cm.execCommand("goColumnRight");
1289
- eqPos(cm.getCursor(), Pos(2, 0));
1290
- }, {value: "line1\n ine2\n"});
1291
-
1292
- testCM("verticalMovementCommands", function(cm) {
1293
- cm.execCommand("goLineUp");
1294
- eqPos(cm.getCursor(), Pos(0, 0));
1295
- cm.execCommand("goLineDown");
1296
- if (!phantom) // This fails in PhantomJS, though not in a real Webkit
1297
- eqPos(cm.getCursor(), Pos(1, 0));
1298
- cm.setCursor(Pos(1, 12));
1299
- cm.execCommand("goLineDown");
1300
- eqPos(cm.getCursor(), Pos(2, 5));
1301
- cm.execCommand("goLineDown");
1302
- eqPos(cm.getCursor(), Pos(3, 0));
1303
- cm.execCommand("goLineUp");
1304
- eqPos(cm.getCursor(), Pos(2, 5));
1305
- cm.execCommand("goLineUp");
1306
- eqPos(cm.getCursor(), Pos(1, 12));
1307
- cm.execCommand("goPageDown");
1308
- eqPos(cm.getCursor(), Pos(5, 0));
1309
- cm.execCommand("goPageDown"); cm.execCommand("goLineDown");
1310
- eqPos(cm.getCursor(), Pos(5, 0));
1311
- cm.execCommand("goPageUp");
1312
- eqPos(cm.getCursor(), Pos(0, 0));
1313
- }, {value: "line1\nlong long line2\nline3\n\nline5\n"});
1314
-
1315
- testCM("verticalMovementCommandsWrapping", function(cm) {
1316
- cm.setSize(120);
1317
- cm.setCursor(Pos(0, 5));
1318
- cm.execCommand("goLineDown");
1319
- eq(cm.getCursor().line, 0);
1320
- is(cm.getCursor().ch > 5, "moved beyond wrap");
1321
- for (var i = 0; ; ++i) {
1322
- is(i < 20, "no endless loop");
1323
- cm.execCommand("goLineDown");
1324
- var cur = cm.getCursor();
1325
- if (cur.line == 1) eq(cur.ch, 5);
1326
- if (cur.line == 2) { eq(cur.ch, 1); break; }
1327
- }
1328
- }, {value: "a very long line that wraps around somehow so that we can test cursor movement\nshortone\nk",
1329
- lineWrapping: true});
1330
-
1331
- testCM("rtlMovement", function(cm) {
1332
- if (cm.getOption("inputStyle") != "textarea") return;
1333
- forEach(["خحج", "خحabcخحج", "abخحخحجcd", "abخde", "abخح2342خ1حج", "خ1ح2خح3حxج",
1334
- "خحcd", "1خحcd", "abcdeح1ج", "خمرحبها مها!", "foobarر", "خ ة ق",
1335
- "<img src=\"/בדיקה3.jpg\">", "يتم السحب في 05 فبراير 2014"], function(line) {
1336
- var inv = line.charCodeAt(0) > 128;
1337
- cm.setValue(line + "\n"); cm.execCommand(inv ? "goLineEnd" : "goLineStart");
1338
- var cursors = byClassName(cm.getWrapperElement(), "CodeMirror-cursors")[0];
1339
- var cursor = cursors.firstChild;
1340
- var prevX = cursor.offsetLeft, prevY = cursor.offsetTop;
1341
- for (var i = 0; i <= line.length; ++i) {
1342
- cm.execCommand("goCharRight");
1343
- cursor = cursors.firstChild;
1344
- if (i == line.length) is(cursor.offsetTop > prevY, "next line");
1345
- else is(cursor.offsetLeft > prevX, "moved right");
1346
- prevX = cursor.offsetLeft; prevY = cursor.offsetTop;
1347
- }
1348
- cm.setCursor(0, 0); cm.execCommand(inv ? "goLineStart" : "goLineEnd");
1349
- prevX = cursors.firstChild.offsetLeft;
1350
- for (var i = 0; i < line.length; ++i) {
1351
- cm.execCommand("goCharLeft");
1352
- cursor = cursors.firstChild;
1353
- is(cursor.offsetLeft < prevX, "moved left");
1354
- prevX = cursor.offsetLeft;
1355
- }
1356
- });
1357
- }, null, ie_lt9);
1358
-
1359
- // Verify that updating a line clears its bidi ordering
1360
- testCM("bidiUpdate", function(cm) {
1361
- cm.setCursor(Pos(0, 2));
1362
- cm.replaceSelection("خحج", "start");
1363
- cm.execCommand("goCharRight");
1364
- eqPos(cm.getCursor(), Pos(0, 4));
1365
- }, {value: "abcd\n"});
1366
-
1367
- testCM("movebyTextUnit", function(cm) {
1368
- cm.setValue("בְּרֵאשִ\nééé́\n");
1369
- cm.execCommand("goLineEnd");
1370
- for (var i = 0; i < 4; ++i) cm.execCommand("goCharRight");
1371
- eqPos(cm.getCursor(), Pos(0, 0));
1372
- cm.execCommand("goCharRight");
1373
- eqPos(cm.getCursor(), Pos(1, 0));
1374
- cm.execCommand("goCharRight");
1375
- cm.execCommand("goCharRight");
1376
- eqPos(cm.getCursor(), Pos(1, 4));
1377
- cm.execCommand("goCharRight");
1378
- eqPos(cm.getCursor(), Pos(1, 7));
1379
- });
1380
-
1381
- testCM("lineChangeEvents", function(cm) {
1382
- addDoc(cm, 3, 5);
1383
- var log = [], want = ["ch 0", "ch 1", "del 2", "ch 0", "ch 0", "del 1", "del 3", "del 4"];
1384
- for (var i = 0; i < 5; ++i) {
1385
- CodeMirror.on(cm.getLineHandle(i), "delete", function(i) {
1386
- return function() {log.push("del " + i);};
1387
- }(i));
1388
- CodeMirror.on(cm.getLineHandle(i), "change", function(i) {
1389
- return function() {log.push("ch " + i);};
1390
- }(i));
1391
- }
1392
- cm.replaceRange("x", Pos(0, 1));
1393
- cm.replaceRange("xy", Pos(1, 1), Pos(2));
1394
- cm.replaceRange("foo\nbar", Pos(0, 1));
1395
- cm.replaceRange("", Pos(0, 0), Pos(cm.lineCount()));
1396
- eq(log.length, want.length, "same length");
1397
- for (var i = 0; i < log.length; ++i)
1398
- eq(log[i], want[i]);
1399
- });
1400
-
1401
- testCM("scrollEntirelyToRight", function(cm) {
1402
- if (phantom || cm.getOption("inputStyle") != "textarea") return;
1403
- addDoc(cm, 500, 2);
1404
- cm.setCursor(Pos(0, 500));
1405
- var wrap = cm.getWrapperElement(), cur = byClassName(wrap, "CodeMirror-cursor")[0];
1406
- is(wrap.getBoundingClientRect().right > cur.getBoundingClientRect().left);
1407
- });
1408
-
1409
- testCM("lineWidgets", function(cm) {
1410
- addDoc(cm, 500, 3);
1411
- var last = cm.charCoords(Pos(2, 0));
1412
- var node = document.createElement("div");
1413
- node.innerHTML = "hi";
1414
- var widget = cm.addLineWidget(1, node);
1415
- is(last.top < cm.charCoords(Pos(2, 0)).top, "took up space");
1416
- cm.setCursor(Pos(1, 1));
1417
- cm.execCommand("goLineDown");
1418
- eqPos(cm.getCursor(), Pos(2, 1));
1419
- cm.execCommand("goLineUp");
1420
- eqPos(cm.getCursor(), Pos(1, 1));
1421
- });
1422
-
1423
- testCM("lineWidgetFocus", function(cm) {
1424
- var place = document.getElementById("testground");
1425
- place.className = "offscreen";
1426
- try {
1427
- addDoc(cm, 500, 10);
1428
- var node = document.createElement("input");
1429
- var widget = cm.addLineWidget(1, node);
1430
- node.focus();
1431
- eq(document.activeElement, node);
1432
- cm.replaceRange("new stuff", Pos(1, 0));
1433
- eq(document.activeElement, node);
1434
- } finally {
1435
- place.className = "";
1436
- }
1437
- });
1438
-
1439
- testCM("lineWidgetCautiousRedraw", function(cm) {
1440
- var node = document.createElement("div");
1441
- node.innerHTML = "hahah";
1442
- var w = cm.addLineWidget(0, node);
1443
- var redrawn = false;
1444
- w.on("redraw", function() { redrawn = true; });
1445
- cm.replaceSelection("0");
1446
- is(!redrawn);
1447
- }, {value: "123\n456"});
1448
-
1449
-
1450
- var knownScrollbarWidth;
1451
- function scrollbarWidth(measure) {
1452
- if (knownScrollbarWidth != null) return knownScrollbarWidth;
1453
- var div = document.createElement('div');
1454
- div.style.cssText = "width: 50px; height: 50px; overflow-x: scroll";
1455
- document.body.appendChild(div);
1456
- knownScrollbarWidth = div.offsetHeight - div.clientHeight;
1457
- document.body.removeChild(div);
1458
- return knownScrollbarWidth || 0;
1459
- }
1460
-
1461
- testCM("lineWidgetChanged", function(cm) {
1462
- addDoc(cm, 2, 300);
1463
- var halfScrollbarWidth = scrollbarWidth(cm.display.measure)/2;
1464
- cm.setOption('lineNumbers', true);
1465
- cm.setSize(600, cm.defaultTextHeight() * 50);
1466
- cm.scrollTo(null, cm.heightAtLine(125, "local"));
1467
-
1468
- var expectedWidgetHeight = 60;
1469
- var expectedLinesInWidget = 3;
1470
- function w() {
1471
- var node = document.createElement("div");
1472
- // we use these children with just under half width of the line to check measurements are made with correct width
1473
- // when placed in the measure div.
1474
- // If the widget is measured at a width much narrower than it is displayed at, the underHalf children will span two lines and break the test.
1475
- // If the widget is measured at a width much wider than it is displayed at, the overHalf children will combine and break the test.
1476
- // Note that this test only checks widgets where coverGutter is true, because these require extra styling to get the width right.
1477
- // It may also be worthwhile to check this for non-coverGutter widgets.
1478
- // Visually:
1479
- // Good:
1480
- // | ------------- display width ------------- |
1481
- // | ------- widget-width when measured ------ |
1482
- // | | -- under-half -- | | -- under-half -- | |
1483
- // | | --- over-half --- | |
1484
- // | | --- over-half --- | |
1485
- // Height: measured as 3 lines, same as it will be when actually displayed
1486
-
1487
- // Bad (too narrow):
1488
- // | ------------- display width ------------- |
1489
- // | ------ widget-width when measured ----- | < -- uh oh
1490
- // | | -- under-half -- | |
1491
- // | | -- under-half -- | | < -- when measured, shoved to next line
1492
- // | | --- over-half --- | |
1493
- // | | --- over-half --- | |
1494
- // Height: measured as 4 lines, more than expected . Will be displayed as 3 lines!
1495
-
1496
- // Bad (too wide):
1497
- // | ------------- display width ------------- |
1498
- // | -------- widget-width when measured ------- | < -- uh oh
1499
- // | | -- under-half -- | | -- under-half -- | |
1500
- // | | --- over-half --- | | --- over-half --- | | < -- when measured, combined on one line
1501
- // Height: measured as 2 lines, less than expected. Will be displayed as 3 lines!
1502
-
1503
- var barelyUnderHalfWidthHtml = '<div style="display: inline-block; height: 1px; width: '+(285 - halfScrollbarWidth)+'px;"></div>';
1504
- var barelyOverHalfWidthHtml = '<div style="display: inline-block; height: 1px; width: '+(305 - halfScrollbarWidth)+'px;"></div>';
1505
- node.innerHTML = new Array(3).join(barelyUnderHalfWidthHtml) + new Array(3).join(barelyOverHalfWidthHtml);
1506
- node.style.cssText = "background: yellow;font-size:0;line-height: " + (expectedWidgetHeight/expectedLinesInWidget) + "px;";
1507
- return node;
1508
- }
1509
- var info0 = cm.getScrollInfo();
1510
- var w0 = cm.addLineWidget(0, w(), { coverGutter: true });
1511
- var w150 = cm.addLineWidget(150, w(), { coverGutter: true });
1512
- var w300 = cm.addLineWidget(300, w(), { coverGutter: true });
1513
- var info1 = cm.getScrollInfo();
1514
- eq(info0.height + (3 * expectedWidgetHeight), info1.height);
1515
- eq(info0.top + expectedWidgetHeight, info1.top);
1516
- expectedWidgetHeight = 12;
1517
- w0.node.style.lineHeight = w150.node.style.lineHeight = w300.node.style.lineHeight = (expectedWidgetHeight/expectedLinesInWidget) + "px";
1518
- w0.changed(); w150.changed(); w300.changed();
1519
- var info2 = cm.getScrollInfo();
1520
- eq(info0.height + (3 * expectedWidgetHeight), info2.height);
1521
- eq(info0.top + expectedWidgetHeight, info2.top);
1522
- });
1523
-
1524
- testCM("getLineNumber", function(cm) {
1525
- addDoc(cm, 2, 20);
1526
- var h1 = cm.getLineHandle(1);
1527
- eq(cm.getLineNumber(h1), 1);
1528
- cm.replaceRange("hi\nbye\n", Pos(0, 0));
1529
- eq(cm.getLineNumber(h1), 3);
1530
- cm.setValue("");
1531
- eq(cm.getLineNumber(h1), null);
1532
- });
1533
-
1534
- testCM("jumpTheGap", function(cm) {
1535
- if (phantom) return;
1536
- var longLine = "abcdef ghiklmnop qrstuvw xyz ";
1537
- longLine += longLine; longLine += longLine; longLine += longLine;
1538
- cm.replaceRange(longLine, Pos(2, 0), Pos(2));
1539
- cm.setSize("200px", null);
1540
- cm.getWrapperElement().style.lineHeight = 2;
1541
- cm.refresh();
1542
- cm.setCursor(Pos(0, 1));
1543
- cm.execCommand("goLineDown");
1544
- eqPos(cm.getCursor(), Pos(1, 1));
1545
- cm.execCommand("goLineDown");
1546
- eqPos(cm.getCursor(), Pos(2, 1));
1547
- cm.execCommand("goLineDown");
1548
- eq(cm.getCursor().line, 2);
1549
- is(cm.getCursor().ch > 1);
1550
- cm.execCommand("goLineUp");
1551
- eqPos(cm.getCursor(), Pos(2, 1));
1552
- cm.execCommand("goLineUp");
1553
- eqPos(cm.getCursor(), Pos(1, 1));
1554
- var node = document.createElement("div");
1555
- node.innerHTML = "hi"; node.style.height = "30px";
1556
- cm.addLineWidget(0, node);
1557
- cm.addLineWidget(1, node.cloneNode(true), {above: true});
1558
- cm.setCursor(Pos(0, 2));
1559
- cm.execCommand("goLineDown");
1560
- eqPos(cm.getCursor(), Pos(1, 2));
1561
- cm.execCommand("goLineUp");
1562
- eqPos(cm.getCursor(), Pos(0, 2));
1563
- }, {lineWrapping: true, value: "abc\ndef\nghi\njkl\n"});
1564
-
1565
- testCM("addLineClass", function(cm) {
1566
- function cls(line, text, bg, wrap, gutter) {
1567
- var i = cm.lineInfo(line);
1568
- eq(i.textClass, text);
1569
- eq(i.bgClass, bg);
1570
- eq(i.wrapClass, wrap);
1571
- if (typeof i.handle.gutterClass !== 'undefined') {
1572
- eq(i.handle.gutterClass, gutter);
1573
- }
1574
- }
1575
- cm.addLineClass(0, "text", "foo");
1576
- cm.addLineClass(0, "text", "bar");
1577
- cm.addLineClass(1, "background", "baz");
1578
- cm.addLineClass(1, "wrap", "foo");
1579
- cm.addLineClass(1, "gutter", "gutter-class");
1580
- cls(0, "foo bar", null, null, null);
1581
- cls(1, null, "baz", "foo", "gutter-class");
1582
- var lines = cm.display.lineDiv;
1583
- eq(byClassName(lines, "foo").length, 2);
1584
- eq(byClassName(lines, "bar").length, 1);
1585
- eq(byClassName(lines, "baz").length, 1);
1586
- eq(byClassName(lines, "gutter-class").length, 2); // Gutter classes are reflected in 2 nodes
1587
- cm.removeLineClass(0, "text", "foo");
1588
- cls(0, "bar", null, null, null);
1589
- cm.removeLineClass(0, "text", "foo");
1590
- cls(0, "bar", null, null, null);
1591
- cm.removeLineClass(0, "text", "bar");
1592
- cls(0, null, null, null);
1593
-
1594
- cm.addLineClass(1, "wrap", "quux");
1595
- cls(1, null, "baz", "foo quux", "gutter-class");
1596
- cm.removeLineClass(1, "wrap");
1597
- cls(1, null, "baz", null, "gutter-class");
1598
- cm.removeLineClass(1, "gutter", "gutter-class");
1599
- eq(byClassName(lines, "gutter-class").length, 0);
1600
- cls(1, null, "baz", null, null);
1601
-
1602
- cm.addLineClass(1, "gutter", "gutter-class");
1603
- cls(1, null, "baz", null, "gutter-class");
1604
- cm.removeLineClass(1, "gutter", "gutter-class");
1605
- cls(1, null, "baz", null, null);
1606
-
1607
- }, {value: "hohoho\n", lineNumbers: true});
1608
-
1609
- testCM("atomicMarker", function(cm) {
1610
- addDoc(cm, 10, 10);
1611
- function atom(ll, cl, lr, cr, li, ri) {
1612
- return cm.markText(Pos(ll, cl), Pos(lr, cr),
1613
- {atomic: true, inclusiveLeft: li, inclusiveRight: ri});
1614
- }
1615
- var m = atom(0, 1, 0, 5);
1616
- cm.setCursor(Pos(0, 1));
1617
- cm.execCommand("goCharRight");
1618
- eqPos(cm.getCursor(), Pos(0, 5));
1619
- cm.execCommand("goCharLeft");
1620
- eqPos(cm.getCursor(), Pos(0, 1));
1621
- m.clear();
1622
- m = atom(0, 0, 0, 5, true);
1623
- eqPos(cm.getCursor(), Pos(0, 5), "pushed out");
1624
- cm.execCommand("goCharLeft");
1625
- eqPos(cm.getCursor(), Pos(0, 5));
1626
- m.clear();
1627
- m = atom(8, 4, 9, 10, false, true);
1628
- cm.setCursor(Pos(9, 8));
1629
- eqPos(cm.getCursor(), Pos(8, 4), "set");
1630
- cm.execCommand("goCharRight");
1631
- eqPos(cm.getCursor(), Pos(8, 4), "char right");
1632
- cm.execCommand("goLineDown");
1633
- eqPos(cm.getCursor(), Pos(8, 4), "line down");
1634
- cm.execCommand("goCharLeft");
1635
- eqPos(cm.getCursor(), Pos(8, 3));
1636
- m.clear();
1637
- m = atom(1, 1, 3, 8);
1638
- cm.setCursor(Pos(0, 0));
1639
- cm.setCursor(Pos(2, 0));
1640
- eqPos(cm.getCursor(), Pos(3, 8));
1641
- cm.execCommand("goCharLeft");
1642
- eqPos(cm.getCursor(), Pos(1, 1));
1643
- cm.execCommand("goCharRight");
1644
- eqPos(cm.getCursor(), Pos(3, 8));
1645
- cm.execCommand("goLineUp");
1646
- eqPos(cm.getCursor(), Pos(1, 1));
1647
- cm.execCommand("goLineDown");
1648
- eqPos(cm.getCursor(), Pos(3, 8));
1649
- cm.execCommand("delCharBefore");
1650
- eq(cm.getValue().length, 80, "del chunk");
1651
- m = atom(3, 0, 5, 5);
1652
- cm.setCursor(Pos(3, 0));
1653
- cm.execCommand("delWordAfter");
1654
- eq(cm.getValue().length, 53, "del chunk");
1655
- });
1656
-
1657
- testCM("selectionBias", function(cm) {
1658
- cm.markText(Pos(0, 1), Pos(0, 3), {atomic: true});
1659
- cm.setCursor(Pos(0, 2));
1660
- eqPos(cm.getCursor(), Pos(0, 3));
1661
- cm.setCursor(Pos(0, 2));
1662
- eqPos(cm.getCursor(), Pos(0, 1));
1663
- cm.setCursor(Pos(0, 2), null, {bias: -1});
1664
- eqPos(cm.getCursor(), Pos(0, 1));
1665
- cm.setCursor(Pos(0, 4));
1666
- cm.setCursor(Pos(0, 2), null, {bias: 1});
1667
- eqPos(cm.getCursor(), Pos(0, 3));
1668
- }, {value: "12345"});
1669
-
1670
- testCM("selectionHomeEnd", function(cm) {
1671
- cm.markText(Pos(1, 0), Pos(1, 1), {atomic: true, inclusiveLeft: true});
1672
- cm.markText(Pos(1, 3), Pos(1, 4), {atomic: true, inclusiveRight: true});
1673
- cm.setCursor(Pos(1, 2));
1674
- cm.execCommand("goLineStart");
1675
- eqPos(cm.getCursor(), Pos(1, 1));
1676
- cm.execCommand("goLineEnd");
1677
- eqPos(cm.getCursor(), Pos(1, 3));
1678
- }, {value: "ab\ncdef\ngh"});
1679
-
1680
- testCM("readOnlyMarker", function(cm) {
1681
- function mark(ll, cl, lr, cr, at) {
1682
- return cm.markText(Pos(ll, cl), Pos(lr, cr),
1683
- {readOnly: true, atomic: at});
1684
- }
1685
- var m = mark(0, 1, 0, 4);
1686
- cm.setCursor(Pos(0, 2));
1687
- cm.replaceSelection("hi", "end");
1688
- eqPos(cm.getCursor(), Pos(0, 2));
1689
- eq(cm.getLine(0), "abcde");
1690
- cm.execCommand("selectAll");
1691
- cm.replaceSelection("oops", "around");
1692
- eq(cm.getValue(), "oopsbcd");
1693
- cm.undo();
1694
- eqPos(m.find().from, Pos(0, 1));
1695
- eqPos(m.find().to, Pos(0, 4));
1696
- m.clear();
1697
- cm.setCursor(Pos(0, 2));
1698
- cm.replaceSelection("hi", "around");
1699
- eq(cm.getLine(0), "abhicde");
1700
- eqPos(cm.getCursor(), Pos(0, 4));
1701
- m = mark(0, 2, 2, 2, true);
1702
- cm.setSelection(Pos(1, 1), Pos(2, 4));
1703
- cm.replaceSelection("t", "end");
1704
- eqPos(cm.getCursor(), Pos(2, 3));
1705
- eq(cm.getLine(2), "klto");
1706
- cm.execCommand("goCharLeft");
1707
- cm.execCommand("goCharLeft");
1708
- eqPos(cm.getCursor(), Pos(0, 2));
1709
- cm.setSelection(Pos(0, 1), Pos(0, 3));
1710
- cm.replaceSelection("xx", "around");
1711
- eqPos(cm.getCursor(), Pos(0, 3));
1712
- eq(cm.getLine(0), "axxhicde");
1713
- }, {value: "abcde\nfghij\nklmno\n"});
1714
-
1715
- testCM("dirtyBit", function(cm) {
1716
- eq(cm.isClean(), true);
1717
- cm.replaceSelection("boo", null, "test");
1718
- eq(cm.isClean(), false);
1719
- cm.undo();
1720
- eq(cm.isClean(), true);
1721
- cm.replaceSelection("boo", null, "test");
1722
- cm.replaceSelection("baz", null, "test");
1723
- cm.undo();
1724
- eq(cm.isClean(), false);
1725
- cm.markClean();
1726
- eq(cm.isClean(), true);
1727
- cm.undo();
1728
- eq(cm.isClean(), false);
1729
- cm.redo();
1730
- eq(cm.isClean(), true);
1731
- });
1732
-
1733
- testCM("changeGeneration", function(cm) {
1734
- cm.replaceSelection("x");
1735
- var softGen = cm.changeGeneration();
1736
- cm.replaceSelection("x");
1737
- cm.undo();
1738
- eq(cm.getValue(), "");
1739
- is(!cm.isClean(softGen));
1740
- cm.replaceSelection("x");
1741
- var hardGen = cm.changeGeneration(true);
1742
- cm.replaceSelection("x");
1743
- cm.undo();
1744
- eq(cm.getValue(), "x");
1745
- is(cm.isClean(hardGen));
1746
- });
1747
-
1748
- testCM("addKeyMap", function(cm) {
1749
- function sendKey(code) {
1750
- cm.triggerOnKeyDown({type: "keydown", keyCode: code,
1751
- preventDefault: function(){}, stopPropagation: function(){}});
1752
- }
1753
-
1754
- sendKey(39);
1755
- eqPos(cm.getCursor(), Pos(0, 1));
1756
- var test = 0;
1757
- var map1 = {Right: function() { ++test; }}, map2 = {Right: function() { test += 10; }}
1758
- cm.addKeyMap(map1);
1759
- sendKey(39);
1760
- eqPos(cm.getCursor(), Pos(0, 1));
1761
- eq(test, 1);
1762
- cm.addKeyMap(map2, true);
1763
- sendKey(39);
1764
- eq(test, 2);
1765
- cm.removeKeyMap(map1);
1766
- sendKey(39);
1767
- eq(test, 12);
1768
- cm.removeKeyMap(map2);
1769
- sendKey(39);
1770
- eq(test, 12);
1771
- eqPos(cm.getCursor(), Pos(0, 2));
1772
- cm.addKeyMap({Right: function() { test = 55; }, name: "mymap"});
1773
- sendKey(39);
1774
- eq(test, 55);
1775
- cm.removeKeyMap("mymap");
1776
- sendKey(39);
1777
- eqPos(cm.getCursor(), Pos(0, 3));
1778
- }, {value: "abc"});
1779
-
1780
- testCM("findPosH", function(cm) {
1781
- forEach([{from: Pos(0, 0), to: Pos(0, 1), by: 1},
1782
- {from: Pos(0, 0), to: Pos(0, 0), by: -1, hitSide: true},
1783
- {from: Pos(0, 0), to: Pos(0, 4), by: 1, unit: "word"},
1784
- {from: Pos(0, 0), to: Pos(0, 8), by: 2, unit: "word"},
1785
- {from: Pos(0, 0), to: Pos(2, 0), by: 20, unit: "word", hitSide: true},
1786
- {from: Pos(0, 7), to: Pos(0, 5), by: -1, unit: "word"},
1787
- {from: Pos(0, 4), to: Pos(0, 8), by: 1, unit: "word"},
1788
- {from: Pos(1, 0), to: Pos(1, 18), by: 3, unit: "word"},
1789
- {from: Pos(1, 22), to: Pos(1, 5), by: -3, unit: "word"},
1790
- {from: Pos(1, 15), to: Pos(1, 10), by: -5},
1791
- {from: Pos(1, 15), to: Pos(1, 10), by: -5, unit: "column"},
1792
- {from: Pos(1, 15), to: Pos(1, 0), by: -50, unit: "column", hitSide: true},
1793
- {from: Pos(1, 15), to: Pos(1, 24), by: 50, unit: "column", hitSide: true},
1794
- {from: Pos(1, 15), to: Pos(2, 0), by: 50, hitSide: true}], function(t) {
1795
- var r = cm.findPosH(t.from, t.by, t.unit || "char");
1796
- eqPos(r, t.to);
1797
- eq(!!r.hitSide, !!t.hitSide);
1798
- });
1799
- }, {value: "line one\nline two.something.other\n"});
1800
-
1801
- testCM("beforeChange", function(cm) {
1802
- cm.on("beforeChange", function(cm, change) {
1803
- var text = [];
1804
- for (var i = 0; i < change.text.length; ++i)
1805
- text.push(change.text[i].replace(/\s/g, "_"));
1806
- change.update(null, null, text);
1807
- });
1808
- cm.setValue("hello, i am a\nnew document\n");
1809
- eq(cm.getValue(), "hello,_i_am_a\nnew_document\n");
1810
- CodeMirror.on(cm.getDoc(), "beforeChange", function(doc, change) {
1811
- if (change.from.line == 0) change.cancel();
1812
- });
1813
- cm.setValue("oops"); // Canceled
1814
- eq(cm.getValue(), "hello,_i_am_a\nnew_document\n");
1815
- cm.replaceRange("hey hey hey", Pos(1, 0), Pos(2, 0));
1816
- eq(cm.getValue(), "hello,_i_am_a\nhey_hey_hey");
1817
- }, {value: "abcdefghijk"});
1818
-
1819
- testCM("beforeChangeUndo", function(cm) {
1820
- cm.replaceRange("hi", Pos(0, 0), Pos(0));
1821
- cm.replaceRange("bye", Pos(0, 0), Pos(0));
1822
- eq(cm.historySize().undo, 2);
1823
- cm.on("beforeChange", function(cm, change) {
1824
- is(!change.update);
1825
- change.cancel();
1826
- });
1827
- cm.undo();
1828
- eq(cm.historySize().undo, 0);
1829
- eq(cm.getValue(), "bye\ntwo");
1830
- }, {value: "one\ntwo"});
1831
-
1832
- testCM("beforeSelectionChange", function(cm) {
1833
- function notAtEnd(cm, pos) {
1834
- var len = cm.getLine(pos.line).length;
1835
- if (!len || pos.ch == len) return Pos(pos.line, pos.ch - 1);
1836
- return pos;
1837
- }
1838
- cm.on("beforeSelectionChange", function(cm, obj) {
1839
- obj.update([{anchor: notAtEnd(cm, obj.ranges[0].anchor),
1840
- head: notAtEnd(cm, obj.ranges[0].head)}]);
1841
- });
1842
-
1843
- addDoc(cm, 10, 10);
1844
- cm.execCommand("goLineEnd");
1845
- eqPos(cm.getCursor(), Pos(0, 9));
1846
- cm.execCommand("selectAll");
1847
- eqPos(cm.getCursor("start"), Pos(0, 0));
1848
- eqPos(cm.getCursor("end"), Pos(9, 9));
1849
- });
1850
-
1851
- testCM("change_removedText", function(cm) {
1852
- cm.setValue("abc\ndef");
1853
-
1854
- var removedText = [];
1855
- cm.on("change", function(cm, change) {
1856
- removedText.push(change.removed);
1857
- });
1858
-
1859
- cm.operation(function() {
1860
- cm.replaceRange("xyz", Pos(0, 0), Pos(1,1));
1861
- cm.replaceRange("123", Pos(0,0));
1862
- });
1863
-
1864
- eq(removedText.length, 2);
1865
- eq(removedText[0].join("\n"), "abc\nd");
1866
- eq(removedText[1].join("\n"), "");
1867
-
1868
- var removedText = [];
1869
- cm.undo();
1870
- eq(removedText.length, 2);
1871
- eq(removedText[0].join("\n"), "123");
1872
- eq(removedText[1].join("\n"), "xyz");
1873
-
1874
- var removedText = [];
1875
- cm.redo();
1876
- eq(removedText.length, 2);
1877
- eq(removedText[0].join("\n"), "abc\nd");
1878
- eq(removedText[1].join("\n"), "");
1879
- });
1880
-
1881
- testCM("lineStyleFromMode", function(cm) {
1882
- CodeMirror.defineMode("test_mode", function() {
1883
- return {token: function(stream) {
1884
- if (stream.match(/^\[[^\]]*\]/)) return " line-brackets ";
1885
- if (stream.match(/^\([^\)]*\)/)) return " line-background-parens ";
1886
- if (stream.match(/^<[^>]*>/)) return " span line-line line-background-bg ";
1887
- stream.match(/^\s+|^\S+/);
1888
- }};
1889
- });
1890
- cm.setOption("mode", "test_mode");
1891
- var bracketElts = byClassName(cm.getWrapperElement(), "brackets");
1892
- eq(bracketElts.length, 1, "brackets count");
1893
- eq(bracketElts[0].nodeName, "PRE");
1894
- is(!/brackets.*brackets/.test(bracketElts[0].className));
1895
- var parenElts = byClassName(cm.getWrapperElement(), "parens");
1896
- eq(parenElts.length, 1, "parens count");
1897
- eq(parenElts[0].nodeName, "DIV");
1898
- is(!/parens.*parens/.test(parenElts[0].className));
1899
- eq(parenElts[0].parentElement.nodeName, "DIV");
1900
-
1901
- eq(byClassName(cm.getWrapperElement(), "bg").length, 1);
1902
- eq(byClassName(cm.getWrapperElement(), "line").length, 1);
1903
- var spanElts = byClassName(cm.getWrapperElement(), "cm-span");
1904
- eq(spanElts.length, 2);
1905
- is(/^\s*cm-span\s*$/.test(spanElts[0].className));
1906
- }, {value: "line1: [br] [br]\nline2: (par) (par)\nline3: <tag> <tag>"});
1907
-
1908
- testCM("lineStyleFromBlankLine", function(cm) {
1909
- CodeMirror.defineMode("lineStyleFromBlankLine_mode", function() {
1910
- return {token: function(stream) { stream.skipToEnd(); return "comment"; },
1911
- blankLine: function() { return "line-blank"; }};
1912
- });
1913
- cm.setOption("mode", "lineStyleFromBlankLine_mode");
1914
- var blankElts = byClassName(cm.getWrapperElement(), "blank");
1915
- eq(blankElts.length, 1);
1916
- eq(blankElts[0].nodeName, "PRE");
1917
- cm.replaceRange("x", Pos(1, 0));
1918
- blankElts = byClassName(cm.getWrapperElement(), "blank");
1919
- eq(blankElts.length, 0);
1920
- }, {value: "foo\n\nbar"});
1921
-
1922
- CodeMirror.registerHelper("xxx", "a", "A");
1923
- CodeMirror.registerHelper("xxx", "b", "B");
1924
- CodeMirror.defineMode("yyy", function() {
1925
- return {
1926
- token: function(stream) { stream.skipToEnd(); },
1927
- xxx: ["a", "b", "q"]
1928
- };
1929
- });
1930
- CodeMirror.registerGlobalHelper("xxx", "c", function(m) { return m.enableC; }, "C");
1931
-
1932
- testCM("helpers", function(cm) {
1933
- cm.setOption("mode", "yyy");
1934
- eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "A/B");
1935
- cm.setOption("mode", {name: "yyy", modeProps: {xxx: "b", enableC: true}});
1936
- eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "B/C");
1937
- cm.setOption("mode", "javascript");
1938
- eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "");
1939
- });
1940
-
1941
- testCM("selectionHistory", function(cm) {
1942
- for (var i = 0; i < 3; i++) {
1943
- cm.setExtending(true);
1944
- cm.execCommand("goCharRight");
1945
- cm.setExtending(false);
1946
- cm.execCommand("goCharRight");
1947
- cm.execCommand("goCharRight");
1948
- }
1949
- cm.execCommand("undoSelection");
1950
- eq(cm.getSelection(), "c");
1951
- cm.execCommand("undoSelection");
1952
- eq(cm.getSelection(), "");
1953
- eqPos(cm.getCursor(), Pos(0, 4));
1954
- cm.execCommand("undoSelection");
1955
- eq(cm.getSelection(), "b");
1956
- cm.execCommand("redoSelection");
1957
- eq(cm.getSelection(), "");
1958
- eqPos(cm.getCursor(), Pos(0, 4));
1959
- cm.execCommand("redoSelection");
1960
- eq(cm.getSelection(), "c");
1961
- cm.execCommand("redoSelection");
1962
- eq(cm.getSelection(), "");
1963
- eqPos(cm.getCursor(), Pos(0, 6));
1964
- }, {value: "a b c d"});
1965
-
1966
- testCM("selectionChangeReducesRedo", function(cm) {
1967
- cm.replaceSelection("X");
1968
- cm.execCommand("goCharRight");
1969
- cm.undoSelection();
1970
- cm.execCommand("selectAll");
1971
- cm.undoSelection();
1972
- eq(cm.getValue(), "Xabc");
1973
- eqPos(cm.getCursor(), Pos(0, 1));
1974
- cm.undoSelection();
1975
- eq(cm.getValue(), "abc");
1976
- }, {value: "abc"});
1977
-
1978
- testCM("selectionHistoryNonOverlapping", function(cm) {
1979
- cm.setSelection(Pos(0, 0), Pos(0, 1));
1980
- cm.setSelection(Pos(0, 2), Pos(0, 3));
1981
- cm.execCommand("undoSelection");
1982
- eqPos(cm.getCursor("anchor"), Pos(0, 0));
1983
- eqPos(cm.getCursor("head"), Pos(0, 1));
1984
- }, {value: "1234"});
1985
-
1986
- testCM("cursorMotionSplitsHistory", function(cm) {
1987
- cm.replaceSelection("a");
1988
- cm.execCommand("goCharRight");
1989
- cm.replaceSelection("b");
1990
- cm.replaceSelection("c");
1991
- cm.undo();
1992
- eq(cm.getValue(), "a1234");
1993
- eqPos(cm.getCursor(), Pos(0, 2));
1994
- cm.undo();
1995
- eq(cm.getValue(), "1234");
1996
- eqPos(cm.getCursor(), Pos(0, 0));
1997
- }, {value: "1234"});
1998
-
1999
- testCM("selChangeInOperationDoesNotSplit", function(cm) {
2000
- for (var i = 0; i < 4; i++) {
2001
- cm.operation(function() {
2002
- cm.replaceSelection("x");
2003
- cm.setCursor(Pos(0, cm.getCursor().ch - 1));
2004
- });
2005
- }
2006
- eqPos(cm.getCursor(), Pos(0, 0));
2007
- eq(cm.getValue(), "xxxxa");
2008
- cm.undo();
2009
- eq(cm.getValue(), "a");
2010
- }, {value: "a"});
2011
-
2012
- testCM("alwaysMergeSelEventWithChangeOrigin", function(cm) {
2013
- cm.replaceSelection("U", null, "foo");
2014
- cm.setSelection(Pos(0, 0), Pos(0, 1), {origin: "foo"});
2015
- cm.undoSelection();
2016
- eq(cm.getValue(), "a");
2017
- cm.replaceSelection("V", null, "foo");
2018
- cm.setSelection(Pos(0, 0), Pos(0, 1), {origin: "bar"});
2019
- cm.undoSelection();
2020
- eq(cm.getValue(), "Va");
2021
- }, {value: "a"});
2022
-
2023
- testCM("getTokenAt", function(cm) {
2024
- var tokPlus = cm.getTokenAt(Pos(0, 2));
2025
- eq(tokPlus.type, "operator");
2026
- eq(tokPlus.string, "+");
2027
- var toks = cm.getLineTokens(0);
2028
- eq(toks.length, 3);
2029
- forEach([["number", "1"], ["operator", "+"], ["number", "2"]], function(expect, i) {
2030
- eq(toks[i].type, expect[0]);
2031
- eq(toks[i].string, expect[1]);
2032
- });
2033
- }, {value: "1+2", mode: "javascript"});
2034
-
2035
- testCM("getTokenTypeAt", function(cm) {
2036
- eq(cm.getTokenTypeAt(Pos(0, 0)), "number");
2037
- eq(cm.getTokenTypeAt(Pos(0, 6)), "string");
2038
- cm.addOverlay({
2039
- token: function(stream) {
2040
- if (stream.match("foo")) return "foo";
2041
- else stream.next();
2042
- }
2043
- });
2044
- eq(byClassName(cm.getWrapperElement(), "cm-foo").length, 1);
2045
- eq(cm.getTokenTypeAt(Pos(0, 6)), "string");
2046
- }, {value: "1 + 'foo'", mode: "javascript"});
2047
-
2048
- testCM("resizeLineWidget", function(cm) {
2049
- addDoc(cm, 200, 3);
2050
- var widget = document.createElement("pre");
2051
- widget.innerHTML = "imwidget";
2052
- widget.style.background = "yellow";
2053
- cm.addLineWidget(1, widget, {noHScroll: true});
2054
- cm.setSize(40);
2055
- is(widget.parentNode.offsetWidth < 42);
2056
- });
2057
-
2058
- testCM("combinedOperations", function(cm) {
2059
- var place = document.getElementById("testground");
2060
- var other = CodeMirror(place, {value: "123"});
2061
- try {
2062
- cm.operation(function() {
2063
- cm.addLineClass(0, "wrap", "foo");
2064
- other.addLineClass(0, "wrap", "foo");
2065
- });
2066
- eq(byClassName(cm.getWrapperElement(), "foo").length, 1);
2067
- eq(byClassName(other.getWrapperElement(), "foo").length, 1);
2068
- cm.operation(function() {
2069
- cm.removeLineClass(0, "wrap", "foo");
2070
- other.removeLineClass(0, "wrap", "foo");
2071
- });
2072
- eq(byClassName(cm.getWrapperElement(), "foo").length, 0);
2073
- eq(byClassName(other.getWrapperElement(), "foo").length, 0);
2074
- } finally {
2075
- place.removeChild(other.getWrapperElement());
2076
- }
2077
- }, {value: "abc"});
2078
-
2079
- testCM("eventOrder", function(cm) {
2080
- var seen = [];
2081
- cm.on("change", function() {
2082
- if (!seen.length) cm.replaceSelection(".");
2083
- seen.push("change");
2084
- });
2085
- cm.on("cursorActivity", function() {
2086
- cm.replaceSelection("!");
2087
- seen.push("activity");
2088
- });
2089
- cm.replaceSelection("/");
2090
- eq(seen.join(","), "change,change,activity,change");
2091
- });
2092
-
2093
- testCM("splitSpaces_nonspecial", function(cm) {
2094
- eq(byClassName(cm.getWrapperElement(), "cm-invalidchar").length, 0);
2095
- }, {
2096
- specialChars: /[\u00a0]/,
2097
- value: "spaces -> <- between"
2098
- });
2099
-
2100
- test("core_rmClass", function() {
2101
- var node = document.createElement("div");
2102
- node.className = "foo-bar baz-quux yadda";
2103
- CodeMirror.rmClass(node, "quux");
2104
- eq(node.className, "foo-bar baz-quux yadda");
2105
- CodeMirror.rmClass(node, "baz-quux");
2106
- eq(node.className, "foo-bar yadda");
2107
- CodeMirror.rmClass(node, "yadda");
2108
- eq(node.className, "foo-bar");
2109
- CodeMirror.rmClass(node, "foo-bar");
2110
- eq(node.className, "");
2111
- node.className = " foo ";
2112
- CodeMirror.rmClass(node, "foo");
2113
- eq(node.className, "");
2114
- });
2115
-
2116
- test("core_addClass", function() {
2117
- var node = document.createElement("div");
2118
- CodeMirror.addClass(node, "a");
2119
- eq(node.className, "a");
2120
- CodeMirror.addClass(node, "a");
2121
- eq(node.className, "a");
2122
- CodeMirror.addClass(node, "b");
2123
- eq(node.className, "a b");
2124
- CodeMirror.addClass(node, "a");
2125
- CodeMirror.addClass(node, "b");
2126
- eq(node.className, "a b");
2127
- });
2128
-
2129
- testCM("lineSeparator", function(cm) {
2130
- eq(cm.lineCount(), 3);
2131
- eq(cm.getLine(1), "bar\r");
2132
- eq(cm.getLine(2), "baz\rquux");
2133
- cm.setOption("lineSeparator", "\r");
2134
- eq(cm.lineCount(), 5);
2135
- eq(cm.getLine(4), "quux");
2136
- eq(cm.getValue(), "foo\rbar\r\rbaz\rquux");
2137
- eq(cm.getValue("\n"), "foo\nbar\n\nbaz\nquux");
2138
- cm.setOption("lineSeparator", null);
2139
- cm.setValue("foo\nbar\r\nbaz\rquux");
2140
- eq(cm.lineCount(), 4);
2141
- }, {value: "foo\nbar\r\nbaz\rquux",
2142
- lineSeparator: "\n"});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/vim_test.js DELETED
@@ -1,3955 +0,0 @@
1
- CodeMirror.Vim.suppressErrorLogging = true;
2
-
3
- var code = '' +
4
- ' wOrd1 (#%\n' +
5
- ' word3] \n' +
6
- 'aopop pop 0 1 2 3 4\n' +
7
- ' (a) [b] {c} \n' +
8
- 'int getchar(void) {\n' +
9
- ' static char buf[BUFSIZ];\n' +
10
- ' static char *bufp = buf;\n' +
11
- ' if (n == 0) { /* buffer is empty */\n' +
12
- ' n = read(0, buf, sizeof buf);\n' +
13
- ' bufp = buf;\n' +
14
- ' }\n' +
15
- '\n' +
16
- ' return (--n >= 0) ? (unsigned char) *bufp++ : EOF;\n' +
17
- ' \n' +
18
- '}\n';
19
-
20
- var lines = (function() {
21
- lineText = code.split('\n');
22
- var ret = [];
23
- for (var i = 0; i < lineText.length; i++) {
24
- ret[i] = {
25
- line: i,
26
- length: lineText[i].length,
27
- lineText: lineText[i],
28
- textStart: /^\s*/.exec(lineText[i])[0].length
29
- };
30
- }
31
- return ret;
32
- })();
33
- var endOfDocument = makeCursor(lines.length - 1,
34
- lines[lines.length - 1].length);
35
- var wordLine = lines[0];
36
- var bigWordLine = lines[1];
37
- var charLine = lines[2];
38
- var bracesLine = lines[3];
39
- var seekBraceLine = lines[4];
40
-
41
- var word1 = {
42
- start: { line: wordLine.line, ch: 1 },
43
- end: { line: wordLine.line, ch: 5 }
44
- };
45
- var word2 = {
46
- start: { line: wordLine.line, ch: word1.end.ch + 2 },
47
- end: { line: wordLine.line, ch: word1.end.ch + 4 }
48
- };
49
- var word3 = {
50
- start: { line: bigWordLine.line, ch: 1 },
51
- end: { line: bigWordLine.line, ch: 5 }
52
- };
53
- var bigWord1 = word1;
54
- var bigWord2 = word2;
55
- var bigWord3 = {
56
- start: { line: bigWordLine.line, ch: 1 },
57
- end: { line: bigWordLine.line, ch: 7 }
58
- };
59
- var bigWord4 = {
60
- start: { line: bigWordLine.line, ch: bigWord1.end.ch + 3 },
61
- end: { line: bigWordLine.line, ch: bigWord1.end.ch + 7 }
62
- };
63
-
64
- var oChars = [ { line: charLine.line, ch: 1 },
65
- { line: charLine.line, ch: 3 },
66
- { line: charLine.line, ch: 7 } ];
67
- var pChars = [ { line: charLine.line, ch: 2 },
68
- { line: charLine.line, ch: 4 },
69
- { line: charLine.line, ch: 6 },
70
- { line: charLine.line, ch: 8 } ];
71
- var numChars = [ { line: charLine.line, ch: 10 },
72
- { line: charLine.line, ch: 12 },
73
- { line: charLine.line, ch: 14 },
74
- { line: charLine.line, ch: 16 },
75
- { line: charLine.line, ch: 18 }];
76
- var parens1 = {
77
- start: { line: bracesLine.line, ch: 1 },
78
- end: { line: bracesLine.line, ch: 3 }
79
- };
80
- var squares1 = {
81
- start: { line: bracesLine.line, ch: 5 },
82
- end: { line: bracesLine.line, ch: 7 }
83
- };
84
- var curlys1 = {
85
- start: { line: bracesLine.line, ch: 9 },
86
- end: { line: bracesLine.line, ch: 11 }
87
- };
88
- var seekOutside = {
89
- start: { line: seekBraceLine.line, ch: 1 },
90
- end: { line: seekBraceLine.line, ch: 16 }
91
- };
92
- var seekInside = {
93
- start: { line: seekBraceLine.line, ch: 14 },
94
- end: { line: seekBraceLine.line, ch: 11 }
95
- };
96
-
97
- function copyCursor(cur) {
98
- return { ch: cur.ch, line: cur.line };
99
- }
100
-
101
- function forEach(arr, func) {
102
- for (var i = 0; i < arr.length; i++) {
103
- func(arr[i], i, arr);
104
- }
105
- }
106
-
107
- function testVim(name, run, opts, expectedFail) {
108
- var vimOpts = {
109
- lineNumbers: true,
110
- vimMode: true,
111
- showCursorWhenSelecting: true,
112
- value: code
113
- };
114
- for (var prop in opts) {
115
- if (opts.hasOwnProperty(prop)) {
116
- vimOpts[prop] = opts[prop];
117
- }
118
- }
119
- return test('vim_' + name, function() {
120
- var place = document.getElementById("testground");
121
- var cm = CodeMirror(place, vimOpts);
122
- var vim = CodeMirror.Vim.maybeInitVimState_(cm);
123
-
124
- function doKeysFn(cm) {
125
- return function(args) {
126
- if (args instanceof Array) {
127
- arguments = args;
128
- }
129
- for (var i = 0; i < arguments.length; i++) {
130
- CodeMirror.Vim.handleKey(cm, arguments[i]);
131
- }
132
- }
133
- }
134
- function doInsertModeKeysFn(cm) {
135
- return function(args) {
136
- if (args instanceof Array) { arguments = args; }
137
- function executeHandler(handler) {
138
- if (typeof handler == 'string') {
139
- CodeMirror.commands[handler](cm);
140
- } else {
141
- handler(cm);
142
- }
143
- return true;
144
- }
145
- for (var i = 0; i < arguments.length; i++) {
146
- var key = arguments[i];
147
- // Find key in keymap and handle.
148
- var handled = CodeMirror.lookupKey(key, 'vim-insert', executeHandler);
149
- // Record for insert mode.
150
- if (handled == "handled" && cm.state.vim.insertMode && arguments[i] != 'Esc') {
151
- var lastChange = CodeMirror.Vim.getVimGlobalState_().macroModeState.lastInsertModeChanges;
152
- if (lastChange) {
153
- lastChange.changes.push(new CodeMirror.Vim.InsertModeKey(key));
154
- }
155
- }
156
- }
157
- }
158
- }
159
- function doExFn(cm) {
160
- return function(command) {
161
- cm.openDialog = helpers.fakeOpenDialog(command);
162
- helpers.doKeys(':');
163
- }
164
- }
165
- function assertCursorAtFn(cm) {
166
- return function(line, ch) {
167
- var pos;
168
- if (ch == null && typeof line.line == 'number') {
169
- pos = line;
170
- } else {
171
- pos = makeCursor(line, ch);
172
- }
173
- eqPos(pos, cm.getCursor());
174
- }
175
- }
176
- function fakeOpenDialog(result) {
177
- return function(text, callback) {
178
- return callback(result);
179
- }
180
- }
181
- function fakeOpenNotification(matcher) {
182
- return function(text) {
183
- matcher(text);
184
- }
185
- }
186
- var helpers = {
187
- doKeys: doKeysFn(cm),
188
- // Warning: Only emulates keymap events, not character insertions. Use
189
- // replaceRange to simulate character insertions.
190
- // Keys are in CodeMirror format, NOT vim format.
191
- doInsertModeKeys: doInsertModeKeysFn(cm),
192
- doEx: doExFn(cm),
193
- assertCursorAt: assertCursorAtFn(cm),
194
- fakeOpenDialog: fakeOpenDialog,
195
- fakeOpenNotification: fakeOpenNotification,
196
- getRegisterController: function() {
197
- return CodeMirror.Vim.getRegisterController();
198
- }
199
- }
200
- CodeMirror.Vim.resetVimGlobalState_();
201
- var successful = false;
202
- var savedOpenNotification = cm.openNotification;
203
- var savedOpenDialog = cm.openDialog;
204
- try {
205
- run(cm, vim, helpers);
206
- successful = true;
207
- } finally {
208
- cm.openNotification = savedOpenNotification;
209
- cm.openDialog = savedOpenDialog;
210
- if (!successful || verbose) {
211
- place.style.visibility = "visible";
212
- } else {
213
- place.removeChild(cm.getWrapperElement());
214
- }
215
- }
216
- }, expectedFail);
217
- };
218
- testVim('qq@q', function(cm, vim, helpers) {
219
- cm.setCursor(0, 0);
220
- helpers.doKeys('q', 'q', 'l', 'l', 'q');
221
- helpers.assertCursorAt(0,2);
222
- helpers.doKeys('@', 'q');
223
- helpers.assertCursorAt(0,4);
224
- }, { value: ' '});
225
- testVim('@@', function(cm, vim, helpers) {
226
- cm.setCursor(0, 0);
227
- helpers.doKeys('q', 'q', 'l', 'l', 'q');
228
- helpers.assertCursorAt(0,2);
229
- helpers.doKeys('@', 'q');
230
- helpers.assertCursorAt(0,4);
231
- helpers.doKeys('@', '@');
232
- helpers.assertCursorAt(0,6);
233
- }, { value: ' '});
234
- var jumplistScene = ''+
235
- 'word\n'+
236
- '(word)\n'+
237
- '{word\n'+
238
- 'word.\n'+
239
- '\n'+
240
- 'word search\n'+
241
- '}word\n'+
242
- 'word\n'+
243
- 'word\n';
244
- function testJumplist(name, keys, endPos, startPos, dialog) {
245
- endPos = makeCursor(endPos[0], endPos[1]);
246
- startPos = makeCursor(startPos[0], startPos[1]);
247
- testVim(name, function(cm, vim, helpers) {
248
- CodeMirror.Vim.resetVimGlobalState_();
249
- if(dialog)cm.openDialog = helpers.fakeOpenDialog('word');
250
- cm.setCursor(startPos);
251
- helpers.doKeys.apply(null, keys);
252
- helpers.assertCursorAt(endPos);
253
- }, {value: jumplistScene});
254
- };
255
- testJumplist('jumplist_H', ['H', '<C-o>'], [5,2], [5,2]);
256
- testJumplist('jumplist_M', ['M', '<C-o>'], [2,2], [2,2]);
257
- testJumplist('jumplist_L', ['L', '<C-o>'], [2,2], [2,2]);
258
- testJumplist('jumplist_[[', ['[', '[', '<C-o>'], [5,2], [5,2]);
259
- testJumplist('jumplist_]]', [']', ']', '<C-o>'], [2,2], [2,2]);
260
- testJumplist('jumplist_G', ['G', '<C-o>'], [5,2], [5,2]);
261
- testJumplist('jumplist_gg', ['g', 'g', '<C-o>'], [5,2], [5,2]);
262
- testJumplist('jumplist_%', ['%', '<C-o>'], [1,5], [1,5]);
263
- testJumplist('jumplist_{', ['{', '<C-o>'], [1,5], [1,5]);
264
- testJumplist('jumplist_}', ['}', '<C-o>'], [1,5], [1,5]);
265
- testJumplist('jumplist_\'', ['m', 'a', 'h', '\'', 'a', 'h', '<C-i>'], [1,0], [1,5]);
266
- testJumplist('jumplist_`', ['m', 'a', 'h', '`', 'a', 'h', '<C-i>'], [1,5], [1,5]);
267
- testJumplist('jumplist_*_cachedCursor', ['*', '<C-o>'], [1,3], [1,3]);
268
- testJumplist('jumplist_#_cachedCursor', ['#', '<C-o>'], [1,3], [1,3]);
269
- testJumplist('jumplist_n', ['#', 'n', '<C-o>'], [1,1], [2,3]);
270
- testJumplist('jumplist_N', ['#', 'N', '<C-o>'], [1,1], [2,3]);
271
- testJumplist('jumplist_repeat_<c-o>', ['*', '*', '*', '3', '<C-o>'], [2,3], [2,3]);
272
- testJumplist('jumplist_repeat_<c-i>', ['*', '*', '*', '3', '<C-o>', '2', '<C-i>'], [5,0], [2,3]);
273
- testJumplist('jumplist_repeated_motion', ['3', '*', '<C-o>'], [2,3], [2,3]);
274
- testJumplist('jumplist_/', ['/', '<C-o>'], [2,3], [2,3], 'dialog');
275
- testJumplist('jumplist_?', ['?', '<C-o>'], [2,3], [2,3], 'dialog');
276
- testJumplist('jumplist_skip_delted_mark<c-o>',
277
- ['*', 'n', 'n', 'k', 'd', 'k', '<C-o>', '<C-o>', '<C-o>'],
278
- [0,2], [0,2]);
279
- testJumplist('jumplist_skip_delted_mark<c-i>',
280
- ['*', 'n', 'n', 'k', 'd', 'k', '<C-o>', '<C-i>', '<C-i>'],
281
- [1,0], [0,2]);
282
-
283
- /**
284
- * @param name Name of the test
285
- * @param keys An array of keys or a string with a single key to simulate.
286
- * @param endPos The expected end position of the cursor.
287
- * @param startPos The position the cursor should start at, defaults to 0, 0.
288
- */
289
- function testMotion(name, keys, endPos, startPos) {
290
- testVim(name, function(cm, vim, helpers) {
291
- if (!startPos) {
292
- startPos = { line: 0, ch: 0 };
293
- }
294
- cm.setCursor(startPos);
295
- helpers.doKeys(keys);
296
- helpers.assertCursorAt(endPos);
297
- });
298
- };
299
-
300
- function makeCursor(line, ch) {
301
- return { line: line, ch: ch };
302
- };
303
-
304
- function offsetCursor(cur, offsetLine, offsetCh) {
305
- return { line: cur.line + offsetLine, ch: cur.ch + offsetCh };
306
- };
307
-
308
- // Motion tests
309
- testMotion('|', '|', makeCursor(0, 0), makeCursor(0,4));
310
- testMotion('|_repeat', ['3', '|'], makeCursor(0, 2), makeCursor(0,4));
311
- testMotion('h', 'h', makeCursor(0, 0), word1.start);
312
- testMotion('h_repeat', ['3', 'h'], offsetCursor(word1.end, 0, -3), word1.end);
313
- testMotion('l', 'l', makeCursor(0, 1));
314
- testMotion('l_repeat', ['2', 'l'], makeCursor(0, 2));
315
- testMotion('j', 'j', offsetCursor(word1.end, 1, 0), word1.end);
316
- testMotion('j_repeat', ['2', 'j'], offsetCursor(word1.end, 2, 0), word1.end);
317
- testMotion('j_repeat_clip', ['1000', 'j'], endOfDocument);
318
- testMotion('k', 'k', offsetCursor(word3.end, -1, 0), word3.end);
319
- testMotion('k_repeat', ['2', 'k'], makeCursor(0, 4), makeCursor(2, 4));
320
- testMotion('k_repeat_clip', ['1000', 'k'], makeCursor(0, 4), makeCursor(2, 4));
321
- testMotion('w', 'w', word1.start);
322
- testMotion('w_multiple_newlines_no_space', 'w', makeCursor(12, 2), makeCursor(11, 2));
323
- testMotion('w_multiple_newlines_with_space', 'w', makeCursor(14, 0), makeCursor(12, 51));
324
- testMotion('w_repeat', ['2', 'w'], word2.start);
325
- testMotion('w_wrap', ['w'], word3.start, word2.start);
326
- testMotion('w_endOfDocument', 'w', endOfDocument, endOfDocument);
327
- testMotion('w_start_to_end', ['1000', 'w'], endOfDocument, makeCursor(0, 0));
328
- testMotion('W', 'W', bigWord1.start);
329
- testMotion('W_repeat', ['2', 'W'], bigWord3.start, bigWord1.start);
330
- testMotion('e', 'e', word1.end);
331
- testMotion('e_repeat', ['2', 'e'], word2.end);
332
- testMotion('e_wrap', 'e', word3.end, word2.end);
333
- testMotion('e_endOfDocument', 'e', endOfDocument, endOfDocument);
334
- testMotion('e_start_to_end', ['1000', 'e'], endOfDocument, makeCursor(0, 0));
335
- testMotion('b', 'b', word3.start, word3.end);
336
- testMotion('b_repeat', ['2', 'b'], word2.start, word3.end);
337
- testMotion('b_wrap', 'b', word2.start, word3.start);
338
- testMotion('b_startOfDocument', 'b', makeCursor(0, 0), makeCursor(0, 0));
339
- testMotion('b_end_to_start', ['1000', 'b'], makeCursor(0, 0), endOfDocument);
340
- testMotion('ge', ['g', 'e'], word2.end, word3.end);
341
- testMotion('ge_repeat', ['2', 'g', 'e'], word1.end, word3.start);
342
- testMotion('ge_wrap', ['g', 'e'], word2.end, word3.start);
343
- testMotion('ge_startOfDocument', ['g', 'e'], makeCursor(0, 0),
344
- makeCursor(0, 0));
345
- testMotion('ge_end_to_start', ['1000', 'g', 'e'], makeCursor(0, 0), endOfDocument);
346
- testMotion('gg', ['g', 'g'], makeCursor(lines[0].line, lines[0].textStart),
347
- makeCursor(3, 1));
348
- testMotion('gg_repeat', ['3', 'g', 'g'],
349
- makeCursor(lines[2].line, lines[2].textStart));
350
- testMotion('G', 'G',
351
- makeCursor(lines[lines.length - 1].line, lines[lines.length - 1].textStart),
352
- makeCursor(3, 1));
353
- testMotion('G_repeat', ['3', 'G'], makeCursor(lines[2].line,
354
- lines[2].textStart));
355
- // TODO: Make the test code long enough to test Ctrl-F and Ctrl-B.
356
- testMotion('0', '0', makeCursor(0, 0), makeCursor(0, 8));
357
- testMotion('^', '^', makeCursor(0, lines[0].textStart), makeCursor(0, 8));
358
- testMotion('+', '+', makeCursor(1, lines[1].textStart), makeCursor(0, 8));
359
- testMotion('-', '-', makeCursor(0, lines[0].textStart), makeCursor(1, 4));
360
- testMotion('_', ['6','_'], makeCursor(5, lines[5].textStart), makeCursor(0, 8));
361
- testMotion('$', '$', makeCursor(0, lines[0].length - 1), makeCursor(0, 1));
362
- testMotion('$_repeat', ['2', '$'], makeCursor(1, lines[1].length - 1),
363
- makeCursor(0, 3));
364
- testMotion('f', ['f', 'p'], pChars[0], makeCursor(charLine.line, 0));
365
- testMotion('f_repeat', ['2', 'f', 'p'], pChars[2], pChars[0]);
366
- testMotion('f_num', ['f', '2'], numChars[2], makeCursor(charLine.line, 0));
367
- testMotion('t', ['t','p'], offsetCursor(pChars[0], 0, -1),
368
- makeCursor(charLine.line, 0));
369
- testMotion('t_repeat', ['2', 't', 'p'], offsetCursor(pChars[2], 0, -1),
370
- pChars[0]);
371
- testMotion('F', ['F', 'p'], pChars[0], pChars[1]);
372
- testMotion('F_repeat', ['2', 'F', 'p'], pChars[0], pChars[2]);
373
- testMotion('T', ['T', 'p'], offsetCursor(pChars[0], 0, 1), pChars[1]);
374
- testMotion('T_repeat', ['2', 'T', 'p'], offsetCursor(pChars[0], 0, 1), pChars[2]);
375
- testMotion('%_parens', ['%'], parens1.end, parens1.start);
376
- testMotion('%_squares', ['%'], squares1.end, squares1.start);
377
- testMotion('%_braces', ['%'], curlys1.end, curlys1.start);
378
- testMotion('%_seek_outside', ['%'], seekOutside.end, seekOutside.start);
379
- testMotion('%_seek_inside', ['%'], seekInside.end, seekInside.start);
380
- testVim('%_seek_skip', function(cm, vim, helpers) {
381
- cm.setCursor(0,0);
382
- helpers.doKeys(['%']);
383
- helpers.assertCursorAt(0,9);
384
- }, {value:'01234"("()'});
385
- testVim('%_skip_string', function(cm, vim, helpers) {
386
- cm.setCursor(0,0);
387
- helpers.doKeys(['%']);
388
- helpers.assertCursorAt(0,4);
389
- cm.setCursor(0,2);
390
- helpers.doKeys(['%']);
391
- helpers.assertCursorAt(0,0);
392
- }, {value:'(")")'});
393
- testVim('%_skip_comment', function(cm, vim, helpers) {
394
- cm.setCursor(0,0);
395
- helpers.doKeys(['%']);
396
- helpers.assertCursorAt(0,6);
397
- cm.setCursor(0,3);
398
- helpers.doKeys(['%']);
399
- helpers.assertCursorAt(0,0);
400
- }, {value:'(/*)*/)'});
401
- // Make sure that moving down after going to the end of a line always leaves you
402
- // at the end of a line, but preserves the offset in other cases
403
- testVim('Changing lines after Eol operation', function(cm, vim, helpers) {
404
- cm.setCursor(0,0);
405
- helpers.doKeys(['$']);
406
- helpers.doKeys(['j']);
407
- // After moving to Eol and then down, we should be at Eol of line 2
408
- helpers.assertCursorAt({ line: 1, ch: lines[1].length - 1 });
409
- helpers.doKeys(['j']);
410
- // After moving down, we should be at Eol of line 3
411
- helpers.assertCursorAt({ line: 2, ch: lines[2].length - 1 });
412
- helpers.doKeys(['h']);
413
- helpers.doKeys(['j']);
414
- // After moving back one space and then down, since line 4 is shorter than line 2, we should
415
- // be at Eol of line 2 - 1
416
- helpers.assertCursorAt({ line: 3, ch: lines[3].length - 1 });
417
- helpers.doKeys(['j']);
418
- helpers.doKeys(['j']);
419
- // After moving down again, since line 3 has enough characters, we should be back to the
420
- // same place we were at on line 1
421
- helpers.assertCursorAt({ line: 5, ch: lines[2].length - 2 });
422
- });
423
- //making sure gj and gk recover from clipping
424
- testVim('gj_gk_clipping', function(cm,vim,helpers){
425
- cm.setCursor(0, 1);
426
- helpers.doKeys('g','j','g','j');
427
- helpers.assertCursorAt(2, 1);
428
- helpers.doKeys('g','k','g','k');
429
- helpers.assertCursorAt(0, 1);
430
- },{value: 'line 1\n\nline 2'});
431
- //testing a mix of j/k and gj/gk
432
- testVim('j_k_and_gj_gk', function(cm,vim,helpers){
433
- cm.setSize(120);
434
- cm.setCursor(0, 0);
435
- //go to the last character on the first line
436
- helpers.doKeys('$');
437
- //move up/down on the column within the wrapped line
438
- //side-effect: cursor is not locked to eol anymore
439
- helpers.doKeys('g','k');
440
- var cur=cm.getCursor();
441
- eq(cur.line,0);
442
- is((cur.ch<176),'gk didn\'t move cursor back (1)');
443
- helpers.doKeys('g','j');
444
- helpers.assertCursorAt(0, 176);
445
- //should move to character 177 on line 2 (j/k preserve character index within line)
446
- helpers.doKeys('j');
447
- //due to different line wrapping, the cursor can be on a different screen-x now
448
- //gj and gk preserve screen-x on movement, much like moveV
449
- helpers.doKeys('3','g','k');
450
- cur=cm.getCursor();
451
- eq(cur.line,1);
452
- is((cur.ch<176),'gk didn\'t move cursor back (2)');
453
- helpers.doKeys('g','j','2','g','j');
454
- //should return to the same character-index
455
- helpers.doKeys('k');
456
- helpers.assertCursorAt(0, 176);
457
- },{ lineWrapping:true, value: 'This line is intentially long to test movement of gj and gk over wrapped lines. I will start on the end of this line, then make a step up and back to set the origin for j and k.\nThis line is supposed to be even longer than the previous. I will jump here and make another wiggle with gj and gk, before I jump back to the line above. Both wiggles should not change my cursor\'s target character but both j/k and gj/gk change each other\'s reference position.'});
458
- testVim('gj_gk', function(cm, vim, helpers) {
459
- if (phantom) return;
460
- cm.setSize(120);
461
- // Test top of document edge case.
462
- cm.setCursor(0, 4);
463
- helpers.doKeys('g', 'j');
464
- helpers.doKeys('10', 'g', 'k');
465
- helpers.assertCursorAt(0, 4);
466
-
467
- // Test moving down preserves column position.
468
- helpers.doKeys('g', 'j');
469
- var pos1 = cm.getCursor();
470
- var expectedPos2 = { line: 0, ch: (pos1.ch - 4) * 2 + 4};
471
- helpers.doKeys('g', 'j');
472
- helpers.assertCursorAt(expectedPos2);
473
-
474
- // Move to the last character
475
- cm.setCursor(0, 0);
476
- // Move left to reset HSPos
477
- helpers.doKeys('h');
478
- // Test bottom of document edge case.
479
- helpers.doKeys('100', 'g', 'j');
480
- var endingPos = cm.getCursor();
481
- is(endingPos != 0, 'gj should not be on wrapped line 0');
482
- var topLeftCharCoords = cm.charCoords(makeCursor(0, 0));
483
- var endingCharCoords = cm.charCoords(endingPos);
484
- is(topLeftCharCoords.left == endingCharCoords.left, 'gj should end up on column 0');
485
- },{ lineNumbers: false, lineWrapping:true, value: 'Thislineisintentiallylongtotestmovementofgjandgkoverwrappedlines.' });
486
- testVim('}', function(cm, vim, helpers) {
487
- cm.setCursor(0, 0);
488
- helpers.doKeys('}');
489
- helpers.assertCursorAt(1, 0);
490
- cm.setCursor(0, 0);
491
- helpers.doKeys('2', '}');
492
- helpers.assertCursorAt(4, 0);
493
- cm.setCursor(0, 0);
494
- helpers.doKeys('6', '}');
495
- helpers.assertCursorAt(5, 0);
496
- }, { value: 'a\n\nb\nc\n\nd' });
497
- testVim('{', function(cm, vim, helpers) {
498
- cm.setCursor(5, 0);
499
- helpers.doKeys('{');
500
- helpers.assertCursorAt(4, 0);
501
- cm.setCursor(5, 0);
502
- helpers.doKeys('2', '{');
503
- helpers.assertCursorAt(1, 0);
504
- cm.setCursor(5, 0);
505
- helpers.doKeys('6', '{');
506
- helpers.assertCursorAt(0, 0);
507
- }, { value: 'a\n\nb\nc\n\nd' });
508
- testVim('paragraph_motions', function(cm, vim, helpers) {
509
- cm.setCursor(10, 0);
510
- helpers.doKeys('{');
511
- helpers.assertCursorAt(4, 0);
512
- helpers.doKeys('{');
513
- helpers.assertCursorAt(0, 0);
514
- helpers.doKeys('2', '}');
515
- helpers.assertCursorAt(7, 0);
516
- helpers.doKeys('2', '}');
517
- helpers.assertCursorAt(16, 0);
518
-
519
- cm.setCursor(9, 0);
520
- helpers.doKeys('}');
521
- helpers.assertCursorAt(14, 0);
522
-
523
- cm.setCursor(6, 0);
524
- helpers.doKeys('}');
525
- helpers.assertCursorAt(7, 0);
526
-
527
- // ip inside empty space
528
- cm.setCursor(10, 0);
529
- helpers.doKeys('v', 'i', 'p');
530
- eqPos(Pos(7, 0), cm.getCursor('anchor'));
531
- eqPos(Pos(12, 0), cm.getCursor('head'));
532
- helpers.doKeys('i', 'p');
533
- eqPos(Pos(7, 0), cm.getCursor('anchor'));
534
- eqPos(Pos(13, 1), cm.getCursor('head'));
535
- helpers.doKeys('2', 'i', 'p');
536
- eqPos(Pos(7, 0), cm.getCursor('anchor'));
537
- eqPos(Pos(16, 1), cm.getCursor('head'));
538
-
539
- // should switch to visualLine mode
540
- cm.setCursor(14, 0);
541
- helpers.doKeys('<Esc>', 'v', 'i', 'p');
542
- helpers.assertCursorAt(14, 0);
543
-
544
- cm.setCursor(14, 0);
545
- helpers.doKeys('<Esc>', 'V', 'i', 'p');
546
- eqPos(Pos(16, 1), cm.getCursor('head'));
547
-
548
- // ap inside empty space
549
- cm.setCursor(10, 0);
550
- helpers.doKeys('<Esc>', 'v', 'a', 'p');
551
- eqPos(Pos(7, 0), cm.getCursor('anchor'));
552
- eqPos(Pos(13, 1), cm.getCursor('head'));
553
- helpers.doKeys('a', 'p');
554
- eqPos(Pos(7, 0), cm.getCursor('anchor'));
555
- eqPos(Pos(16, 1), cm.getCursor('head'));
556
-
557
- cm.setCursor(13, 0);
558
- helpers.doKeys('v', 'a', 'p');
559
- eqPos(Pos(13, 0), cm.getCursor('anchor'));
560
- eqPos(Pos(14, 0), cm.getCursor('head'));
561
-
562
- cm.setCursor(16, 0);
563
- helpers.doKeys('v', 'a', 'p');
564
- eqPos(Pos(14, 0), cm.getCursor('anchor'));
565
- eqPos(Pos(16, 1), cm.getCursor('head'));
566
-
567
- cm.setCursor(0, 0);
568
- helpers.doKeys('v', 'a', 'p');
569
- eqPos(Pos(0, 0), cm.getCursor('anchor'));
570
- eqPos(Pos(4, 0), cm.getCursor('head'));
571
-
572
- cm.setCursor(0, 0);
573
- helpers.doKeys('d', 'i', 'p');
574
- var register = helpers.getRegisterController().getRegister();
575
- eq('a\na\n', register.toString());
576
- is(register.linewise);
577
- helpers.doKeys('3', 'j', 'p');
578
- helpers.doKeys('y', 'i', 'p');
579
- is(register.linewise);
580
- eq('b\na\na\nc\n', register.toString());
581
- }, { value: 'a\na\n\n\n\nb\nc\n\n\n\n\n\n\nd\n\ne\nf' });
582
-
583
- // Operator tests
584
- testVim('dl', function(cm, vim, helpers) {
585
- var curStart = makeCursor(0, 0);
586
- cm.setCursor(curStart);
587
- helpers.doKeys('d', 'l');
588
- eq('word1 ', cm.getValue());
589
- var register = helpers.getRegisterController().getRegister();
590
- eq(' ', register.toString());
591
- is(!register.linewise);
592
- eqPos(curStart, cm.getCursor());
593
- }, { value: ' word1 ' });
594
- testVim('dl_eol', function(cm, vim, helpers) {
595
- cm.setCursor(0, 6);
596
- helpers.doKeys('d', 'l');
597
- eq(' word1', cm.getValue());
598
- var register = helpers.getRegisterController().getRegister();
599
- eq(' ', register.toString());
600
- is(!register.linewise);
601
- helpers.assertCursorAt(0, 5);
602
- }, { value: ' word1 ' });
603
- testVim('dl_repeat', function(cm, vim, helpers) {
604
- var curStart = makeCursor(0, 0);
605
- cm.setCursor(curStart);
606
- helpers.doKeys('2', 'd', 'l');
607
- eq('ord1 ', cm.getValue());
608
- var register = helpers.getRegisterController().getRegister();
609
- eq(' w', register.toString());
610
- is(!register.linewise);
611
- eqPos(curStart, cm.getCursor());
612
- }, { value: ' word1 ' });
613
- testVim('dh', function(cm, vim, helpers) {
614
- var curStart = makeCursor(0, 3);
615
- cm.setCursor(curStart);
616
- helpers.doKeys('d', 'h');
617
- eq(' wrd1 ', cm.getValue());
618
- var register = helpers.getRegisterController().getRegister();
619
- eq('o', register.toString());
620
- is(!register.linewise);
621
- eqPos(offsetCursor(curStart, 0 , -1), cm.getCursor());
622
- }, { value: ' word1 ' });
623
- testVim('dj', function(cm, vim, helpers) {
624
- var curStart = makeCursor(0, 3);
625
- cm.setCursor(curStart);
626
- helpers.doKeys('d', 'j');
627
- eq(' word3', cm.getValue());
628
- var register = helpers.getRegisterController().getRegister();
629
- eq(' word1\nword2\n', register.toString());
630
- is(register.linewise);
631
- helpers.assertCursorAt(0, 1);
632
- }, { value: ' word1\nword2\n word3' });
633
- testVim('dj_end_of_document', function(cm, vim, helpers) {
634
- var curStart = makeCursor(0, 3);
635
- cm.setCursor(curStart);
636
- helpers.doKeys('d', 'j');
637
- eq(' word1 ', cm.getValue());
638
- var register = helpers.getRegisterController().getRegister();
639
- eq('', register.toString());
640
- is(!register.linewise);
641
- helpers.assertCursorAt(0, 3);
642
- }, { value: ' word1 ' });
643
- testVim('dk', function(cm, vim, helpers) {
644
- var curStart = makeCursor(1, 3);
645
- cm.setCursor(curStart);
646
- helpers.doKeys('d', 'k');
647
- eq(' word3', cm.getValue());
648
- var register = helpers.getRegisterController().getRegister();
649
- eq(' word1\nword2\n', register.toString());
650
- is(register.linewise);
651
- helpers.assertCursorAt(0, 1);
652
- }, { value: ' word1\nword2\n word3' });
653
- testVim('dk_start_of_document', function(cm, vim, helpers) {
654
- var curStart = makeCursor(0, 3);
655
- cm.setCursor(curStart);
656
- helpers.doKeys('d', 'k');
657
- eq(' word1 ', cm.getValue());
658
- var register = helpers.getRegisterController().getRegister();
659
- eq('', register.toString());
660
- is(!register.linewise);
661
- helpers.assertCursorAt(0, 3);
662
- }, { value: ' word1 ' });
663
- testVim('dw_space', function(cm, vim, helpers) {
664
- var curStart = makeCursor(0, 0);
665
- cm.setCursor(curStart);
666
- helpers.doKeys('d', 'w');
667
- eq('word1 ', cm.getValue());
668
- var register = helpers.getRegisterController().getRegister();
669
- eq(' ', register.toString());
670
- is(!register.linewise);
671
- eqPos(curStart, cm.getCursor());
672
- }, { value: ' word1 ' });
673
- testVim('dw_word', function(cm, vim, helpers) {
674
- var curStart = makeCursor(0, 1);
675
- cm.setCursor(curStart);
676
- helpers.doKeys('d', 'w');
677
- eq(' word2', cm.getValue());
678
- var register = helpers.getRegisterController().getRegister();
679
- eq('word1 ', register.toString());
680
- is(!register.linewise);
681
- eqPos(curStart, cm.getCursor());
682
- }, { value: ' word1 word2' });
683
- testVim('dw_unicode_word', function(cm, vim, helpers) {
684
- helpers.doKeys('d', 'w');
685
- eq(cm.getValue().length, 10);
686
- helpers.doKeys('d', 'w');
687
- eq(cm.getValue().length, 6);
688
- helpers.doKeys('d', 'w');
689
- eq(cm.getValue().length, 5);
690
- helpers.doKeys('d', 'e');
691
- eq(cm.getValue().length, 2);
692
- }, { value: ' \u0562\u0561\u0580\u0587\xbbe\xb5g ' });
693
- testVim('dw_only_word', function(cm, vim, helpers) {
694
- // Test that if there is only 1 word left, dw deletes till the end of the
695
- // line.
696
- cm.setCursor(0, 1);
697
- helpers.doKeys('d', 'w');
698
- eq(' ', cm.getValue());
699
- var register = helpers.getRegisterController().getRegister();
700
- eq('word1 ', register.toString());
701
- is(!register.linewise);
702
- helpers.assertCursorAt(0, 0);
703
- }, { value: ' word1 ' });
704
- testVim('dw_eol', function(cm, vim, helpers) {
705
- // Assert that dw does not delete the newline if last word to delete is at end
706
- // of line.
707
- cm.setCursor(0, 1);
708
- helpers.doKeys('d', 'w');
709
- eq(' \nword2', cm.getValue());
710
- var register = helpers.getRegisterController().getRegister();
711
- eq('word1', register.toString());
712
- is(!register.linewise);
713
- helpers.assertCursorAt(0, 0);
714
- }, { value: ' word1\nword2' });
715
- testVim('dw_eol_with_multiple_newlines', function(cm, vim, helpers) {
716
- // Assert that dw does not delete the newline if last word to delete is at end
717
- // of line and it is followed by multiple newlines.
718
- cm.setCursor(0, 1);
719
- helpers.doKeys('d', 'w');
720
- eq(' \n\nword2', cm.getValue());
721
- var register = helpers.getRegisterController().getRegister();
722
- eq('word1', register.toString());
723
- is(!register.linewise);
724
- helpers.assertCursorAt(0, 0);
725
- }, { value: ' word1\n\nword2' });
726
- testVim('dw_empty_line_followed_by_whitespace', function(cm, vim, helpers) {
727
- cm.setCursor(0, 0);
728
- helpers.doKeys('d', 'w');
729
- eq(' \nword', cm.getValue());
730
- }, { value: '\n \nword' });
731
- testVim('dw_empty_line_followed_by_word', function(cm, vim, helpers) {
732
- cm.setCursor(0, 0);
733
- helpers.doKeys('d', 'w');
734
- eq('word', cm.getValue());
735
- }, { value: '\nword' });
736
- testVim('dw_empty_line_followed_by_empty_line', function(cm, vim, helpers) {
737
- cm.setCursor(0, 0);
738
- helpers.doKeys('d', 'w');
739
- eq('\n', cm.getValue());
740
- }, { value: '\n\n' });
741
- testVim('dw_whitespace_followed_by_whitespace', function(cm, vim, helpers) {
742
- cm.setCursor(0, 0);
743
- helpers.doKeys('d', 'w');
744
- eq('\n \n', cm.getValue());
745
- }, { value: ' \n \n' });
746
- testVim('dw_whitespace_followed_by_empty_line', function(cm, vim, helpers) {
747
- cm.setCursor(0, 0);
748
- helpers.doKeys('d', 'w');
749
- eq('\n\n', cm.getValue());
750
- }, { value: ' \n\n' });
751
- testVim('dw_word_whitespace_word', function(cm, vim, helpers) {
752
- cm.setCursor(0, 0);
753
- helpers.doKeys('d', 'w');
754
- eq('\n \nword2', cm.getValue());
755
- }, { value: 'word1\n \nword2'})
756
- testVim('dw_end_of_document', function(cm, vim, helpers) {
757
- cm.setCursor(1, 2);
758
- helpers.doKeys('d', 'w');
759
- eq('\nab', cm.getValue());
760
- }, { value: '\nabc' });
761
- testVim('dw_repeat', function(cm, vim, helpers) {
762
- // Assert that dw does delete newline if it should go to the next line, and
763
- // that repeat works properly.
764
- cm.setCursor(0, 1);
765
- helpers.doKeys('d', '2', 'w');
766
- eq(' ', cm.getValue());
767
- var register = helpers.getRegisterController().getRegister();
768
- eq('word1\nword2', register.toString());
769
- is(!register.linewise);
770
- helpers.assertCursorAt(0, 0);
771
- }, { value: ' word1\nword2' });
772
- testVim('de_word_start_and_empty_lines', function(cm, vim, helpers) {
773
- cm.setCursor(0, 0);
774
- helpers.doKeys('d', 'e');
775
- eq('\n\n', cm.getValue());
776
- }, { value: 'word\n\n' });
777
- testVim('de_word_end_and_empty_lines', function(cm, vim, helpers) {
778
- cm.setCursor(0, 3);
779
- helpers.doKeys('d', 'e');
780
- eq('wor', cm.getValue());
781
- }, { value: 'word\n\n\n' });
782
- testVim('de_whitespace_and_empty_lines', function(cm, vim, helpers) {
783
- cm.setCursor(0, 0);
784
- helpers.doKeys('d', 'e');
785
- eq('', cm.getValue());
786
- }, { value: ' \n\n\n' });
787
- testVim('de_end_of_document', function(cm, vim, helpers) {
788
- cm.setCursor(1, 2);
789
- helpers.doKeys('d', 'e');
790
- eq('\nab', cm.getValue());
791
- }, { value: '\nabc' });
792
- testVim('db_empty_lines', function(cm, vim, helpers) {
793
- cm.setCursor(2, 0);
794
- helpers.doKeys('d', 'b');
795
- eq('\n\n', cm.getValue());
796
- }, { value: '\n\n\n' });
797
- testVim('db_word_start_and_empty_lines', function(cm, vim, helpers) {
798
- cm.setCursor(2, 0);
799
- helpers.doKeys('d', 'b');
800
- eq('\nword', cm.getValue());
801
- }, { value: '\n\nword' });
802
- testVim('db_word_end_and_empty_lines', function(cm, vim, helpers) {
803
- cm.setCursor(2, 3);
804
- helpers.doKeys('d', 'b');
805
- eq('\n\nd', cm.getValue());
806
- }, { value: '\n\nword' });
807
- testVim('db_whitespace_and_empty_lines', function(cm, vim, helpers) {
808
- cm.setCursor(2, 0);
809
- helpers.doKeys('d', 'b');
810
- eq('', cm.getValue());
811
- }, { value: '\n \n' });
812
- testVim('db_start_of_document', function(cm, vim, helpers) {
813
- cm.setCursor(0, 0);
814
- helpers.doKeys('d', 'b');
815
- eq('abc\n', cm.getValue());
816
- }, { value: 'abc\n' });
817
- testVim('dge_empty_lines', function(cm, vim, helpers) {
818
- cm.setCursor(1, 0);
819
- helpers.doKeys('d', 'g', 'e');
820
- // Note: In real VIM the result should be '', but it's not quite consistent,
821
- // since 2 newlines are deleted. But in the similar case of word\n\n, only
822
- // 1 newline is deleted. We'll diverge from VIM's behavior since it's much
823
- // easier this way.
824
- eq('\n', cm.getValue());
825
- }, { value: '\n\n' });
826
- testVim('dge_word_and_empty_lines', function(cm, vim, helpers) {
827
- cm.setCursor(1, 0);
828
- helpers.doKeys('d', 'g', 'e');
829
- eq('wor\n', cm.getValue());
830
- }, { value: 'word\n\n'});
831
- testVim('dge_whitespace_and_empty_lines', function(cm, vim, helpers) {
832
- cm.setCursor(2, 0);
833
- helpers.doKeys('d', 'g', 'e');
834
- eq('', cm.getValue());
835
- }, { value: '\n \n' });
836
- testVim('dge_start_of_document', function(cm, vim, helpers) {
837
- cm.setCursor(0, 0);
838
- helpers.doKeys('d', 'g', 'e');
839
- eq('bc\n', cm.getValue());
840
- }, { value: 'abc\n' });
841
- testVim('d_inclusive', function(cm, vim, helpers) {
842
- // Assert that when inclusive is set, the character the cursor is on gets
843
- // deleted too.
844
- var curStart = makeCursor(0, 1);
845
- cm.setCursor(curStart);
846
- helpers.doKeys('d', 'e');
847
- eq(' ', cm.getValue());
848
- var register = helpers.getRegisterController().getRegister();
849
- eq('word1', register.toString());
850
- is(!register.linewise);
851
- eqPos(curStart, cm.getCursor());
852
- }, { value: ' word1 ' });
853
- testVim('d_reverse', function(cm, vim, helpers) {
854
- // Test that deleting in reverse works.
855
- cm.setCursor(1, 0);
856
- helpers.doKeys('d', 'b');
857
- eq(' word2 ', cm.getValue());
858
- var register = helpers.getRegisterController().getRegister();
859
- eq('word1\n', register.toString());
860
- is(!register.linewise);
861
- helpers.assertCursorAt(0, 1);
862
- }, { value: ' word1\nword2 ' });
863
- testVim('dd', function(cm, vim, helpers) {
864
- cm.setCursor(0, 3);
865
- var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
866
- { line: 1, ch: 0 });
867
- var expectedLineCount = cm.lineCount() - 1;
868
- helpers.doKeys('d', 'd');
869
- eq(expectedLineCount, cm.lineCount());
870
- var register = helpers.getRegisterController().getRegister();
871
- eq(expectedBuffer, register.toString());
872
- is(register.linewise);
873
- helpers.assertCursorAt(0, lines[1].textStart);
874
- });
875
- testVim('dd_prefix_repeat', function(cm, vim, helpers) {
876
- cm.setCursor(0, 3);
877
- var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
878
- { line: 2, ch: 0 });
879
- var expectedLineCount = cm.lineCount() - 2;
880
- helpers.doKeys('2', 'd', 'd');
881
- eq(expectedLineCount, cm.lineCount());
882
- var register = helpers.getRegisterController().getRegister();
883
- eq(expectedBuffer, register.toString());
884
- is(register.linewise);
885
- helpers.assertCursorAt(0, lines[2].textStart);
886
- });
887
- testVim('dd_motion_repeat', function(cm, vim, helpers) {
888
- cm.setCursor(0, 3);
889
- var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
890
- { line: 2, ch: 0 });
891
- var expectedLineCount = cm.lineCount() - 2;
892
- helpers.doKeys('d', '2', 'd');
893
- eq(expectedLineCount, cm.lineCount());
894
- var register = helpers.getRegisterController().getRegister();
895
- eq(expectedBuffer, register.toString());
896
- is(register.linewise);
897
- helpers.assertCursorAt(0, lines[2].textStart);
898
- });
899
- testVim('dd_multiply_repeat', function(cm, vim, helpers) {
900
- cm.setCursor(0, 3);
901
- var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
902
- { line: 6, ch: 0 });
903
- var expectedLineCount = cm.lineCount() - 6;
904
- helpers.doKeys('2', 'd', '3', 'd');
905
- eq(expectedLineCount, cm.lineCount());
906
- var register = helpers.getRegisterController().getRegister();
907
- eq(expectedBuffer, register.toString());
908
- is(register.linewise);
909
- helpers.assertCursorAt(0, lines[6].textStart);
910
- });
911
- testVim('dd_lastline', function(cm, vim, helpers) {
912
- cm.setCursor(cm.lineCount(), 0);
913
- var expectedLineCount = cm.lineCount() - 1;
914
- helpers.doKeys('d', 'd');
915
- eq(expectedLineCount, cm.lineCount());
916
- helpers.assertCursorAt(cm.lineCount() - 1, 0);
917
- });
918
- testVim('dd_only_line', function(cm, vim, helpers) {
919
- cm.setCursor(0, 0);
920
- var expectedRegister = cm.getValue() + "\n";
921
- helpers.doKeys('d','d');
922
- eq(1, cm.lineCount());
923
- eq('', cm.getValue());
924
- var register = helpers.getRegisterController().getRegister();
925
- eq(expectedRegister, register.toString());
926
- }, { value: "thisistheonlyline" });
927
- // Yank commands should behave the exact same as d commands, expect that nothing
928
- // gets deleted.
929
- testVim('yw_repeat', function(cm, vim, helpers) {
930
- // Assert that yw does yank newline if it should go to the next line, and
931
- // that repeat works properly.
932
- var curStart = makeCursor(0, 1);
933
- cm.setCursor(curStart);
934
- helpers.doKeys('y', '2', 'w');
935
- eq(' word1\nword2', cm.getValue());
936
- var register = helpers.getRegisterController().getRegister();
937
- eq('word1\nword2', register.toString());
938
- is(!register.linewise);
939
- eqPos(curStart, cm.getCursor());
940
- }, { value: ' word1\nword2' });
941
- testVim('yy_multiply_repeat', function(cm, vim, helpers) {
942
- var curStart = makeCursor(0, 3);
943
- cm.setCursor(curStart);
944
- var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
945
- { line: 6, ch: 0 });
946
- var expectedLineCount = cm.lineCount();
947
- helpers.doKeys('2', 'y', '3', 'y');
948
- eq(expectedLineCount, cm.lineCount());
949
- var register = helpers.getRegisterController().getRegister();
950
- eq(expectedBuffer, register.toString());
951
- is(register.linewise);
952
- eqPos(curStart, cm.getCursor());
953
- });
954
- // Change commands behave like d commands except that it also enters insert
955
- // mode. In addition, when the change is linewise, an additional newline is
956
- // inserted so that insert mode starts on that line.
957
- testVim('cw', function(cm, vim, helpers) {
958
- cm.setCursor(0, 0);
959
- helpers.doKeys('c', '2', 'w');
960
- eq(' word3', cm.getValue());
961
- helpers.assertCursorAt(0, 0);
962
- }, { value: 'word1 word2 word3'});
963
- testVim('cw_repeat', function(cm, vim, helpers) {
964
- // Assert that cw does delete newline if it should go to the next line, and
965
- // that repeat works properly.
966
- var curStart = makeCursor(0, 1);
967
- cm.setCursor(curStart);
968
- helpers.doKeys('c', '2', 'w');
969
- eq(' ', cm.getValue());
970
- var register = helpers.getRegisterController().getRegister();
971
- eq('word1\nword2', register.toString());
972
- is(!register.linewise);
973
- eqPos(curStart, cm.getCursor());
974
- eq('vim-insert', cm.getOption('keyMap'));
975
- }, { value: ' word1\nword2' });
976
- testVim('cc_multiply_repeat', function(cm, vim, helpers) {
977
- cm.setCursor(0, 3);
978
- var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
979
- { line: 6, ch: 0 });
980
- var expectedLineCount = cm.lineCount() - 5;
981
- helpers.doKeys('2', 'c', '3', 'c');
982
- eq(expectedLineCount, cm.lineCount());
983
- var register = helpers.getRegisterController().getRegister();
984
- eq(expectedBuffer, register.toString());
985
- is(register.linewise);
986
- eq('vim-insert', cm.getOption('keyMap'));
987
- });
988
- testVim('ct', function(cm, vim, helpers) {
989
- cm.setCursor(0, 9);
990
- helpers.doKeys('c', 't', 'w');
991
- eq(' word1 word3', cm.getValue());
992
- helpers.doKeys('<Esc>', 'c', '|');
993
- eq(' word3', cm.getValue());
994
- helpers.assertCursorAt(0, 0);
995
- helpers.doKeys('<Esc>', '2', 'u', 'w', 'h');
996
- helpers.doKeys('c', '2', 'g', 'e');
997
- eq(' wordword3', cm.getValue());
998
- }, { value: ' word1 word2 word3'});
999
- testVim('cc_should_not_append_to_document', function(cm, vim, helpers) {
1000
- var expectedLineCount = cm.lineCount();
1001
- cm.setCursor(cm.lastLine(), 0);
1002
- helpers.doKeys('c', 'c');
1003
- eq(expectedLineCount, cm.lineCount());
1004
- });
1005
- function fillArray(val, times) {
1006
- var arr = [];
1007
- for (var i = 0; i < times; i++) {
1008
- arr.push(val);
1009
- }
1010
- return arr;
1011
- }
1012
- testVim('c_visual_block', function(cm, vim, helpers) {
1013
- cm.setCursor(0, 1);
1014
- helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'l', 'c');
1015
- var replacement = fillArray('hello', 3);
1016
- cm.replaceSelections(replacement);
1017
- eq('1hello\n5hello\nahellofg', cm.getValue());
1018
- helpers.doKeys('<Esc>');
1019
- cm.setCursor(2, 3);
1020
- helpers.doKeys('<C-v>', '2', 'k', 'h', 'C');
1021
- replacement = fillArray('world', 3);
1022
- cm.replaceSelections(replacement);
1023
- eq('1hworld\n5hworld\nahworld', cm.getValue());
1024
- }, {value: '1234\n5678\nabcdefg'});
1025
- testVim('c_visual_block_replay', function(cm, vim, helpers) {
1026
- cm.setCursor(0, 1);
1027
- helpers.doKeys('<C-v>', '2', 'j', 'l', 'c');
1028
- var replacement = fillArray('fo', 3);
1029
- cm.replaceSelections(replacement);
1030
- eq('1fo4\n5fo8\nafodefg', cm.getValue());
1031
- helpers.doKeys('<Esc>');
1032
- cm.setCursor(0, 0);
1033
- helpers.doKeys('.');
1034
- eq('foo4\nfoo8\nfoodefg', cm.getValue());
1035
- }, {value: '1234\n5678\nabcdefg'});
1036
-
1037
- testVim('d_visual_block', function(cm, vim, helpers) {
1038
- cm.setCursor(0, 1);
1039
- helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'l', 'd');
1040
- eq('1\n5\nafg', cm.getValue());
1041
- }, {value: '1234\n5678\nabcdefg'});
1042
- testVim('D_visual_block', function(cm, vim, helpers) {
1043
- cm.setCursor(0, 1);
1044
- helpers.doKeys('<C-v>', '2', 'j', 'l', 'D');
1045
- eq('1\n5\na', cm.getValue());
1046
- }, {value: '1234\n5678\nabcdefg'});
1047
-
1048
- // Swapcase commands edit in place and do not modify registers.
1049
- testVim('g~w_repeat', function(cm, vim, helpers) {
1050
- // Assert that dw does delete newline if it should go to the next line, and
1051
- // that repeat works properly.
1052
- var curStart = makeCursor(0, 1);
1053
- cm.setCursor(curStart);
1054
- helpers.doKeys('g', '~', '2', 'w');
1055
- eq(' WORD1\nWORD2', cm.getValue());
1056
- var register = helpers.getRegisterController().getRegister();
1057
- eq('', register.toString());
1058
- is(!register.linewise);
1059
- eqPos(curStart, cm.getCursor());
1060
- }, { value: ' word1\nword2' });
1061
- testVim('g~g~', function(cm, vim, helpers) {
1062
- var curStart = makeCursor(0, 3);
1063
- cm.setCursor(curStart);
1064
- var expectedLineCount = cm.lineCount();
1065
- var expectedValue = cm.getValue().toUpperCase();
1066
- helpers.doKeys('2', 'g', '~', '3', 'g', '~');
1067
- eq(expectedValue, cm.getValue());
1068
- var register = helpers.getRegisterController().getRegister();
1069
- eq('', register.toString());
1070
- is(!register.linewise);
1071
- eqPos(curStart, cm.getCursor());
1072
- }, { value: ' word1\nword2\nword3\nword4\nword5\nword6' });
1073
- testVim('gu_and_gU', function(cm, vim, helpers) {
1074
- var curStart = makeCursor(0, 7);
1075
- var value = cm.getValue();
1076
- cm.setCursor(curStart);
1077
- helpers.doKeys('2', 'g', 'U', 'w');
1078
- eq(cm.getValue(), 'wa wb xX WC wd');
1079
- eqPos(curStart, cm.getCursor());
1080
- helpers.doKeys('2', 'g', 'u', 'w');
1081
- eq(cm.getValue(), value);
1082
-
1083
- helpers.doKeys('2', 'g', 'U', 'B');
1084
- eq(cm.getValue(), 'wa WB Xx wc wd');
1085
- eqPos(makeCursor(0, 3), cm.getCursor());
1086
-
1087
- cm.setCursor(makeCursor(0, 4));
1088
- helpers.doKeys('g', 'u', 'i', 'w');
1089
- eq(cm.getValue(), 'wa wb Xx wc wd');
1090
- eqPos(makeCursor(0, 3), cm.getCursor());
1091
-
1092
- // TODO: support gUgU guu
1093
- // eqPos(makeCursor(0, 0), cm.getCursor());
1094
-
1095
- var register = helpers.getRegisterController().getRegister();
1096
- eq('', register.toString());
1097
- is(!register.linewise);
1098
- }, { value: 'wa wb xx wc wd' });
1099
- testVim('visual_block_~', function(cm, vim, helpers) {
1100
- cm.setCursor(1, 1);
1101
- helpers.doKeys('<C-v>', 'l', 'l', 'j', '~');
1102
- helpers.assertCursorAt(1, 1);
1103
- eq('hello\nwoRLd\naBCDe', cm.getValue());
1104
- cm.setCursor(2, 0);
1105
- helpers.doKeys('v', 'l', 'l', '~');
1106
- helpers.assertCursorAt(2, 0);
1107
- eq('hello\nwoRLd\nAbcDe', cm.getValue());
1108
- },{value: 'hello\nwOrld\nabcde' });
1109
- testVim('._swapCase_visualBlock', function(cm, vim, helpers) {
1110
- helpers.doKeys('<C-v>', 'j', 'j', 'l', '~');
1111
- cm.setCursor(0, 3);
1112
- helpers.doKeys('.');
1113
- eq('HelLO\nWorLd\nAbcdE', cm.getValue());
1114
- },{value: 'hEllo\nwOrlD\naBcDe' });
1115
- testVim('._delete_visualBlock', function(cm, vim, helpers) {
1116
- helpers.doKeys('<C-v>', 'j', 'x');
1117
- eq('ive\ne\nsome\nsugar', cm.getValue());
1118
- helpers.doKeys('.');
1119
- eq('ve\n\nsome\nsugar', cm.getValue());
1120
- helpers.doKeys('j', 'j', '.');
1121
- eq('ve\n\nome\nugar', cm.getValue());
1122
- helpers.doKeys('u', '<C-r>', '.');
1123
- eq('ve\n\nme\ngar', cm.getValue());
1124
- },{value: 'give\nme\nsome\nsugar' });
1125
- testVim('>{motion}', function(cm, vim, helpers) {
1126
- cm.setCursor(1, 3);
1127
- var expectedLineCount = cm.lineCount();
1128
- var expectedValue = ' word1\n word2\nword3 ';
1129
- helpers.doKeys('>', 'k');
1130
- eq(expectedValue, cm.getValue());
1131
- var register = helpers.getRegisterController().getRegister();
1132
- eq('', register.toString());
1133
- is(!register.linewise);
1134
- helpers.assertCursorAt(0, 3);
1135
- }, { value: ' word1\nword2\nword3 ', indentUnit: 2 });
1136
- testVim('>>', function(cm, vim, helpers) {
1137
- cm.setCursor(0, 3);
1138
- var expectedLineCount = cm.lineCount();
1139
- var expectedValue = ' word1\n word2\nword3 ';
1140
- helpers.doKeys('2', '>', '>');
1141
- eq(expectedValue, cm.getValue());
1142
- var register = helpers.getRegisterController().getRegister();
1143
- eq('', register.toString());
1144
- is(!register.linewise);
1145
- helpers.assertCursorAt(0, 3);
1146
- }, { value: ' word1\nword2\nword3 ', indentUnit: 2 });
1147
- testVim('<{motion}', function(cm, vim, helpers) {
1148
- cm.setCursor(1, 3);
1149
- var expectedLineCount = cm.lineCount();
1150
- var expectedValue = ' word1\nword2\nword3 ';
1151
- helpers.doKeys('<', 'k');
1152
- eq(expectedValue, cm.getValue());
1153
- var register = helpers.getRegisterController().getRegister();
1154
- eq('', register.toString());
1155
- is(!register.linewise);
1156
- helpers.assertCursorAt(0, 1);
1157
- }, { value: ' word1\n word2\nword3 ', indentUnit: 2 });
1158
- testVim('<<', function(cm, vim, helpers) {
1159
- cm.setCursor(0, 3);
1160
- var expectedLineCount = cm.lineCount();
1161
- var expectedValue = ' word1\nword2\nword3 ';
1162
- helpers.doKeys('2', '<', '<');
1163
- eq(expectedValue, cm.getValue());
1164
- var register = helpers.getRegisterController().getRegister();
1165
- eq('', register.toString());
1166
- is(!register.linewise);
1167
- helpers.assertCursorAt(0, 1);
1168
- }, { value: ' word1\n word2\nword3 ', indentUnit: 2 });
1169
-
1170
- // Edit tests
1171
- function testEdit(name, before, pos, edit, after) {
1172
- return testVim(name, function(cm, vim, helpers) {
1173
- var ch = before.search(pos)
1174
- var line = before.substring(0, ch).split('\n').length - 1;
1175
- if (line) {
1176
- ch = before.substring(0, ch).split('\n').pop().length;
1177
- }
1178
- cm.setCursor(line, ch);
1179
- helpers.doKeys.apply(this, edit.split(''));
1180
- eq(after, cm.getValue());
1181
- }, {value: before});
1182
- }
1183
-
1184
- // These Delete tests effectively cover word-wise Change, Visual & Yank.
1185
- // Tabs are used as differentiated whitespace to catch edge cases.
1186
- // Normal word:
1187
- testEdit('diw_mid_spc', 'foo \tbAr\t baz', /A/, 'diw', 'foo \t\t baz');
1188
- testEdit('daw_mid_spc', 'foo \tbAr\t baz', /A/, 'daw', 'foo \tbaz');
1189
- testEdit('diw_mid_punct', 'foo \tbAr.\t baz', /A/, 'diw', 'foo \t.\t baz');
1190
- testEdit('daw_mid_punct', 'foo \tbAr.\t baz', /A/, 'daw', 'foo.\t baz');
1191
- testEdit('diw_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'diw', 'foo \t,.\t baz');
1192
- testEdit('daw_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'daw', 'foo \t,.\t baz');
1193
- testEdit('diw_start_spc', 'bAr \tbaz', /A/, 'diw', ' \tbaz');
1194
- testEdit('daw_start_spc', 'bAr \tbaz', /A/, 'daw', 'baz');
1195
- testEdit('diw_start_punct', 'bAr. \tbaz', /A/, 'diw', '. \tbaz');
1196
- testEdit('daw_start_punct', 'bAr. \tbaz', /A/, 'daw', '. \tbaz');
1197
- testEdit('diw_end_spc', 'foo \tbAr', /A/, 'diw', 'foo \t');
1198
- testEdit('daw_end_spc', 'foo \tbAr', /A/, 'daw', 'foo');
1199
- testEdit('diw_end_punct', 'foo \tbAr.', /A/, 'diw', 'foo \t.');
1200
- testEdit('daw_end_punct', 'foo \tbAr.', /A/, 'daw', 'foo.');
1201
- // Big word:
1202
- testEdit('diW_mid_spc', 'foo \tbAr\t baz', /A/, 'diW', 'foo \t\t baz');
1203
- testEdit('daW_mid_spc', 'foo \tbAr\t baz', /A/, 'daW', 'foo \tbaz');
1204
- testEdit('diW_mid_punct', 'foo \tbAr.\t baz', /A/, 'diW', 'foo \t\t baz');
1205
- testEdit('daW_mid_punct', 'foo \tbAr.\t baz', /A/, 'daW', 'foo \tbaz');
1206
- testEdit('diW_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'diW', 'foo \t\t baz');
1207
- testEdit('daW_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'daW', 'foo \tbaz');
1208
- testEdit('diW_start_spc', 'bAr\t baz', /A/, 'diW', '\t baz');
1209
- testEdit('daW_start_spc', 'bAr\t baz', /A/, 'daW', 'baz');
1210
- testEdit('diW_start_punct', 'bAr.\t baz', /A/, 'diW', '\t baz');
1211
- testEdit('daW_start_punct', 'bAr.\t baz', /A/, 'daW', 'baz');
1212
- testEdit('diW_end_spc', 'foo \tbAr', /A/, 'diW', 'foo \t');
1213
- testEdit('daW_end_spc', 'foo \tbAr', /A/, 'daW', 'foo');
1214
- testEdit('diW_end_punct', 'foo \tbAr.', /A/, 'diW', 'foo \t');
1215
- testEdit('daW_end_punct', 'foo \tbAr.', /A/, 'daW', 'foo');
1216
- // Deleting text objects
1217
- // Open and close on same line
1218
- testEdit('di(_open_spc', 'foo (bAr) baz', /\(/, 'di(', 'foo () baz');
1219
- testEdit('di)_open_spc', 'foo (bAr) baz', /\(/, 'di)', 'foo () baz');
1220
- testEdit('dib_open_spc', 'foo (bAr) baz', /\(/, 'dib', 'foo () baz');
1221
- testEdit('da(_open_spc', 'foo (bAr) baz', /\(/, 'da(', 'foo baz');
1222
- testEdit('da)_open_spc', 'foo (bAr) baz', /\(/, 'da)', 'foo baz');
1223
-
1224
- testEdit('di(_middle_spc', 'foo (bAr) baz', /A/, 'di(', 'foo () baz');
1225
- testEdit('di)_middle_spc', 'foo (bAr) baz', /A/, 'di)', 'foo () baz');
1226
- testEdit('da(_middle_spc', 'foo (bAr) baz', /A/, 'da(', 'foo baz');
1227
- testEdit('da)_middle_spc', 'foo (bAr) baz', /A/, 'da)', 'foo baz');
1228
-
1229
- testEdit('di(_close_spc', 'foo (bAr) baz', /\)/, 'di(', 'foo () baz');
1230
- testEdit('di)_close_spc', 'foo (bAr) baz', /\)/, 'di)', 'foo () baz');
1231
- testEdit('da(_close_spc', 'foo (bAr) baz', /\)/, 'da(', 'foo baz');
1232
- testEdit('da)_close_spc', 'foo (bAr) baz', /\)/, 'da)', 'foo baz');
1233
-
1234
- // delete around and inner b.
1235
- testEdit('dab_on_(_should_delete_around_()block', 'o( in(abc) )', /\(a/, 'dab', 'o( in )');
1236
-
1237
- // delete around and inner B.
1238
- testEdit('daB_on_{_should_delete_around_{}block', 'o{ in{abc} }', /{a/, 'daB', 'o{ in }');
1239
- testEdit('diB_on_{_should_delete_inner_{}block', 'o{ in{abc} }', /{a/, 'diB', 'o{ in{} }');
1240
-
1241
- testEdit('da{_on_{_should_delete_inner_block', 'o{ in{abc} }', /{a/, 'da{', 'o{ in }');
1242
- testEdit('di[_on_(_should_not_delete', 'foo (bAr) baz', /\(/, 'di[', 'foo (bAr) baz');
1243
- testEdit('di[_on_)_should_not_delete', 'foo (bAr) baz', /\)/, 'di[', 'foo (bAr) baz');
1244
- testEdit('da[_on_(_should_not_delete', 'foo (bAr) baz', /\(/, 'da[', 'foo (bAr) baz');
1245
- testEdit('da[_on_)_should_not_delete', 'foo (bAr) baz', /\)/, 'da[', 'foo (bAr) baz');
1246
- testMotion('di(_outside_should_stay', ['d', 'i', '('], { line: 0, ch: 0}, { line: 0, ch: 0});
1247
-
1248
- // Open and close on different lines, equally indented
1249
- testEdit('di{_middle_spc', 'a{\n\tbar\n}b', /r/, 'di{', 'a{}b');
1250
- testEdit('di}_middle_spc', 'a{\n\tbar\n}b', /r/, 'di}', 'a{}b');
1251
- testEdit('da{_middle_spc', 'a{\n\tbar\n}b', /r/, 'da{', 'ab');
1252
- testEdit('da}_middle_spc', 'a{\n\tbar\n}b', /r/, 'da}', 'ab');
1253
- testEdit('daB_middle_spc', 'a{\n\tbar\n}b', /r/, 'daB', 'ab');
1254
-
1255
- // open and close on diff lines, open indented less than close
1256
- testEdit('di{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'di{', 'a{}b');
1257
- testEdit('di}_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'di}', 'a{}b');
1258
- testEdit('da{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'da{', 'ab');
1259
- testEdit('da}_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'da}', 'ab');
1260
-
1261
- // open and close on diff lines, open indented more than close
1262
- testEdit('di[_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'di[', 'a\t[]b');
1263
- testEdit('di]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'di]', 'a\t[]b');
1264
- testEdit('da[_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da[', 'a\tb');
1265
- testEdit('da]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da]', 'a\tb');
1266
-
1267
- function testSelection(name, before, pos, keys, sel) {
1268
- return testVim(name, function(cm, vim, helpers) {
1269
- var ch = before.search(pos)
1270
- var line = before.substring(0, ch).split('\n').length - 1;
1271
- if (line) {
1272
- ch = before.substring(0, ch).split('\n').pop().length;
1273
- }
1274
- cm.setCursor(line, ch);
1275
- helpers.doKeys.apply(this, keys.split(''));
1276
- eq(sel, cm.getSelection());
1277
- }, {value: before});
1278
- }
1279
- testSelection('viw_middle_spc', 'foo \tbAr\t baz', /A/, 'viw', 'bAr');
1280
- testSelection('vaw_middle_spc', 'foo \tbAr\t baz', /A/, 'vaw', 'bAr\t ');
1281
- testSelection('viw_middle_punct', 'foo \tbAr,\t baz', /A/, 'viw', 'bAr');
1282
- testSelection('vaW_middle_punct', 'foo \tbAr,\t baz', /A/, 'vaW', 'bAr,\t ');
1283
- testSelection('viw_start_spc', 'foo \tbAr\t baz', /b/, 'viw', 'bAr');
1284
- testSelection('viw_end_spc', 'foo \tbAr\t baz', /r/, 'viw', 'bAr');
1285
- testSelection('viw_eol', 'foo \tbAr', /r/, 'viw', 'bAr');
1286
- testSelection('vi{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'vi{', '\n\tbar\n\t');
1287
- testSelection('va{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'va{', '{\n\tbar\n\t}');
1288
-
1289
- testVim('mouse_select', function(cm, vim, helpers) {
1290
- cm.setSelection(Pos(0, 2), Pos(0, 4), {origin: '*mouse'});
1291
- is(cm.state.vim.visualMode);
1292
- is(!cm.state.vim.visualLine);
1293
- is(!cm.state.vim.visualBlock);
1294
- helpers.doKeys('<Esc>');
1295
- is(!cm.somethingSelected());
1296
- helpers.doKeys('g', 'v');
1297
- eq('cd', cm.getSelection());
1298
- }, {value: 'abcdef'});
1299
-
1300
- // Operator-motion tests
1301
- testVim('D', function(cm, vim, helpers) {
1302
- cm.setCursor(0, 3);
1303
- helpers.doKeys('D');
1304
- eq(' wo\nword2\n word3', cm.getValue());
1305
- var register = helpers.getRegisterController().getRegister();
1306
- eq('rd1', register.toString());
1307
- is(!register.linewise);
1308
- helpers.assertCursorAt(0, 2);
1309
- }, { value: ' word1\nword2\n word3' });
1310
- testVim('C', function(cm, vim, helpers) {
1311
- var curStart = makeCursor(0, 3);
1312
- cm.setCursor(curStart);
1313
- helpers.doKeys('C');
1314
- eq(' wo\nword2\n word3', cm.getValue());
1315
- var register = helpers.getRegisterController().getRegister();
1316
- eq('rd1', register.toString());
1317
- is(!register.linewise);
1318
- eqPos(curStart, cm.getCursor());
1319
- eq('vim-insert', cm.getOption('keyMap'));
1320
- }, { value: ' word1\nword2\n word3' });
1321
- testVim('Y', function(cm, vim, helpers) {
1322
- var curStart = makeCursor(0, 3);
1323
- cm.setCursor(curStart);
1324
- helpers.doKeys('Y');
1325
- eq(' word1\nword2\n word3', cm.getValue());
1326
- var register = helpers.getRegisterController().getRegister();
1327
- eq('rd1', register.toString());
1328
- is(!register.linewise);
1329
- helpers.assertCursorAt(0, 3);
1330
- }, { value: ' word1\nword2\n word3' });
1331
- testVim('~', function(cm, vim, helpers) {
1332
- helpers.doKeys('3', '~');
1333
- eq('ABCdefg', cm.getValue());
1334
- helpers.assertCursorAt(0, 3);
1335
- }, { value: 'abcdefg' });
1336
-
1337
- // Action tests
1338
- testVim('ctrl-a', function(cm, vim, helpers) {
1339
- cm.setCursor(0, 0);
1340
- helpers.doKeys('<C-a>');
1341
- eq('-9', cm.getValue());
1342
- helpers.assertCursorAt(0, 1);
1343
- helpers.doKeys('2','<C-a>');
1344
- eq('-7', cm.getValue());
1345
- }, {value: '-10'});
1346
- testVim('ctrl-x', function(cm, vim, helpers) {
1347
- cm.setCursor(0, 0);
1348
- helpers.doKeys('<C-x>');
1349
- eq('-1', cm.getValue());
1350
- helpers.assertCursorAt(0, 1);
1351
- helpers.doKeys('2','<C-x>');
1352
- eq('-3', cm.getValue());
1353
- }, {value: '0'});
1354
- testVim('<C-x>/<C-a> search forward', function(cm, vim, helpers) {
1355
- forEach(['<C-x>', '<C-a>'], function(key) {
1356
- cm.setCursor(0, 0);
1357
- helpers.doKeys(key);
1358
- helpers.assertCursorAt(0, 5);
1359
- helpers.doKeys('l');
1360
- helpers.doKeys(key);
1361
- helpers.assertCursorAt(0, 10);
1362
- cm.setCursor(0, 11);
1363
- helpers.doKeys(key);
1364
- helpers.assertCursorAt(0, 11);
1365
- });
1366
- }, {value: '__jmp1 jmp2 jmp'});
1367
- testVim('a', function(cm, vim, helpers) {
1368
- cm.setCursor(0, 1);
1369
- helpers.doKeys('a');
1370
- helpers.assertCursorAt(0, 2);
1371
- eq('vim-insert', cm.getOption('keyMap'));
1372
- });
1373
- testVim('a_eol', function(cm, vim, helpers) {
1374
- cm.setCursor(0, lines[0].length - 1);
1375
- helpers.doKeys('a');
1376
- helpers.assertCursorAt(0, lines[0].length);
1377
- eq('vim-insert', cm.getOption('keyMap'));
1378
- });
1379
- testVim('A_endOfSelectedArea', function(cm, vim, helpers) {
1380
- cm.setCursor(0, 0);
1381
- helpers.doKeys('v', 'j', 'l');
1382
- helpers.doKeys('A');
1383
- helpers.assertCursorAt(1, 2);
1384
- eq('vim-insert', cm.getOption('keyMap'));
1385
- }, {value: 'foo\nbar'});
1386
- testVim('i', function(cm, vim, helpers) {
1387
- cm.setCursor(0, 1);
1388
- helpers.doKeys('i');
1389
- helpers.assertCursorAt(0, 1);
1390
- eq('vim-insert', cm.getOption('keyMap'));
1391
- });
1392
- testVim('i_repeat', function(cm, vim, helpers) {
1393
- helpers.doKeys('3', 'i');
1394
- cm.replaceRange('test', cm.getCursor());
1395
- helpers.doKeys('<Esc>');
1396
- eq('testtesttest', cm.getValue());
1397
- helpers.assertCursorAt(0, 11);
1398
- }, { value: '' });
1399
- testVim('i_repeat_delete', function(cm, vim, helpers) {
1400
- cm.setCursor(0, 4);
1401
- helpers.doKeys('2', 'i');
1402
- cm.replaceRange('z', cm.getCursor());
1403
- helpers.doInsertModeKeys('Backspace', 'Backspace');
1404
- helpers.doKeys('<Esc>');
1405
- eq('abe', cm.getValue());
1406
- helpers.assertCursorAt(0, 1);
1407
- }, { value: 'abcde' });
1408
- testVim('A', function(cm, vim, helpers) {
1409
- helpers.doKeys('A');
1410
- helpers.assertCursorAt(0, lines[0].length);
1411
- eq('vim-insert', cm.getOption('keyMap'));
1412
- });
1413
- testVim('A_visual_block', function(cm, vim, helpers) {
1414
- cm.setCursor(0, 1);
1415
- helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'A');
1416
- var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' ');
1417
- replacement.pop();
1418
- cm.replaceSelections(replacement);
1419
- eq('testhello\nmehello\npleahellose', cm.getValue());
1420
- helpers.doKeys('<Esc>');
1421
- cm.setCursor(0, 0);
1422
- helpers.doKeys('.');
1423
- // TODO this doesn't work yet
1424
- // eq('teshellothello\nme hello hello\nplehelloahellose', cm.getValue());
1425
- }, {value: 'test\nme\nplease'});
1426
- testVim('I', function(cm, vim, helpers) {
1427
- cm.setCursor(0, 4);
1428
- helpers.doKeys('I');
1429
- helpers.assertCursorAt(0, lines[0].textStart);
1430
- eq('vim-insert', cm.getOption('keyMap'));
1431
- });
1432
- testVim('I_repeat', function(cm, vim, helpers) {
1433
- cm.setCursor(0, 1);
1434
- helpers.doKeys('3', 'I');
1435
- cm.replaceRange('test', cm.getCursor());
1436
- helpers.doKeys('<Esc>');
1437
- eq('testtesttestblah', cm.getValue());
1438
- helpers.assertCursorAt(0, 11);
1439
- }, { value: 'blah' });
1440
- testVim('I_visual_block', function(cm, vim, helpers) {
1441
- cm.setCursor(0, 0);
1442
- helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'I');
1443
- var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' ');
1444
- replacement.pop();
1445
- cm.replaceSelections(replacement);
1446
- eq('hellotest\nhellome\nhelloplease', cm.getValue());
1447
- }, {value: 'test\nme\nplease'});
1448
- testVim('o', function(cm, vim, helpers) {
1449
- cm.setCursor(0, 4);
1450
- helpers.doKeys('o');
1451
- eq('word1\n\nword2', cm.getValue());
1452
- helpers.assertCursorAt(1, 0);
1453
- eq('vim-insert', cm.getOption('keyMap'));
1454
- }, { value: 'word1\nword2' });
1455
- testVim('o_repeat', function(cm, vim, helpers) {
1456
- cm.setCursor(0, 0);
1457
- helpers.doKeys('3', 'o');
1458
- cm.replaceRange('test', cm.getCursor());
1459
- helpers.doKeys('<Esc>');
1460
- eq('\ntest\ntest\ntest', cm.getValue());
1461
- helpers.assertCursorAt(3, 3);
1462
- }, { value: '' });
1463
- testVim('O', function(cm, vim, helpers) {
1464
- cm.setCursor(0, 4);
1465
- helpers.doKeys('O');
1466
- eq('\nword1\nword2', cm.getValue());
1467
- helpers.assertCursorAt(0, 0);
1468
- eq('vim-insert', cm.getOption('keyMap'));
1469
- }, { value: 'word1\nword2' });
1470
- testVim('J', function(cm, vim, helpers) {
1471
- cm.setCursor(0, 4);
1472
- helpers.doKeys('J');
1473
- var expectedValue = 'word1 word2\nword3\n word4';
1474
- eq(expectedValue, cm.getValue());
1475
- helpers.assertCursorAt(0, expectedValue.indexOf('word2') - 1);
1476
- }, { value: 'word1 \n word2\nword3\n word4' });
1477
- testVim('J_repeat', function(cm, vim, helpers) {
1478
- cm.setCursor(0, 4);
1479
- helpers.doKeys('3', 'J');
1480
- var expectedValue = 'word1 word2 word3\n word4';
1481
- eq(expectedValue, cm.getValue());
1482
- helpers.assertCursorAt(0, expectedValue.indexOf('word3') - 1);
1483
- }, { value: 'word1 \n word2\nword3\n word4' });
1484
- testVim('p', function(cm, vim, helpers) {
1485
- cm.setCursor(0, 1);
1486
- helpers.getRegisterController().pushText('"', 'yank', 'abc\ndef', false);
1487
- helpers.doKeys('p');
1488
- eq('__abc\ndef_', cm.getValue());
1489
- helpers.assertCursorAt(1, 2);
1490
- }, { value: '___' });
1491
- testVim('p_register', function(cm, vim, helpers) {
1492
- cm.setCursor(0, 1);
1493
- helpers.getRegisterController().getRegister('a').setText('abc\ndef', false);
1494
- helpers.doKeys('"', 'a', 'p');
1495
- eq('__abc\ndef_', cm.getValue());
1496
- helpers.assertCursorAt(1, 2);
1497
- }, { value: '___' });
1498
- testVim('p_wrong_register', function(cm, vim, helpers) {
1499
- cm.setCursor(0, 1);
1500
- helpers.getRegisterController().getRegister('a').setText('abc\ndef', false);
1501
- helpers.doKeys('p');
1502
- eq('___', cm.getValue());
1503
- helpers.assertCursorAt(0, 1);
1504
- }, { value: '___' });
1505
- testVim('p_line', function(cm, vim, helpers) {
1506
- cm.setCursor(0, 1);
1507
- helpers.getRegisterController().pushText('"', 'yank', ' a\nd\n', true);
1508
- helpers.doKeys('2', 'p');
1509
- eq('___\n a\nd\n a\nd', cm.getValue());
1510
- helpers.assertCursorAt(1, 2);
1511
- }, { value: '___' });
1512
- testVim('p_lastline', function(cm, vim, helpers) {
1513
- cm.setCursor(0, 1);
1514
- helpers.getRegisterController().pushText('"', 'yank', ' a\nd', true);
1515
- helpers.doKeys('2', 'p');
1516
- eq('___\n a\nd\n a\nd', cm.getValue());
1517
- helpers.assertCursorAt(1, 2);
1518
- }, { value: '___' });
1519
- testVim(']p_first_indent_is_smaller', function(cm, vim, helpers) {
1520
- helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true);
1521
- helpers.doKeys(']', 'p');
1522
- eq(' ___\n abc\n def', cm.getValue());
1523
- }, { value: ' ___' });
1524
- testVim(']p_first_indent_is_larger', function(cm, vim, helpers) {
1525
- helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true);
1526
- helpers.doKeys(']', 'p');
1527
- eq(' ___\n abc\ndef', cm.getValue());
1528
- }, { value: ' ___' });
1529
- testVim(']p_with_tab_indents', function(cm, vim, helpers) {
1530
- helpers.getRegisterController().pushText('"', 'yank', '\t\tabc\n\t\t\tdef\n', true);
1531
- helpers.doKeys(']', 'p');
1532
- eq('\t___\n\tabc\n\t\tdef', cm.getValue());
1533
- }, { value: '\t___', indentWithTabs: true});
1534
- testVim(']p_with_spaces_translated_to_tabs', function(cm, vim, helpers) {
1535
- helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true);
1536
- helpers.doKeys(']', 'p');
1537
- eq('\t___\n\tabc\n\t\tdef', cm.getValue());
1538
- }, { value: '\t___', indentWithTabs: true, tabSize: 2 });
1539
- testVim('[p', function(cm, vim, helpers) {
1540
- helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true);
1541
- helpers.doKeys('[', 'p');
1542
- eq(' abc\n def\n ___', cm.getValue());
1543
- }, { value: ' ___' });
1544
- testVim('P', function(cm, vim, helpers) {
1545
- cm.setCursor(0, 1);
1546
- helpers.getRegisterController().pushText('"', 'yank', 'abc\ndef', false);
1547
- helpers.doKeys('P');
1548
- eq('_abc\ndef__', cm.getValue());
1549
- helpers.assertCursorAt(1, 3);
1550
- }, { value: '___' });
1551
- testVim('P_line', function(cm, vim, helpers) {
1552
- cm.setCursor(0, 1);
1553
- helpers.getRegisterController().pushText('"', 'yank', ' a\nd\n', true);
1554
- helpers.doKeys('2', 'P');
1555
- eq(' a\nd\n a\nd\n___', cm.getValue());
1556
- helpers.assertCursorAt(0, 2);
1557
- }, { value: '___' });
1558
- testVim('r', function(cm, vim, helpers) {
1559
- cm.setCursor(0, 1);
1560
- helpers.doKeys('3', 'r', 'u');
1561
- eq('wuuuet\nanother', cm.getValue(),'3r failed');
1562
- helpers.assertCursorAt(0, 3);
1563
- cm.setCursor(0, 4);
1564
- helpers.doKeys('v', 'j', 'h', 'r', '<Space>');
1565
- eq('wuuu \n her', cm.getValue(),'Replacing selection by space-characters failed');
1566
- }, { value: 'wordet\nanother' });
1567
- testVim('r_visual_block', function(cm, vim, helpers) {
1568
- cm.setCursor(2, 3);
1569
- helpers.doKeys('<C-v>', 'k', 'k', 'h', 'h', 'r', 'l');
1570
- eq('1lll\n5lll\nalllefg', cm.getValue());
1571
- helpers.doKeys('<C-v>', 'l', 'j', 'r', '<Space>');
1572
- eq('1 l\n5 l\nalllefg', cm.getValue());
1573
- cm.setCursor(2, 0);
1574
- helpers.doKeys('o');
1575
- helpers.doKeys('<Esc>');
1576
- cm.replaceRange('\t\t', cm.getCursor());
1577
- helpers.doKeys('<C-v>', 'h', 'h', 'r', 'r');
1578
- eq('1 l\n5 l\nalllefg\nrrrrrrrr', cm.getValue());
1579
- }, {value: '1234\n5678\nabcdefg'});
1580
- testVim('R', function(cm, vim, helpers) {
1581
- cm.setCursor(0, 1);
1582
- helpers.doKeys('R');
1583
- helpers.assertCursorAt(0, 1);
1584
- eq('vim-replace', cm.getOption('keyMap'));
1585
- is(cm.state.overwrite, 'Setting overwrite state failed');
1586
- });
1587
- testVim('mark', function(cm, vim, helpers) {
1588
- cm.setCursor(2, 2);
1589
- helpers.doKeys('m', 't');
1590
- cm.setCursor(0, 0);
1591
- helpers.doKeys('`', 't');
1592
- helpers.assertCursorAt(2, 2);
1593
- cm.setCursor(2, 0);
1594
- cm.replaceRange(' h', cm.getCursor());
1595
- cm.setCursor(0, 0);
1596
- helpers.doKeys('\'', 't');
1597
- helpers.assertCursorAt(2, 3);
1598
- });
1599
- testVim('jumpToMark_next', function(cm, vim, helpers) {
1600
- cm.setCursor(2, 2);
1601
- helpers.doKeys('m', 't');
1602
- cm.setCursor(0, 0);
1603
- helpers.doKeys(']', '`');
1604
- helpers.assertCursorAt(2, 2);
1605
- cm.setCursor(0, 0);
1606
- helpers.doKeys(']', '\'');
1607
- helpers.assertCursorAt(2, 0);
1608
- });
1609
- testVim('jumpToMark_next_repeat', function(cm, vim, helpers) {
1610
- cm.setCursor(2, 2);
1611
- helpers.doKeys('m', 'a');
1612
- cm.setCursor(3, 2);
1613
- helpers.doKeys('m', 'b');
1614
- cm.setCursor(4, 2);
1615
- helpers.doKeys('m', 'c');
1616
- cm.setCursor(0, 0);
1617
- helpers.doKeys('2', ']', '`');
1618
- helpers.assertCursorAt(3, 2);
1619
- cm.setCursor(0, 0);
1620
- helpers.doKeys('2', ']', '\'');
1621
- helpers.assertCursorAt(3, 1);
1622
- });
1623
- testVim('jumpToMark_next_sameline', function(cm, vim, helpers) {
1624
- cm.setCursor(2, 0);
1625
- helpers.doKeys('m', 'a');
1626
- cm.setCursor(2, 4);
1627
- helpers.doKeys('m', 'b');
1628
- cm.setCursor(2, 2);
1629
- helpers.doKeys(']', '`');
1630
- helpers.assertCursorAt(2, 4);
1631
- });
1632
- testVim('jumpToMark_next_onlyprev', function(cm, vim, helpers) {
1633
- cm.setCursor(2, 0);
1634
- helpers.doKeys('m', 'a');
1635
- cm.setCursor(4, 0);
1636
- helpers.doKeys(']', '`');
1637
- helpers.assertCursorAt(4, 0);
1638
- });
1639
- testVim('jumpToMark_next_nomark', function(cm, vim, helpers) {
1640
- cm.setCursor(2, 2);
1641
- helpers.doKeys(']', '`');
1642
- helpers.assertCursorAt(2, 2);
1643
- helpers.doKeys(']', '\'');
1644
- helpers.assertCursorAt(2, 0);
1645
- });
1646
- testVim('jumpToMark_next_linewise_over', function(cm, vim, helpers) {
1647
- cm.setCursor(2, 2);
1648
- helpers.doKeys('m', 'a');
1649
- cm.setCursor(3, 4);
1650
- helpers.doKeys('m', 'b');
1651
- cm.setCursor(2, 1);
1652
- helpers.doKeys(']', '\'');
1653
- helpers.assertCursorAt(3, 1);
1654
- });
1655
- testVim('jumpToMark_next_action', function(cm, vim, helpers) {
1656
- cm.setCursor(2, 2);
1657
- helpers.doKeys('m', 't');
1658
- cm.setCursor(0, 0);
1659
- helpers.doKeys('d', ']', '`');
1660
- helpers.assertCursorAt(0, 0);
1661
- var actual = cm.getLine(0);
1662
- var expected = 'pop pop 0 1 2 3 4';
1663
- eq(actual, expected, "Deleting while jumping to the next mark failed.");
1664
- });
1665
- testVim('jumpToMark_next_line_action', function(cm, vim, helpers) {
1666
- cm.setCursor(2, 2);
1667
- helpers.doKeys('m', 't');
1668
- cm.setCursor(0, 0);
1669
- helpers.doKeys('d', ']', '\'');
1670
- helpers.assertCursorAt(0, 1);
1671
- var actual = cm.getLine(0);
1672
- var expected = ' (a) [b] {c} '
1673
- eq(actual, expected, "Deleting while jumping to the next mark line failed.");
1674
- });
1675
- testVim('jumpToMark_prev', function(cm, vim, helpers) {
1676
- cm.setCursor(2, 2);
1677
- helpers.doKeys('m', 't');
1678
- cm.setCursor(4, 0);
1679
- helpers.doKeys('[', '`');
1680
- helpers.assertCursorAt(2, 2);
1681
- cm.setCursor(4, 0);
1682
- helpers.doKeys('[', '\'');
1683
- helpers.assertCursorAt(2, 0);
1684
- });
1685
- testVim('jumpToMark_prev_repeat', function(cm, vim, helpers) {
1686
- cm.setCursor(2, 2);
1687
- helpers.doKeys('m', 'a');
1688
- cm.setCursor(3, 2);
1689
- helpers.doKeys('m', 'b');
1690
- cm.setCursor(4, 2);
1691
- helpers.doKeys('m', 'c');
1692
- cm.setCursor(5, 0);
1693
- helpers.doKeys('2', '[', '`');
1694
- helpers.assertCursorAt(3, 2);
1695
- cm.setCursor(5, 0);
1696
- helpers.doKeys('2', '[', '\'');
1697
- helpers.assertCursorAt(3, 1);
1698
- });
1699
- testVim('jumpToMark_prev_sameline', function(cm, vim, helpers) {
1700
- cm.setCursor(2, 0);
1701
- helpers.doKeys('m', 'a');
1702
- cm.setCursor(2, 4);
1703
- helpers.doKeys('m', 'b');
1704
- cm.setCursor(2, 2);
1705
- helpers.doKeys('[', '`');
1706
- helpers.assertCursorAt(2, 0);
1707
- });
1708
- testVim('jumpToMark_prev_onlynext', function(cm, vim, helpers) {
1709
- cm.setCursor(4, 4);
1710
- helpers.doKeys('m', 'a');
1711
- cm.setCursor(2, 0);
1712
- helpers.doKeys('[', '`');
1713
- helpers.assertCursorAt(2, 0);
1714
- });
1715
- testVim('jumpToMark_prev_nomark', function(cm, vim, helpers) {
1716
- cm.setCursor(2, 2);
1717
- helpers.doKeys('[', '`');
1718
- helpers.assertCursorAt(2, 2);
1719
- helpers.doKeys('[', '\'');
1720
- helpers.assertCursorAt(2, 0);
1721
- });
1722
- testVim('jumpToMark_prev_linewise_over', function(cm, vim, helpers) {
1723
- cm.setCursor(2, 2);
1724
- helpers.doKeys('m', 'a');
1725
- cm.setCursor(3, 4);
1726
- helpers.doKeys('m', 'b');
1727
- cm.setCursor(3, 6);
1728
- helpers.doKeys('[', '\'');
1729
- helpers.assertCursorAt(2, 0);
1730
- });
1731
- testVim('delmark_single', function(cm, vim, helpers) {
1732
- cm.setCursor(1, 2);
1733
- helpers.doKeys('m', 't');
1734
- helpers.doEx('delmarks t');
1735
- cm.setCursor(0, 0);
1736
- helpers.doKeys('`', 't');
1737
- helpers.assertCursorAt(0, 0);
1738
- });
1739
- testVim('delmark_range', function(cm, vim, helpers) {
1740
- cm.setCursor(1, 2);
1741
- helpers.doKeys('m', 'a');
1742
- cm.setCursor(2, 2);
1743
- helpers.doKeys('m', 'b');
1744
- cm.setCursor(3, 2);
1745
- helpers.doKeys('m', 'c');
1746
- cm.setCursor(4, 2);
1747
- helpers.doKeys('m', 'd');
1748
- cm.setCursor(5, 2);
1749
- helpers.doKeys('m', 'e');
1750
- helpers.doEx('delmarks b-d');
1751
- cm.setCursor(0, 0);
1752
- helpers.doKeys('`', 'a');
1753
- helpers.assertCursorAt(1, 2);
1754
- helpers.doKeys('`', 'b');
1755
- helpers.assertCursorAt(1, 2);
1756
- helpers.doKeys('`', 'c');
1757
- helpers.assertCursorAt(1, 2);
1758
- helpers.doKeys('`', 'd');
1759
- helpers.assertCursorAt(1, 2);
1760
- helpers.doKeys('`', 'e');
1761
- helpers.assertCursorAt(5, 2);
1762
- });
1763
- testVim('delmark_multi', function(cm, vim, helpers) {
1764
- cm.setCursor(1, 2);
1765
- helpers.doKeys('m', 'a');
1766
- cm.setCursor(2, 2);
1767
- helpers.doKeys('m', 'b');
1768
- cm.setCursor(3, 2);
1769
- helpers.doKeys('m', 'c');
1770
- cm.setCursor(4, 2);
1771
- helpers.doKeys('m', 'd');
1772
- cm.setCursor(5, 2);
1773
- helpers.doKeys('m', 'e');
1774
- helpers.doEx('delmarks bcd');
1775
- cm.setCursor(0, 0);
1776
- helpers.doKeys('`', 'a');
1777
- helpers.assertCursorAt(1, 2);
1778
- helpers.doKeys('`', 'b');
1779
- helpers.assertCursorAt(1, 2);
1780
- helpers.doKeys('`', 'c');
1781
- helpers.assertCursorAt(1, 2);
1782
- helpers.doKeys('`', 'd');
1783
- helpers.assertCursorAt(1, 2);
1784
- helpers.doKeys('`', 'e');
1785
- helpers.assertCursorAt(5, 2);
1786
- });
1787
- testVim('delmark_multi_space', function(cm, vim, helpers) {
1788
- cm.setCursor(1, 2);
1789
- helpers.doKeys('m', 'a');
1790
- cm.setCursor(2, 2);
1791
- helpers.doKeys('m', 'b');
1792
- cm.setCursor(3, 2);
1793
- helpers.doKeys('m', 'c');
1794
- cm.setCursor(4, 2);
1795
- helpers.doKeys('m', 'd');
1796
- cm.setCursor(5, 2);
1797
- helpers.doKeys('m', 'e');
1798
- helpers.doEx('delmarks b c d');
1799
- cm.setCursor(0, 0);
1800
- helpers.doKeys('`', 'a');
1801
- helpers.assertCursorAt(1, 2);
1802
- helpers.doKeys('`', 'b');
1803
- helpers.assertCursorAt(1, 2);
1804
- helpers.doKeys('`', 'c');
1805
- helpers.assertCursorAt(1, 2);
1806
- helpers.doKeys('`', 'd');
1807
- helpers.assertCursorAt(1, 2);
1808
- helpers.doKeys('`', 'e');
1809
- helpers.assertCursorAt(5, 2);
1810
- });
1811
- testVim('delmark_all', function(cm, vim, helpers) {
1812
- cm.setCursor(1, 2);
1813
- helpers.doKeys('m', 'a');
1814
- cm.setCursor(2, 2);
1815
- helpers.doKeys('m', 'b');
1816
- cm.setCursor(3, 2);
1817
- helpers.doKeys('m', 'c');
1818
- cm.setCursor(4, 2);
1819
- helpers.doKeys('m', 'd');
1820
- cm.setCursor(5, 2);
1821
- helpers.doKeys('m', 'e');
1822
- helpers.doEx('delmarks a b-de');
1823
- cm.setCursor(0, 0);
1824
- helpers.doKeys('`', 'a');
1825
- helpers.assertCursorAt(0, 0);
1826
- helpers.doKeys('`', 'b');
1827
- helpers.assertCursorAt(0, 0);
1828
- helpers.doKeys('`', 'c');
1829
- helpers.assertCursorAt(0, 0);
1830
- helpers.doKeys('`', 'd');
1831
- helpers.assertCursorAt(0, 0);
1832
- helpers.doKeys('`', 'e');
1833
- helpers.assertCursorAt(0, 0);
1834
- });
1835
- testVim('visual', function(cm, vim, helpers) {
1836
- helpers.doKeys('l', 'v', 'l', 'l');
1837
- helpers.assertCursorAt(0, 4);
1838
- eqPos(makeCursor(0, 1), cm.getCursor('anchor'));
1839
- helpers.doKeys('d');
1840
- eq('15', cm.getValue());
1841
- }, { value: '12345' });
1842
- testVim('visual_yank', function(cm, vim, helpers) {
1843
- helpers.doKeys('v', '3', 'l', 'y');
1844
- helpers.assertCursorAt(0, 0);
1845
- helpers.doKeys('p');
1846
- eq('aa te test for yank', cm.getValue());
1847
- }, { value: 'a test for yank' })
1848
- testVim('visual_w', function(cm, vim, helpers) {
1849
- helpers.doKeys('v', 'w');
1850
- eq(cm.getSelection(), 'motion t');
1851
- }, { value: 'motion test'});
1852
- testVim('visual_initial_selection', function(cm, vim, helpers) {
1853
- cm.setCursor(0, 1);
1854
- helpers.doKeys('v');
1855
- cm.getSelection('n');
1856
- }, { value: 'init'});
1857
- testVim('visual_crossover_left', function(cm, vim, helpers) {
1858
- cm.setCursor(0, 2);
1859
- helpers.doKeys('v', 'l', 'h', 'h');
1860
- cm.getSelection('ro');
1861
- }, { value: 'cross'});
1862
- testVim('visual_crossover_left', function(cm, vim, helpers) {
1863
- cm.setCursor(0, 2);
1864
- helpers.doKeys('v', 'h', 'l', 'l');
1865
- cm.getSelection('os');
1866
- }, { value: 'cross'});
1867
- testVim('visual_crossover_up', function(cm, vim, helpers) {
1868
- cm.setCursor(3, 2);
1869
- helpers.doKeys('v', 'j', 'k', 'k');
1870
- eqPos(Pos(2, 2), cm.getCursor('head'));
1871
- eqPos(Pos(3, 3), cm.getCursor('anchor'));
1872
- helpers.doKeys('k');
1873
- eqPos(Pos(1, 2), cm.getCursor('head'));
1874
- eqPos(Pos(3, 3), cm.getCursor('anchor'));
1875
- }, { value: 'cross\ncross\ncross\ncross\ncross\n'});
1876
- testVim('visual_crossover_down', function(cm, vim, helpers) {
1877
- cm.setCursor(1, 2);
1878
- helpers.doKeys('v', 'k', 'j', 'j');
1879
- eqPos(Pos(2, 3), cm.getCursor('head'));
1880
- eqPos(Pos(1, 2), cm.getCursor('anchor'));
1881
- helpers.doKeys('j');
1882
- eqPos(Pos(3, 3), cm.getCursor('head'));
1883
- eqPos(Pos(1, 2), cm.getCursor('anchor'));
1884
- }, { value: 'cross\ncross\ncross\ncross\ncross\n'});
1885
- testVim('visual_exit', function(cm, vim, helpers) {
1886
- helpers.doKeys('<C-v>', 'l', 'j', 'j', '<Esc>');
1887
- eqPos(cm.getCursor('anchor'), cm.getCursor('head'));
1888
- eq(vim.visualMode, false);
1889
- }, { value: 'hello\nworld\nfoo' });
1890
- testVim('visual_line', function(cm, vim, helpers) {
1891
- helpers.doKeys('l', 'V', 'l', 'j', 'j', 'd');
1892
- eq(' 4\n 5', cm.getValue());
1893
- }, { value: ' 1\n 2\n 3\n 4\n 5' });
1894
- testVim('visual_block_move_to_eol', function(cm, vim, helpers) {
1895
- // moveToEol should move all block cursors to end of line
1896
- cm.setCursor(0, 0);
1897
- helpers.doKeys('<C-v>', 'G', '$');
1898
- var selections = cm.getSelections().join();
1899
- eq('123,45,6', selections);
1900
- // Checks that with cursor at Infinity, finding words backwards still works.
1901
- helpers.doKeys('2', 'k', 'b');
1902
- selections = cm.getSelections().join();
1903
- eq('1', selections);
1904
- }, {value: '123\n45\n6'});
1905
- testVim('visual_block_different_line_lengths', function(cm, vim, helpers) {
1906
- // test the block selection with lines of different length
1907
- // i.e. extending the selection
1908
- // till the end of the longest line.
1909
- helpers.doKeys('<C-v>', 'l', 'j', 'j', '6', 'l', 'd');
1910
- helpers.doKeys('d', 'd', 'd', 'd');
1911
- eq('', cm.getValue());
1912
- }, {value: '1234\n5678\nabcdefg'});
1913
- testVim('visual_block_truncate_on_short_line', function(cm, vim, helpers) {
1914
- // check for left side selection in case
1915
- // of moving up to a shorter line.
1916
- cm.replaceRange('', cm.getCursor());
1917
- cm.setCursor(3, 4);
1918
- helpers.doKeys('<C-v>', 'l', 'k', 'k', 'd');
1919
- eq('hello world\n{\ntis\nsa!', cm.getValue());
1920
- }, {value: 'hello world\n{\nthis is\nsparta!'});
1921
- testVim('visual_block_corners', function(cm, vim, helpers) {
1922
- cm.setCursor(1, 2);
1923
- helpers.doKeys('<C-v>', '2', 'l', 'k');
1924
- // circle around the anchor
1925
- // and check the selections
1926
- var selections = cm.getSelections();
1927
- eq('345891', selections.join(''));
1928
- helpers.doKeys('4', 'h');
1929
- selections = cm.getSelections();
1930
- eq('123678', selections.join(''));
1931
- helpers.doKeys('j', 'j');
1932
- selections = cm.getSelections();
1933
- eq('678abc', selections.join(''));
1934
- helpers.doKeys('4', 'l');
1935
- selections = cm.getSelections();
1936
- eq('891cde', selections.join(''));
1937
- }, {value: '12345\n67891\nabcde'});
1938
- testVim('visual_block_mode_switch', function(cm, vim, helpers) {
1939
- // switch between visual modes
1940
- cm.setCursor(1, 1);
1941
- // blockwise to characterwise visual
1942
- helpers.doKeys('<C-v>', 'j', 'l', 'v');
1943
- selections = cm.getSelections();
1944
- eq('7891\nabc', selections.join(''));
1945
- // characterwise to blockwise
1946
- helpers.doKeys('<C-v>');
1947
- selections = cm.getSelections();
1948
- eq('78bc', selections.join(''));
1949
- // blockwise to linewise visual
1950
- helpers.doKeys('V');
1951
- selections = cm.getSelections();
1952
- eq('67891\nabcde', selections.join(''));
1953
- }, {value: '12345\n67891\nabcde'});
1954
- testVim('visual_block_crossing_short_line', function(cm, vim, helpers) {
1955
- // visual block with long and short lines
1956
- cm.setCursor(0, 3);
1957
- helpers.doKeys('<C-v>', 'j', 'j', 'j');
1958
- var selections = cm.getSelections().join();
1959
- eq('4,,d,b', selections);
1960
- helpers.doKeys('3', 'k');
1961
- selections = cm.getSelections().join();
1962
- eq('4', selections);
1963
- helpers.doKeys('5', 'j', 'k');
1964
- selections = cm.getSelections().join("");
1965
- eq(10, selections.length);
1966
- }, {value: '123456\n78\nabcdefg\nfoobar\n}\n'});
1967
- testVim('visual_block_curPos_on_exit', function(cm, vim, helpers) {
1968
- cm.setCursor(0, 0);
1969
- helpers.doKeys('<C-v>', '3' , 'l', '<Esc>');
1970
- eqPos(makeCursor(0, 3), cm.getCursor());
1971
- helpers.doKeys('h', '<C-v>', '2' , 'j' ,'3' , 'l');
1972
- eq(cm.getSelections().join(), "3456,,cdef");
1973
- helpers.doKeys('4' , 'h');
1974
- eq(cm.getSelections().join(), "23,8,bc");
1975
- helpers.doKeys('2' , 'l');
1976
- eq(cm.getSelections().join(), "34,,cd");
1977
- }, {value: '123456\n78\nabcdefg\nfoobar'});
1978
-
1979
- testVim('visual_marks', function(cm, vim, helpers) {
1980
- helpers.doKeys('l', 'v', 'l', 'l', 'j', 'j', 'v');
1981
- // Test visual mode marks
1982
- cm.setCursor(2, 1);
1983
- helpers.doKeys('\'', '<');
1984
- helpers.assertCursorAt(0, 1);
1985
- helpers.doKeys('\'', '>');
1986
- helpers.assertCursorAt(2, 0);
1987
- });
1988
- testVim('visual_join', function(cm, vim, helpers) {
1989
- helpers.doKeys('l', 'V', 'l', 'j', 'j', 'J');
1990
- eq(' 1 2 3\n 4\n 5', cm.getValue());
1991
- is(!vim.visualMode);
1992
- }, { value: ' 1\n 2\n 3\n 4\n 5' });
1993
- testVim('visual_join_2', function(cm, vim, helpers) {
1994
- helpers.doKeys('G', 'V', 'g', 'g', 'J');
1995
- eq('1 2 3 4 5 6 ', cm.getValue());
1996
- is(!vim.visualMode);
1997
- }, { value: '1\n2\n3\n4\n5\n6\n'});
1998
- testVim('visual_blank', function(cm, vim, helpers) {
1999
- helpers.doKeys('v', 'k');
2000
- eq(vim.visualMode, true);
2001
- }, { value: '\n' });
2002
- testVim('reselect_visual', function(cm, vim, helpers) {
2003
- helpers.doKeys('l', 'v', 'l', 'l', 'l', 'y', 'g', 'v');
2004
- helpers.assertCursorAt(0, 5);
2005
- eqPos(makeCursor(0, 1), cm.getCursor('anchor'));
2006
- helpers.doKeys('v');
2007
- cm.setCursor(1, 0);
2008
- helpers.doKeys('v', 'l', 'l', 'p');
2009
- eq('123456\n2345\nbar', cm.getValue());
2010
- cm.setCursor(0, 0);
2011
- helpers.doKeys('g', 'v');
2012
- // here the fake cursor is at (1, 3)
2013
- helpers.assertCursorAt(1, 4);
2014
- eqPos(makeCursor(1, 0), cm.getCursor('anchor'));
2015
- helpers.doKeys('v');
2016
- cm.setCursor(2, 0);
2017
- helpers.doKeys('v', 'l', 'l', 'g', 'v');
2018
- helpers.assertCursorAt(1, 4);
2019
- eqPos(makeCursor(1, 0), cm.getCursor('anchor'));
2020
- helpers.doKeys('g', 'v');
2021
- helpers.assertCursorAt(2, 3);
2022
- eqPos(makeCursor(2, 0), cm.getCursor('anchor'));
2023
- eq('123456\n2345\nbar', cm.getValue());
2024
- }, { value: '123456\nfoo\nbar' });
2025
- testVim('reselect_visual_line', function(cm, vim, helpers) {
2026
- helpers.doKeys('l', 'V', 'j', 'j', 'V', 'g', 'v', 'd');
2027
- eq('foo\nand\nbar', cm.getValue());
2028
- cm.setCursor(1, 0);
2029
- helpers.doKeys('V', 'y', 'j');
2030
- helpers.doKeys('V', 'p' , 'g', 'v', 'd');
2031
- eq('foo\nand', cm.getValue());
2032
- }, { value: 'hello\nthis\nis\nfoo\nand\nbar' });
2033
- testVim('reselect_visual_block', function(cm, vim, helpers) {
2034
- cm.setCursor(1, 2);
2035
- helpers.doKeys('<C-v>', 'k', 'h', '<C-v>');
2036
- cm.setCursor(2, 1);
2037
- helpers.doKeys('v', 'l', 'g', 'v');
2038
- eqPos(Pos(1, 2), vim.sel.anchor);
2039
- eqPos(Pos(0, 1), vim.sel.head);
2040
- // Ensure selection is done with visual block mode rather than one
2041
- // continuous range.
2042
- eq(cm.getSelections().join(''), '23oo')
2043
- helpers.doKeys('g', 'v');
2044
- eqPos(Pos(2, 1), vim.sel.anchor);
2045
- eqPos(Pos(2, 2), vim.sel.head);
2046
- helpers.doKeys('<Esc>');
2047
- // Ensure selection of deleted range
2048
- cm.setCursor(1, 1);
2049
- helpers.doKeys('v', '<C-v>', 'j', 'd', 'g', 'v');
2050
- eq(cm.getSelections().join(''), 'or');
2051
- }, { value: '123456\nfoo\nbar' });
2052
- testVim('s_normal', function(cm, vim, helpers) {
2053
- cm.setCursor(0, 1);
2054
- helpers.doKeys('s');
2055
- helpers.doKeys('<Esc>');
2056
- eq('ac', cm.getValue());
2057
- }, { value: 'abc'});
2058
- testVim('s_visual', function(cm, vim, helpers) {
2059
- cm.setCursor(0, 1);
2060
- helpers.doKeys('v', 's');
2061
- helpers.doKeys('<Esc>');
2062
- helpers.assertCursorAt(0, 0);
2063
- eq('ac', cm.getValue());
2064
- }, { value: 'abc'});
2065
- testVim('o_visual', function(cm, vim, helpers) {
2066
- cm.setCursor(0,0);
2067
- helpers.doKeys('v','l','l','l','o');
2068
- helpers.assertCursorAt(0,0);
2069
- helpers.doKeys('v','v','j','j','j','o');
2070
- helpers.assertCursorAt(0,0);
2071
- helpers.doKeys('O');
2072
- helpers.doKeys('l','l')
2073
- helpers.assertCursorAt(3, 3);
2074
- helpers.doKeys('d');
2075
- eq('p',cm.getValue());
2076
- }, { value: 'abcd\nefgh\nijkl\nmnop'});
2077
- testVim('o_visual_block', function(cm, vim, helpers) {
2078
- cm.setCursor(0, 1);
2079
- helpers.doKeys('<C-v>','3','j','l','l', 'o');
2080
- eqPos(Pos(3, 3), vim.sel.anchor);
2081
- eqPos(Pos(0, 1), vim.sel.head);
2082
- helpers.doKeys('O');
2083
- eqPos(Pos(3, 1), vim.sel.anchor);
2084
- eqPos(Pos(0, 3), vim.sel.head);
2085
- helpers.doKeys('o');
2086
- eqPos(Pos(0, 3), vim.sel.anchor);
2087
- eqPos(Pos(3, 1), vim.sel.head);
2088
- }, { value: 'abcd\nefgh\nijkl\nmnop'});
2089
- testVim('changeCase_visual', function(cm, vim, helpers) {
2090
- cm.setCursor(0, 0);
2091
- helpers.doKeys('v', 'l', 'l');
2092
- helpers.doKeys('U');
2093
- helpers.assertCursorAt(0, 0);
2094
- helpers.doKeys('v', 'l', 'l');
2095
- helpers.doKeys('u');
2096
- helpers.assertCursorAt(0, 0);
2097
- helpers.doKeys('l', 'l', 'l', '.');
2098
- helpers.assertCursorAt(0, 3);
2099
- cm.setCursor(0, 0);
2100
- helpers.doKeys('q', 'a', 'v', 'j', 'U', 'q');
2101
- helpers.assertCursorAt(0, 0);
2102
- helpers.doKeys('j', '@', 'a');
2103
- helpers.assertCursorAt(1, 0);
2104
- cm.setCursor(3, 0);
2105
- helpers.doKeys('V', 'U', 'j', '.');
2106
- eq('ABCDEF\nGHIJKL\nMnopq\nSHORT LINE\nLONG LINE OF TEXT', cm.getValue());
2107
- }, { value: 'abcdef\nghijkl\nmnopq\nshort line\nlong line of text'});
2108
- testVim('changeCase_visual_block', function(cm, vim, helpers) {
2109
- cm.setCursor(2, 1);
2110
- helpers.doKeys('<C-v>', 'k', 'k', 'h', 'U');
2111
- eq('ABcdef\nGHijkl\nMNopq\nfoo', cm.getValue());
2112
- cm.setCursor(0, 2);
2113
- helpers.doKeys('.');
2114
- eq('ABCDef\nGHIJkl\nMNOPq\nfoo', cm.getValue());
2115
- // check when last line is shorter.
2116
- cm.setCursor(2, 2);
2117
- helpers.doKeys('.');
2118
- eq('ABCDef\nGHIJkl\nMNOPq\nfoO', cm.getValue());
2119
- }, { value: 'abcdef\nghijkl\nmnopq\nfoo'});
2120
- testVim('visual_paste', function(cm, vim, helpers) {
2121
- cm.setCursor(0, 0);
2122
- helpers.doKeys('v', 'l', 'l', 'y');
2123
- helpers.assertCursorAt(0, 0);
2124
- helpers.doKeys('3', 'l', 'j', 'v', 'l', 'p');
2125
- helpers.assertCursorAt(1, 5);
2126
- eq('this is a\nunithitest for visual paste', cm.getValue());
2127
- cm.setCursor(0, 0);
2128
- // in case of pasting whole line
2129
- helpers.doKeys('y', 'y');
2130
- cm.setCursor(1, 6);
2131
- helpers.doKeys('v', 'l', 'l', 'l', 'p');
2132
- helpers.assertCursorAt(2, 0);
2133
- eq('this is a\nunithi\nthis is a\n for visual paste', cm.getValue());
2134
- }, { value: 'this is a\nunit test for visual paste'});
2135
-
2136
- // This checks the contents of the register used to paste the text
2137
- testVim('v_paste_from_register', function(cm, vim, helpers) {
2138
- cm.setCursor(0, 0);
2139
- helpers.doKeys('"', 'a', 'y', 'w');
2140
- cm.setCursor(1, 0);
2141
- helpers.doKeys('v', 'p');
2142
- cm.openDialog = helpers.fakeOpenDialog('registers');
2143
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
2144
- is(/a\s+register/.test(text));
2145
- });
2146
- }, { value: 'register contents\nare not erased'});
2147
- testVim('S_normal', function(cm, vim, helpers) {
2148
- cm.setCursor(0, 1);
2149
- helpers.doKeys('j', 'S');
2150
- helpers.doKeys('<Esc>');
2151
- helpers.assertCursorAt(1, 0);
2152
- eq('aa\n\ncc', cm.getValue());
2153
- }, { value: 'aa\nbb\ncc'});
2154
- testVim('blockwise_paste', function(cm, vim, helpers) {
2155
- cm.setCursor(0, 0);
2156
- helpers.doKeys('<C-v>', '3', 'j', 'l', 'y');
2157
- cm.setCursor(0, 2);
2158
- // paste one char after the current cursor position
2159
- helpers.doKeys('p');
2160
- eq('helhelo\nworwold\nfoofo\nbarba', cm.getValue());
2161
- cm.setCursor(0, 0);
2162
- helpers.doKeys('v', '4', 'l', 'y');
2163
- cm.setCursor(0, 0);
2164
- helpers.doKeys('<C-v>', '3', 'j', 'p');
2165
- eq('helheelhelo\norwold\noofo\narba', cm.getValue());
2166
- }, { value: 'hello\nworld\nfoo\nbar'});
2167
- testVim('blockwise_paste_long/short_line', function(cm, vim, helpers) {
2168
- // extend short lines in case of different line lengths.
2169
- cm.setCursor(0, 0);
2170
- helpers.doKeys('<C-v>', 'j', 'j', 'y');
2171
- cm.setCursor(0, 3);
2172
- helpers.doKeys('p');
2173
- eq('hellho\nfoo f\nbar b', cm.getValue());
2174
- }, { value: 'hello\nfoo\nbar'});
2175
- testVim('blockwise_paste_cut_paste', function(cm, vim, helpers) {
2176
- cm.setCursor(0, 0);
2177
- helpers.doKeys('<C-v>', '2', 'j', 'x');
2178
- cm.setCursor(0, 0);
2179
- helpers.doKeys('P');
2180
- eq('cut\nand\npaste\nme', cm.getValue());
2181
- }, { value: 'cut\nand\npaste\nme'});
2182
- testVim('blockwise_paste_from_register', function(cm, vim, helpers) {
2183
- cm.setCursor(0, 0);
2184
- helpers.doKeys('<C-v>', '2', 'j', '"', 'a', 'y');
2185
- cm.setCursor(0, 3);
2186
- helpers.doKeys('"', 'a', 'p');
2187
- eq('foobfar\nhellho\nworlwd', cm.getValue());
2188
- }, { value: 'foobar\nhello\nworld'});
2189
- testVim('blockwise_paste_last_line', function(cm, vim, helpers) {
2190
- cm.setCursor(0, 0);
2191
- helpers.doKeys('<C-v>', '2', 'j', 'l', 'y');
2192
- cm.setCursor(3, 0);
2193
- helpers.doKeys('p');
2194
- eq('cut\nand\npaste\nmcue\n an\n pa', cm.getValue());
2195
- }, { value: 'cut\nand\npaste\nme'});
2196
-
2197
- testVim('S_visual', function(cm, vim, helpers) {
2198
- cm.setCursor(0, 1);
2199
- helpers.doKeys('v', 'j', 'S');
2200
- helpers.doKeys('<Esc>');
2201
- helpers.assertCursorAt(0, 0);
2202
- eq('\ncc', cm.getValue());
2203
- }, { value: 'aa\nbb\ncc'});
2204
-
2205
- testVim('d_/', function(cm, vim, helpers) {
2206
- cm.openDialog = helpers.fakeOpenDialog('match');
2207
- helpers.doKeys('2', 'd', '/');
2208
- helpers.assertCursorAt(0, 0);
2209
- eq('match \n next', cm.getValue());
2210
- cm.openDialog = helpers.fakeOpenDialog('2');
2211
- helpers.doKeys('d', ':');
2212
- // TODO eq(' next', cm.getValue());
2213
- }, { value: 'text match match \n next' });
2214
- testVim('/ and n/N', function(cm, vim, helpers) {
2215
- cm.openDialog = helpers.fakeOpenDialog('match');
2216
- helpers.doKeys('/');
2217
- helpers.assertCursorAt(0, 11);
2218
- helpers.doKeys('n');
2219
- helpers.assertCursorAt(1, 6);
2220
- helpers.doKeys('N');
2221
- helpers.assertCursorAt(0, 11);
2222
-
2223
- cm.setCursor(0, 0);
2224
- helpers.doKeys('2', '/');
2225
- helpers.assertCursorAt(1, 6);
2226
- }, { value: 'match nope match \n nope Match' });
2227
- testVim('/_case', function(cm, vim, helpers) {
2228
- cm.openDialog = helpers.fakeOpenDialog('Match');
2229
- helpers.doKeys('/');
2230
- helpers.assertCursorAt(1, 6);
2231
- }, { value: 'match nope match \n nope Match' });
2232
- testVim('/_2_pcre', function(cm, vim, helpers) {
2233
- CodeMirror.Vim.setOption('pcre', true);
2234
- cm.openDialog = helpers.fakeOpenDialog('(word){2}');
2235
- helpers.doKeys('/');
2236
- helpers.assertCursorAt(1, 9);
2237
- helpers.doKeys('n');
2238
- helpers.assertCursorAt(2, 1);
2239
- }, { value: 'word\n another wordword\n wordwordword\n' });
2240
- testVim('/_2_nopcre', function(cm, vim, helpers) {
2241
- CodeMirror.Vim.setOption('pcre', false);
2242
- cm.openDialog = helpers.fakeOpenDialog('\\(word\\)\\{2}');
2243
- helpers.doKeys('/');
2244
- helpers.assertCursorAt(1, 9);
2245
- helpers.doKeys('n');
2246
- helpers.assertCursorAt(2, 1);
2247
- }, { value: 'word\n another wordword\n wordwordword\n' });
2248
- testVim('/_nongreedy', function(cm, vim, helpers) {
2249
- cm.openDialog = helpers.fakeOpenDialog('aa');
2250
- helpers.doKeys('/');
2251
- helpers.assertCursorAt(0, 4);
2252
- helpers.doKeys('n');
2253
- helpers.assertCursorAt(1, 3);
2254
- helpers.doKeys('n');
2255
- helpers.assertCursorAt(0, 0);
2256
- }, { value: 'aaa aa \n a aa'});
2257
- testVim('?_nongreedy', function(cm, vim, helpers) {
2258
- cm.openDialog = helpers.fakeOpenDialog('aa');
2259
- helpers.doKeys('?');
2260
- helpers.assertCursorAt(1, 3);
2261
- helpers.doKeys('n');
2262
- helpers.assertCursorAt(0, 4);
2263
- helpers.doKeys('n');
2264
- helpers.assertCursorAt(0, 0);
2265
- }, { value: 'aaa aa \n a aa'});
2266
- testVim('/_greedy', function(cm, vim, helpers) {
2267
- cm.openDialog = helpers.fakeOpenDialog('a+');
2268
- helpers.doKeys('/');
2269
- helpers.assertCursorAt(0, 4);
2270
- helpers.doKeys('n');
2271
- helpers.assertCursorAt(1, 1);
2272
- helpers.doKeys('n');
2273
- helpers.assertCursorAt(1, 3);
2274
- helpers.doKeys('n');
2275
- helpers.assertCursorAt(0, 0);
2276
- }, { value: 'aaa aa \n a aa'});
2277
- testVim('?_greedy', function(cm, vim, helpers) {
2278
- cm.openDialog = helpers.fakeOpenDialog('a+');
2279
- helpers.doKeys('?');
2280
- helpers.assertCursorAt(1, 3);
2281
- helpers.doKeys('n');
2282
- helpers.assertCursorAt(1, 1);
2283
- helpers.doKeys('n');
2284
- helpers.assertCursorAt(0, 4);
2285
- helpers.doKeys('n');
2286
- helpers.assertCursorAt(0, 0);
2287
- }, { value: 'aaa aa \n a aa'});
2288
- testVim('/_greedy_0_or_more', function(cm, vim, helpers) {
2289
- cm.openDialog = helpers.fakeOpenDialog('a*');
2290
- helpers.doKeys('/');
2291
- helpers.assertCursorAt(0, 3);
2292
- helpers.doKeys('n');
2293
- helpers.assertCursorAt(0, 4);
2294
- helpers.doKeys('n');
2295
- helpers.assertCursorAt(0, 5);
2296
- helpers.doKeys('n');
2297
- helpers.assertCursorAt(1, 0);
2298
- helpers.doKeys('n');
2299
- helpers.assertCursorAt(1, 1);
2300
- helpers.doKeys('n');
2301
- helpers.assertCursorAt(0, 0);
2302
- }, { value: 'aaa aa\n aa'});
2303
- testVim('?_greedy_0_or_more', function(cm, vim, helpers) {
2304
- cm.openDialog = helpers.fakeOpenDialog('a*');
2305
- helpers.doKeys('?');
2306
- helpers.assertCursorAt(1, 1);
2307
- helpers.doKeys('n');
2308
- helpers.assertCursorAt(1, 0);
2309
- helpers.doKeys('n');
2310
- helpers.assertCursorAt(0, 5);
2311
- helpers.doKeys('n');
2312
- helpers.assertCursorAt(0, 4);
2313
- helpers.doKeys('n');
2314
- helpers.assertCursorAt(0, 3);
2315
- helpers.doKeys('n');
2316
- helpers.assertCursorAt(0, 0);
2317
- }, { value: 'aaa aa\n aa'});
2318
- testVim('? and n/N', function(cm, vim, helpers) {
2319
- cm.openDialog = helpers.fakeOpenDialog('match');
2320
- helpers.doKeys('?');
2321
- helpers.assertCursorAt(1, 6);
2322
- helpers.doKeys('n');
2323
- helpers.assertCursorAt(0, 11);
2324
- helpers.doKeys('N');
2325
- helpers.assertCursorAt(1, 6);
2326
-
2327
- cm.setCursor(0, 0);
2328
- helpers.doKeys('2', '?');
2329
- helpers.assertCursorAt(0, 11);
2330
- }, { value: 'match nope match \n nope Match' });
2331
- testVim('*', function(cm, vim, helpers) {
2332
- cm.setCursor(0, 9);
2333
- helpers.doKeys('*');
2334
- helpers.assertCursorAt(0, 22);
2335
-
2336
- cm.setCursor(0, 9);
2337
- helpers.doKeys('2', '*');
2338
- helpers.assertCursorAt(1, 8);
2339
- }, { value: 'nomatch match nomatch match \nnomatch Match' });
2340
- testVim('*_no_word', function(cm, vim, helpers) {
2341
- cm.setCursor(0, 0);
2342
- helpers.doKeys('*');
2343
- helpers.assertCursorAt(0, 0);
2344
- }, { value: ' \n match \n' });
2345
- testVim('*_symbol', function(cm, vim, helpers) {
2346
- cm.setCursor(0, 0);
2347
- helpers.doKeys('*');
2348
- helpers.assertCursorAt(1, 0);
2349
- }, { value: ' /}\n/} match \n' });
2350
- testVim('#', function(cm, vim, helpers) {
2351
- cm.setCursor(0, 9);
2352
- helpers.doKeys('#');
2353
- helpers.assertCursorAt(1, 8);
2354
-
2355
- cm.setCursor(0, 9);
2356
- helpers.doKeys('2', '#');
2357
- helpers.assertCursorAt(0, 22);
2358
- }, { value: 'nomatch match nomatch match \nnomatch Match' });
2359
- testVim('*_seek', function(cm, vim, helpers) {
2360
- // Should skip over space and symbols.
2361
- cm.setCursor(0, 3);
2362
- helpers.doKeys('*');
2363
- helpers.assertCursorAt(0, 22);
2364
- }, { value: ' := match nomatch match \nnomatch Match' });
2365
- testVim('#', function(cm, vim, helpers) {
2366
- // Should skip over space and symbols.
2367
- cm.setCursor(0, 3);
2368
- helpers.doKeys('#');
2369
- helpers.assertCursorAt(1, 8);
2370
- }, { value: ' := match nomatch match \nnomatch Match' });
2371
- testVim('g*', function(cm, vim, helpers) {
2372
- cm.setCursor(0, 8);
2373
- helpers.doKeys('g', '*');
2374
- helpers.assertCursorAt(0, 18);
2375
- cm.setCursor(0, 8);
2376
- helpers.doKeys('3', 'g', '*');
2377
- helpers.assertCursorAt(1, 8);
2378
- }, { value: 'matches match alsoMatch\nmatchme matching' });
2379
- testVim('g#', function(cm, vim, helpers) {
2380
- cm.setCursor(0, 8);
2381
- helpers.doKeys('g', '#');
2382
- helpers.assertCursorAt(0, 0);
2383
- cm.setCursor(0, 8);
2384
- helpers.doKeys('3', 'g', '#');
2385
- helpers.assertCursorAt(1, 0);
2386
- }, { value: 'matches match alsoMatch\nmatchme matching' });
2387
- testVim('macro_insert', function(cm, vim, helpers) {
2388
- cm.setCursor(0, 0);
2389
- helpers.doKeys('q', 'a', '0', 'i');
2390
- cm.replaceRange('foo', cm.getCursor());
2391
- helpers.doKeys('<Esc>');
2392
- helpers.doKeys('q', '@', 'a');
2393
- eq('foofoo', cm.getValue());
2394
- }, { value: ''});
2395
- testVim('macro_insert_repeat', function(cm, vim, helpers) {
2396
- cm.setCursor(0, 0);
2397
- helpers.doKeys('q', 'a', '$', 'a');
2398
- cm.replaceRange('larry.', cm.getCursor());
2399
- helpers.doKeys('<Esc>');
2400
- helpers.doKeys('a');
2401
- cm.replaceRange('curly.', cm.getCursor());
2402
- helpers.doKeys('<Esc>');
2403
- helpers.doKeys('q');
2404
- helpers.doKeys('a');
2405
- cm.replaceRange('moe.', cm.getCursor());
2406
- helpers.doKeys('<Esc>');
2407
- helpers.doKeys('@', 'a');
2408
- // At this point, the most recent edit should be the 2nd insert change
2409
- // inside the macro, i.e. "curly.".
2410
- helpers.doKeys('.');
2411
- eq('larry.curly.moe.larry.curly.curly.', cm.getValue());
2412
- }, { value: ''});
2413
- testVim('macro_space', function(cm, vim, helpers) {
2414
- cm.setCursor(0, 0);
2415
- helpers.doKeys('<Space>', '<Space>');
2416
- helpers.assertCursorAt(0, 2);
2417
- helpers.doKeys('q', 'a', '<Space>', '<Space>', 'q');
2418
- helpers.assertCursorAt(0, 4);
2419
- helpers.doKeys('@', 'a');
2420
- helpers.assertCursorAt(0, 6);
2421
- helpers.doKeys('@', 'a');
2422
- helpers.assertCursorAt(0, 8);
2423
- }, { value: 'one line of text.'});
2424
- testVim('macro_t_search', function(cm, vim, helpers) {
2425
- cm.setCursor(0, 0);
2426
- helpers.doKeys('q', 'a', 't', 'e', 'q');
2427
- helpers.assertCursorAt(0, 1);
2428
- helpers.doKeys('l', '@', 'a');
2429
- helpers.assertCursorAt(0, 6);
2430
- helpers.doKeys('l', ';');
2431
- helpers.assertCursorAt(0, 12);
2432
- }, { value: 'one line of text.'});
2433
- testVim('macro_f_search', function(cm, vim, helpers) {
2434
- cm.setCursor(0, 0);
2435
- helpers.doKeys('q', 'b', 'f', 'e', 'q');
2436
- helpers.assertCursorAt(0, 2);
2437
- helpers.doKeys('@', 'b');
2438
- helpers.assertCursorAt(0, 7);
2439
- helpers.doKeys(';');
2440
- helpers.assertCursorAt(0, 13);
2441
- }, { value: 'one line of text.'});
2442
- testVim('macro_slash_search', function(cm, vim, helpers) {
2443
- cm.setCursor(0, 0);
2444
- helpers.doKeys('q', 'c');
2445
- cm.openDialog = helpers.fakeOpenDialog('e');
2446
- helpers.doKeys('/', 'q');
2447
- helpers.assertCursorAt(0, 2);
2448
- helpers.doKeys('@', 'c');
2449
- helpers.assertCursorAt(0, 7);
2450
- helpers.doKeys('n');
2451
- helpers.assertCursorAt(0, 13);
2452
- }, { value: 'one line of text.'});
2453
- testVim('macro_multislash_search', function(cm, vim, helpers) {
2454
- cm.setCursor(0, 0);
2455
- helpers.doKeys('q', 'd');
2456
- cm.openDialog = helpers.fakeOpenDialog('e');
2457
- helpers.doKeys('/');
2458
- cm.openDialog = helpers.fakeOpenDialog('t');
2459
- helpers.doKeys('/', 'q');
2460
- helpers.assertCursorAt(0, 12);
2461
- helpers.doKeys('@', 'd');
2462
- helpers.assertCursorAt(0, 15);
2463
- }, { value: 'one line of text to rule them all.'});
2464
- testVim('macro_last_ex_command_register', function (cm, vim, helpers) {
2465
- cm.setCursor(0, 0);
2466
- helpers.doEx('s/a/b');
2467
- helpers.doKeys('2', '@', ':');
2468
- eq('bbbaa', cm.getValue());
2469
- helpers.assertCursorAt(0, 2);
2470
- }, { value: 'aaaaa'});
2471
- testVim('macro_parens', function(cm, vim, helpers) {
2472
- cm.setCursor(0, 0);
2473
- helpers.doKeys('q', 'z', 'i');
2474
- cm.replaceRange('(', cm.getCursor());
2475
- helpers.doKeys('<Esc>');
2476
- helpers.doKeys('e', 'a');
2477
- cm.replaceRange(')', cm.getCursor());
2478
- helpers.doKeys('<Esc>');
2479
- helpers.doKeys('q');
2480
- helpers.doKeys('w', '@', 'z');
2481
- helpers.doKeys('w', '@', 'z');
2482
- eq('(see) (spot) (run)', cm.getValue());
2483
- }, { value: 'see spot run'});
2484
- testVim('macro_overwrite', function(cm, vim, helpers) {
2485
- cm.setCursor(0, 0);
2486
- helpers.doKeys('q', 'z', '0', 'i');
2487
- cm.replaceRange('I ', cm.getCursor());
2488
- helpers.doKeys('<Esc>');
2489
- helpers.doKeys('q');
2490
- helpers.doKeys('e');
2491
- // Now replace the macro with something else.
2492
- helpers.doKeys('q', 'z', 'a');
2493
- cm.replaceRange('.', cm.getCursor());
2494
- helpers.doKeys('<Esc>');
2495
- helpers.doKeys('q');
2496
- helpers.doKeys('e', '@', 'z');
2497
- helpers.doKeys('e', '@', 'z');
2498
- eq('I see. spot. run.', cm.getValue());
2499
- }, { value: 'see spot run'});
2500
- testVim('macro_search_f', function(cm, vim, helpers) {
2501
- cm.setCursor(0, 0);
2502
- helpers.doKeys('q', 'a', 'f', ' ');
2503
- helpers.assertCursorAt(0,3);
2504
- helpers.doKeys('q', '0');
2505
- helpers.assertCursorAt(0,0);
2506
- helpers.doKeys('@', 'a');
2507
- helpers.assertCursorAt(0,3);
2508
- }, { value: 'The quick brown fox jumped over the lazy dog.'});
2509
- testVim('macro_search_2f', function(cm, vim, helpers) {
2510
- cm.setCursor(0, 0);
2511
- helpers.doKeys('q', 'a', '2', 'f', ' ');
2512
- helpers.assertCursorAt(0,9);
2513
- helpers.doKeys('q', '0');
2514
- helpers.assertCursorAt(0,0);
2515
- helpers.doKeys('@', 'a');
2516
- helpers.assertCursorAt(0,9);
2517
- }, { value: 'The quick brown fox jumped over the lazy dog.'});
2518
- testVim('yank_register', function(cm, vim, helpers) {
2519
- cm.setCursor(0, 0);
2520
- helpers.doKeys('"', 'a', 'y', 'y');
2521
- helpers.doKeys('j', '"', 'b', 'y', 'y');
2522
- cm.openDialog = helpers.fakeOpenDialog('registers');
2523
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
2524
- is(/a\s+foo/.test(text));
2525
- is(/b\s+bar/.test(text));
2526
- });
2527
- helpers.doKeys(':');
2528
- }, { value: 'foo\nbar'});
2529
- testVim('yank_visual_block', function(cm, vim, helpers) {
2530
- cm.setCursor(0, 1);
2531
- helpers.doKeys('<C-v>', 'l', 'j', '"', 'a', 'y');
2532
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
2533
- is(/a\s+oo\nar/.test(text));
2534
- });
2535
- helpers.doKeys(':');
2536
- }, { value: 'foo\nbar'});
2537
- testVim('yank_append_line_to_line_register', function(cm, vim, helpers) {
2538
- cm.setCursor(0, 0);
2539
- helpers.doKeys('"', 'a', 'y', 'y');
2540
- helpers.doKeys('j', '"', 'A', 'y', 'y');
2541
- cm.openDialog = helpers.fakeOpenDialog('registers');
2542
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
2543
- is(/a\s+foo\nbar/.test(text));
2544
- is(/"\s+foo\nbar/.test(text));
2545
- });
2546
- helpers.doKeys(':');
2547
- }, { value: 'foo\nbar'});
2548
- testVim('yank_append_word_to_word_register', function(cm, vim, helpers) {
2549
- cm.setCursor(0, 0);
2550
- helpers.doKeys('"', 'a', 'y', 'w');
2551
- helpers.doKeys('j', '"', 'A', 'y', 'w');
2552
- cm.openDialog = helpers.fakeOpenDialog('registers');
2553
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
2554
- is(/a\s+foobar/.test(text));
2555
- is(/"\s+foobar/.test(text));
2556
- });
2557
- helpers.doKeys(':');
2558
- }, { value: 'foo\nbar'});
2559
- testVim('yank_append_line_to_word_register', function(cm, vim, helpers) {
2560
- cm.setCursor(0, 0);
2561
- helpers.doKeys('"', 'a', 'y', 'w');
2562
- helpers.doKeys('j', '"', 'A', 'y', 'y');
2563
- cm.openDialog = helpers.fakeOpenDialog('registers');
2564
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
2565
- is(/a\s+foo\nbar/.test(text));
2566
- is(/"\s+foo\nbar/.test(text));
2567
- });
2568
- helpers.doKeys(':');
2569
- }, { value: 'foo\nbar'});
2570
- testVim('yank_append_word_to_line_register', function(cm, vim, helpers) {
2571
- cm.setCursor(0, 0);
2572
- helpers.doKeys('"', 'a', 'y', 'y');
2573
- helpers.doKeys('j', '"', 'A', 'y', 'w');
2574
- cm.openDialog = helpers.fakeOpenDialog('registers');
2575
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
2576
- is(/a\s+foo\nbar/.test(text));
2577
- is(/"\s+foo\nbar/.test(text));
2578
- });
2579
- helpers.doKeys(':');
2580
- }, { value: 'foo\nbar'});
2581
- testVim('macro_register', function(cm, vim, helpers) {
2582
- cm.setCursor(0, 0);
2583
- helpers.doKeys('q', 'a', 'i');
2584
- cm.replaceRange('gangnam', cm.getCursor());
2585
- helpers.doKeys('<Esc>');
2586
- helpers.doKeys('q');
2587
- helpers.doKeys('q', 'b', 'o');
2588
- cm.replaceRange('style', cm.getCursor());
2589
- helpers.doKeys('<Esc>');
2590
- helpers.doKeys('q');
2591
- cm.openDialog = helpers.fakeOpenDialog('registers');
2592
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
2593
- is(/a\s+i/.test(text));
2594
- is(/b\s+o/.test(text));
2595
- });
2596
- helpers.doKeys(':');
2597
- }, { value: ''});
2598
- testVim('._register', function(cm,vim,helpers) {
2599
- cm.setCursor(0,0);
2600
- helpers.doKeys('i');
2601
- cm.replaceRange('foo',cm.getCursor());
2602
- helpers.doKeys('<Esc>');
2603
- cm.openDialog = helpers.fakeOpenDialog('registers');
2604
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
2605
- is(/\.\s+foo/.test(text));
2606
- });
2607
- helpers.doKeys(':');
2608
- }, {value: ''});
2609
- testVim(':_register', function(cm,vim,helpers) {
2610
- helpers.doEx('bar');
2611
- cm.openDialog = helpers.fakeOpenDialog('registers');
2612
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
2613
- is(/:\s+bar/.test(text));
2614
- });
2615
- helpers.doKeys(':');
2616
- }, {value: ''});
2617
- testVim('search_register_escape', function(cm, vim, helpers) {
2618
- // Check that the register is restored if the user escapes rather than confirms.
2619
- cm.openDialog = helpers.fakeOpenDialog('waldo');
2620
- helpers.doKeys('/');
2621
- var onKeyDown;
2622
- var onKeyUp;
2623
- var KEYCODES = {
2624
- f: 70,
2625
- o: 79,
2626
- Esc: 27
2627
- };
2628
- cm.openDialog = function(template, callback, options) {
2629
- onKeyDown = options.onKeyDown;
2630
- onKeyUp = options.onKeyUp;
2631
- };
2632
- var close = function() {};
2633
- helpers.doKeys('/');
2634
- // Fake some keyboard events coming in.
2635
- onKeyDown({keyCode: KEYCODES.f}, '', close);
2636
- onKeyUp({keyCode: KEYCODES.f}, '', close);
2637
- onKeyDown({keyCode: KEYCODES.o}, 'f', close);
2638
- onKeyUp({keyCode: KEYCODES.o}, 'f', close);
2639
- onKeyDown({keyCode: KEYCODES.o}, 'fo', close);
2640
- onKeyUp({keyCode: KEYCODES.o}, 'fo', close);
2641
- onKeyDown({keyCode: KEYCODES.Esc}, 'foo', close);
2642
- cm.openDialog = helpers.fakeOpenDialog('registers');
2643
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
2644
- is(/waldo/.test(text));
2645
- is(!/foo/.test(text));
2646
- });
2647
- helpers.doKeys(':');
2648
- }, {value: ''});
2649
- testVim('search_register', function(cm, vim, helpers) {
2650
- cm.openDialog = helpers.fakeOpenDialog('foo');
2651
- helpers.doKeys('/');
2652
- cm.openDialog = helpers.fakeOpenDialog('registers');
2653
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
2654
- is(/\/\s+foo/.test(text));
2655
- });
2656
- helpers.doKeys(':');
2657
- }, {value: ''});
2658
- testVim('search_history', function(cm, vim, helpers) {
2659
- cm.openDialog = helpers.fakeOpenDialog('this');
2660
- helpers.doKeys('/');
2661
- cm.openDialog = helpers.fakeOpenDialog('checks');
2662
- helpers.doKeys('/');
2663
- cm.openDialog = helpers.fakeOpenDialog('search');
2664
- helpers.doKeys('/');
2665
- cm.openDialog = helpers.fakeOpenDialog('history');
2666
- helpers.doKeys('/');
2667
- cm.openDialog = helpers.fakeOpenDialog('checks');
2668
- helpers.doKeys('/');
2669
- var onKeyDown;
2670
- var onKeyUp;
2671
- var query = '';
2672
- var keyCodes = {
2673
- Up: 38,
2674
- Down: 40
2675
- };
2676
- cm.openDialog = function(template, callback, options) {
2677
- onKeyUp = options.onKeyUp;
2678
- onKeyDown = options.onKeyDown;
2679
- };
2680
- var close = function(newVal) {
2681
- if (typeof newVal == 'string') query = newVal;
2682
- }
2683
- helpers.doKeys('/');
2684
- onKeyDown({keyCode: keyCodes.Up}, query, close);
2685
- onKeyUp({keyCode: keyCodes.Up}, query, close);
2686
- eq(query, 'checks');
2687
- onKeyDown({keyCode: keyCodes.Up}, query, close);
2688
- onKeyUp({keyCode: keyCodes.Up}, query, close);
2689
- eq(query, 'history');
2690
- onKeyDown({keyCode: keyCodes.Up}, query, close);
2691
- onKeyUp({keyCode: keyCodes.Up}, query, close);
2692
- eq(query, 'search');
2693
- onKeyDown({keyCode: keyCodes.Up}, query, close);
2694
- onKeyUp({keyCode: keyCodes.Up}, query, close);
2695
- eq(query, 'this');
2696
- onKeyDown({keyCode: keyCodes.Down}, query, close);
2697
- onKeyUp({keyCode: keyCodes.Down}, query, close);
2698
- eq(query, 'search');
2699
- }, {value: ''});
2700
- testVim('exCommand_history', function(cm, vim, helpers) {
2701
- cm.openDialog = helpers.fakeOpenDialog('registers');
2702
- helpers.doKeys(':');
2703
- cm.openDialog = helpers.fakeOpenDialog('sort');
2704
- helpers.doKeys(':');
2705
- cm.openDialog = helpers.fakeOpenDialog('map');
2706
- helpers.doKeys(':');
2707
- cm.openDialog = helpers.fakeOpenDialog('invalid');
2708
- helpers.doKeys(':');
2709
- var onKeyDown;
2710
- var onKeyUp;
2711
- var input = '';
2712
- var keyCodes = {
2713
- Up: 38,
2714
- Down: 40,
2715
- s: 115
2716
- };
2717
- cm.openDialog = function(template, callback, options) {
2718
- onKeyUp = options.onKeyUp;
2719
- onKeyDown = options.onKeyDown;
2720
- };
2721
- var close = function(newVal) {
2722
- if (typeof newVal == 'string') input = newVal;
2723
- }
2724
- helpers.doKeys(':');
2725
- onKeyDown({keyCode: keyCodes.Up}, input, close);
2726
- eq(input, 'invalid');
2727
- onKeyDown({keyCode: keyCodes.Up}, input, close);
2728
- eq(input, 'map');
2729
- onKeyDown({keyCode: keyCodes.Up}, input, close);
2730
- eq(input, 'sort');
2731
- onKeyDown({keyCode: keyCodes.Up}, input, close);
2732
- eq(input, 'registers');
2733
- onKeyDown({keyCode: keyCodes.s}, '', close);
2734
- input = 's';
2735
- onKeyDown({keyCode: keyCodes.Up}, input, close);
2736
- eq(input, 'sort');
2737
- }, {value: ''});
2738
- testVim('search_clear', function(cm, vim, helpers) {
2739
- var onKeyDown;
2740
- var input = '';
2741
- var keyCodes = {
2742
- Ctrl: 17,
2743
- u: 85
2744
- };
2745
- cm.openDialog = function(template, callback, options) {
2746
- onKeyDown = options.onKeyDown;
2747
- };
2748
- var close = function(newVal) {
2749
- if (typeof newVal == 'string') input = newVal;
2750
- }
2751
- helpers.doKeys('/');
2752
- input = 'foo';
2753
- onKeyDown({keyCode: keyCodes.Ctrl}, input, close);
2754
- onKeyDown({keyCode: keyCodes.u, ctrlKey: true}, input, close);
2755
- eq(input, '');
2756
- });
2757
- testVim('exCommand_clear', function(cm, vim, helpers) {
2758
- var onKeyDown;
2759
- var input = '';
2760
- var keyCodes = {
2761
- Ctrl: 17,
2762
- u: 85
2763
- };
2764
- cm.openDialog = function(template, callback, options) {
2765
- onKeyDown = options.onKeyDown;
2766
- };
2767
- var close = function(newVal) {
2768
- if (typeof newVal == 'string') input = newVal;
2769
- }
2770
- helpers.doKeys(':');
2771
- input = 'foo';
2772
- onKeyDown({keyCode: keyCodes.Ctrl}, input, close);
2773
- onKeyDown({keyCode: keyCodes.u, ctrlKey: true}, input, close);
2774
- eq(input, '');
2775
- });
2776
- testVim('.', function(cm, vim, helpers) {
2777
- cm.setCursor(0, 0);
2778
- helpers.doKeys('2', 'd', 'w');
2779
- helpers.doKeys('.');
2780
- eq('5 6', cm.getValue());
2781
- }, { value: '1 2 3 4 5 6'});
2782
- testVim('._repeat', function(cm, vim, helpers) {
2783
- cm.setCursor(0, 0);
2784
- helpers.doKeys('2', 'd', 'w');
2785
- helpers.doKeys('3', '.');
2786
- eq('6', cm.getValue());
2787
- }, { value: '1 2 3 4 5 6'});
2788
- testVim('._insert', function(cm, vim, helpers) {
2789
- helpers.doKeys('i');
2790
- cm.replaceRange('test', cm.getCursor());
2791
- helpers.doKeys('<Esc>');
2792
- helpers.doKeys('.');
2793
- eq('testestt', cm.getValue());
2794
- helpers.assertCursorAt(0, 6);
2795
- }, { value: ''});
2796
- testVim('._insert_repeat', function(cm, vim, helpers) {
2797
- helpers.doKeys('i');
2798
- cm.replaceRange('test', cm.getCursor());
2799
- cm.setCursor(0, 4);
2800
- helpers.doKeys('<Esc>');
2801
- helpers.doKeys('2', '.');
2802
- eq('testesttestt', cm.getValue());
2803
- helpers.assertCursorAt(0, 10);
2804
- }, { value: ''});
2805
- testVim('._repeat_insert', function(cm, vim, helpers) {
2806
- helpers.doKeys('3', 'i');
2807
- cm.replaceRange('te', cm.getCursor());
2808
- cm.setCursor(0, 2);
2809
- helpers.doKeys('<Esc>');
2810
- helpers.doKeys('.');
2811
- eq('tetettetetee', cm.getValue());
2812
- helpers.assertCursorAt(0, 10);
2813
- }, { value: ''});
2814
- testVim('._insert_o', function(cm, vim, helpers) {
2815
- helpers.doKeys('o');
2816
- cm.replaceRange('z', cm.getCursor());
2817
- cm.setCursor(1, 1);
2818
- helpers.doKeys('<Esc>');
2819
- helpers.doKeys('.');
2820
- eq('\nz\nz', cm.getValue());
2821
- helpers.assertCursorAt(2, 0);
2822
- }, { value: ''});
2823
- testVim('._insert_o_repeat', function(cm, vim, helpers) {
2824
- helpers.doKeys('o');
2825
- cm.replaceRange('z', cm.getCursor());
2826
- helpers.doKeys('<Esc>');
2827
- cm.setCursor(1, 0);
2828
- helpers.doKeys('2', '.');
2829
- eq('\nz\nz\nz', cm.getValue());
2830
- helpers.assertCursorAt(3, 0);
2831
- }, { value: ''});
2832
- testVim('._insert_o_indent', function(cm, vim, helpers) {
2833
- helpers.doKeys('o');
2834
- cm.replaceRange('z', cm.getCursor());
2835
- helpers.doKeys('<Esc>');
2836
- cm.setCursor(1, 2);
2837
- helpers.doKeys('.');
2838
- eq('{\n z\n z', cm.getValue());
2839
- helpers.assertCursorAt(2, 2);
2840
- }, { value: '{'});
2841
- testVim('._insert_cw', function(cm, vim, helpers) {
2842
- helpers.doKeys('c', 'w');
2843
- cm.replaceRange('test', cm.getCursor());
2844
- helpers.doKeys('<Esc>');
2845
- cm.setCursor(0, 3);
2846
- helpers.doKeys('2', 'l');
2847
- helpers.doKeys('.');
2848
- eq('test test word3', cm.getValue());
2849
- helpers.assertCursorAt(0, 8);
2850
- }, { value: 'word1 word2 word3' });
2851
- testVim('._insert_cw_repeat', function(cm, vim, helpers) {
2852
- // For some reason, repeat cw in desktop VIM will does not repeat insert mode
2853
- // changes. Will conform to that behavior.
2854
- helpers.doKeys('c', 'w');
2855
- cm.replaceRange('test', cm.getCursor());
2856
- helpers.doKeys('<Esc>');
2857
- cm.setCursor(0, 4);
2858
- helpers.doKeys('l');
2859
- helpers.doKeys('2', '.');
2860
- eq('test test', cm.getValue());
2861
- helpers.assertCursorAt(0, 8);
2862
- }, { value: 'word1 word2 word3' });
2863
- testVim('._delete', function(cm, vim, helpers) {
2864
- cm.setCursor(0, 5);
2865
- helpers.doKeys('i');
2866
- helpers.doInsertModeKeys('Backspace');
2867
- helpers.doKeys('<Esc>');
2868
- helpers.doKeys('.');
2869
- eq('zace', cm.getValue());
2870
- helpers.assertCursorAt(0, 1);
2871
- }, { value: 'zabcde'});
2872
- testVim('._delete_repeat', function(cm, vim, helpers) {
2873
- cm.setCursor(0, 6);
2874
- helpers.doKeys('i');
2875
- helpers.doInsertModeKeys('Backspace');
2876
- helpers.doKeys('<Esc>');
2877
- helpers.doKeys('2', '.');
2878
- eq('zzce', cm.getValue());
2879
- helpers.assertCursorAt(0, 1);
2880
- }, { value: 'zzabcde'});
2881
- testVim('._visual_>', function(cm, vim, helpers) {
2882
- cm.setCursor(0, 0);
2883
- helpers.doKeys('V', 'j', '>');
2884
- cm.setCursor(2, 0)
2885
- helpers.doKeys('.');
2886
- eq(' 1\n 2\n 3\n 4', cm.getValue());
2887
- helpers.assertCursorAt(2, 2);
2888
- }, { value: '1\n2\n3\n4'});
2889
- testVim('f;', function(cm, vim, helpers) {
2890
- cm.setCursor(0, 0);
2891
- helpers.doKeys('f', 'x');
2892
- helpers.doKeys(';');
2893
- helpers.doKeys('2', ';');
2894
- eq(9, cm.getCursor().ch);
2895
- }, { value: '01x3xx678x'});
2896
- testVim('F;', function(cm, vim, helpers) {
2897
- cm.setCursor(0, 8);
2898
- helpers.doKeys('F', 'x');
2899
- helpers.doKeys(';');
2900
- helpers.doKeys('2', ';');
2901
- eq(2, cm.getCursor().ch);
2902
- }, { value: '01x3xx6x8x'});
2903
- testVim('t;', function(cm, vim, helpers) {
2904
- cm.setCursor(0, 0);
2905
- helpers.doKeys('t', 'x');
2906
- helpers.doKeys(';');
2907
- helpers.doKeys('2', ';');
2908
- eq(8, cm.getCursor().ch);
2909
- }, { value: '01x3xx678x'});
2910
- testVim('T;', function(cm, vim, helpers) {
2911
- cm.setCursor(0, 9);
2912
- helpers.doKeys('T', 'x');
2913
- helpers.doKeys(';');
2914
- helpers.doKeys('2', ';');
2915
- eq(2, cm.getCursor().ch);
2916
- }, { value: '0xx3xx678x'});
2917
- testVim('f,', function(cm, vim, helpers) {
2918
- cm.setCursor(0, 6);
2919
- helpers.doKeys('f', 'x');
2920
- helpers.doKeys(',');
2921
- helpers.doKeys('2', ',');
2922
- eq(2, cm.getCursor().ch);
2923
- }, { value: '01x3xx678x'});
2924
- testVim('F,', function(cm, vim, helpers) {
2925
- cm.setCursor(0, 3);
2926
- helpers.doKeys('F', 'x');
2927
- helpers.doKeys(',');
2928
- helpers.doKeys('2', ',');
2929
- eq(9, cm.getCursor().ch);
2930
- }, { value: '01x3xx678x'});
2931
- testVim('t,', function(cm, vim, helpers) {
2932
- cm.setCursor(0, 6);
2933
- helpers.doKeys('t', 'x');
2934
- helpers.doKeys(',');
2935
- helpers.doKeys('2', ',');
2936
- eq(3, cm.getCursor().ch);
2937
- }, { value: '01x3xx678x'});
2938
- testVim('T,', function(cm, vim, helpers) {
2939
- cm.setCursor(0, 4);
2940
- helpers.doKeys('T', 'x');
2941
- helpers.doKeys(',');
2942
- helpers.doKeys('2', ',');
2943
- eq(8, cm.getCursor().ch);
2944
- }, { value: '01x3xx67xx'});
2945
- testVim('fd,;', function(cm, vim, helpers) {
2946
- cm.setCursor(0, 0);
2947
- helpers.doKeys('f', '4');
2948
- cm.setCursor(0, 0);
2949
- helpers.doKeys('d', ';');
2950
- eq('56789', cm.getValue());
2951
- helpers.doKeys('u');
2952
- cm.setCursor(0, 9);
2953
- helpers.doKeys('d', ',');
2954
- eq('01239', cm.getValue());
2955
- }, { value: '0123456789'});
2956
- testVim('Fd,;', function(cm, vim, helpers) {
2957
- cm.setCursor(0, 9);
2958
- helpers.doKeys('F', '4');
2959
- cm.setCursor(0, 9);
2960
- helpers.doKeys('d', ';');
2961
- eq('01239', cm.getValue());
2962
- helpers.doKeys('u');
2963
- cm.setCursor(0, 0);
2964
- helpers.doKeys('d', ',');
2965
- eq('56789', cm.getValue());
2966
- }, { value: '0123456789'});
2967
- testVim('td,;', function(cm, vim, helpers) {
2968
- cm.setCursor(0, 0);
2969
- helpers.doKeys('t', '4');
2970
- cm.setCursor(0, 0);
2971
- helpers.doKeys('d', ';');
2972
- eq('456789', cm.getValue());
2973
- helpers.doKeys('u');
2974
- cm.setCursor(0, 9);
2975
- helpers.doKeys('d', ',');
2976
- eq('012349', cm.getValue());
2977
- }, { value: '0123456789'});
2978
- testVim('Td,;', function(cm, vim, helpers) {
2979
- cm.setCursor(0, 9);
2980
- helpers.doKeys('T', '4');
2981
- cm.setCursor(0, 9);
2982
- helpers.doKeys('d', ';');
2983
- eq('012349', cm.getValue());
2984
- helpers.doKeys('u');
2985
- cm.setCursor(0, 0);
2986
- helpers.doKeys('d', ',');
2987
- eq('456789', cm.getValue());
2988
- }, { value: '0123456789'});
2989
- testVim('fc,;', function(cm, vim, helpers) {
2990
- cm.setCursor(0, 0);
2991
- helpers.doKeys('f', '4');
2992
- cm.setCursor(0, 0);
2993
- helpers.doKeys('c', ';', '<Esc>');
2994
- eq('56789', cm.getValue());
2995
- helpers.doKeys('u');
2996
- cm.setCursor(0, 9);
2997
- helpers.doKeys('c', ',');
2998
- eq('01239', cm.getValue());
2999
- }, { value: '0123456789'});
3000
- testVim('Fc,;', function(cm, vim, helpers) {
3001
- cm.setCursor(0, 9);
3002
- helpers.doKeys('F', '4');
3003
- cm.setCursor(0, 9);
3004
- helpers.doKeys('c', ';', '<Esc>');
3005
- eq('01239', cm.getValue());
3006
- helpers.doKeys('u');
3007
- cm.setCursor(0, 0);
3008
- helpers.doKeys('c', ',');
3009
- eq('56789', cm.getValue());
3010
- }, { value: '0123456789'});
3011
- testVim('tc,;', function(cm, vim, helpers) {
3012
- cm.setCursor(0, 0);
3013
- helpers.doKeys('t', '4');
3014
- cm.setCursor(0, 0);
3015
- helpers.doKeys('c', ';', '<Esc>');
3016
- eq('456789', cm.getValue());
3017
- helpers.doKeys('u');
3018
- cm.setCursor(0, 9);
3019
- helpers.doKeys('c', ',');
3020
- eq('012349', cm.getValue());
3021
- }, { value: '0123456789'});
3022
- testVim('Tc,;', function(cm, vim, helpers) {
3023
- cm.setCursor(0, 9);
3024
- helpers.doKeys('T', '4');
3025
- cm.setCursor(0, 9);
3026
- helpers.doKeys('c', ';', '<Esc>');
3027
- eq('012349', cm.getValue());
3028
- helpers.doKeys('u');
3029
- cm.setCursor(0, 0);
3030
- helpers.doKeys('c', ',');
3031
- eq('456789', cm.getValue());
3032
- }, { value: '0123456789'});
3033
- testVim('fy,;', function(cm, vim, helpers) {
3034
- cm.setCursor(0, 0);
3035
- helpers.doKeys('f', '4');
3036
- cm.setCursor(0, 0);
3037
- helpers.doKeys('y', ';', 'P');
3038
- eq('012340123456789', cm.getValue());
3039
- helpers.doKeys('u');
3040
- cm.setCursor(0, 9);
3041
- helpers.doKeys('y', ',', 'P');
3042
- eq('012345678456789', cm.getValue());
3043
- }, { value: '0123456789'});
3044
- testVim('Fy,;', function(cm, vim, helpers) {
3045
- cm.setCursor(0, 9);
3046
- helpers.doKeys('F', '4');
3047
- cm.setCursor(0, 9);
3048
- helpers.doKeys('y', ';', 'p');
3049
- eq('012345678945678', cm.getValue());
3050
- helpers.doKeys('u');
3051
- cm.setCursor(0, 0);
3052
- helpers.doKeys('y', ',', 'P');
3053
- eq('012340123456789', cm.getValue());
3054
- }, { value: '0123456789'});
3055
- testVim('ty,;', function(cm, vim, helpers) {
3056
- cm.setCursor(0, 0);
3057
- helpers.doKeys('t', '4');
3058
- cm.setCursor(0, 0);
3059
- helpers.doKeys('y', ';', 'P');
3060
- eq('01230123456789', cm.getValue());
3061
- helpers.doKeys('u');
3062
- cm.setCursor(0, 9);
3063
- helpers.doKeys('y', ',', 'p');
3064
- eq('01234567895678', cm.getValue());
3065
- }, { value: '0123456789'});
3066
- testVim('Ty,;', function(cm, vim, helpers) {
3067
- cm.setCursor(0, 9);
3068
- helpers.doKeys('T', '4');
3069
- cm.setCursor(0, 9);
3070
- helpers.doKeys('y', ';', 'p');
3071
- eq('01234567895678', cm.getValue());
3072
- helpers.doKeys('u');
3073
- cm.setCursor(0, 0);
3074
- helpers.doKeys('y', ',', 'P');
3075
- eq('01230123456789', cm.getValue());
3076
- }, { value: '0123456789'});
3077
- testVim('HML', function(cm, vim, helpers) {
3078
- var lines = 35;
3079
- var textHeight = cm.defaultTextHeight();
3080
- cm.setSize(600, lines*textHeight);
3081
- cm.setCursor(120, 0);
3082
- helpers.doKeys('H');
3083
- helpers.assertCursorAt(86, 2);
3084
- helpers.doKeys('L');
3085
- helpers.assertCursorAt(120, 4);
3086
- helpers.doKeys('M');
3087
- helpers.assertCursorAt(103,4);
3088
- }, { value: (function(){
3089
- var lines = new Array(100);
3090
- var upper = ' xx\n';
3091
- var lower = ' xx\n';
3092
- upper = lines.join(upper);
3093
- lower = lines.join(lower);
3094
- return upper + lower;
3095
- })()});
3096
-
3097
- var zVals = [];
3098
- forEach(['zb','zz','zt','z-','z.','z<CR>'], function(e, idx){
3099
- var lineNum = 250;
3100
- var lines = 35;
3101
- testVim(e, function(cm, vim, helpers) {
3102
- var k1 = e[0];
3103
- var k2 = e.substring(1);
3104
- var textHeight = cm.defaultTextHeight();
3105
- cm.setSize(600, lines*textHeight);
3106
- cm.setCursor(lineNum, 0);
3107
- helpers.doKeys(k1, k2);
3108
- zVals[idx] = cm.getScrollInfo().top;
3109
- }, { value: (function(){
3110
- return new Array(500).join('\n');
3111
- })()});
3112
- });
3113
- testVim('zb<zz', function(cm, vim, helpers){
3114
- eq(zVals[0]<zVals[1], true);
3115
- });
3116
- testVim('zz<zt', function(cm, vim, helpers){
3117
- eq(zVals[1]<zVals[2], true);
3118
- });
3119
- testVim('zb==z-', function(cm, vim, helpers){
3120
- eq(zVals[0], zVals[3]);
3121
- });
3122
- testVim('zz==z.', function(cm, vim, helpers){
3123
- eq(zVals[1], zVals[4]);
3124
- });
3125
- testVim('zt==z<CR>', function(cm, vim, helpers){
3126
- eq(zVals[2], zVals[5]);
3127
- });
3128
-
3129
- var moveTillCharacterSandbox =
3130
- 'The quick brown fox \n';
3131
- testVim('moveTillCharacter', function(cm, vim, helpers){
3132
- cm.setCursor(0, 0);
3133
- // Search for the 'q'.
3134
- cm.openDialog = helpers.fakeOpenDialog('q');
3135
- helpers.doKeys('/');
3136
- eq(4, cm.getCursor().ch);
3137
- // Jump to just before the first o in the list.
3138
- helpers.doKeys('t');
3139
- helpers.doKeys('o');
3140
- eq('The quick brown fox \n', cm.getValue());
3141
- // Delete that one character.
3142
- helpers.doKeys('d');
3143
- helpers.doKeys('t');
3144
- helpers.doKeys('o');
3145
- eq('The quick bown fox \n', cm.getValue());
3146
- // Delete everything until the next 'o'.
3147
- helpers.doKeys('.');
3148
- eq('The quick box \n', cm.getValue());
3149
- // An unmatched character should have no effect.
3150
- helpers.doKeys('d');
3151
- helpers.doKeys('t');
3152
- helpers.doKeys('q');
3153
- eq('The quick box \n', cm.getValue());
3154
- // Matches should only be possible on single lines.
3155
- helpers.doKeys('d');
3156
- helpers.doKeys('t');
3157
- helpers.doKeys('z');
3158
- eq('The quick box \n', cm.getValue());
3159
- // After all that, the search for 'q' should still be active, so the 'N' command
3160
- // can run it again in reverse. Use that to delete everything back to the 'q'.
3161
- helpers.doKeys('d');
3162
- helpers.doKeys('N');
3163
- eq('The ox \n', cm.getValue());
3164
- eq(4, cm.getCursor().ch);
3165
- }, { value: moveTillCharacterSandbox});
3166
- testVim('searchForPipe', function(cm, vim, helpers){
3167
- CodeMirror.Vim.setOption('pcre', false);
3168
- cm.setCursor(0, 0);
3169
- // Search for the '|'.
3170
- cm.openDialog = helpers.fakeOpenDialog('|');
3171
- helpers.doKeys('/');
3172
- eq(4, cm.getCursor().ch);
3173
- }, { value: 'this|that'});
3174
-
3175
-
3176
- var scrollMotionSandbox =
3177
- '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n';
3178
- testVim('scrollMotion', function(cm, vim, helpers){
3179
- var prevCursor, prevScrollInfo;
3180
- cm.setCursor(0, 0);
3181
- // ctrl-y at the top of the file should have no effect.
3182
- helpers.doKeys('<C-y>');
3183
- eq(0, cm.getCursor().line);
3184
- prevScrollInfo = cm.getScrollInfo();
3185
- helpers.doKeys('<C-e>');
3186
- eq(1, cm.getCursor().line);
3187
- is(prevScrollInfo.top < cm.getScrollInfo().top);
3188
- // Jump to the end of the sandbox.
3189
- cm.setCursor(1000, 0);
3190
- prevCursor = cm.getCursor();
3191
- // ctrl-e at the bottom of the file should have no effect.
3192
- helpers.doKeys('<C-e>');
3193
- eq(prevCursor.line, cm.getCursor().line);
3194
- prevScrollInfo = cm.getScrollInfo();
3195
- helpers.doKeys('<C-y>');
3196
- eq(prevCursor.line - 1, cm.getCursor().line, "Y");
3197
- is(prevScrollInfo.top > cm.getScrollInfo().top);
3198
- }, { value: scrollMotionSandbox});
3199
-
3200
- var squareBracketMotionSandbox = ''+
3201
- '({\n'+//0
3202
- ' ({\n'+//11
3203
- ' /*comment {\n'+//2
3204
- ' */(\n'+//3
3205
- '#else \n'+//4
3206
- ' /* )\n'+//5
3207
- '#if }\n'+//6
3208
- ' )}*/\n'+//7
3209
- ')}\n'+//8
3210
- '{}\n'+//9
3211
- '#else {{\n'+//10
3212
- '{}\n'+//11
3213
- '}\n'+//12
3214
- '{\n'+//13
3215
- '#endif\n'+//14
3216
- '}\n'+//15
3217
- '}\n'+//16
3218
- '#else';//17
3219
- testVim('[[, ]]', function(cm, vim, helpers) {
3220
- cm.setCursor(0, 0);
3221
- helpers.doKeys(']', ']');
3222
- helpers.assertCursorAt(9,0);
3223
- helpers.doKeys('2', ']', ']');
3224
- helpers.assertCursorAt(13,0);
3225
- helpers.doKeys(']', ']');
3226
- helpers.assertCursorAt(17,0);
3227
- helpers.doKeys('[', '[');
3228
- helpers.assertCursorAt(13,0);
3229
- helpers.doKeys('2', '[', '[');
3230
- helpers.assertCursorAt(9,0);
3231
- helpers.doKeys('[', '[');
3232
- helpers.assertCursorAt(0,0);
3233
- }, { value: squareBracketMotionSandbox});
3234
- testVim('[], ][', function(cm, vim, helpers) {
3235
- cm.setCursor(0, 0);
3236
- helpers.doKeys(']', '[');
3237
- helpers.assertCursorAt(12,0);
3238
- helpers.doKeys('2', ']', '[');
3239
- helpers.assertCursorAt(16,0);
3240
- helpers.doKeys(']', '[');
3241
- helpers.assertCursorAt(17,0);
3242
- helpers.doKeys('[', ']');
3243
- helpers.assertCursorAt(16,0);
3244
- helpers.doKeys('2', '[', ']');
3245
- helpers.assertCursorAt(12,0);
3246
- helpers.doKeys('[', ']');
3247
- helpers.assertCursorAt(0,0);
3248
- }, { value: squareBracketMotionSandbox});
3249
- testVim('[{, ]}', function(cm, vim, helpers) {
3250
- cm.setCursor(4, 10);
3251
- helpers.doKeys('[', '{');
3252
- helpers.assertCursorAt(2,12);
3253
- helpers.doKeys('2', '[', '{');
3254
- helpers.assertCursorAt(0,1);
3255
- cm.setCursor(4, 10);
3256
- helpers.doKeys(']', '}');
3257
- helpers.assertCursorAt(6,11);
3258
- helpers.doKeys('2', ']', '}');
3259
- helpers.assertCursorAt(8,1);
3260
- cm.setCursor(0,1);
3261
- helpers.doKeys(']', '}');
3262
- helpers.assertCursorAt(8,1);
3263
- helpers.doKeys('[', '{');
3264
- helpers.assertCursorAt(0,1);
3265
- }, { value: squareBracketMotionSandbox});
3266
- testVim('[(, ])', function(cm, vim, helpers) {
3267
- cm.setCursor(4, 10);
3268
- helpers.doKeys('[', '(');
3269
- helpers.assertCursorAt(3,14);
3270
- helpers.doKeys('2', '[', '(');
3271
- helpers.assertCursorAt(0,0);
3272
- cm.setCursor(4, 10);
3273
- helpers.doKeys(']', ')');
3274
- helpers.assertCursorAt(5,11);
3275
- helpers.doKeys('2', ']', ')');
3276
- helpers.assertCursorAt(8,0);
3277
- helpers.doKeys('[', '(');
3278
- helpers.assertCursorAt(0,0);
3279
- helpers.doKeys(']', ')');
3280
- helpers.assertCursorAt(8,0);
3281
- }, { value: squareBracketMotionSandbox});
3282
- testVim('[*, ]*, [/, ]/', function(cm, vim, helpers) {
3283
- forEach(['*', '/'], function(key){
3284
- cm.setCursor(7, 0);
3285
- helpers.doKeys('2', '[', key);
3286
- helpers.assertCursorAt(2,2);
3287
- helpers.doKeys('2', ']', key);
3288
- helpers.assertCursorAt(7,5);
3289
- });
3290
- }, { value: squareBracketMotionSandbox});
3291
- testVim('[#, ]#', function(cm, vim, helpers) {
3292
- cm.setCursor(10, 3);
3293
- helpers.doKeys('2', '[', '#');
3294
- helpers.assertCursorAt(4,0);
3295
- helpers.doKeys('5', ']', '#');
3296
- helpers.assertCursorAt(17,0);
3297
- cm.setCursor(10, 3);
3298
- helpers.doKeys(']', '#');
3299
- helpers.assertCursorAt(14,0);
3300
- }, { value: squareBracketMotionSandbox});
3301
- testVim('[m, ]m, [M, ]M', function(cm, vim, helpers) {
3302
- cm.setCursor(11, 0);
3303
- helpers.doKeys('[', 'm');
3304
- helpers.assertCursorAt(10,7);
3305
- helpers.doKeys('4', '[', 'm');
3306
- helpers.assertCursorAt(1,3);
3307
- helpers.doKeys('5', ']', 'm');
3308
- helpers.assertCursorAt(11,0);
3309
- helpers.doKeys('[', 'M');
3310
- helpers.assertCursorAt(9,1);
3311
- helpers.doKeys('3', ']', 'M');
3312
- helpers.assertCursorAt(15,0);
3313
- helpers.doKeys('5', '[', 'M');
3314
- helpers.assertCursorAt(7,3);
3315
- }, { value: squareBracketMotionSandbox});
3316
-
3317
- // Ex mode tests
3318
- testVim('ex_go_to_line', function(cm, vim, helpers) {
3319
- cm.setCursor(0, 0);
3320
- helpers.doEx('4');
3321
- helpers.assertCursorAt(3, 0);
3322
- }, { value: 'a\nb\nc\nd\ne\n'});
3323
- testVim('ex_write', function(cm, vim, helpers) {
3324
- var tmp = CodeMirror.commands.save;
3325
- var written;
3326
- var actualCm;
3327
- CodeMirror.commands.save = function(cm) {
3328
- written = true;
3329
- actualCm = cm;
3330
- };
3331
- // Test that w, wr, wri ... write all trigger :write.
3332
- var command = 'write';
3333
- for (var i = 1; i < command.length; i++) {
3334
- written = false;
3335
- actualCm = null;
3336
- helpers.doEx(command.substring(0, i));
3337
- eq(written, true);
3338
- eq(actualCm, cm);
3339
- }
3340
- CodeMirror.commands.save = tmp;
3341
- });
3342
- testVim('ex_sort', function(cm, vim, helpers) {
3343
- helpers.doEx('sort');
3344
- eq('Z\na\nb\nc\nd', cm.getValue());
3345
- }, { value: 'b\nZ\nd\nc\na'});
3346
- testVim('ex_sort_reverse', function(cm, vim, helpers) {
3347
- helpers.doEx('sort!');
3348
- eq('d\nc\nb\na', cm.getValue());
3349
- }, { value: 'b\nd\nc\na'});
3350
- testVim('ex_sort_range', function(cm, vim, helpers) {
3351
- helpers.doEx('2,3sort');
3352
- eq('b\nc\nd\na', cm.getValue());
3353
- }, { value: 'b\nd\nc\na'});
3354
- testVim('ex_sort_oneline', function(cm, vim, helpers) {
3355
- helpers.doEx('2sort');
3356
- // Expect no change.
3357
- eq('b\nd\nc\na', cm.getValue());
3358
- }, { value: 'b\nd\nc\na'});
3359
- testVim('ex_sort_ignoreCase', function(cm, vim, helpers) {
3360
- helpers.doEx('sort i');
3361
- eq('a\nb\nc\nd\nZ', cm.getValue());
3362
- }, { value: 'b\nZ\nd\nc\na'});
3363
- testVim('ex_sort_unique', function(cm, vim, helpers) {
3364
- helpers.doEx('sort u');
3365
- eq('Z\na\nb\nc\nd', cm.getValue());
3366
- }, { value: 'b\nZ\na\na\nd\na\nc\na'});
3367
- testVim('ex_sort_decimal', function(cm, vim, helpers) {
3368
- helpers.doEx('sort d');
3369
- eq('d3\n s5\n6\n.9', cm.getValue());
3370
- }, { value: '6\nd3\n s5\n.9'});
3371
- testVim('ex_sort_decimal_negative', function(cm, vim, helpers) {
3372
- helpers.doEx('sort d');
3373
- eq('z-9\nd3\n s5\n6\n.9', cm.getValue());
3374
- }, { value: '6\nd3\n s5\n.9\nz-9'});
3375
- testVim('ex_sort_decimal_reverse', function(cm, vim, helpers) {
3376
- helpers.doEx('sort! d');
3377
- eq('.9\n6\n s5\nd3', cm.getValue());
3378
- }, { value: '6\nd3\n s5\n.9'});
3379
- testVim('ex_sort_hex', function(cm, vim, helpers) {
3380
- helpers.doEx('sort x');
3381
- eq(' s5\n6\n.9\n&0xB\nd3', cm.getValue());
3382
- }, { value: '6\nd3\n s5\n&0xB\n.9'});
3383
- testVim('ex_sort_octal', function(cm, vim, helpers) {
3384
- helpers.doEx('sort o');
3385
- eq('.8\n.9\nd3\n s5\n6', cm.getValue());
3386
- }, { value: '6\nd3\n s5\n.9\n.8'});
3387
- testVim('ex_sort_decimal_mixed', function(cm, vim, helpers) {
3388
- helpers.doEx('sort d');
3389
- eq('y\nz\nc1\nb2\na3', cm.getValue());
3390
- }, { value: 'a3\nz\nc1\ny\nb2'});
3391
- testVim('ex_sort_decimal_mixed_reverse', function(cm, vim, helpers) {
3392
- helpers.doEx('sort! d');
3393
- eq('a3\nb2\nc1\nz\ny', cm.getValue());
3394
- }, { value: 'a3\nz\nc1\ny\nb2'});
3395
- testVim('ex_sort_patterns_not_supported', function(cm, vim, helpers) {
3396
- var notified = false;
3397
- cm.openNotification = helpers.fakeOpenNotification(function(text) {
3398
- notified = /patterns not supported/.test(text);
3399
- });
3400
- helpers.doEx('sort /abc/');
3401
- is(notified, 'No notification.');
3402
- });
3403
- // test for :global command
3404
- testVim('ex_global', function(cm, vim, helpers) {
3405
- cm.setCursor(0, 0);
3406
- helpers.doEx('g/one/s//two');
3407
- eq('two two\n two two\n two two', cm.getValue());
3408
- helpers.doEx('1,2g/two/s//one');
3409
- eq('one one\n one one\n two two', cm.getValue());
3410
- }, {value: 'one one\n one one\n one one'});
3411
- testVim('ex_global_confirm', function(cm, vim, helpers) {
3412
- cm.setCursor(0, 0);
3413
- var onKeyDown;
3414
- var openDialogSave = cm.openDialog;
3415
- var KEYCODES = {
3416
- a: 65,
3417
- n: 78,
3418
- q: 81,
3419
- y: 89
3420
- };
3421
- // Intercept the ex command, 'global'
3422
- cm.openDialog = function(template, callback, options) {
3423
- // Intercept the prompt for the embedded ex command, 'substitute'
3424
- cm.openDialog = function(template, callback, options) {
3425
- onKeyDown = options.onKeyDown;
3426
- };
3427
- callback('g/one/s//two/gc');
3428
- };
3429
- helpers.doKeys(':');
3430
- var close = function() {};
3431
- onKeyDown({keyCode: KEYCODES.n}, '', close);
3432
- onKeyDown({keyCode: KEYCODES.y}, '', close);
3433
- onKeyDown({keyCode: KEYCODES.a}, '', close);
3434
- onKeyDown({keyCode: KEYCODES.q}, '', close);
3435
- onKeyDown({keyCode: KEYCODES.y}, '', close);
3436
- eq('one two\n two two\n one one\n two one\n one one', cm.getValue());
3437
- }, {value: 'one one\n one one\n one one\n one one\n one one'});
3438
- // Basic substitute tests.
3439
- testVim('ex_substitute_same_line', function(cm, vim, helpers) {
3440
- cm.setCursor(1, 0);
3441
- helpers.doEx('s/one/two/g');
3442
- eq('one one\n two two', cm.getValue());
3443
- }, { value: 'one one\n one one'});
3444
- testVim('ex_substitute_full_file', function(cm, vim, helpers) {
3445
- cm.setCursor(1, 0);
3446
- helpers.doEx('%s/one/two/g');
3447
- eq('two two\n two two', cm.getValue());
3448
- }, { value: 'one one\n one one'});
3449
- testVim('ex_substitute_input_range', function(cm, vim, helpers) {
3450
- cm.setCursor(1, 0);
3451
- helpers.doEx('1,3s/\\d/0/g');
3452
- eq('0\n0\n0\n4', cm.getValue());
3453
- }, { value: '1\n2\n3\n4' });
3454
- testVim('ex_substitute_visual_range', function(cm, vim, helpers) {
3455
- cm.setCursor(1, 0);
3456
- // Set last visual mode selection marks '< and '> at lines 2 and 4
3457
- helpers.doKeys('V', '2', 'j', 'v');
3458
- helpers.doEx('\'<,\'>s/\\d/0/g');
3459
- eq('1\n0\n0\n0\n5', cm.getValue());
3460
- }, { value: '1\n2\n3\n4\n5' });
3461
- testVim('ex_substitute_empty_query', function(cm, vim, helpers) {
3462
- // If the query is empty, use last query.
3463
- cm.setCursor(1, 0);
3464
- cm.openDialog = helpers.fakeOpenDialog('1');
3465
- helpers.doKeys('/');
3466
- helpers.doEx('s//b/g');
3467
- eq('abb ab2 ab3', cm.getValue());
3468
- }, { value: 'a11 a12 a13' });
3469
- testVim('ex_substitute_javascript', function(cm, vim, helpers) {
3470
- CodeMirror.Vim.setOption('pcre', false);
3471
- cm.setCursor(1, 0);
3472
- // Throw all the things that javascript likes to treat as special values
3473
- // into the replace part. All should be literal (this is VIM).
3474
- helpers.doEx('s/\\(\\d+\\)/$$ $\' $` $& \\1/g')
3475
- eq('a $$ $\' $` $& 0 b', cm.getValue());
3476
- }, { value: 'a 0 b' });
3477
- testVim('ex_substitute_empty_arguments', function(cm,vim,helpers) {
3478
- cm.setCursor(0, 0);
3479
- helpers.doEx('s/a/b/g');
3480
- cm.setCursor(1, 0);
3481
- helpers.doEx('s');
3482
- eq('b b\nb a', cm.getValue());
3483
- }, {value: 'a a\na a'});
3484
-
3485
- // More complex substitute tests that test both pcre and nopcre options.
3486
- function testSubstitute(name, options) {
3487
- testVim(name + '_pcre', function(cm, vim, helpers) {
3488
- cm.setCursor(1, 0);
3489
- CodeMirror.Vim.setOption('pcre', true);
3490
- helpers.doEx(options.expr);
3491
- eq(options.expectedValue, cm.getValue());
3492
- }, options);
3493
- // If no noPcreExpr is defined, assume that it's the same as the expr.
3494
- var noPcreExpr = options.noPcreExpr ? options.noPcreExpr : options.expr;
3495
- testVim(name + '_nopcre', function(cm, vim, helpers) {
3496
- cm.setCursor(1, 0);
3497
- CodeMirror.Vim.setOption('pcre', false);
3498
- helpers.doEx(noPcreExpr);
3499
- eq(options.expectedValue, cm.getValue());
3500
- }, options);
3501
- }
3502
- testSubstitute('ex_substitute_capture', {
3503
- value: 'a11 a12 a13',
3504
- expectedValue: 'a1111 a1212 a1313',
3505
- // $n is a backreference
3506
- expr: 's/(\\d+)/$1$1/g',
3507
- // \n is a backreference.
3508
- noPcreExpr: 's/\\(\\d+\\)/\\1\\1/g'});
3509
- testSubstitute('ex_substitute_capture2', {
3510
- value: 'a 0 b',
3511
- expectedValue: 'a $00 b',
3512
- expr: 's/(\\d+)/$$$1$1/g',
3513
- noPcreExpr: 's/\\(\\d+\\)/$\\1\\1/g'});
3514
- testSubstitute('ex_substitute_nocapture', {
3515
- value: 'a11 a12 a13',
3516
- expectedValue: 'a$1$1 a$1$1 a$1$1',
3517
- expr: 's/(\\d+)/$$1$$1/g',
3518
- noPcreExpr: 's/\\(\\d+\\)/$1$1/g'});
3519
- testSubstitute('ex_substitute_nocapture2', {
3520
- value: 'a 0 b',
3521
- expectedValue: 'a $10 b',
3522
- expr: 's/(\\d+)/$$1$1/g',
3523
- noPcreExpr: 's/\\(\\d+\\)/\\$1\\1/g'});
3524
- testSubstitute('ex_substitute_nocapture', {
3525
- value: 'a b c',
3526
- expectedValue: 'a $ c',
3527
- expr: 's/b/$$/',
3528
- noPcreExpr: 's/b/$/'});
3529
- testSubstitute('ex_substitute_slash_regex', {
3530
- value: 'one/two \n three/four',
3531
- expectedValue: 'one|two \n three|four',
3532
- expr: '%s/\\//|'});
3533
- testSubstitute('ex_substitute_pipe_regex', {
3534
- value: 'one|two \n three|four',
3535
- expectedValue: 'one,two \n three,four',
3536
- expr: '%s/\\|/,/',
3537
- noPcreExpr: '%s/|/,/'});
3538
- testSubstitute('ex_substitute_or_regex', {
3539
- value: 'one|two \n three|four',
3540
- expectedValue: 'ana|twa \n thraa|faar',
3541
- expr: '%s/o|e|u/a/g',
3542
- noPcreExpr: '%s/o\\|e\\|u/a/g'});
3543
- testSubstitute('ex_substitute_or_word_regex', {
3544
- value: 'one|two \n three|four',
3545
- expectedValue: 'five|five \n three|four',
3546
- expr: '%s/(one|two)/five/g',
3547
- noPcreExpr: '%s/\\(one\\|two\\)/five/g'});
3548
- testSubstitute('ex_substitute_backslashslash_regex', {
3549
- value: 'one\\two \n three\\four',
3550
- expectedValue: 'one,two \n three,four',
3551
- expr: '%s/\\\\/,'});
3552
- testSubstitute('ex_substitute_slash_replacement', {
3553
- value: 'one,two \n three,four',
3554
- expectedValue: 'one/two \n three/four',
3555
- expr: '%s/,/\\/'});
3556
- testSubstitute('ex_substitute_backslash_replacement', {
3557
- value: 'one,two \n three,four',
3558
- expectedValue: 'one\\two \n three\\four',
3559
- expr: '%s/,/\\\\/g'});
3560
- testSubstitute('ex_substitute_multibackslash_replacement', {
3561
- value: 'one,two \n three,four',
3562
- expectedValue: 'one\\\\\\\\two \n three\\\\\\\\four', // 2*8 backslashes.
3563
- expr: '%s/,/\\\\\\\\\\\\\\\\/g'}); // 16 backslashes.
3564
- testSubstitute('ex_substitute_newline_replacement', {
3565
- value: 'one,two \n three,four',
3566
- expectedValue: 'one\ntwo \n three\nfour',
3567
- expr: '%s/,/\\n/g'});
3568
- testSubstitute('ex_substitute_braces_word', {
3569
- value: 'ababab abb ab{2}',
3570
- expectedValue: 'ab abb ab{2}',
3571
- expr: '%s/(ab){2}//g',
3572
- noPcreExpr: '%s/\\(ab\\)\\{2\\}//g'});
3573
- testSubstitute('ex_substitute_braces_range', {
3574
- value: 'a aa aaa aaaa',
3575
- expectedValue: 'a a',
3576
- expr: '%s/a{2,3}//g',
3577
- noPcreExpr: '%s/a\\{2,3\\}//g'});
3578
- testSubstitute('ex_substitute_braces_literal', {
3579
- value: 'ababab abb ab{2}',
3580
- expectedValue: 'ababab abb ',
3581
- expr: '%s/ab\\{2\\}//g',
3582
- noPcreExpr: '%s/ab{2}//g'});
3583
- testSubstitute('ex_substitute_braces_char', {
3584
- value: 'ababab abb ab{2}',
3585
- expectedValue: 'ababab ab{2}',
3586
- expr: '%s/ab{2}//g',
3587
- noPcreExpr: '%s/ab\\{2\\}//g'});
3588
- testSubstitute('ex_substitute_braces_no_escape', {
3589
- value: 'ababab abb ab{2}',
3590
- expectedValue: 'ababab ab{2}',
3591
- expr: '%s/ab{2}//g',
3592
- noPcreExpr: '%s/ab\\{2}//g'});
3593
- testSubstitute('ex_substitute_count', {
3594
- value: '1\n2\n3\n4',
3595
- expectedValue: '1\n0\n0\n4',
3596
- expr: 's/\\d/0/i 2'});
3597
- testSubstitute('ex_substitute_count_with_range', {
3598
- value: '1\n2\n3\n4',
3599
- expectedValue: '1\n2\n0\n0',
3600
- expr: '1,3s/\\d/0/ 3'});
3601
- testSubstitute('ex_substitute_not_global', {
3602
- value: 'aaa\nbaa\ncaa',
3603
- expectedValue: 'xaa\nbxa\ncxa',
3604
- expr: '%s/a/x/'});
3605
- function testSubstituteConfirm(name, command, initialValue, expectedValue, keys, finalPos) {
3606
- testVim(name, function(cm, vim, helpers) {
3607
- var savedOpenDialog = cm.openDialog;
3608
- var savedKeyName = CodeMirror.keyName;
3609
- var onKeyDown;
3610
- var recordedCallback;
3611
- var closed = true; // Start out closed, set false on second openDialog.
3612
- function close() {
3613
- closed = true;
3614
- }
3615
- // First openDialog should save callback.
3616
- cm.openDialog = function(template, callback, options) {
3617
- recordedCallback = callback;
3618
- }
3619
- // Do first openDialog.
3620
- helpers.doKeys(':');
3621
- // Second openDialog should save keyDown handler.
3622
- cm.openDialog = function(template, callback, options) {
3623
- onKeyDown = options.onKeyDown;
3624
- closed = false;
3625
- };
3626
- // Return the command to Vim and trigger second openDialog.
3627
- recordedCallback(command);
3628
- // The event should really use keyCode, but here just mock it out and use
3629
- // key and replace keyName to just return key.
3630
- CodeMirror.keyName = function (e) { return e.key; }
3631
- keys = keys.toUpperCase();
3632
- for (var i = 0; i < keys.length; i++) {
3633
- is(!closed);
3634
- onKeyDown({ key: keys.charAt(i) }, '', close);
3635
- }
3636
- try {
3637
- eq(expectedValue, cm.getValue());
3638
- helpers.assertCursorAt(finalPos);
3639
- is(closed);
3640
- } catch(e) {
3641
- throw e
3642
- } finally {
3643
- // Restore overriden functions.
3644
- CodeMirror.keyName = savedKeyName;
3645
- cm.openDialog = savedOpenDialog;
3646
- }
3647
- }, { value: initialValue });
3648
- };
3649
- testSubstituteConfirm('ex_substitute_confirm_emptydoc',
3650
- '%s/x/b/c', '', '', '', makeCursor(0, 0));
3651
- testSubstituteConfirm('ex_substitute_confirm_nomatch',
3652
- '%s/x/b/c', 'ba a\nbab', 'ba a\nbab', '', makeCursor(0, 0));
3653
- testSubstituteConfirm('ex_substitute_confirm_accept',
3654
- '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'yyy', makeCursor(1, 1));
3655
- testSubstituteConfirm('ex_substitute_confirm_random_keys',
3656
- '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'ysdkywerty', makeCursor(1, 1));
3657
- testSubstituteConfirm('ex_substitute_confirm_some',
3658
- '%s/a/b/cg', 'ba a\nbab', 'bb a\nbbb', 'yny', makeCursor(1, 1));
3659
- testSubstituteConfirm('ex_substitute_confirm_all',
3660
- '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'a', makeCursor(1, 1));
3661
- testSubstituteConfirm('ex_substitute_confirm_accept_then_all',
3662
- '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'ya', makeCursor(1, 1));
3663
- testSubstituteConfirm('ex_substitute_confirm_quit',
3664
- '%s/a/b/cg', 'ba a\nbab', 'bb a\nbab', 'yq', makeCursor(0, 3));
3665
- testSubstituteConfirm('ex_substitute_confirm_last',
3666
- '%s/a/b/cg', 'ba a\nbab', 'bb b\nbab', 'yl', makeCursor(0, 3));
3667
- testSubstituteConfirm('ex_substitute_confirm_oneline',
3668
- '1s/a/b/cg', 'ba a\nbab', 'bb b\nbab', 'yl', makeCursor(0, 3));
3669
- testSubstituteConfirm('ex_substitute_confirm_range_accept',
3670
- '1,2s/a/b/cg', 'aa\na \na\na', 'bb\nb \na\na', 'yyy', makeCursor(1, 0));
3671
- testSubstituteConfirm('ex_substitute_confirm_range_some',
3672
- '1,3s/a/b/cg', 'aa\na \na\na', 'ba\nb \nb\na', 'ynyy', makeCursor(2, 0));
3673
- testSubstituteConfirm('ex_substitute_confirm_range_all',
3674
- '1,3s/a/b/cg', 'aa\na \na\na', 'bb\nb \nb\na', 'a', makeCursor(2, 0));
3675
- testSubstituteConfirm('ex_substitute_confirm_range_last',
3676
- '1,3s/a/b/cg', 'aa\na \na\na', 'bb\nb \na\na', 'yyl', makeCursor(1, 0));
3677
- //:noh should clear highlighting of search-results but allow to resume search through n
3678
- testVim('ex_noh_clearSearchHighlight', function(cm, vim, helpers) {
3679
- cm.openDialog = helpers.fakeOpenDialog('match');
3680
- helpers.doKeys('?');
3681
- helpers.doEx('noh');
3682
- eq(vim.searchState_.getOverlay(),null,'match-highlighting wasn\'t cleared');
3683
- helpers.doKeys('n');
3684
- helpers.assertCursorAt(0, 11,'can\'t resume search after clearing highlighting');
3685
- }, { value: 'match nope match \n nope Match' });
3686
- testVim('set_boolean', function(cm, vim, helpers) {
3687
- CodeMirror.Vim.defineOption('testoption', true, 'boolean');
3688
- // Test default value is set.
3689
- is(CodeMirror.Vim.getOption('testoption'));
3690
- try {
3691
- // Test fail to set to non-boolean
3692
- CodeMirror.Vim.setOption('testoption', '5');
3693
- fail();
3694
- } catch (expected) {};
3695
- // Test setOption
3696
- CodeMirror.Vim.setOption('testoption', false);
3697
- is(!CodeMirror.Vim.getOption('testoption'));
3698
- });
3699
- testVim('ex_set_boolean', function(cm, vim, helpers) {
3700
- CodeMirror.Vim.defineOption('testoption', true, 'boolean');
3701
- // Test default value is set.
3702
- is(CodeMirror.Vim.getOption('testoption'));
3703
- try {
3704
- // Test fail to set to non-boolean
3705
- helpers.doEx('set testoption=22');
3706
- fail();
3707
- } catch (expected) {};
3708
- // Test setOption
3709
- helpers.doEx('set notestoption');
3710
- is(!CodeMirror.Vim.getOption('testoption'));
3711
- });
3712
- testVim('set_string', function(cm, vim, helpers) {
3713
- CodeMirror.Vim.defineOption('testoption', 'a', 'string');
3714
- // Test default value is set.
3715
- eq('a', CodeMirror.Vim.getOption('testoption'));
3716
- try {
3717
- // Test fail to set non-string.
3718
- CodeMirror.Vim.setOption('testoption', true);
3719
- fail();
3720
- } catch (expected) {};
3721
- try {
3722
- // Test fail to set 'notestoption'
3723
- CodeMirror.Vim.setOption('notestoption', 'b');
3724
- fail();
3725
- } catch (expected) {};
3726
- // Test setOption
3727
- CodeMirror.Vim.setOption('testoption', 'c');
3728
- eq('c', CodeMirror.Vim.getOption('testoption'));
3729
- });
3730
- testVim('ex_set_string', function(cm, vim, helpers) {
3731
- CodeMirror.Vim.defineOption('testopt', 'a', 'string');
3732
- // Test default value is set.
3733
- eq('a', CodeMirror.Vim.getOption('testopt'));
3734
- try {
3735
- // Test fail to set 'notestopt'
3736
- helpers.doEx('set notestopt=b');
3737
- fail();
3738
- } catch (expected) {};
3739
- // Test setOption
3740
- helpers.doEx('set testopt=c')
3741
- eq('c', CodeMirror.Vim.getOption('testopt'));
3742
- helpers.doEx('set testopt=c')
3743
- eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global
3744
- eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); // local
3745
- eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); // global
3746
- eq('c', CodeMirror.Vim.getOption('testopt')); // global
3747
- // Test setOption global
3748
- helpers.doEx('setg testopt=d')
3749
- eq('c', CodeMirror.Vim.getOption('testopt', cm));
3750
- eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'}));
3751
- eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'}));
3752
- eq('d', CodeMirror.Vim.getOption('testopt'));
3753
- // Test setOption local
3754
- helpers.doEx('setl testopt=e')
3755
- eq('e', CodeMirror.Vim.getOption('testopt', cm));
3756
- eq('e', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'}));
3757
- eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'}));
3758
- eq('d', CodeMirror.Vim.getOption('testopt'));
3759
- });
3760
- testVim('ex_set_callback', function(cm, vim, helpers) {
3761
- var global;
3762
-
3763
- function cb(val, cm, cfg) {
3764
- if (val === undefined) {
3765
- // Getter
3766
- if (cm) {
3767
- return cm._local;
3768
- } else {
3769
- return global;
3770
- }
3771
- } else {
3772
- // Setter
3773
- if (cm) {
3774
- cm._local = val;
3775
- } else {
3776
- global = val;
3777
- }
3778
- }
3779
- }
3780
-
3781
- CodeMirror.Vim.defineOption('testopt', 'a', 'string', cb);
3782
- // Test default value is set.
3783
- eq('a', CodeMirror.Vim.getOption('testopt'));
3784
- try {
3785
- // Test fail to set 'notestopt'
3786
- helpers.doEx('set notestopt=b');
3787
- fail();
3788
- } catch (expected) {};
3789
- // Test setOption (Identical to the string tests, but via callback instead)
3790
- helpers.doEx('set testopt=c')
3791
- eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global
3792
- eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); // local
3793
- eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); // global
3794
- eq('c', CodeMirror.Vim.getOption('testopt')); // global
3795
- // Test setOption global
3796
- helpers.doEx('setg testopt=d')
3797
- eq('c', CodeMirror.Vim.getOption('testopt', cm));
3798
- eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'}));
3799
- eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'}));
3800
- eq('d', CodeMirror.Vim.getOption('testopt'));
3801
- // Test setOption local
3802
- helpers.doEx('setl testopt=e')
3803
- eq('e', CodeMirror.Vim.getOption('testopt', cm));
3804
- eq('e', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'}));
3805
- eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'}));
3806
- eq('d', CodeMirror.Vim.getOption('testopt'));
3807
- })
3808
- testVim('ex_set_filetype', function(cm, vim, helpers) {
3809
- CodeMirror.defineMode('test_mode', function() {
3810
- return {token: function(stream) {
3811
- stream.match(/^\s+|^\S+/);
3812
- }};
3813
- });
3814
- CodeMirror.defineMode('test_mode_2', function() {
3815
- return {token: function(stream) {
3816
- stream.match(/^\s+|^\S+/);
3817
- }};
3818
- });
3819
- // Test mode is set.
3820
- helpers.doEx('set filetype=test_mode');
3821
- eq('test_mode', cm.getMode().name);
3822
- // Test 'ft' alias also sets mode.
3823
- helpers.doEx('set ft=test_mode_2');
3824
- eq('test_mode_2', cm.getMode().name);
3825
- });
3826
- testVim('ex_set_filetype_null', function(cm, vim, helpers) {
3827
- CodeMirror.defineMode('test_mode', function() {
3828
- return {token: function(stream) {
3829
- stream.match(/^\s+|^\S+/);
3830
- }};
3831
- });
3832
- cm.setOption('mode', 'test_mode');
3833
- // Test mode is set to null.
3834
- helpers.doEx('set filetype=');
3835
- eq('null', cm.getMode().name);
3836
- });
3837
- // TODO: Reset key maps after each test.
3838
- testVim('ex_map_key2key', function(cm, vim, helpers) {
3839
- helpers.doEx('map a x');
3840
- helpers.doKeys('a');
3841
- helpers.assertCursorAt(0, 0);
3842
- eq('bc', cm.getValue());
3843
- }, { value: 'abc' });
3844
- testVim('ex_unmap_key2key', function(cm, vim, helpers) {
3845
- helpers.doEx('unmap a');
3846
- helpers.doKeys('a');
3847
- eq('vim-insert', cm.getOption('keyMap'));
3848
- }, { value: 'abc' });
3849
- testVim('ex_unmap_key2key_does_not_remove_default', function(cm, vim, helpers) {
3850
- try {
3851
- helpers.doEx('unmap a');
3852
- fail();
3853
- } catch (expected) {}
3854
- helpers.doKeys('a');
3855
- eq('vim-insert', cm.getOption('keyMap'));
3856
- }, { value: 'abc' });
3857
- testVim('ex_map_key2key_to_colon', function(cm, vim, helpers) {
3858
- helpers.doEx('map ; :');
3859
- var dialogOpened = false;
3860
- cm.openDialog = function() {
3861
- dialogOpened = true;
3862
- }
3863
- helpers.doKeys(';');
3864
- eq(dialogOpened, true);
3865
- });
3866
- testVim('ex_map_ex2key:', function(cm, vim, helpers) {
3867
- helpers.doEx('map :del x');
3868
- helpers.doEx('del');
3869
- helpers.assertCursorAt(0, 0);
3870
- eq('bc', cm.getValue());
3871
- }, { value: 'abc' });
3872
- testVim('ex_map_ex2ex', function(cm, vim, helpers) {
3873
- helpers.doEx('map :del :w');
3874
- var tmp = CodeMirror.commands.save;
3875
- var written = false;
3876
- var actualCm;
3877
- CodeMirror.commands.save = function(cm) {
3878
- written = true;
3879
- actualCm = cm;
3880
- };
3881
- helpers.doEx('del');
3882
- CodeMirror.commands.save = tmp;
3883
- eq(written, true);
3884
- eq(actualCm, cm);
3885
- });
3886
- testVim('ex_map_key2ex', function(cm, vim, helpers) {
3887
- helpers.doEx('map a :w');
3888
- var tmp = CodeMirror.commands.save;
3889
- var written = false;
3890
- var actualCm;
3891
- CodeMirror.commands.save = function(cm) {
3892
- written = true;
3893
- actualCm = cm;
3894
- };
3895
- helpers.doKeys('a');
3896
- CodeMirror.commands.save = tmp;
3897
- eq(written, true);
3898
- eq(actualCm, cm);
3899
- });
3900
- testVim('ex_map_key2key_visual_api', function(cm, vim, helpers) {
3901
- CodeMirror.Vim.map('b', ':w', 'visual');
3902
- var tmp = CodeMirror.commands.save;
3903
- var written = false;
3904
- var actualCm;
3905
- CodeMirror.commands.save = function(cm) {
3906
- written = true;
3907
- actualCm = cm;
3908
- };
3909
- // Mapping should not work in normal mode.
3910
- helpers.doKeys('b');
3911
- eq(written, false);
3912
- // Mapping should work in visual mode.
3913
- helpers.doKeys('v', 'b');
3914
- eq(written, true);
3915
- eq(actualCm, cm);
3916
-
3917
- CodeMirror.commands.save = tmp;
3918
- });
3919
- testVim('ex_imap', function(cm, vim, helpers) {
3920
- CodeMirror.Vim.map('jk', '<Esc>', 'insert');
3921
- helpers.doKeys('i');
3922
- is(vim.insertMode);
3923
- helpers.doKeys('j', 'k');
3924
- is(!vim.insertMode);
3925
- })
3926
-
3927
- // Testing registration of functions as ex-commands and mapping to <Key>-keys
3928
- testVim('ex_api_test', function(cm, vim, helpers) {
3929
- var res=false;
3930
- var val='from';
3931
- CodeMirror.Vim.defineEx('extest','ext',function(cm,params){
3932
- if(params.args)val=params.args[0];
3933
- else res=true;
3934
- });
3935
- helpers.doEx(':ext to');
3936
- eq(val,'to','Defining ex-command failed');
3937
- CodeMirror.Vim.map('<C-CR><Space>',':ext');
3938
- helpers.doKeys('<C-CR>','<Space>');
3939
- is(res,'Mapping to key failed');
3940
- });
3941
- // For now, this test needs to be last because it messes up : for future tests.
3942
- testVim('ex_map_key2key_from_colon', function(cm, vim, helpers) {
3943
- helpers.doEx('map : x');
3944
- helpers.doKeys(':');
3945
- helpers.assertCursorAt(0, 0);
3946
- eq('bc', cm.getValue());
3947
- }, { value: 'abc' });
3948
-
3949
- // Test event handlers
3950
- testVim('beforeSelectionChange', function(cm, vim, helpers) {
3951
- cm.setCursor(0, 100);
3952
- eqPos(cm.getCursor('head'), cm.getCursor('anchor'));
3953
- }, { value: 'abc' });
3954
-
3955
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/toolset/types/.travis.yml DELETED
@@ -1,29 +0,0 @@
1
- language: php
2
-
3
- notifications:
4
- email:
5
- on_success: never
6
- on_failure: change
7
-
8
- branches:
9
- only:
10
- - master
11
-
12
- php:
13
- - 5.3
14
- - 5.6
15
-
16
- env:
17
- - WP_VERSION=latest WP_MULTISITE=0
18
- - WP_VERSION=3.7 WP_MULTISITE=0
19
- - WP_VERSION=4.4.1 WP_MULTISITE=0
20
-
21
- matrix:
22
- include:
23
- - php: 5.3
24
- env: WP_VERSION=latest WP_MULTISITE=1
25
-
26
- before_script:
27
- - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION
28
-
29
- script: phpunit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/.editorconfig DELETED
@@ -1,18 +0,0 @@
1
- ; top-most EditorConfig file
2
- root = true
3
-
4
- ; Unix-style newlines
5
- [*]
6
- end_of_line = LF
7
-
8
- [*.php]
9
- indent_style = space
10
- indent_size = 4
11
-
12
- [*.test]
13
- indent_style = space
14
- indent_size = 4
15
-
16
- [*.rst]
17
- indent_style = space
18
- indent_size = 4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/.php_cs.dist DELETED
@@ -1,15 +0,0 @@
1
- <?php
2
-
3
- return PhpCsFixer\Config::create()
4
- ->setRules(array(
5
- '@Symfony' => true,
6
- '@Symfony:risky' => true,
7
- 'array_syntax' => array('syntax' => 'long'),
8
- 'php_unit_fqcn_annotation' => false,
9
- 'no_unreachable_default_argument_value' => false,
10
- 'braces' => array('allow_single_line_closure' => true),
11
- 'heredoc_to_nowdoc' => false,
12
- ))
13
- ->setRiskyAllowed(true)
14
- ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__))
15
- ;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/.travis.yml DELETED
@@ -1,43 +0,0 @@
1
- language: php
2
-
3
- sudo: false
4
-
5
- cache:
6
- directories:
7
- - vendor
8
- - $HOME/.composer/cache/files
9
-
10
- php:
11
- - 5.2
12
- - 5.3
13
- - 5.4
14
- - 5.5
15
- - 5.6
16
- - 7.0
17
- - 7.1
18
- - hhvm
19
-
20
- env:
21
- - TWIG_EXT=no
22
- - TWIG_EXT=yes
23
-
24
- before_install:
25
- - if [[ ! $TRAVIS_PHP_VERSION = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi
26
-
27
- install:
28
- # Composer is not available on PHP 5.2
29
- - if [ ${TRAVIS_PHP_VERSION:0:3} != "5.2" ]; then travis_retry composer install; fi
30
-
31
- before_script:
32
- - if [ "$TWIG_EXT" == "yes" ]; then sh -c "cd ext/twig && phpize && ./configure --enable-twig && make && make install"; fi
33
- - if [ "$TWIG_EXT" == "yes" ]; then echo "extension=twig.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi
34
-
35
- matrix:
36
- fast_finish: true
37
- exclude:
38
- - php: hhvm
39
- env: TWIG_EXT=yes
40
- - php: 7.0
41
- env: TWIG_EXT=yes
42
- - php: 7.1
43
- env: TWIG_EXT=yes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/advanced.rst DELETED
@@ -1,962 +0,0 @@
1
- Extending Twig
2
- ==============
3
-
4
- .. caution::
5
-
6
- This section describes how to extend Twig as of **Twig 1.12**. If you are
7
- using an older version, read the :doc:`legacy<advanced_legacy>` chapter
8
- instead.
9
-
10
- Twig can be extended in many ways; you can add extra tags, filters, tests,
11
- operators, global variables, and functions. You can even extend the parser
12
- itself with node visitors.
13
-
14
- .. note::
15
-
16
- The first section of this chapter describes how to extend Twig easily. If
17
- you want to reuse your changes in different projects or if you want to
18
- share them with others, you should then create an extension as described
19
- in the following section.
20
-
21
- .. caution::
22
-
23
- When extending Twig without creating an extension, Twig won't be able to
24
- recompile your templates when the PHP code is updated. To see your changes
25
- in real-time, either disable template caching or package your code into an
26
- extension (see the next section of this chapter).
27
-
28
- Before extending Twig, you must understand the differences between all the
29
- different possible extension points and when to use them.
30
-
31
- First, remember that Twig has two main language constructs:
32
-
33
- * ``{{ }}``: used to print the result of an expression evaluation;
34
-
35
- * ``{% %}``: used to execute statements.
36
-
37
- To understand why Twig exposes so many extension points, let's see how to
38
- implement a *Lorem ipsum* generator (it needs to know the number of words to
39
- generate).
40
-
41
- You can use a ``lipsum`` *tag*:
42
-
43
- .. code-block:: jinja
44
-
45
- {% lipsum 40 %}
46
-
47
- That works, but using a tag for ``lipsum`` is not a good idea for at least
48
- three main reasons:
49
-
50
- * ``lipsum`` is not a language construct;
51
- * The tag outputs something;
52
- * The tag is not flexible as you cannot use it in an expression:
53
-
54
- .. code-block:: jinja
55
-
56
- {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}
57
-
58
- In fact, you rarely need to create tags; and that's good news because tags are
59
- the most complex extension point of Twig.
60
-
61
- Now, let's use a ``lipsum`` *filter*:
62
-
63
- .. code-block:: jinja
64
-
65
- {{ 40|lipsum }}
66
-
67
- Again, it works, but it looks weird. A filter transforms the passed value to
68
- something else but here we use the value to indicate the number of words to
69
- generate (so, ``40`` is an argument of the filter, not the value we want to
70
- transform).
71
-
72
- Next, let's use a ``lipsum`` *function*:
73
-
74
- .. code-block:: jinja
75
-
76
- {{ lipsum(40) }}
77
-
78
- Here we go. For this specific example, the creation of a function is the
79
- extension point to use. And you can use it anywhere an expression is accepted:
80
-
81
- .. code-block:: jinja
82
-
83
- {{ 'some text' ~ lipsum(40) ~ 'some more text' }}
84
-
85
- {% set lipsum = lipsum(40) %}
86
-
87
- Last but not the least, you can also use a *global* object with a method able
88
- to generate lorem ipsum text:
89
-
90
- .. code-block:: jinja
91
-
92
- {{ text.lipsum(40) }}
93
-
94
- As a rule of thumb, use functions for frequently used features and global
95
- objects for everything else.
96
-
97
- Keep in mind the following when you want to extend Twig:
98
-
99
- ========== ========================== ========== =========================
100
- What? Implementation difficulty? How often? When?
101
- ========== ========================== ========== =========================
102
- *macro* trivial frequent Content generation
103
- *global* trivial frequent Helper object
104
- *function* trivial frequent Content generation
105
- *filter* trivial frequent Value transformation
106
- *tag* complex rare DSL language construct
107
- *test* trivial rare Boolean decision
108
- *operator* trivial rare Values transformation
109
- ========== ========================== ========== =========================
110
-
111
- Globals
112
- -------
113
-
114
- A global variable is like any other template variable, except that it's
115
- available in all templates and macros::
116
-
117
- $twig = new Twig_Environment($loader);
118
- $twig->addGlobal('text', new Text());
119
-
120
- You can then use the ``text`` variable anywhere in a template:
121
-
122
- .. code-block:: jinja
123
-
124
- {{ text.lipsum(40) }}
125
-
126
- Filters
127
- -------
128
-
129
- Creating a filter is as simple as associating a name with a PHP callable::
130
-
131
- // an anonymous function
132
- $filter = new Twig_SimpleFilter('rot13', function ($string) {
133
- return str_rot13($string);
134
- });
135
-
136
- // or a simple PHP function
137
- $filter = new Twig_SimpleFilter('rot13', 'str_rot13');
138
-
139
- // or a class static method
140
- $filter = new Twig_SimpleFilter('rot13', array('SomeClass', 'rot13Filter'));
141
- $filter = new Twig_SimpleFilter('rot13', 'SomeClass::rot13Filter');
142
-
143
- // or a class method
144
- $filter = new Twig_SimpleFilter('rot13', array($this, 'rot13Filter'));
145
- // the one below needs a runtime implementation (see below for more information)
146
- $filter = new Twig_SimpleFilter('rot13', array('SomeClass', 'rot13Filter'));
147
-
148
- The first argument passed to the ``Twig_SimpleFilter`` constructor is the name
149
- of the filter you will use in templates and the second one is the PHP callable
150
- to associate with it.
151
-
152
- Then, add the filter to your Twig environment::
153
-
154
- $twig = new Twig_Environment($loader);
155
- $twig->addFilter($filter);
156
-
157
- And here is how to use it in a template:
158
-
159
- .. code-block:: jinja
160
-
161
- {{ 'Twig'|rot13 }}
162
-
163
- {# will output Gjvt #}
164
-
165
- When called by Twig, the PHP callable receives the left side of the filter
166
- (before the pipe ``|``) as the first argument and the extra arguments passed
167
- to the filter (within parentheses ``()``) as extra arguments.
168
-
169
- For instance, the following code:
170
-
171
- .. code-block:: jinja
172
-
173
- {{ 'TWIG'|lower }}
174
- {{ now|date('d/m/Y') }}
175
-
176
- is compiled to something like the following::
177
-
178
- <?php echo strtolower('TWIG') ?>
179
- <?php echo twig_date_format_filter($now, 'd/m/Y') ?>
180
-
181
- The ``Twig_SimpleFilter`` class takes an array of options as its last
182
- argument::
183
-
184
- $filter = new Twig_SimpleFilter('rot13', 'str_rot13', $options);
185
-
186
- Environment-aware Filters
187
- ~~~~~~~~~~~~~~~~~~~~~~~~~
188
-
189
- If you want to access the current environment instance in your filter, set the
190
- ``needs_environment`` option to ``true``; Twig will pass the current
191
- environment as the first argument to the filter call::
192
-
193
- $filter = new Twig_SimpleFilter('rot13', function (Twig_Environment $env, $string) {
194
- // get the current charset for instance
195
- $charset = $env->getCharset();
196
-
197
- return str_rot13($string);
198
- }, array('needs_environment' => true));
199
-
200
- Context-aware Filters
201
- ~~~~~~~~~~~~~~~~~~~~~
202
-
203
- If you want to access the current context in your filter, set the
204
- ``needs_context`` option to ``true``; Twig will pass the current context as
205
- the first argument to the filter call (or the second one if
206
- ``needs_environment`` is also set to ``true``)::
207
-
208
- $filter = new Twig_SimpleFilter('rot13', function ($context, $string) {
209
- // ...
210
- }, array('needs_context' => true));
211
-
212
- $filter = new Twig_SimpleFilter('rot13', function (Twig_Environment $env, $context, $string) {
213
- // ...
214
- }, array('needs_context' => true, 'needs_environment' => true));
215
-
216
- Automatic Escaping
217
- ~~~~~~~~~~~~~~~~~~
218
-
219
- If automatic escaping is enabled, the output of the filter may be escaped
220
- before printing. If your filter acts as an escaper (or explicitly outputs HTML
221
- or JavaScript code), you will want the raw output to be printed. In such a
222
- case, set the ``is_safe`` option::
223
-
224
- $filter = new Twig_SimpleFilter('nl2br', 'nl2br', array('is_safe' => array('html')));
225
-
226
- Some filters may need to work on input that is already escaped or safe, for
227
- example when adding (safe) HTML tags to originally unsafe output. In such a
228
- case, set the ``pre_escape`` option to escape the input data before it is run
229
- through your filter::
230
-
231
- $filter = new Twig_SimpleFilter('somefilter', 'somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
232
-
233
- Variadic Filters
234
- ~~~~~~~~~~~~~~~~
235
-
236
- .. versionadded:: 1.19
237
- Support for variadic filters was added in Twig 1.19.
238
-
239
- When a filter should accept an arbitrary number of arguments, set the
240
- ``is_variadic`` option to ``true``; Twig will pass the extra arguments as the
241
- last argument to the filter call as an array::
242
-
243
- $filter = new Twig_SimpleFilter('thumbnail', function ($file, array $options = array()) {
244
- // ...
245
- }, array('is_variadic' => true));
246
-
247
- Be warned that named arguments passed to a variadic filter cannot be checked
248
- for validity as they will automatically end up in the option array.
249
-
250
- Dynamic Filters
251
- ~~~~~~~~~~~~~~~
252
-
253
- A filter name containing the special ``*`` character is a dynamic filter as
254
- the ``*`` can be any string::
255
-
256
- $filter = new Twig_SimpleFilter('*_path', function ($name, $arguments) {
257
- // ...
258
- });
259
-
260
- The following filters will be matched by the above defined dynamic filter:
261
-
262
- * ``product_path``
263
- * ``category_path``
264
-
265
- A dynamic filter can define more than one dynamic parts::
266
-
267
- $filter = new Twig_SimpleFilter('*_path_*', function ($name, $suffix, $arguments) {
268
- // ...
269
- });
270
-
271
- The filter will receive all dynamic part values before the normal filter
272
- arguments, but after the environment and the context. For instance, a call to
273
- ``'foo'|a_path_b()`` will result in the following arguments to be passed to
274
- the filter: ``('a', 'b', 'foo')``.
275
-
276
- Deprecated Filters
277
- ~~~~~~~~~~~~~~~~~~
278
-
279
- .. versionadded:: 1.21
280
- Support for deprecated filters was added in Twig 1.21.
281
-
282
- You can mark a filter as being deprecated by setting the ``deprecated`` option
283
- to ``true``. You can also give an alternative filter that replaces the
284
- deprecated one when that makes sense::
285
-
286
- $filter = new Twig_SimpleFilter('obsolete', function () {
287
- // ...
288
- }, array('deprecated' => true, 'alternative' => 'new_one'));
289
-
290
- When a filter is deprecated, Twig emits a deprecation notice when compiling a
291
- template using it. See :ref:`deprecation-notices` for more information.
292
-
293
- Functions
294
- ---------
295
-
296
- Functions are defined in the exact same way as filters, but you need to create
297
- an instance of ``Twig_SimpleFunction``::
298
-
299
- $twig = new Twig_Environment($loader);
300
- $function = new Twig_SimpleFunction('function_name', function () {
301
- // ...
302
- });
303
- $twig->addFunction($function);
304
-
305
- Functions support the same features as filters, except for the ``pre_escape``
306
- and ``preserves_safety`` options.
307
-
308
- Tests
309
- -----
310
-
311
- Tests are defined in the exact same way as filters and functions, but you need
312
- to create an instance of ``Twig_SimpleTest``::
313
-
314
- $twig = new Twig_Environment($loader);
315
- $test = new Twig_SimpleTest('test_name', function () {
316
- // ...
317
- });
318
- $twig->addTest($test);
319
-
320
- Tests allow you to create custom application specific logic for evaluating
321
- boolean conditions. As a simple example, let's create a Twig test that checks if
322
- objects are 'red'::
323
-
324
- $twig = new Twig_Environment($loader);
325
- $test = new Twig_SimpleTest('red', function ($value) {
326
- if (isset($value->color) && $value->color == 'red') {
327
- return true;
328
- }
329
- if (isset($value->paint) && $value->paint == 'red') {
330
- return true;
331
- }
332
- return false;
333
- });
334
- $twig->addTest($test);
335
-
336
- Test functions should always return true/false.
337
-
338
- When creating tests you can use the ``node_class`` option to provide custom test
339
- compilation. This is useful if your test can be compiled into PHP primitives.
340
- This is used by many of the tests built into Twig::
341
-
342
- $twig = new Twig_Environment($loader);
343
- $test = new Twig_SimpleTest(
344
- 'odd',
345
- null,
346
- array('node_class' => 'Twig_Node_Expression_Test_Odd'));
347
- $twig->addTest($test);
348
-
349
- class Twig_Node_Expression_Test_Odd extends Twig_Node_Expression_Test
350
- {
351
- public function compile(Twig_Compiler $compiler)
352
- {
353
- $compiler
354
- ->raw('(')
355
- ->subcompile($this->getNode('node'))
356
- ->raw(' % 2 == 1')
357
- ->raw(')')
358
- ;
359
- }
360
- }
361
-
362
- The above example shows how you can create tests that use a node class. The
363
- node class has access to one sub-node called 'node'. This sub-node contains the
364
- value that is being tested. When the ``odd`` filter is used in code such as:
365
-
366
- .. code-block:: jinja
367
-
368
- {% if my_value is odd %}
369
-
370
- The ``node`` sub-node will contain an expression of ``my_value``. Node-based
371
- tests also have access to the ``arguments`` node. This node will contain the
372
- various other arguments that have been provided to your test.
373
-
374
- If you want to pass a variable number of positional or named arguments to the
375
- test, set the ``is_variadic`` option to ``true``. Tests also support dynamic
376
- name feature as filters and functions.
377
-
378
- Tags
379
- ----
380
-
381
- One of the most exciting features of a template engine like Twig is the
382
- possibility to define new language constructs. This is also the most complex
383
- feature as you need to understand how Twig's internals work.
384
-
385
- Let's create a simple ``set`` tag that allows the definition of simple
386
- variables from within a template. The tag can be used like follows:
387
-
388
- .. code-block:: jinja
389
-
390
- {% set name = "value" %}
391
-
392
- {{ name }}
393
-
394
- {# should output value #}
395
-
396
- .. note::
397
-
398
- The ``set`` tag is part of the Core extension and as such is always
399
- available. The built-in version is slightly more powerful and supports
400
- multiple assignments by default (cf. the template designers chapter for
401
- more information).
402
-
403
- Three steps are needed to define a new tag:
404
-
405
- * Defining a Token Parser class (responsible for parsing the template code);
406
-
407
- * Defining a Node class (responsible for converting the parsed code to PHP);
408
-
409
- * Registering the tag.
410
-
411
- Registering a new tag
412
- ~~~~~~~~~~~~~~~~~~~~~
413
-
414
- Adding a tag is as simple as calling the ``addTokenParser`` method on the
415
- ``Twig_Environment`` instance::
416
-
417
- $twig = new Twig_Environment($loader);
418
- $twig->addTokenParser(new Project_Set_TokenParser());
419
-
420
- Defining a Token Parser
421
- ~~~~~~~~~~~~~~~~~~~~~~~
422
-
423
- Now, let's see the actual code of this class::
424
-
425
- class Project_Set_TokenParser extends Twig_TokenParser
426
- {
427
- public function parse(Twig_Token $token)
428
- {
429
- $parser = $this->parser;
430
- $stream = $parser->getStream();
431
-
432
- $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
433
- $stream->expect(Twig_Token::OPERATOR_TYPE, '=');
434
- $value = $parser->getExpressionParser()->parseExpression();
435
- $stream->expect(Twig_Token::BLOCK_END_TYPE);
436
-
437
- return new Project_Set_Node($name, $value, $token->getLine(), $this->getTag());
438
- }
439
-
440
- public function getTag()
441
- {
442
- return 'set';
443
- }
444
- }
445
-
446
- The ``getTag()`` method must return the tag we want to parse, here ``set``.
447
-
448
- The ``parse()`` method is invoked whenever the parser encounters a ``set``
449
- tag. It should return a ``Twig_Node`` instance that represents the node (the
450
- ``Project_Set_Node`` calls creating is explained in the next section).
451
-
452
- The parsing process is simplified thanks to a bunch of methods you can call
453
- from the token stream (``$this->parser->getStream()``):
454
-
455
- * ``getCurrent()``: Gets the current token in the stream.
456
-
457
- * ``next()``: Moves to the next token in the stream, *but returns the old one*.
458
-
459
- * ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether
460
- the current token is of a particular type or value (or both). The value may be an
461
- array of several possible values.
462
-
463
- * ``expect($type[, $value[, $message]])``: If the current token isn't of the given
464
- type/value a syntax error is thrown. Otherwise, if the type and value are correct,
465
- the token is returned and the stream moves to the next token.
466
-
467
- * ``look()``: Looks a the next token without consuming it.
468
-
469
- Parsing expressions is done by calling the ``parseExpression()`` like we did for
470
- the ``set`` tag.
471
-
472
- .. tip::
473
-
474
- Reading the existing ``TokenParser`` classes is the best way to learn all
475
- the nitty-gritty details of the parsing process.
476
-
477
- Defining a Node
478
- ~~~~~~~~~~~~~~~
479
-
480
- The ``Project_Set_Node`` class itself is rather simple::
481
-
482
- class Project_Set_Node extends Twig_Node
483
- {
484
- public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
485
- {
486
- parent::__construct(array('value' => $value), array('name' => $name), $line, $tag);
487
- }
488
-
489
- public function compile(Twig_Compiler $compiler)
490
- {
491
- $compiler
492
- ->addDebugInfo($this)
493
- ->write('$context[\''.$this->getAttribute('name').'\'] = ')
494
- ->subcompile($this->getNode('value'))
495
- ->raw(";\n")
496
- ;
497
- }
498
- }
499
-
500
- The compiler implements a fluid interface and provides methods that helps the
501
- developer generate beautiful and readable PHP code:
502
-
503
- * ``subcompile()``: Compiles a node.
504
-
505
- * ``raw()``: Writes the given string as is.
506
-
507
- * ``write()``: Writes the given string by adding indentation at the beginning
508
- of each line.
509
-
510
- * ``string()``: Writes a quoted string.
511
-
512
- * ``repr()``: Writes a PHP representation of a given value (see
513
- ``Twig_Node_For`` for a usage example).
514
-
515
- * ``addDebugInfo()``: Adds the line of the original template file related to
516
- the current node as a comment.
517
-
518
- * ``indent()``: Indents the generated code (see ``Twig_Node_Block`` for a
519
- usage example).
520
-
521
- * ``outdent()``: Outdents the generated code (see ``Twig_Node_Block`` for a
522
- usage example).
523
-
524
- .. _creating_extensions:
525
-
526
- Creating an Extension
527
- ---------------------
528
-
529
- The main motivation for writing an extension is to move often used code into a
530
- reusable class like adding support for internationalization. An extension can
531
- define tags, filters, tests, operators, global variables, functions, and node
532
- visitors.
533
-
534
- Most of the time, it is useful to create a single extension for your project,
535
- to host all the specific tags and filters you want to add to Twig.
536
-
537
- .. tip::
538
-
539
- When packaging your code into an extension, Twig is smart enough to
540
- recompile your templates whenever you make a change to it (when
541
- ``auto_reload`` is enabled).
542
-
543
- .. note::
544
-
545
- Before writing your own extensions, have a look at the Twig official
546
- extension repository: http://github.com/twigphp/Twig-extensions.
547
-
548
- An extension is a class that implements the following interface::
549
-
550
- interface Twig_ExtensionInterface
551
- {
552
- /**
553
- * Initializes the runtime environment.
554
- *
555
- * This is where you can load some file that contains filter functions for instance.
556
- *
557
- * @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_InitRuntimeInterface instead
558
- */
559
- function initRuntime(Twig_Environment $environment);
560
-
561
- /**
562
- * Returns the token parser instances to add to the existing list.
563
- *
564
- * @return (Twig_TokenParserInterface|Twig_TokenParserBrokerInterface)[]
565
- */
566
- function getTokenParsers();
567
-
568
- /**
569
- * Returns the node visitor instances to add to the existing list.
570
- *
571
- * @return Twig_NodeVisitorInterface[]
572
- */
573
- function getNodeVisitors();
574
-
575
- /**
576
- * Returns a list of filters to add to the existing list.
577
- *
578
- * @return Twig_SimpleFilter[]
579
- */
580
- function getFilters();
581
-
582
- /**
583
- * Returns a list of tests to add to the existing list.
584
- *
585
- * @return Twig_SimpleTest[]
586
- */
587
- function getTests();
588
-
589
- /**
590
- * Returns a list of functions to add to the existing list.
591
- *
592
- * @return Twig_SimpleFunction[]
593
- */
594
- function getFunctions();
595
-
596
- /**
597
- * Returns a list of operators to add to the existing list.
598
- *
599
- * @return array<array> First array of unary operators, second array of binary operators
600
- */
601
- function getOperators();
602
-
603
- /**
604
- * Returns a list of global variables to add to the existing list.
605
- *
606
- * @return array An array of global variables
607
- *
608
- * @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_GlobalsInterface instead
609
- */
610
- function getGlobals();
611
-
612
- /**
613
- * Returns the name of the extension.
614
- *
615
- * @return string The extension name
616
- *
617
- * @deprecated since 1.26 (to be removed in 2.0), not used anymore internally
618
- */
619
- function getName();
620
- }
621
-
622
- To keep your extension class clean and lean, inherit from the built-in
623
- ``Twig_Extension`` class instead of implementing the interface as it provides
624
- empty implementations for all methods:
625
-
626
- class Project_Twig_Extension extends Twig_Extension
627
- {
628
- }
629
-
630
- Of course, this extension does nothing for now. We will customize it in the
631
- next sections.
632
-
633
- .. note::
634
-
635
- Prior to Twig 1.26, you must implement the ``getName()`` method which must
636
- return a unique identifier for the extension.
637
-
638
- Twig does not care where you save your extension on the filesystem, as all
639
- extensions must be registered explicitly to be available in your templates.
640
-
641
- You can register an extension by using the ``addExtension()`` method on your
642
- main ``Environment`` object::
643
-
644
- $twig = new Twig_Environment($loader);
645
- $twig->addExtension(new Project_Twig_Extension());
646
-
647
- .. tip::
648
-
649
- The Twig core extensions are great examples of how extensions work.
650
-
651
- Globals
652
- ~~~~~~~
653
-
654
- Global variables can be registered in an extension via the ``getGlobals()``
655
- method::
656
-
657
- class Project_Twig_Extension extends Twig_Extension implements Twig_Extension_GlobalsInterface
658
- {
659
- public function getGlobals()
660
- {
661
- return array(
662
- 'text' => new Text(),
663
- );
664
- }
665
-
666
- // ...
667
- }
668
-
669
- Functions
670
- ~~~~~~~~~
671
-
672
- Functions can be registered in an extension via the ``getFunctions()``
673
- method::
674
-
675
- class Project_Twig_Extension extends Twig_Extension
676
- {
677
- public function getFunctions()
678
- {
679
- return array(
680
- new Twig_SimpleFunction('lipsum', 'generate_lipsum'),
681
- );
682
- }
683
-
684
- // ...
685
- }
686
-
687
- Filters
688
- ~~~~~~~
689
-
690
- To add a filter to an extension, you need to override the ``getFilters()``
691
- method. This method must return an array of filters to add to the Twig
692
- environment::
693
-
694
- class Project_Twig_Extension extends Twig_Extension
695
- {
696
- public function getFilters()
697
- {
698
- return array(
699
- new Twig_SimpleFilter('rot13', 'str_rot13'),
700
- );
701
- }
702
-
703
- // ...
704
- }
705
-
706
- Tags
707
- ~~~~
708
-
709
- Adding a tag in an extension can be done by overriding the
710
- ``getTokenParsers()`` method. This method must return an array of tags to add
711
- to the Twig environment::
712
-
713
- class Project_Twig_Extension extends Twig_Extension
714
- {
715
- public function getTokenParsers()
716
- {
717
- return array(new Project_Set_TokenParser());
718
- }
719
-
720
- // ...
721
- }
722
-
723
- In the above code, we have added a single new tag, defined by the
724
- ``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is
725
- responsible for parsing the tag and compiling it to PHP.
726
-
727
- Operators
728
- ~~~~~~~~~
729
-
730
- The ``getOperators()`` methods lets you add new operators. Here is how to add
731
- ``!``, ``||``, and ``&&`` operators::
732
-
733
- class Project_Twig_Extension extends Twig_Extension
734
- {
735
- public function getOperators()
736
- {
737
- return array(
738
- array(
739
- '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
740
- ),
741
- array(
742
- '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
743
- '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
744
- ),
745
- );
746
- }
747
-
748
- // ...
749
- }
750
-
751
- Tests
752
- ~~~~~
753
-
754
- The ``getTests()`` method lets you add new test functions::
755
-
756
- class Project_Twig_Extension extends Twig_Extension
757
- {
758
- public function getTests()
759
- {
760
- return array(
761
- new Twig_SimpleTest('even', 'twig_test_even'),
762
- );
763
- }
764
-
765
- // ...
766
- }
767
-
768
- Definition vs Runtime
769
- ~~~~~~~~~~~~~~~~~~~~~
770
-
771
- Twig filters, functions, and tests runtime implementations can be defined as
772
- any valid PHP callable:
773
-
774
- * **functions/static methods**: Simple to implement and fast (used by all Twig
775
- core extensions); but it is hard for the runtime to depend on external
776
- objects;
777
-
778
- * **closures**: Simple to implement;
779
-
780
- * **object methods**: More flexible and required if your runtime code depends
781
- on external objects.
782
-
783
- The simplest way to use methods is to define them on the extension itself::
784
-
785
- class Project_Twig_Extension extends Twig_Extension
786
- {
787
- private $rot13Provider;
788
-
789
- public function __construct($rot13Provider)
790
- {
791
- $this->rot13Provider = $rot13Provider;
792
- }
793
-
794
- public function getFunctions()
795
- {
796
- return array(
797
- new Twig_SimpleFunction('rot13', array($this, 'rot13')),
798
- );
799
- }
800
-
801
- public function rot13($value)
802
- {
803
- return $rot13Provider->rot13($value);
804
- }
805
- }
806
-
807
- This is very convenient but not recommended as it makes template compilation
808
- depend on runtime dependencies even if they are not needed (think for instance
809
- as a dependency that connects to a database engine).
810
-
811
- As of Twig 1.26, you can easily decouple the extension definitions from their
812
- runtime implementations by registering a ``Twig_RuntimeLoaderInterface``
813
- instance on the environment that knows how to instantiate such runtime classes
814
- (runtime classes must be autoload-able)::
815
-
816
- class RuntimeLoader implements Twig_RuntimeLoaderInterface
817
- {
818
- public function load($class)
819
- {
820
- // implement the logic to create an instance of $class
821
- // and inject its dependencies
822
- // most of the time, it means using your dependency injection container
823
- if ('Project_Twig_RuntimeExtension' === $class) {
824
- return new $class(new Rot13Provider());
825
- } else {
826
- // ...
827
- }
828
- }
829
- }
830
-
831
- $twig->addRuntimeLoader(new RuntimeLoader());
832
-
833
- .. note::
834
-
835
- As of Twig 1.32, Twig comes with a PSR-11 compatible runtime loader
836
- (``Twig_ContainerRuntimeLoader``) that works on PHP 5.3+.
837
-
838
- It is now possible to move the runtime logic to a new
839
- ``Project_Twig_RuntimeExtension`` class and use it directly in the extension::
840
-
841
- class Project_Twig_RuntimeExtension extends Twig_Extension
842
- {
843
- private $rot13Provider;
844
-
845
- public function __construct($rot13Provider)
846
- {
847
- $this->rot13Provider = $rot13Provider;
848
- }
849
-
850
- public function rot13($value)
851
- {
852
- return $rot13Provider->rot13($value);
853
- }
854
- }
855
-
856
- class Project_Twig_Extension extends Twig_Extension
857
- {
858
- public function getFunctions()
859
- {
860
- return array(
861
- new Twig_SimpleFunction('rot13', array('Project_Twig_RuntimeExtension', 'rot13')),
862
- // or
863
- new Twig_SimpleFunction('rot13', 'Project_Twig_RuntimeExtension::rot13'),
864
- );
865
- }
866
- }
867
-
868
- Overloading
869
- -----------
870
-
871
- To overload an already defined filter, test, operator, global variable, or
872
- function, re-define it in an extension and register it **as late as
873
- possible** (order matters)::
874
-
875
- class MyCoreExtension extends Twig_Extension
876
- {
877
- public function getFilters()
878
- {
879
- return array(
880
- new Twig_SimpleFilter('date', array($this, 'dateFilter')),
881
- );
882
- }
883
-
884
- public function dateFilter($timestamp, $format = 'F j, Y H:i')
885
- {
886
- // do something different from the built-in date filter
887
- }
888
- }
889
-
890
- $twig = new Twig_Environment($loader);
891
- $twig->addExtension(new MyCoreExtension());
892
-
893
- Here, we have overloaded the built-in ``date`` filter with a custom one.
894
-
895
- If you do the same on the ``Twig_Environment`` itself, beware that it takes
896
- precedence over any other registered extensions::
897
-
898
- $twig = new Twig_Environment($loader);
899
- $twig->addFilter(new Twig_SimpleFilter('date', function ($timestamp, $format = 'F j, Y H:i') {
900
- // do something different from the built-in date filter
901
- }));
902
- // the date filter will come from the above registration, not
903
- // from the registered extension below
904
- $twig->addExtension(new MyCoreExtension());
905
-
906
- .. caution::
907
-
908
- Note that overloading the built-in Twig elements is not recommended as it
909
- might be confusing.
910
-
911
- Testing an Extension
912
- --------------------
913
-
914
- Functional Tests
915
- ~~~~~~~~~~~~~~~~
916
-
917
- You can create functional tests for extensions simply by creating the
918
- following file structure in your test directory::
919
-
920
- Fixtures/
921
- filters/
922
- foo.test
923
- bar.test
924
- functions/
925
- foo.test
926
- bar.test
927
- tags/
928
- foo.test
929
- bar.test
930
- IntegrationTest.php
931
-
932
- The ``IntegrationTest.php`` file should look like this::
933
-
934
- class Project_Tests_IntegrationTest extends Twig_Test_IntegrationTestCase
935
- {
936
- public function getExtensions()
937
- {
938
- return array(
939
- new Project_Twig_Extension1(),
940
- new Project_Twig_Extension2(),
941
- );
942
- }
943
-
944
- public function getFixturesDir()
945
- {
946
- return dirname(__FILE__).'/Fixtures/';
947
- }
948
- }
949
-
950
- Fixtures examples can be found within the Twig repository
951
- `tests/Twig/Fixtures`_ directory.
952
-
953
- Node Tests
954
- ~~~~~~~~~~
955
-
956
- Testing the node visitors can be complex, so extend your test cases from
957
- ``Twig_Test_NodeTestCase``. Examples can be found in the Twig repository
958
- `tests/Twig/Node`_ directory.
959
-
960
- .. _`rot13`: http://www.php.net/manual/en/function.str-rot13.php
961
- .. _`tests/Twig/Fixtures`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Fixtures
962
- .. _`tests/Twig/Node`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Node
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/advanced_legacy.rst DELETED
@@ -1,885 +0,0 @@
1
- Extending Twig
2
- ==============
3
-
4
- .. caution::
5
-
6
- This section describes how to extends Twig for versions **older than
7
- 1.12**. If you are using a newer version, read the :doc:`newer<advanced>`
8
- chapter instead.
9
-
10
- Twig can be extended in many ways; you can add extra tags, filters, tests,
11
- operators, global variables, and functions. You can even extend the parser
12
- itself with node visitors.
13
-
14
- .. note::
15
-
16
- The first section of this chapter describes how to extend Twig easily. If
17
- you want to reuse your changes in different projects or if you want to
18
- share them with others, you should then create an extension as described
19
- in the following section.
20
-
21
- .. caution::
22
-
23
- When extending Twig by calling methods on the Twig environment instance,
24
- Twig won't be able to recompile your templates when the PHP code is
25
- updated. To see your changes in real-time, either disable template caching
26
- or package your code into an extension (see the next section of this
27
- chapter).
28
-
29
- Before extending Twig, you must understand the differences between all the
30
- different possible extension points and when to use them.
31
-
32
- First, remember that Twig has two main language constructs:
33
-
34
- * ``{{ }}``: used to print the result of an expression evaluation;
35
-
36
- * ``{% %}``: used to execute statements.
37
-
38
- To understand why Twig exposes so many extension points, let's see how to
39
- implement a *Lorem ipsum* generator (it needs to know the number of words to
40
- generate).
41
-
42
- You can use a ``lipsum`` *tag*:
43
-
44
- .. code-block:: jinja
45
-
46
- {% lipsum 40 %}
47
-
48
- That works, but using a tag for ``lipsum`` is not a good idea for at least
49
- three main reasons:
50
-
51
- * ``lipsum`` is not a language construct;
52
- * The tag outputs something;
53
- * The tag is not flexible as you cannot use it in an expression:
54
-
55
- .. code-block:: jinja
56
-
57
- {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}
58
-
59
- In fact, you rarely need to create tags; and that's good news because tags are
60
- the most complex extension point of Twig.
61
-
62
- Now, let's use a ``lipsum`` *filter*:
63
-
64
- .. code-block:: jinja
65
-
66
- {{ 40|lipsum }}
67
-
68
- Again, it works, but it looks weird. A filter transforms the passed value to
69
- something else but here we use the value to indicate the number of words to
70
- generate (so, ``40`` is an argument of the filter, not the value we want to
71
- transform).
72
-
73
- Next, let's use a ``lipsum`` *function*:
74
-
75
- .. code-block:: jinja
76
-
77
- {{ lipsum(40) }}
78
-
79
- Here we go. For this specific example, the creation of a function is the
80
- extension point to use. And you can use it anywhere an expression is accepted:
81
-
82
- .. code-block:: jinja
83
-
84
- {{ 'some text' ~ ipsum(40) ~ 'some more text' }}
85
-
86
- {% set ipsum = ipsum(40) %}
87
-
88
- Last but not the least, you can also use a *global* object with a method able
89
- to generate lorem ipsum text:
90
-
91
- .. code-block:: jinja
92
-
93
- {{ text.lipsum(40) }}
94
-
95
- As a rule of thumb, use functions for frequently used features and global
96
- objects for everything else.
97
-
98
- Keep in mind the following when you want to extend Twig:
99
-
100
- ========== ========================== ========== =========================
101
- What? Implementation difficulty? How often? When?
102
- ========== ========================== ========== =========================
103
- *macro* trivial frequent Content generation
104
- *global* trivial frequent Helper object
105
- *function* trivial frequent Content generation
106
- *filter* trivial frequent Value transformation
107
- *tag* complex rare DSL language construct
108
- *test* trivial rare Boolean decision
109
- *operator* trivial rare Values transformation
110
- ========== ========================== ========== =========================
111
-
112
- Globals
113
- -------
114
-
115
- A global variable is like any other template variable, except that it's
116
- available in all templates and macros::
117
-
118
- $twig = new Twig_Environment($loader);
119
- $twig->addGlobal('text', new Text());
120
-
121
- You can then use the ``text`` variable anywhere in a template:
122
-
123
- .. code-block:: jinja
124
-
125
- {{ text.lipsum(40) }}
126
-
127
- Filters
128
- -------
129
-
130
- A filter is a regular PHP function or an object method that takes the left
131
- side of the filter (before the pipe ``|``) as first argument and the extra
132
- arguments passed to the filter (within parentheses ``()``) as extra arguments.
133
-
134
- Defining a filter is as easy as associating the filter name with a PHP
135
- callable. For instance, let's say you have the following code in a template:
136
-
137
- .. code-block:: jinja
138
-
139
- {{ 'TWIG'|lower }}
140
-
141
- When compiling this template to PHP, Twig looks for the PHP callable
142
- associated with the ``lower`` filter. The ``lower`` filter is a built-in Twig
143
- filter, and it is simply mapped to the PHP ``strtolower()`` function. After
144
- compilation, the generated PHP code is roughly equivalent to:
145
-
146
- .. code-block:: html+php
147
-
148
- <?php echo strtolower('TWIG') ?>
149
-
150
- As you can see, the ``'TWIG'`` string is passed as a first argument to the PHP
151
- function.
152
-
153
- A filter can also take extra arguments like in the following example:
154
-
155
- .. code-block:: jinja
156
-
157
- {{ now|date('d/m/Y') }}
158
-
159
- In this case, the extra arguments are passed to the function after the main
160
- argument, and the compiled code is equivalent to:
161
-
162
- .. code-block:: html+php
163
-
164
- <?php echo twig_date_format_filter($now, 'd/m/Y') ?>
165
-
166
- Let's see how to create a new filter.
167
-
168
- In this section, we will create a ``rot13`` filter, which should return the
169
- `rot13`_ transformation of a string. Here is an example of its usage and the
170
- expected output:
171
-
172
- .. code-block:: jinja
173
-
174
- {{ "Twig"|rot13 }}
175
-
176
- {# should displays Gjvt #}
177
-
178
- Adding a filter is as simple as calling the ``addFilter()`` method on the
179
- ``Twig_Environment`` instance::
180
-
181
- $twig = new Twig_Environment($loader);
182
- $twig->addFilter('rot13', new Twig_Filter_Function('str_rot13'));
183
-
184
- The second argument of ``addFilter()`` is an instance of ``Twig_Filter``.
185
- Here, we use ``Twig_Filter_Function`` as the filter is a PHP function. The
186
- first argument passed to the ``Twig_Filter_Function`` constructor is the name
187
- of the PHP function to call, here ``str_rot13``, a native PHP function.
188
-
189
- Let's say I now want to be able to add a prefix before the converted string:
190
-
191
- .. code-block:: jinja
192
-
193
- {{ "Twig"|rot13('prefix_') }}
194
-
195
- {# should displays prefix_Gjvt #}
196
-
197
- As the PHP ``str_rot13()`` function does not support this requirement, let's
198
- create a new PHP function::
199
-
200
- function project_compute_rot13($string, $prefix = '')
201
- {
202
- return $prefix.str_rot13($string);
203
- }
204
-
205
- As you can see, the ``prefix`` argument of the filter is passed as an extra
206
- argument to the ``project_compute_rot13()`` function.
207
-
208
- Adding this filter is as easy as before::
209
-
210
- $twig->addFilter('rot13', new Twig_Filter_Function('project_compute_rot13'));
211
-
212
- For better encapsulation, a filter can also be defined as a static method of a
213
- class. The ``Twig_Filter_Function`` class can also be used to register such
214
- static methods as filters::
215
-
216
- $twig->addFilter('rot13', new Twig_Filter_Function('SomeClass::rot13Filter'));
217
-
218
- .. tip::
219
-
220
- In an extension, you can also define a filter as a static method of the
221
- extension class.
222
-
223
- Environment aware Filters
224
- ~~~~~~~~~~~~~~~~~~~~~~~~~
225
-
226
- The ``Twig_Filter`` classes take options as their last argument. For instance,
227
- if you want access to the current environment instance in your filter, set the
228
- ``needs_environment`` option to ``true``::
229
-
230
- $filter = new Twig_Filter_Function('str_rot13', array('needs_environment' => true));
231
-
232
- Twig will then pass the current environment as the first argument to the
233
- filter call::
234
-
235
- function twig_compute_rot13(Twig_Environment $env, $string)
236
- {
237
- // get the current charset for instance
238
- $charset = $env->getCharset();
239
-
240
- return str_rot13($string);
241
- }
242
-
243
- Automatic Escaping
244
- ~~~~~~~~~~~~~~~~~~
245
-
246
- If automatic escaping is enabled, the output of the filter may be escaped
247
- before printing. If your filter acts as an escaper (or explicitly outputs HTML
248
- or JavaScript code), you will want the raw output to be printed. In such a
249
- case, set the ``is_safe`` option::
250
-
251
- $filter = new Twig_Filter_Function('nl2br', array('is_safe' => array('html')));
252
-
253
- Some filters may need to work on input that is already escaped or safe, for
254
- example when adding (safe) HTML tags to originally unsafe output. In such a
255
- case, set the ``pre_escape`` option to escape the input data before it is run
256
- through your filter::
257
-
258
- $filter = new Twig_Filter_Function('somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
259
-
260
- Dynamic Filters
261
- ~~~~~~~~~~~~~~~
262
-
263
- .. versionadded:: 1.5
264
- Dynamic filters support was added in Twig 1.5.
265
-
266
- A filter name containing the special ``*`` character is a dynamic filter as
267
- the ``*`` can be any string::
268
-
269
- $twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
270
-
271
- function twig_path($name, $arguments)
272
- {
273
- // ...
274
- }
275
-
276
- The following filters will be matched by the above defined dynamic filter:
277
-
278
- * ``product_path``
279
- * ``category_path``
280
-
281
- A dynamic filter can define more than one dynamic parts::
282
-
283
- $twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
284
-
285
- function twig_path($name, $suffix, $arguments)
286
- {
287
- // ...
288
- }
289
-
290
- The filter will receive all dynamic part values before the normal filters
291
- arguments. For instance, a call to ``'foo'|a_path_b()`` will result in the
292
- following PHP call: ``twig_path('a', 'b', 'foo')``.
293
-
294
- Functions
295
- ---------
296
-
297
- A function is a regular PHP function or an object method that can be called from
298
- templates.
299
-
300
- .. code-block:: jinja
301
-
302
- {{ constant("DATE_W3C") }}
303
-
304
- When compiling this template to PHP, Twig looks for the PHP callable
305
- associated with the ``constant`` function. The ``constant`` function is a built-in Twig
306
- function, and it is simply mapped to the PHP ``constant()`` function. After
307
- compilation, the generated PHP code is roughly equivalent to:
308
-
309
- .. code-block:: html+php
310
-
311
- <?php echo constant('DATE_W3C') ?>
312
-
313
- Adding a function is similar to adding a filter. This can be done by calling the
314
- ``addFunction()`` method on the ``Twig_Environment`` instance::
315
-
316
- $twig = new Twig_Environment($loader);
317
- $twig->addFunction('functionName', new Twig_Function_Function('someFunction'));
318
-
319
- You can also expose extension methods as functions in your templates::
320
-
321
- // $this is an object that implements Twig_ExtensionInterface.
322
- $twig = new Twig_Environment($loader);
323
- $twig->addFunction('otherFunction', new Twig_Function_Method($this, 'someMethod'));
324
-
325
- Functions also support ``needs_environment`` and ``is_safe`` parameters.
326
-
327
- Dynamic Functions
328
- ~~~~~~~~~~~~~~~~~
329
-
330
- .. versionadded:: 1.5
331
- Dynamic functions support was added in Twig 1.5.
332
-
333
- A function name containing the special ``*`` character is a dynamic function
334
- as the ``*`` can be any string::
335
-
336
- $twig->addFunction('*_path', new Twig_Function_Function('twig_path'));
337
-
338
- function twig_path($name, $arguments)
339
- {
340
- // ...
341
- }
342
-
343
- The following functions will be matched by the above defined dynamic function:
344
-
345
- * ``product_path``
346
- * ``category_path``
347
-
348
- A dynamic function can define more than one dynamic parts::
349
-
350
- $twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
351
-
352
- function twig_path($name, $suffix, $arguments)
353
- {
354
- // ...
355
- }
356
-
357
- The function will receive all dynamic part values before the normal functions
358
- arguments. For instance, a call to ``a_path_b('foo')`` will result in the
359
- following PHP call: ``twig_path('a', 'b', 'foo')``.
360
-
361
- Tags
362
- ----
363
-
364
- One of the most exciting feature of a template engine like Twig is the
365
- possibility to define new language constructs. This is also the most complex
366
- feature as you need to understand how Twig's internals work.
367
-
368
- Let's create a simple ``set`` tag that allows the definition of simple
369
- variables from within a template. The tag can be used like follows:
370
-
371
- .. code-block:: jinja
372
-
373
- {% set name = "value" %}
374
-
375
- {{ name }}
376
-
377
- {# should output value #}
378
-
379
- .. note::
380
-
381
- The ``set`` tag is part of the Core extension and as such is always
382
- available. The built-in version is slightly more powerful and supports
383
- multiple assignments by default (cf. the template designers chapter for
384
- more information).
385
-
386
- Three steps are needed to define a new tag:
387
-
388
- * Defining a Token Parser class (responsible for parsing the template code);
389
-
390
- * Defining a Node class (responsible for converting the parsed code to PHP);
391
-
392
- * Registering the tag.
393
-
394
- Registering a new tag
395
- ~~~~~~~~~~~~~~~~~~~~~
396
-
397
- Adding a tag is as simple as calling the ``addTokenParser`` method on the
398
- ``Twig_Environment`` instance::
399
-
400
- $twig = new Twig_Environment($loader);
401
- $twig->addTokenParser(new Project_Set_TokenParser());
402
-
403
- Defining a Token Parser
404
- ~~~~~~~~~~~~~~~~~~~~~~~
405
-
406
- Now, let's see the actual code of this class::
407
-
408
- class Project_Set_TokenParser extends Twig_TokenParser
409
- {
410
- public function parse(Twig_Token $token)
411
- {
412
- $lineno = $token->getLine();
413
- $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
414
- $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, '=');
415
- $value = $this->parser->getExpressionParser()->parseExpression();
416
-
417
- $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
418
-
419
- return new Project_Set_Node($name, $value, $lineno, $this->getTag());
420
- }
421
-
422
- public function getTag()
423
- {
424
- return 'set';
425
- }
426
- }
427
-
428
- The ``getTag()`` method must return the tag we want to parse, here ``set``.
429
-
430
- The ``parse()`` method is invoked whenever the parser encounters a ``set``
431
- tag. It should return a ``Twig_Node`` instance that represents the node (the
432
- ``Project_Set_Node`` calls creating is explained in the next section).
433
-
434
- The parsing process is simplified thanks to a bunch of methods you can call
435
- from the token stream (``$this->parser->getStream()``):
436
-
437
- * ``getCurrent()``: Gets the current token in the stream.
438
-
439
- * ``next()``: Moves to the next token in the stream, *but returns the old one*.
440
-
441
- * ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether
442
- the current token is of a particular type or value (or both). The value may be an
443
- array of several possible values.
444
-
445
- * ``expect($type[, $value[, $message]])``: If the current token isn't of the given
446
- type/value a syntax error is thrown. Otherwise, if the type and value are correct,
447
- the token is returned and the stream moves to the next token.
448
-
449
- * ``look()``: Looks a the next token without consuming it.
450
-
451
- Parsing expressions is done by calling the ``parseExpression()`` like we did for
452
- the ``set`` tag.
453
-
454
- .. tip::
455
-
456
- Reading the existing ``TokenParser`` classes is the best way to learn all
457
- the nitty-gritty details of the parsing process.
458
-
459
- Defining a Node
460
- ~~~~~~~~~~~~~~~
461
-
462
- The ``Project_Set_Node`` class itself is rather simple::
463
-
464
- class Project_Set_Node extends Twig_Node
465
- {
466
- public function __construct($name, Twig_Node_Expression $value, $lineno, $tag = null)
467
- {
468
- parent::__construct(array('value' => $value), array('name' => $name), $lineno, $tag);
469
- }
470
-
471
- public function compile(Twig_Compiler $compiler)
472
- {
473
- $compiler
474
- ->addDebugInfo($this)
475
- ->write('$context[\''.$this->getAttribute('name').'\'] = ')
476
- ->subcompile($this->getNode('value'))
477
- ->raw(";\n")
478
- ;
479
- }
480
- }
481
-
482
- The compiler implements a fluid interface and provides methods that helps the
483
- developer generate beautiful and readable PHP code:
484
-
485
- * ``subcompile()``: Compiles a node.
486
-
487
- * ``raw()``: Writes the given string as is.
488
-
489
- * ``write()``: Writes the given string by adding indentation at the beginning
490
- of each line.
491
-
492
- * ``string()``: Writes a quoted string.
493
-
494
- * ``repr()``: Writes a PHP representation of a given value (see
495
- ``Twig_Node_For`` for a usage example).
496
-
497
- * ``addDebugInfo()``: Adds the line of the original template file related to
498
- the current node as a comment.
499
-
500
- * ``indent()``: Indents the generated code (see ``Twig_Node_Block`` for a
501
- usage example).
502
-
503
- * ``outdent()``: Outdents the generated code (see ``Twig_Node_Block`` for a
504
- usage example).
505
-
506
- .. _creating_extensions:
507
-
508
- Creating an Extension
509
- ---------------------
510
-
511
- The main motivation for writing an extension is to move often used code into a
512
- reusable class like adding support for internationalization. An extension can
513
- define tags, filters, tests, operators, global variables, functions, and node
514
- visitors.
515
-
516
- Creating an extension also makes for a better separation of code that is
517
- executed at compilation time and code needed at runtime. As such, it makes
518
- your code faster.
519
-
520
- Most of the time, it is useful to create a single extension for your project,
521
- to host all the specific tags and filters you want to add to Twig.
522
-
523
- .. tip::
524
-
525
- When packaging your code into an extension, Twig is smart enough to
526
- recompile your templates whenever you make a change to it (when the
527
- ``auto_reload`` is enabled).
528
-
529
- .. note::
530
-
531
- Before writing your own extensions, have a look at the Twig official
532
- extension repository: http://github.com/twigphp/Twig-extensions.
533
-
534
- An extension is a class that implements the following interface::
535
-
536
- interface Twig_ExtensionInterface
537
- {
538
- /**
539
- * Initializes the runtime environment.
540
- *
541
- * This is where you can load some file that contains filter functions for instance.
542
- */
543
- function initRuntime(Twig_Environment $environment);
544
-
545
- /**
546
- * Returns the token parser instances to add to the existing list.
547
- *
548
- * @return (Twig_TokenParserInterface|Twig_TokenParserBrokerInterface)[]
549
- */
550
- function getTokenParsers();
551
-
552
- /**
553
- * Returns the node visitor instances to add to the existing list.
554
- *
555
- * @return Twig_NodeVisitorInterface[]
556
- */
557
- function getNodeVisitors();
558
-
559
- /**
560
- * Returns a list of filters to add to the existing list.
561
- *
562
- * @return Twig_SimpleFilter[]
563
- */
564
- function getFilters();
565
-
566
- /**
567
- * Returns a list of tests to add to the existing list.
568
- *
569
- * @return Twig_SimpleTest[]
570
- */
571
- function getTests();
572
-
573
- /**
574
- * Returns a list of functions to add to the existing list.
575
- *
576
- * @return Twig_SimpleFunction[]
577
- */
578
- function getFunctions();
579
-
580
- /**
581
- * Returns a list of operators to add to the existing list.
582
- *
583
- * @return array<array> First array of unary operators, second array of binary operators
584
- */
585
- function getOperators();
586
-
587
- /**
588
- * Returns a list of global variables to add to the existing list.
589
- *
590
- * @return array An array of global variables
591
- */
592
- function getGlobals();
593
-
594
- /**
595
- * Returns the name of the extension.
596
- *
597
- * @return string The extension name
598
- */
599
- function getName();
600
- }
601
-
602
- To keep your extension class clean and lean, it can inherit from the built-in
603
- ``Twig_Extension`` class instead of implementing the whole interface. That
604
- way, you just need to implement the ``getName()`` method as the
605
- ``Twig_Extension`` provides empty implementations for all other methods.
606
-
607
- The ``getName()`` method must return a unique identifier for your extension.
608
-
609
- Now, with this information in mind, let's create the most basic extension
610
- possible::
611
-
612
- class Project_Twig_Extension extends Twig_Extension
613
- {
614
- public function getName()
615
- {
616
- return 'project';
617
- }
618
- }
619
-
620
- .. note::
621
-
622
- Of course, this extension does nothing for now. We will customize it in
623
- the next sections.
624
-
625
- Twig does not care where you save your extension on the filesystem, as all
626
- extensions must be registered explicitly to be available in your templates.
627
-
628
- You can register an extension by using the ``addExtension()`` method on your
629
- main ``Environment`` object::
630
-
631
- $twig = new Twig_Environment($loader);
632
- $twig->addExtension(new Project_Twig_Extension());
633
-
634
- Of course, you need to first load the extension file by either using
635
- ``require_once()`` or by using an autoloader (see `spl_autoload_register()`_).
636
-
637
- .. tip::
638
-
639
- The bundled extensions are great examples of how extensions work.
640
-
641
- Globals
642
- ~~~~~~~
643
-
644
- Global variables can be registered in an extension via the ``getGlobals()``
645
- method::
646
-
647
- class Project_Twig_Extension extends Twig_Extension
648
- {
649
- public function getGlobals()
650
- {
651
- return array(
652
- 'text' => new Text(),
653
- );
654
- }
655
-
656
- // ...
657
- }
658
-
659
- Functions
660
- ~~~~~~~~~
661
-
662
- Functions can be registered in an extension via the ``getFunctions()``
663
- method::
664
-
665
- class Project_Twig_Extension extends Twig_Extension
666
- {
667
- public function getFunctions()
668
- {
669
- return array(
670
- 'lipsum' => new Twig_Function_Function('generate_lipsum'),
671
- );
672
- }
673
-
674
- // ...
675
- }
676
-
677
- Filters
678
- ~~~~~~~
679
-
680
- To add a filter to an extension, you need to override the ``getFilters()``
681
- method. This method must return an array of filters to add to the Twig
682
- environment::
683
-
684
- class Project_Twig_Extension extends Twig_Extension
685
- {
686
- public function getFilters()
687
- {
688
- return array(
689
- 'rot13' => new Twig_Filter_Function('str_rot13'),
690
- );
691
- }
692
-
693
- // ...
694
- }
695
-
696
- As you can see in the above code, the ``getFilters()`` method returns an array
697
- where keys are the name of the filters (``rot13``) and the values the
698
- definition of the filter (``new Twig_Filter_Function('str_rot13')``).
699
-
700
- As seen in the previous chapter, you can also define filters as static methods
701
- on the extension class::
702
-
703
- $twig->addFilter('rot13', new Twig_Filter_Function('Project_Twig_Extension::rot13Filter'));
704
-
705
- You can also use ``Twig_Filter_Method`` instead of ``Twig_Filter_Function``
706
- when defining a filter to use a method::
707
-
708
- class Project_Twig_Extension extends Twig_Extension
709
- {
710
- public function getFilters()
711
- {
712
- return array(
713
- 'rot13' => new Twig_Filter_Method($this, 'rot13Filter'),
714
- );
715
- }
716
-
717
- public function rot13Filter($string)
718
- {
719
- return str_rot13($string);
720
- }
721
-
722
- // ...
723
- }
724
-
725
- The first argument of the ``Twig_Filter_Method`` constructor is always
726
- ``$this``, the current extension object. The second one is the name of the
727
- method to call.
728
-
729
- Using methods for filters is a great way to package your filter without
730
- polluting the global namespace. This also gives the developer more flexibility
731
- at the cost of a small overhead.
732
-
733
- Overriding default Filters
734
- ..........................
735
-
736
- If some default core filters do not suit your needs, you can easily override
737
- them by creating your own extension. Just use the same names as the one you
738
- want to override::
739
-
740
- class MyCoreExtension extends Twig_Extension
741
- {
742
- public function getFilters()
743
- {
744
- return array(
745
- 'date' => new Twig_Filter_Method($this, 'dateFilter'),
746
- // ...
747
- );
748
- }
749
-
750
- public function dateFilter($timestamp, $format = 'F j, Y H:i')
751
- {
752
- return '...'.twig_date_format_filter($timestamp, $format);
753
- }
754
-
755
- public function getName()
756
- {
757
- return 'project';
758
- }
759
- }
760
-
761
- Here, we override the ``date`` filter with a custom one. Using this extension
762
- is as simple as registering the ``MyCoreExtension`` extension by calling the
763
- ``addExtension()`` method on the environment instance::
764
-
765
- $twig = new Twig_Environment($loader);
766
- $twig->addExtension(new MyCoreExtension());
767
-
768
- Tags
769
- ~~~~
770
-
771
- Adding a tag in an extension can be done by overriding the
772
- ``getTokenParsers()`` method. This method must return an array of tags to add
773
- to the Twig environment::
774
-
775
- class Project_Twig_Extension extends Twig_Extension
776
- {
777
- public function getTokenParsers()
778
- {
779
- return array(new Project_Set_TokenParser());
780
- }
781
-
782
- // ...
783
- }
784
-
785
- In the above code, we have added a single new tag, defined by the
786
- ``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is
787
- responsible for parsing the tag and compiling it to PHP.
788
-
789
- Operators
790
- ~~~~~~~~~
791
-
792
- The ``getOperators()`` methods allows to add new operators. Here is how to add
793
- ``!``, ``||``, and ``&&`` operators::
794
-
795
- class Project_Twig_Extension extends Twig_Extension
796
- {
797
- public function getOperators()
798
- {
799
- return array(
800
- array(
801
- '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
802
- ),
803
- array(
804
- '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
805
- '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
806
- ),
807
- );
808
- }
809
-
810
- // ...
811
- }
812
-
813
- Tests
814
- ~~~~~
815
-
816
- The ``getTests()`` methods allows to add new test functions::
817
-
818
- class Project_Twig_Extension extends Twig_Extension
819
- {
820
- public function getTests()
821
- {
822
- return array(
823
- 'even' => new Twig_Test_Function('twig_test_even'),
824
- );
825
- }
826
-
827
- // ...
828
- }
829
-
830
- Testing an Extension
831
- --------------------
832
-
833
- .. versionadded:: 1.10
834
- Support for functional tests was added in Twig 1.10.
835
-
836
- Functional Tests
837
- ~~~~~~~~~~~~~~~~
838
-
839
- You can create functional tests for extensions simply by creating the
840
- following file structure in your test directory::
841
-
842
- Fixtures/
843
- filters/
844
- foo.test
845
- bar.test
846
- functions/
847
- foo.test
848
- bar.test
849
- tags/
850
- foo.test
851
- bar.test
852
- IntegrationTest.php
853
-
854
- The ``IntegrationTest.php`` file should look like this::
855
-
856
- class Project_Tests_IntegrationTest extends Twig_Test_IntegrationTestCase
857
- {
858
- public function getExtensions()
859
- {
860
- return array(
861
- new Project_Twig_Extension1(),
862
- new Project_Twig_Extension2(),
863
- );
864
- }
865
-
866
- public function getFixturesDir()
867
- {
868
- return dirname(__FILE__).'/Fixtures/';
869
- }
870
- }
871
-
872
- Fixtures examples can be found within the Twig repository
873
- `tests/Twig/Fixtures`_ directory.
874
-
875
- Node Tests
876
- ~~~~~~~~~~
877
-
878
- Testing the node visitors can be complex, so extend your test cases from
879
- ``Twig_Test_NodeTestCase``. Examples can be found in the Twig repository
880
- `tests/Twig/Node`_ directory.
881
-
882
- .. _`spl_autoload_register()`: http://www.php.net/spl_autoload_register
883
- .. _`rot13`: http://www.php.net/manual/en/function.str-rot13.php
884
- .. _`tests/Twig/Fixtures`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Fixtures
885
- .. _`tests/Twig/Node`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Node
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/api.rst DELETED
@@ -1,590 +0,0 @@
1
- Twig for Developers
2
- ===================
3
-
4
- This chapter describes the API to Twig and not the template language. It will
5
- be most useful as reference to those implementing the template interface to
6
- the application and not those who are creating Twig templates.
7
-
8
- Basics
9
- ------
10
-
11
- Twig uses a central object called the **environment** (of class
12
- ``Twig_Environment``). Instances of this class are used to store the
13
- configuration and extensions, and are used to load templates from the file
14
- system or other locations.
15
-
16
- Most applications will create one ``Twig_Environment`` object on application
17
- initialization and use that to load templates. In some cases it's however
18
- useful to have multiple environments side by side, if different configurations
19
- are in use.
20
-
21
- The simplest way to configure Twig to load templates for your application
22
- looks roughly like this::
23
-
24
- require_once '/path/to/lib/Twig/Autoloader.php';
25
- Twig_Autoloader::register();
26
-
27
- $loader = new Twig_Loader_Filesystem('/path/to/templates');
28
- $twig = new Twig_Environment($loader, array(
29
- 'cache' => '/path/to/compilation_cache',
30
- ));
31
-
32
- This will create a template environment with the default settings and a loader
33
- that looks up the templates in the ``/path/to/templates/`` folder. Different
34
- loaders are available and you can also write your own if you want to load
35
- templates from a database or other resources.
36
-
37
- .. note::
38
-
39
- Notice that the second argument of the environment is an array of options.
40
- The ``cache`` option is a compilation cache directory, where Twig caches
41
- the compiled templates to avoid the parsing phase for sub-sequent
42
- requests. It is very different from the cache you might want to add for
43
- the evaluated templates. For such a need, you can use any available PHP
44
- cache library.
45
-
46
- Rendering Templates
47
- -------------------
48
-
49
- To load a template from a Twig environment, call the ``load()`` method which
50
- returns a ``Twig_TemplateWrapper`` instance::
51
-
52
- $template = $twig->load('index.html');
53
-
54
- .. note::
55
-
56
- Before Twig 1.28, you should use ``loadTemplate()`` instead which returns a
57
- ``Twig_Template`` instance.
58
-
59
- To render the template with some variables, call the ``render()`` method::
60
-
61
- echo $template->render(array('the' => 'variables', 'go' => 'here'));
62
-
63
- .. note::
64
-
65
- The ``display()`` method is a shortcut to output the template directly.
66
-
67
- You can also load and render the template in one fell swoop::
68
-
69
- echo $twig->render('index.html', array('the' => 'variables', 'go' => 'here'));
70
-
71
- .. versionadded:: 1.28
72
- The possibility to render blocks from the API was added in Twig 1.28.
73
-
74
- If a template defines blocks, they can be rendered individually via the
75
- ``renderBlock()`` call::
76
-
77
- echo $template->renderBlock('block_name', array('the' => 'variables', 'go' => 'here'));
78
-
79
- .. _environment_options:
80
-
81
- Environment Options
82
- -------------------
83
-
84
- When creating a new ``Twig_Environment`` instance, you can pass an array of
85
- options as the constructor second argument::
86
-
87
- $twig = new Twig_Environment($loader, array('debug' => true));
88
-
89
- The following options are available:
90
-
91
- * ``debug`` *boolean*
92
-
93
- When set to ``true``, the generated templates have a
94
- ``__toString()`` method that you can use to display the generated nodes
95
- (default to ``false``).
96
-
97
- * ``charset`` *string* (defaults to ``utf-8``)
98
-
99
- The charset used by the templates.
100
-
101
- * ``base_template_class`` *string* (defaults to ``Twig_Template``)
102
-
103
- The base template class to use for generated
104
- templates.
105
-
106
- * ``cache`` *string* or ``false``
107
-
108
- An absolute path where to store the compiled templates, or
109
- ``false`` to disable caching (which is the default).
110
-
111
- * ``auto_reload`` *boolean*
112
-
113
- When developing with Twig, it's useful to recompile the
114
- template whenever the source code changes. If you don't provide a value for
115
- the ``auto_reload`` option, it will be determined automatically based on the
116
- ``debug`` value.
117
-
118
- * ``strict_variables`` *boolean*
119
-
120
- If set to ``false``, Twig will silently ignore invalid
121
- variables (variables and or attributes/methods that do not exist) and
122
- replace them with a ``null`` value. When set to ``true``, Twig throws an
123
- exception instead (default to ``false``).
124
-
125
- * ``autoescape`` *string* or *boolean*
126
-
127
- If set to ``true``, HTML auto-escaping will be enabled by
128
- default for all templates (default to ``true``).
129
-
130
- As of Twig 1.8, you can set the escaping strategy to use (``html``, ``js``,
131
- ``false`` to disable).
132
-
133
- As of Twig 1.9, you can set the escaping strategy to use (``css``, ``url``,
134
- ``html_attr``, or a PHP callback that takes the template name and must
135
- return the escaping strategy to use -- the callback cannot be a function name
136
- to avoid collision with built-in escaping strategies).
137
-
138
- As of Twig 1.17, the ``filename`` escaping strategy (renamed to ``name`` as
139
- of Twig 1.27) determines the escaping strategy to use for a template based on
140
- the template filename extension (this strategy does not incur any overhead at
141
- runtime as auto-escaping is done at compilation time.)
142
-
143
- * ``optimizations`` *integer*
144
-
145
- A flag that indicates which optimizations to apply
146
- (default to ``-1`` -- all optimizations are enabled; set it to ``0`` to
147
- disable).
148
-
149
- Loaders
150
- -------
151
-
152
- Loaders are responsible for loading templates from a resource such as the file
153
- system.
154
-
155
- Compilation Cache
156
- ~~~~~~~~~~~~~~~~~
157
-
158
- All template loaders can cache the compiled templates on the filesystem for
159
- future reuse. It speeds up Twig a lot as templates are only compiled once; and
160
- the performance boost is even larger if you use a PHP accelerator such as APC.
161
- See the ``cache`` and ``auto_reload`` options of ``Twig_Environment`` above
162
- for more information.
163
-
164
- Built-in Loaders
165
- ~~~~~~~~~~~~~~~~
166
-
167
- Here is a list of the built-in loaders Twig provides:
168
-
169
- ``Twig_Loader_Filesystem``
170
- ..........................
171
-
172
- .. versionadded:: 1.10
173
- The ``prependPath()`` and support for namespaces were added in Twig 1.10.
174
-
175
- .. versionadded:: 1.27
176
- Relative paths support was added in Twig 1.27.
177
-
178
- ``Twig_Loader_Filesystem`` loads templates from the file system. This loader
179
- can find templates in folders on the file system and is the preferred way to
180
- load them::
181
-
182
- $loader = new Twig_Loader_Filesystem($templateDir);
183
-
184
- It can also look for templates in an array of directories::
185
-
186
- $loader = new Twig_Loader_Filesystem(array($templateDir1, $templateDir2));
187
-
188
- With such a configuration, Twig will first look for templates in
189
- ``$templateDir1`` and if they do not exist, it will fallback to look for them
190
- in the ``$templateDir2``.
191
-
192
- You can add or prepend paths via the ``addPath()`` and ``prependPath()``
193
- methods::
194
-
195
- $loader->addPath($templateDir3);
196
- $loader->prependPath($templateDir4);
197
-
198
- The filesystem loader also supports namespaced templates. This allows to group
199
- your templates under different namespaces which have their own template paths.
200
-
201
- When using the ``setPaths()``, ``addPath()``, and ``prependPath()`` methods,
202
- specify the namespace as the second argument (when not specified, these
203
- methods act on the "main" namespace)::
204
-
205
- $loader->addPath($templateDir, 'admin');
206
-
207
- Namespaced templates can be accessed via the special
208
- ``@namespace_name/template_path`` notation::
209
-
210
- $twig->render('@admin/index.html', array());
211
-
212
- ``Twig_Loader_Filesystem`` support absolute and relative paths. Using relative
213
- paths is preferred as it makes the cache keys independent of the project root
214
- directory (for instance, it allows warming the cache from a build server where
215
- the directory might be different from the one used on production servers)::
216
-
217
- $loader = new Twig_Loader_Filesystem('templates', getcwd().'/..');
218
-
219
- .. note::
220
-
221
- When not passing the root path as a second argument, Twig uses ``getcwd()``
222
- for relative paths.
223
-
224
- ``Twig_Loader_Array``
225
- .....................
226
-
227
- ``Twig_Loader_Array`` loads a template from a PHP array. It's passed an array
228
- of strings bound to template names::
229
-
230
- $loader = new Twig_Loader_Array(array(
231
- 'index.html' => 'Hello {{ name }}!',
232
- ));
233
- $twig = new Twig_Environment($loader);
234
-
235
- echo $twig->render('index.html', array('name' => 'Fabien'));
236
-
237
- This loader is very useful for unit testing. It can also be used for small
238
- projects where storing all templates in a single PHP file might make sense.
239
-
240
- .. tip::
241
-
242
- When using the ``Array`` or ``String`` loaders with a cache mechanism, you
243
- should know that a new cache key is generated each time a template content
244
- "changes" (the cache key being the source code of the template). If you
245
- don't want to see your cache grows out of control, you need to take care
246
- of clearing the old cache file by yourself.
247
-
248
- ``Twig_Loader_Chain``
249
- .....................
250
-
251
- ``Twig_Loader_Chain`` delegates the loading of templates to other loaders::
252
-
253
- $loader1 = new Twig_Loader_Array(array(
254
- 'base.html' => '{% block content %}{% endblock %}',
255
- ));
256
- $loader2 = new Twig_Loader_Array(array(
257
- 'index.html' => '{% extends "base.html" %}{% block content %}Hello {{ name }}{% endblock %}',
258
- 'base.html' => 'Will never be loaded',
259
- ));
260
-
261
- $loader = new Twig_Loader_Chain(array($loader1, $loader2));
262
-
263
- $twig = new Twig_Environment($loader);
264
-
265
- When looking for a template, Twig will try each loader in turn and it will
266
- return as soon as the template is found. When rendering the ``index.html``
267
- template from the above example, Twig will load it with ``$loader2`` but the
268
- ``base.html`` template will be loaded from ``$loader1``.
269
-
270
- ``Twig_Loader_Chain`` accepts any loader that implements
271
- ``Twig_LoaderInterface``.
272
-
273
- .. note::
274
-
275
- You can also add loaders via the ``addLoader()`` method.
276
-
277
- Create your own Loader
278
- ~~~~~~~~~~~~~~~~~~~~~~
279
-
280
- All loaders implement the ``Twig_LoaderInterface``::
281
-
282
- interface Twig_LoaderInterface
283
- {
284
- /**
285
- * Gets the source code of a template, given its name.
286
- *
287
- * @param string $name string The name of the template to load
288
- *
289
- * @return string The template source code
290
- *
291
- * @deprecated since 1.27 (to be removed in 2.0), implement Twig_SourceContextLoaderInterface
292
- */
293
- function getSource($name);
294
-
295
- /**
296
- * Gets the cache key to use for the cache for a given template name.
297
- *
298
- * @param string $name string The name of the template to load
299
- *
300
- * @return string The cache key
301
- */
302
- function getCacheKey($name);
303
-
304
- /**
305
- * Returns true if the template is still fresh.
306
- *
307
- * @param string $name The template name
308
- * @param timestamp $time The last modification time of the cached template
309
- */
310
- function isFresh($name, $time);
311
- }
312
-
313
- The ``isFresh()`` method must return ``true`` if the current cached template
314
- is still fresh, given the last modification time, or ``false`` otherwise.
315
-
316
- .. note::
317
-
318
- As of Twig 1.27, you should also implement
319
- ``Twig_SourceContextLoaderInterface`` to avoid deprecation notices.
320
-
321
- .. tip::
322
-
323
- As of Twig 1.11.0, you can also implement ``Twig_ExistsLoaderInterface``
324
- to make your loader faster when used with the chain loader.
325
-
326
- Using Extensions
327
- ----------------
328
-
329
- Twig extensions are packages that add new features to Twig. Using an
330
- extension is as simple as using the ``addExtension()`` method::
331
-
332
- $twig->addExtension(new Twig_Extension_Sandbox());
333
-
334
- Twig comes bundled with the following extensions:
335
-
336
- * *Twig_Extension_Core*: Defines all the core features of Twig.
337
-
338
- * *Twig_Extension_Escaper*: Adds automatic output-escaping and the possibility
339
- to escape/unescape blocks of code.
340
-
341
- * *Twig_Extension_Sandbox*: Adds a sandbox mode to the default Twig
342
- environment, making it safe to evaluate untrusted code.
343
-
344
- * *Twig_Extension_Profiler*: Enabled the built-in Twig profiler (as of Twig
345
- 1.18).
346
-
347
- * *Twig_Extension_Optimizer*: Optimizes the node tree before compilation.
348
-
349
- The core, escaper, and optimizer extensions do not need to be added to the
350
- Twig environment, as they are registered by default.
351
-
352
- Built-in Extensions
353
- -------------------
354
-
355
- This section describes the features added by the built-in extensions.
356
-
357
- .. tip::
358
-
359
- Read the chapter about extending Twig to learn how to create your own
360
- extensions.
361
-
362
- Core Extension
363
- ~~~~~~~~~~~~~~
364
-
365
- The ``core`` extension defines all the core features of Twig:
366
-
367
- * :doc:`Tags <tags/index>`;
368
- * :doc:`Filters <filters/index>`;
369
- * :doc:`Functions <functions/index>`;
370
- * :doc:`Tests <tests/index>`.
371
-
372
- Escaper Extension
373
- ~~~~~~~~~~~~~~~~~
374
-
375
- The ``escaper`` extension adds automatic output escaping to Twig. It defines a
376
- tag, ``autoescape``, and a filter, ``raw``.
377
-
378
- When creating the escaper extension, you can switch on or off the global
379
- output escaping strategy::
380
-
381
- $escaper = new Twig_Extension_Escaper('html');
382
- $twig->addExtension($escaper);
383
-
384
- If set to ``html``, all variables in templates are escaped (using the ``html``
385
- escaping strategy), except those using the ``raw`` filter:
386
-
387
- .. code-block:: jinja
388
-
389
- {{ article.to_html|raw }}
390
-
391
- You can also change the escaping mode locally by using the ``autoescape`` tag
392
- (see the :doc:`autoescape<tags/autoescape>` doc for the syntax used before
393
- Twig 1.8):
394
-
395
- .. code-block:: jinja
396
-
397
- {% autoescape 'html' %}
398
- {{ var }}
399
- {{ var|raw }} {# var won't be escaped #}
400
- {{ var|escape }} {# var won't be double-escaped #}
401
- {% endautoescape %}
402
-
403
- .. warning::
404
-
405
- The ``autoescape`` tag has no effect on included files.
406
-
407
- The escaping rules are implemented as follows:
408
-
409
- * Literals (integers, booleans, arrays, ...) used in the template directly as
410
- variables or filter arguments are never automatically escaped:
411
-
412
- .. code-block:: jinja
413
-
414
- {{ "Twig<br />" }} {# won't be escaped #}
415
-
416
- {% set text = "Twig<br />" %}
417
- {{ text }} {# will be escaped #}
418
-
419
- * Expressions which the result is always a literal or a variable marked safe
420
- are never automatically escaped:
421
-
422
- .. code-block:: jinja
423
-
424
- {{ foo ? "Twig<br />" : "<br />Twig" }} {# won't be escaped #}
425
-
426
- {% set text = "Twig<br />" %}
427
- {{ foo ? text : "<br />Twig" }} {# will be escaped #}
428
-
429
- {% set text = "Twig<br />" %}
430
- {{ foo ? text|raw : "<br />Twig" }} {# won't be escaped #}
431
-
432
- {% set text = "Twig<br />" %}
433
- {{ foo ? text|escape : "<br />Twig" }} {# the result of the expression won't be escaped #}
434
-
435
- * Escaping is applied before printing, after any other filter is applied:
436
-
437
- .. code-block:: jinja
438
-
439
- {{ var|upper }} {# is equivalent to {{ var|upper|escape }} #}
440
-
441
- * The `raw` filter should only be used at the end of the filter chain:
442
-
443
- .. code-block:: jinja
444
-
445
- {{ var|raw|upper }} {# will be escaped #}
446
-
447
- {{ var|upper|raw }} {# won't be escaped #}
448
-
449
- * Automatic escaping is not applied if the last filter in the chain is marked
450
- safe for the current context (e.g. ``html`` or ``js``). ``escape`` and
451
- ``escape('html')`` are marked safe for HTML, ``escape('js')`` is marked
452
- safe for JavaScript, ``raw`` is marked safe for everything.
453
-
454
- .. code-block:: jinja
455
-
456
- {% autoescape 'js' %}
457
- {{ var|escape('html') }} {# will be escaped for HTML and JavaScript #}
458
- {{ var }} {# will be escaped for JavaScript #}
459
- {{ var|escape('js') }} {# won't be double-escaped #}
460
- {% endautoescape %}
461
-
462
- .. note::
463
-
464
- Note that autoescaping has some limitations as escaping is applied on
465
- expressions after evaluation. For instance, when working with
466
- concatenation, ``{{ foo|raw ~ bar }}`` won't give the expected result as
467
- escaping is applied on the result of the concatenation, not on the
468
- individual variables (so, the ``raw`` filter won't have any effect here).
469
-
470
- Sandbox Extension
471
- ~~~~~~~~~~~~~~~~~
472
-
473
- The ``sandbox`` extension can be used to evaluate untrusted code. Access to
474
- unsafe attributes and methods is prohibited. The sandbox security is managed
475
- by a policy instance. By default, Twig comes with one policy class:
476
- ``Twig_Sandbox_SecurityPolicy``. This class allows you to white-list some
477
- tags, filters, properties, and methods::
478
-
479
- $tags = array('if');
480
- $filters = array('upper');
481
- $methods = array(
482
- 'Article' => array('getTitle', 'getBody'),
483
- );
484
- $properties = array(
485
- 'Article' => array('title', 'body'),
486
- );
487
- $functions = array('range');
488
- $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions);
489
-
490
- With the previous configuration, the security policy will only allow usage of
491
- the ``if`` tag, and the ``upper`` filter. Moreover, the templates will only be
492
- able to call the ``getTitle()`` and ``getBody()`` methods on ``Article``
493
- objects, and the ``title`` and ``body`` public properties. Everything else
494
- won't be allowed and will generate a ``Twig_Sandbox_SecurityError`` exception.
495
-
496
- The policy object is the first argument of the sandbox constructor::
497
-
498
- $sandbox = new Twig_Extension_Sandbox($policy);
499
- $twig->addExtension($sandbox);
500
-
501
- By default, the sandbox mode is disabled and should be enabled when including
502
- untrusted template code by using the ``sandbox`` tag:
503
-
504
- .. code-block:: jinja
505
-
506
- {% sandbox %}
507
- {% include 'user.html' %}
508
- {% endsandbox %}
509
-
510
- You can sandbox all templates by passing ``true`` as the second argument of
511
- the extension constructor::
512
-
513
- $sandbox = new Twig_Extension_Sandbox($policy, true);
514
-
515
- Profiler Extension
516
- ~~~~~~~~~~~~~~~~~~
517
-
518
- .. versionadded:: 1.18
519
- The Profile extension was added in Twig 1.18.
520
-
521
- The ``profiler`` extension enables a profiler for Twig templates; it should
522
- only be used on your development machines as it adds some overhead::
523
-
524
- $profile = new Twig_Profiler_Profile();
525
- $twig->addExtension(new Twig_Extension_Profiler($profile));
526
-
527
- $dumper = new Twig_Profiler_Dumper_Text();
528
- echo $dumper->dump($profile);
529
-
530
- A profile contains information about time and memory consumption for template,
531
- block, and macro executions.
532
-
533
- You can also dump the data in a `Blackfire.io <https://blackfire.io/>`_
534
- compatible format::
535
-
536
- $dumper = new Twig_Profiler_Dumper_Blackfire();
537
- file_put_contents('/path/to/profile.prof', $dumper->dump($profile));
538
-
539
- Upload the profile to visualize it (create a `free account
540
- <https://blackfire.io/signup>`_ first):
541
-
542
- .. code-block:: sh
543
-
544
- blackfire --slot=7 upload /path/to/profile.prof
545
-
546
- Optimizer Extension
547
- ~~~~~~~~~~~~~~~~~~~
548
-
549
- The ``optimizer`` extension optimizes the node tree before compilation::
550
-
551
- $twig->addExtension(new Twig_Extension_Optimizer());
552
-
553
- By default, all optimizations are turned on. You can select the ones you want
554
- to enable by passing them to the constructor::
555
-
556
- $optimizer = new Twig_Extension_Optimizer(Twig_NodeVisitor_Optimizer::OPTIMIZE_FOR);
557
-
558
- $twig->addExtension($optimizer);
559
-
560
- Twig supports the following optimizations:
561
-
562
- * ``Twig_NodeVisitor_Optimizer::OPTIMIZE_ALL``, enables all optimizations
563
- (this is the default value).
564
- * ``Twig_NodeVisitor_Optimizer::OPTIMIZE_NONE``, disables all optimizations.
565
- This reduces the compilation time, but it can increase the execution time
566
- and the consumed memory.
567
- * ``Twig_NodeVisitor_Optimizer::OPTIMIZE_FOR``, optimizes the ``for`` tag by
568
- removing the ``loop`` variable creation whenever possible.
569
- * ``Twig_NodeVisitor_Optimizer::OPTIMIZE_RAW_FILTER``, removes the ``raw``
570
- filter whenever possible.
571
- * ``Twig_NodeVisitor_Optimizer::OPTIMIZE_VAR_ACCESS``, simplifies the creation
572
- and access of variables in the compiled templates whenever possible.
573
-
574
- Exceptions
575
- ----------
576
-
577
- Twig can throw exceptions:
578
-
579
- * ``Twig_Error``: The base exception for all errors.
580
-
581
- * ``Twig_Error_Syntax``: Thrown to tell the user that there is a problem with
582
- the template syntax.
583
-
584
- * ``Twig_Error_Runtime``: Thrown when an error occurs at runtime (when a filter
585
- does not exist for instance).
586
-
587
- * ``Twig_Error_Loader``: Thrown when an error occurs during template loading.
588
-
589
- * ``Twig_Sandbox_SecurityError``: Thrown when an unallowed tag, filter, or
590
- method is called in a sandboxed template.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/coding_standards.rst DELETED
@@ -1,101 +0,0 @@
1
- Coding Standards
2
- ================
3
-
4
- When writing Twig templates, we recommend you to follow these official coding
5
- standards:
6
-
7
- * Put one (and only one) space after the start of a delimiter (``{{``, ``{%``,
8
- and ``{#``) and before the end of a delimiter (``}}``, ``%}``, and ``#}``):
9
-
10
- .. code-block:: jinja
11
-
12
- {{ foo }}
13
- {# comment #}
14
- {% if foo %}{% endif %}
15
-
16
- When using the whitespace control character, do not put any spaces between
17
- it and the delimiter:
18
-
19
- .. code-block:: jinja
20
-
21
- {{- foo -}}
22
- {#- comment -#}
23
- {%- if foo -%}{%- endif -%}
24
-
25
- * Put one (and only one) space before and after the following operators:
26
- comparison operators (``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``), math
27
- operators (``+``, ``-``, ``/``, ``*``, ``%``, ``//``, ``**``), logic
28
- operators (``not``, ``and``, ``or``), ``~``, ``is``, ``in``, and the ternary
29
- operator (``?:``):
30
-
31
- .. code-block:: jinja
32
-
33
- {{ 1 + 2 }}
34
- {{ foo ~ bar }}
35
- {{ true ? true : false }}
36
-
37
- * Put one (and only one) space after the ``:`` sign in hashes and ``,`` in
38
- arrays and hashes:
39
-
40
- .. code-block:: jinja
41
-
42
- {{ [1, 2, 3] }}
43
- {{ {'foo': 'bar'} }}
44
-
45
- * Do not put any spaces after an opening parenthesis and before a closing
46
- parenthesis in expressions:
47
-
48
- .. code-block:: jinja
49
-
50
- {{ 1 + (2 * 3) }}
51
-
52
- * Do not put any spaces before and after string delimiters:
53
-
54
- .. code-block:: jinja
55
-
56
- {{ 'foo' }}
57
- {{ "foo" }}
58
-
59
- * Do not put any spaces before and after the following operators: ``|``,
60
- ``.``, ``..``, ``[]``:
61
-
62
- .. code-block:: jinja
63
-
64
- {{ foo|upper|lower }}
65
- {{ user.name }}
66
- {{ user[name] }}
67
- {% for i in 1..12 %}{% endfor %}
68
-
69
- * Do not put any spaces before and after the parenthesis used for filter and
70
- function calls:
71
-
72
- .. code-block:: jinja
73
-
74
- {{ foo|default('foo') }}
75
- {{ range(1..10) }}
76
-
77
- * Do not put any spaces before and after the opening and the closing of arrays
78
- and hashes:
79
-
80
- .. code-block:: jinja
81
-
82
- {{ [1, 2, 3] }}
83
- {{ {'foo': 'bar'} }}
84
-
85
- * Use lower cased and underscored variable names:
86
-
87
- .. code-block:: jinja
88
-
89
- {% set foo = 'foo' %}
90
- {% set foo_bar = 'foo' %}
91
-
92
- * Indent your code inside tags (use the same indentation as the one used for
93
- the target language of the rendered template):
94
-
95
- .. code-block:: jinja
96
-
97
- {% block foo %}
98
- {% if true %}
99
- true
100
- {% endif %}
101
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/deprecated.rst DELETED
@@ -1,224 +0,0 @@
1
- Deprecated Features
2
- ===================
3
-
4
- This document lists all deprecated features in Twig. Deprecated features are
5
- kept for backward compatibility and removed in the next major release (a
6
- feature that was deprecated in Twig 1.x is removed in Twig 2.0).
7
-
8
- Deprecation Notices
9
- -------------------
10
-
11
- As of Twig 1.21, Twig generates deprecation notices when a template uses
12
- deprecated features. See :ref:`deprecation-notices` for more information.
13
-
14
- Macros
15
- ------
16
-
17
- As of Twig 2.0, macros imported in a file are not available in child templates
18
- anymore (via an ``include`` call for instance). You need to import macros
19
- explicitly in each file where you are using them.
20
-
21
- Token Parsers
22
- -------------
23
-
24
- * As of Twig 1.x, the token parser broker sub-system is deprecated. The
25
- following class and interface will be removed in 2.0:
26
-
27
- * ``Twig_TokenParserBrokerInterface``
28
- * ``Twig_TokenParserBroker``
29
-
30
- * As of Twig 1.27, ``Twig_Parser::getFilename()`` is deprecated. From a token
31
- parser, use ``$this->parser->getStream()->getSourceContext()->getPath()`` instead.
32
-
33
- * As of Twig 1.27, ``Twig_Parser::getEnvironment()`` is deprecated.
34
-
35
- Extensions
36
- ----------
37
-
38
- * As of Twig 1.x, the ability to remove an extension is deprecated and the
39
- ``Twig_Environment::removeExtension()`` method will be removed in 2.0.
40
-
41
- * As of Twig 1.23, the ``Twig_ExtensionInterface::initRuntime()`` method is
42
- deprecated. You have two options to avoid the deprecation notice: if you
43
- implement this method to store the environment for your custom filters,
44
- functions, or tests, use the ``needs_environment`` option instead; if you
45
- have more complex needs, explicitly implement
46
- ``Twig_Extension_InitRuntimeInterface`` (not recommended).
47
-
48
- * As of Twig 1.23, the ``Twig_ExtensionInterface::getGlobals()`` method is
49
- deprecated. Implement ``Twig_Extension_GlobalsInterface`` to avoid
50
- deprecation notices.
51
-
52
- * As of Twig 1.26, the ``Twig_ExtensionInterface::getName()`` method is
53
- deprecated and it is not used internally anymore.
54
-
55
- PEAR
56
- ----
57
-
58
- PEAR support has been discontinued in Twig 1.15.1, and no PEAR packages are
59
- provided anymore. Use Composer instead.
60
-
61
- Filters
62
- -------
63
-
64
- * As of Twig 1.x, use ``Twig_SimpleFilter`` to add a filter. The following
65
- classes and interfaces will be removed in 2.0:
66
-
67
- * ``Twig_FilterInterface``
68
- * ``Twig_FilterCallableInterface``
69
- * ``Twig_Filter``
70
- * ``Twig_Filter_Function``
71
- * ``Twig_Filter_Method``
72
- * ``Twig_Filter_Node``
73
-
74
- * As of Twig 2.x, the ``Twig_SimpleFilter`` class is deprecated and will be
75
- removed in Twig 3.x (use ``Twig_Filter`` instead). In Twig 2.x,
76
- ``Twig_SimpleFilter`` is just an alias for ``Twig_Filter``.
77
-
78
- Functions
79
- ---------
80
-
81
- * As of Twig 1.x, use ``Twig_SimpleFunction`` to add a function. The following
82
- classes and interfaces will be removed in 2.0:
83
-
84
- * ``Twig_FunctionInterface``
85
- * ``Twig_FunctionCallableInterface``
86
- * ``Twig_Function``
87
- * ``Twig_Function_Function``
88
- * ``Twig_Function_Method``
89
- * ``Twig_Function_Node``
90
-
91
- * As of Twig 2.x, the ``Twig_SimpleFunction`` class is deprecated and will be
92
- removed in Twig 3.x (use ``Twig_Function`` instead). In Twig 2.x,
93
- ``Twig_SimpleFunction`` is just an alias for ``Twig_Function``.
94
-
95
- Tests
96
- -----
97
-
98
- * As of Twig 1.x, use ``Twig_SimpleTest`` to add a test. The following classes
99
- and interfaces will be removed in 2.0:
100
-
101
- * ``Twig_TestInterface``
102
- * ``Twig_TestCallableInterface``
103
- * ``Twig_Test``
104
- * ``Twig_Test_Function``
105
- * ``Twig_Test_Method``
106
- * ``Twig_Test_Node``
107
-
108
- * As of Twig 2.x, the ``Twig_SimpleTest`` class is deprecated and will be
109
- removed in Twig 3.x (use ``Twig_Test`` instead). In Twig 2.x,
110
- ``Twig_SimpleTest`` is just an alias for ``Twig_Test``.
111
-
112
- * The ``sameas`` and ``divisibleby`` tests are deprecated in favor of ``same
113
- as`` and ``divisible by`` respectively.
114
-
115
- Tags
116
- ----
117
-
118
- * As of Twig 1.x, the ``raw`` tag is deprecated. You should use ``verbatim``
119
- instead.
120
-
121
- Nodes
122
- -----
123
-
124
- * As of Twig 1.x, ``Node::toXml()`` is deprecated and will be removed in Twig
125
- 2.0.
126
-
127
- * As of Twig 1.26, ``Node::$nodes`` should only contains ``Twig_Node``
128
- instances, storing a ``null`` value is deprecated and won't be possible in
129
- Twig 2.x.
130
-
131
- * As of Twig 1.27, the ``filename`` attribute on ``Twig_Node_Module`` is
132
- deprecated. Use ``getName()`` instead.
133
-
134
- * As of Twig 1.27, the ``Twig_Node::getFilename()/Twig_Node::getLine()``
135
- methods are deprecated, use
136
- ``Twig_Node::getTemplateName()/Twig_Node::getTemplateLine()`` instead.
137
-
138
- Interfaces
139
- ----------
140
-
141
- * As of Twig 2.x, the following interfaces are deprecated and empty (they will
142
- be removed in Twig 3.0):
143
-
144
- * ``Twig_CompilerInterface`` (use ``Twig_Compiler`` instead)
145
- * ``Twig_LexerInterface`` (use ``Twig_Lexer`` instead)
146
- * ``Twig_NodeInterface`` (use ``Twig_Node`` instead)
147
- * ``Twig_ParserInterface`` (use ``Twig_Parser`` instead)
148
- * ``Twig_ExistsLoaderInterface`` (merged with ``Twig_LoaderInterface``)
149
- * ``Twig_SourceContextLoaderInterface`` (merged with ``Twig_LoaderInterface``)
150
- * ``Twig_TemplateInterface`` (use ``Twig_Template`` instead, and use
151
- those constants Twig_Template::ANY_CALL, Twig_Template::ARRAY_CALL,
152
- Twig_Template::METHOD_CALL)
153
-
154
- Compiler
155
- --------
156
-
157
- * As of Twig 1.26, the ``Twig_Compiler::getFilename()`` has been deprecated.
158
- You should not use it anyway as its values is not reliable.
159
-
160
- * As of Twig 1.27, the ``Twig_Compiler::addIndentation()`` has been deprecated.
161
- Use ``Twig_Compiler::write('')`` instead.
162
-
163
- Loaders
164
- -------
165
-
166
- * As of Twig 1.x, ``Twig_Loader_String`` is deprecated and will be removed in
167
- 2.0. You can render a string via ``Twig_Environment::createTemplate()``.
168
-
169
- * As of Twig 1.27, ``Twig_LoaderInterface::getSource()`` is deprecated.
170
- Implement ``Twig_SourceContextLoaderInterface`` instead and use
171
- ``getSourceContext()``.
172
-
173
- Node Visitors
174
- -------------
175
-
176
- * Because of the removal of ``Twig_NodeInterface`` in 2.0, you need to extend
177
- ``Twig_BaseNodeVisitor`` instead of implementing ``Twig_NodeVisitorInterface``
178
- directly to make your node visitors compatible with both Twig 1.x and 2.x.
179
-
180
- Globals
181
- -------
182
-
183
- * As of Twig 2.x, the ability to register a global variable after the runtime
184
- or the extensions have been initialized is not possible anymore (but
185
- changing the value of an already registered global is possible).
186
-
187
- * As of Twig 1.x, using the ``_self`` global variable to get access to the
188
- current ``Twig_Template`` instance is deprecated; most usages only need the
189
- current template name, which will continue to work in Twig 2.0. In Twig 2.0,
190
- ``_self`` returns the current template name instead of the current
191
- ``Twig_Template`` instance. If you are using ``{{ _self.templateName }}``,
192
- just replace it with ``{{ _self }}``.
193
-
194
- Miscellaneous
195
- -------------
196
-
197
- * As of Twig 1.x, ``Twig_Environment::clearTemplateCache()``,
198
- ``Twig_Environment::writeCacheFile()``,
199
- ``Twig_Environment::clearCacheFiles()``,
200
- ``Twig_Environment::getCacheFilename()``,
201
- ``Twig_Environment::getTemplateClassPrefix()``,
202
- ``Twig_Environment::getLexer()``, ``Twig_Environment::getParser()``, and
203
- ``Twig_Environment::getCompiler()`` are deprecated and will be removed in 2.0.
204
-
205
- * As of Twig 1.x, ``Twig_Template::getEnvironment()`` and
206
- ``Twig_TemplateInterface::getEnvironment()`` are deprecated and will be
207
- removed in 2.0.
208
-
209
- * As of Twig 1.21, setting the environment option ``autoescape`` to ``true`` is
210
- deprecated and will be removed in 2.0. Use ``"html"`` instead.
211
-
212
- * As of Twig 1.27, ``Twig_Error::getTemplateFile()`` and
213
- ``Twig_Error::setTemplateFile()`` are deprecated. Use
214
- ``Twig_Error::getTemplateName()`` and ``Twig_Error::setTemplateName()``
215
- instead.
216
-
217
- * As of Twig 1.27, ``Twig_Template::getSource()`` is deprecated. Use
218
- ``Twig_Template::getSourceContext()`` instead.
219
-
220
- * As of Twig 1.27, ``Twig_Parser::addHandler()`` and
221
- ``Twig_Parser::addNodeVisitor()`` are deprecated and will be removed in 2.0.
222
-
223
- * As of Twig 1.29, some classes are marked as being final via the `@final`
224
- annotation. Those classes will be marked as final in 2.0.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/abs.rst DELETED
@@ -1,18 +0,0 @@
1
- ``abs``
2
- =======
3
-
4
- The ``abs`` filter returns the absolute value.
5
-
6
- .. code-block:: jinja
7
-
8
- {# number = -5 #}
9
-
10
- {{ number|abs }}
11
-
12
- {# outputs 5 #}
13
-
14
- .. note::
15
-
16
- Internally, Twig uses the PHP `abs`_ function.
17
-
18
- .. _`abs`: http://php.net/abs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/batch.rst DELETED
@@ -1,51 +0,0 @@
1
- ``batch``
2
- =========
3
-
4
- .. versionadded:: 1.12.3
5
- The ``batch`` filter was added in Twig 1.12.3.
6
-
7
- The ``batch`` filter "batches" items by returning a list of lists with the
8
- given number of items. A second parameter can be provided and used to fill in
9
- missing items:
10
-
11
- .. code-block:: jinja
12
-
13
- {% set items = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] %}
14
-
15
- <table>
16
- {% for row in items|batch(3, 'No item') %}
17
- <tr>
18
- {% for column in row %}
19
- <td>{{ column }}</td>
20
- {% endfor %}
21
- </tr>
22
- {% endfor %}
23
- </table>
24
-
25
- The above example will be rendered as:
26
-
27
- .. code-block:: jinja
28
-
29
- <table>
30
- <tr>
31
- <td>a</td>
32
- <td>b</td>
33
- <td>c</td>
34
- </tr>
35
- <tr>
36
- <td>d</td>
37
- <td>e</td>
38
- <td>f</td>
39
- </tr>
40
- <tr>
41
- <td>g</td>
42
- <td>No item</td>
43
- <td>No item</td>
44
- </tr>
45
- </table>
46
-
47
- Arguments
48
- ---------
49
-
50
- * ``size``: The size of the batch; fractional numbers will be rounded up
51
- * ``fill``: Used to fill in missing items
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/capitalize.rst DELETED
@@ -1,11 +0,0 @@
1
- ``capitalize``
2
- ==============
3
-
4
- The ``capitalize`` filter capitalizes a value. The first character will be
5
- uppercase, all others lowercase:
6
-
7
- .. code-block:: jinja
8
-
9
- {{ 'my first car'|capitalize }}
10
-
11
- {# outputs 'My first car' #}
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/convert_encoding.rst DELETED
@@ -1,28 +0,0 @@
1
- ``convert_encoding``
2
- ====================
3
-
4
- .. versionadded:: 1.4
5
- The ``convert_encoding`` filter was added in Twig 1.4.
6
-
7
- The ``convert_encoding`` filter converts a string from one encoding to
8
- another. The first argument is the expected output charset and the second one
9
- is the input charset:
10
-
11
- .. code-block:: jinja
12
-
13
- {{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}
14
-
15
- .. note::
16
-
17
- This filter relies on the `iconv`_ or `mbstring`_ extension, so one of
18
- them must be installed. In case both are installed, `mbstring`_ is used by
19
- default (Twig before 1.8.1 uses `iconv`_ by default).
20
-
21
- Arguments
22
- ---------
23
-
24
- * ``to``: The output charset
25
- * ``from``: The input charset
26
-
27
- .. _`iconv`: http://php.net/iconv
28
- .. _`mbstring`: http://php.net/mbstring
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/date.rst DELETED
@@ -1,100 +0,0 @@
1
- ``date``
2
- ========
3
-
4
- .. versionadded:: 1.1
5
- The timezone support has been added in Twig 1.1.
6
-
7
- .. versionadded:: 1.5
8
- The default date format support has been added in Twig 1.5.
9
-
10
- .. versionadded:: 1.6.1
11
- The default timezone support has been added in Twig 1.6.1.
12
-
13
- .. versionadded:: 1.11.0
14
- The introduction of the false value for the timezone was introduced in Twig 1.11.0
15
-
16
- The ``date`` filter formats a date to a given format:
17
-
18
- .. code-block:: jinja
19
-
20
- {{ post.published_at|date("m/d/Y") }}
21
-
22
- The format specifier is the same as supported by `date`_,
23
- except when the filtered data is of type `DateInterval`_, when the format must conform to
24
- `DateInterval::format`_ instead.
25
-
26
- The ``date`` filter accepts strings (it must be in a format supported by the
27
- `strtotime`_ function), `DateTime`_ instances, or `DateInterval`_ instances. For
28
- instance, to display the current date, filter the word "now":
29
-
30
- .. code-block:: jinja
31
-
32
- {{ "now"|date("m/d/Y") }}
33
-
34
- To escape words and characters in the date format use ``\\`` in front of each
35
- character:
36
-
37
- .. code-block:: jinja
38
-
39
- {{ post.published_at|date("F jS \\a\\t g:ia") }}
40
-
41
- If the value passed to the ``date`` filter is ``null``, it will return the
42
- current date by default. If an empty string is desired instead of the current
43
- date, use a ternary operator:
44
-
45
- .. code-block:: jinja
46
-
47
- {{ post.published_at is empty ? "" : post.published_at|date("m/d/Y") }}
48
-
49
- If no format is provided, Twig will use the default one: ``F j, Y H:i``. This
50
- default can be easily changed by calling the ``setDateFormat()`` method on the
51
- ``core`` extension instance. The first argument is the default format for
52
- dates and the second one is the default format for date intervals:
53
-
54
- .. code-block:: php
55
-
56
- $twig = new Twig_Environment($loader);
57
- $twig->getExtension('Twig_Extension_Core')->setDateFormat('d/m/Y', '%d days');
58
-
59
- // before Twig 1.26
60
- $twig->getExtension('core')->setDateFormat('d/m/Y', '%d days');
61
-
62
- Timezone
63
- --------
64
-
65
- By default, the date is displayed by applying the default timezone (the one
66
- specified in php.ini or declared in Twig -- see below), but you can override
67
- it by explicitly specifying a timezone:
68
-
69
- .. code-block:: jinja
70
-
71
- {{ post.published_at|date("m/d/Y", "Europe/Paris") }}
72
-
73
- If the date is already a DateTime object, and if you want to keep its current
74
- timezone, pass ``false`` as the timezone value:
75
-
76
- .. code-block:: jinja
77
-
78
- {{ post.published_at|date("m/d/Y", false) }}
79
-
80
- The default timezone can also be set globally by calling ``setTimezone()``:
81
-
82
- .. code-block:: php
83
-
84
- $twig = new Twig_Environment($loader);
85
- $twig->getExtension('Twig_Extension_Core')->setTimezone('Europe/Paris');
86
-
87
- // before Twig 1.26
88
- $twig->getExtension('core')->setTimezone('Europe/Paris');
89
-
90
- Arguments
91
- ---------
92
-
93
- * ``format``: The date format
94
- * ``timezone``: The date timezone
95
-
96
- .. _`strtotime`: http://www.php.net/strtotime
97
- .. _`DateTime`: http://www.php.net/DateTime
98
- .. _`DateInterval`: http://www.php.net/DateInterval
99
- .. _`date`: http://www.php.net/date
100
- .. _`DateInterval::format`: http://www.php.net/DateInterval.format
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/date_modify.rst DELETED
@@ -1,23 +0,0 @@
1
- ``date_modify``
2
- ===============
3
-
4
- .. versionadded:: 1.9.0
5
- The date_modify filter has been added in Twig 1.9.0.
6
-
7
- The ``date_modify`` filter modifies a date with a given modifier string:
8
-
9
- .. code-block:: jinja
10
-
11
- {{ post.published_at|date_modify("+1 day")|date("m/d/Y") }}
12
-
13
- The ``date_modify`` filter accepts strings (it must be in a format supported
14
- by the `strtotime`_ function) or `DateTime`_ instances. You can easily combine
15
- it with the :doc:`date<date>` filter for formatting.
16
-
17
- Arguments
18
- ---------
19
-
20
- * ``modifier``: The modifier
21
-
22
- .. _`strtotime`: http://www.php.net/strtotime
23
- .. _`DateTime`: http://www.php.net/DateTime
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/default.rst DELETED
@@ -1,33 +0,0 @@
1
- ``default``
2
- ===========
3
-
4
- The ``default`` filter returns the passed default value if the value is
5
- undefined or empty, otherwise the value of the variable:
6
-
7
- .. code-block:: jinja
8
-
9
- {{ var|default('var is not defined') }}
10
-
11
- {{ var.foo|default('foo item on var is not defined') }}
12
-
13
- {{ var['foo']|default('foo item on var is not defined') }}
14
-
15
- {{ ''|default('passed var is empty') }}
16
-
17
- When using the ``default`` filter on an expression that uses variables in some
18
- method calls, be sure to use the ``default`` filter whenever a variable can be
19
- undefined:
20
-
21
- .. code-block:: jinja
22
-
23
- {{ var.method(foo|default('foo'))|default('foo') }}
24
-
25
- .. note::
26
-
27
- Read the documentation for the :doc:`defined<../tests/defined>` and
28
- :doc:`empty<../tests/empty>` tests to learn more about their semantics.
29
-
30
- Arguments
31
- ---------
32
-
33
- * ``default``: The default value
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/escape.rst DELETED
@@ -1,119 +0,0 @@
1
- ``escape``
2
- ==========
3
-
4
- .. versionadded:: 1.9.0
5
- The ``css``, ``url``, and ``html_attr`` strategies were added in Twig
6
- 1.9.0.
7
-
8
- .. versionadded:: 1.14.0
9
- The ability to define custom escapers was added in Twig 1.14.0.
10
-
11
- The ``escape`` filter escapes a string for safe insertion into the final
12
- output. It supports different escaping strategies depending on the template
13
- context.
14
-
15
- By default, it uses the HTML escaping strategy:
16
-
17
- .. code-block:: jinja
18
-
19
- {{ user.username|escape }}
20
-
21
- For convenience, the ``e`` filter is defined as an alias:
22
-
23
- .. code-block:: jinja
24
-
25
- {{ user.username|e }}
26
-
27
- The ``escape`` filter can also be used in other contexts than HTML thanks to
28
- an optional argument which defines the escaping strategy to use:
29
-
30
- .. code-block:: jinja
31
-
32
- {{ user.username|e }}
33
- {# is equivalent to #}
34
- {{ user.username|e('html') }}
35
-
36
- And here is how to escape variables included in JavaScript code:
37
-
38
- .. code-block:: jinja
39
-
40
- {{ user.username|escape('js') }}
41
- {{ user.username|e('js') }}
42
-
43
- The ``escape`` filter supports the following escaping strategies:
44
-
45
- * ``html``: escapes a string for the **HTML body** context.
46
-
47
- * ``js``: escapes a string for the **JavaScript context**.
48
-
49
- * ``css``: escapes a string for the **CSS context**. CSS escaping can be
50
- applied to any string being inserted into CSS and escapes everything except
51
- alphanumerics.
52
-
53
- * ``url``: escapes a string for the **URI or parameter contexts**. This should
54
- not be used to escape an entire URI; only a subcomponent being inserted.
55
-
56
- * ``html_attr``: escapes a string for the **HTML attribute** context.
57
-
58
- .. note::
59
-
60
- Internally, ``escape`` uses the PHP native `htmlspecialchars`_ function
61
- for the HTML escaping strategy.
62
-
63
- .. caution::
64
-
65
- When using automatic escaping, Twig tries to not double-escape a variable
66
- when the automatic escaping strategy is the same as the one applied by the
67
- escape filter; but that does not work when using a variable as the
68
- escaping strategy:
69
-
70
- .. code-block:: jinja
71
-
72
- {% set strategy = 'html' %}
73
-
74
- {% autoescape 'html' %}
75
- {{ var|escape('html') }} {# won't be double-escaped #}
76
- {{ var|escape(strategy) }} {# will be double-escaped #}
77
- {% endautoescape %}
78
-
79
- When using a variable as the escaping strategy, you should disable
80
- automatic escaping:
81
-
82
- .. code-block:: jinja
83
-
84
- {% set strategy = 'html' %}
85
-
86
- {% autoescape 'html' %}
87
- {{ var|escape(strategy)|raw }} {# won't be double-escaped #}
88
- {% endautoescape %}
89
-
90
- Custom Escapers
91
- ---------------
92
-
93
- You can define custom escapers by calling the ``setEscaper()`` method on the
94
- ``core`` extension instance. The first argument is the escaper name (to be
95
- used in the ``escape`` call) and the second one must be a valid PHP callable:
96
-
97
- .. code-block:: php
98
-
99
- $twig = new Twig_Environment($loader);
100
- $twig->getExtension('Twig_Extension_Core')->setEscaper('csv', 'csv_escaper');
101
-
102
- // before Twig 1.26
103
- $twig->getExtension('core')->setEscaper('csv', 'csv_escaper');
104
-
105
- When called by Twig, the callable receives the Twig environment instance, the
106
- string to escape, and the charset.
107
-
108
- .. note::
109
-
110
- Built-in escapers cannot be overridden mainly they should be considered as
111
- the final implementation and also for better performance.
112
-
113
- Arguments
114
- ---------
115
-
116
- * ``strategy``: The escaping strategy
117
- * ``charset``: The string charset
118
-
119
- .. _`htmlspecialchars`: http://php.net/htmlspecialchars
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/first.rst DELETED
@@ -1,25 +0,0 @@
1
- ``first``
2
- =========
3
-
4
- .. versionadded:: 1.12.2
5
- The ``first`` filter was added in Twig 1.12.2.
6
-
7
- The ``first`` filter returns the first "element" of a sequence, a mapping, or
8
- a string:
9
-
10
- .. code-block:: jinja
11
-
12
- {{ [1, 2, 3, 4]|first }}
13
- {# outputs 1 #}
14
-
15
- {{ { a: 1, b: 2, c: 3, d: 4 }|first }}
16
- {# outputs 1 #}
17
-
18
- {{ '1234'|first }}
19
- {# outputs 1 #}
20
-
21
- .. note::
22
-
23
- It also works with objects implementing the `Traversable`_ interface.
24
-
25
- .. _`Traversable`: http://php.net/manual/en/class.traversable.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/format.rst DELETED
@@ -1,16 +0,0 @@
1
- ``format``
2
- ==========
3
-
4
- The ``format`` filter formats a given string by replacing the placeholders
5
- (placeholders follows the `sprintf`_ notation):
6
-
7
- .. code-block:: jinja
8
-
9
- {{ "I like %s and %s."|format(foo, "bar") }}
10
-
11
- {# outputs I like foo and bar
12
- if the foo parameter equals to the foo string. #}
13
-
14
- .. _`sprintf`: http://www.php.net/sprintf
15
-
16
- .. seealso:: :doc:`replace<replace>`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/index.rst DELETED
@@ -1,37 +0,0 @@
1
- Filters
2
- =======
3
-
4
- .. toctree::
5
- :maxdepth: 1
6
-
7
- abs
8
- batch
9
- capitalize
10
- convert_encoding
11
- date
12
- date_modify
13
- default
14
- escape
15
- first
16
- format
17
- join
18
- json_encode
19
- keys
20
- last
21
- length
22
- lower
23
- merge
24
- nl2br
25
- number_format
26
- raw
27
- replace
28
- reverse
29
- round
30
- slice
31
- sort
32
- split
33
- striptags
34
- title
35
- trim
36
- upper
37
- url_encode
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/join.rst DELETED
@@ -1,23 +0,0 @@
1
- ``join``
2
- ========
3
-
4
- The ``join`` filter returns a string which is the concatenation of the items
5
- of a sequence:
6
-
7
- .. code-block:: jinja
8
-
9
- {{ [1, 2, 3]|join }}
10
- {# returns 123 #}
11
-
12
- The separator between elements is an empty string per default, but you can
13
- define it with the optional first parameter:
14
-
15
- .. code-block:: jinja
16
-
17
- {{ [1, 2, 3]|join('|') }}
18
- {# outputs 1|2|3 #}
19
-
20
- Arguments
21
- ---------
22
-
23
- * ``glue``: The separator
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/json_encode.rst DELETED
@@ -1,21 +0,0 @@
1
- ``json_encode``
2
- ===============
3
-
4
- The ``json_encode`` filter returns the JSON representation of a value:
5
-
6
- .. code-block:: jinja
7
-
8
- {{ data|json_encode() }}
9
-
10
- .. note::
11
-
12
- Internally, Twig uses the PHP `json_encode`_ function.
13
-
14
- Arguments
15
- ---------
16
-
17
- * ``options``: A bitmask of `json_encode options`_ (``{{
18
- data|json_encode(constant('JSON_PRETTY_PRINT')) }}``)
19
-
20
- .. _`json_encode`: http://php.net/json_encode
21
- .. _`json_encode options`: http://www.php.net/manual/en/json.constants.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/keys.rst DELETED
@@ -1,11 +0,0 @@
1
- ``keys``
2
- ========
3
-
4
- The ``keys`` filter returns the keys of an array. It is useful when you want to
5
- iterate over the keys of an array:
6
-
7
- .. code-block:: jinja
8
-
9
- {% for key in array|keys %}
10
- ...
11
- {% endfor %}
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/last.rst DELETED
@@ -1,25 +0,0 @@
1
- ``last``
2
- ========
3
-
4
- .. versionadded:: 1.12.2
5
- The ``last`` filter was added in Twig 1.12.2.
6
-
7
- The ``last`` filter returns the last "element" of a sequence, a mapping, or
8
- a string:
9
-
10
- .. code-block:: jinja
11
-
12
- {{ [1, 2, 3, 4]|last }}
13
- {# outputs 4 #}
14
-
15
- {{ { a: 1, b: 2, c: 3, d: 4 }|last }}
16
- {# outputs 4 #}
17
-
18
- {{ '1234'|last }}
19
- {# outputs 4 #}
20
-
21
- .. note::
22
-
23
- It also works with objects implementing the `Traversable`_ interface.
24
-
25
- .. _`Traversable`: http://php.net/manual/en/class.traversable.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/length.rst DELETED
@@ -1,21 +0,0 @@
1
- ``length``
2
- ==========
3
-
4
- .. versionadded:: 1.33
5
-
6
- Support for the ``__toString()`` magic method has been added in Twig 1.33.
7
-
8
- The ``length`` filter returns the number of items of a sequence or mapping, or
9
- the length of a string.
10
-
11
- For objects that implement the ``Countable`` interface, ``length`` will use the
12
- return value of the ``count()`` method.
13
-
14
- For objects that implement the ``__toString()`` magic method (and not ``Countable``),
15
- it will return the length of the string provided by that method.
16
-
17
- .. code-block:: jinja
18
-
19
- {% if users|length > 10 %}
20
- ...
21
- {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/lower.rst DELETED
@@ -1,10 +0,0 @@
1
- ``lower``
2
- =========
3
-
4
- The ``lower`` filter converts a value to lowercase:
5
-
6
- .. code-block:: jinja
7
-
8
- {{ 'WELCOME'|lower }}
9
-
10
- {# outputs 'welcome' #}
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/merge.rst DELETED
@@ -1,48 +0,0 @@
1
- ``merge``
2
- =========
3
-
4
- The ``merge`` filter merges an array with another array:
5
-
6
- .. code-block:: jinja
7
-
8
- {% set values = [1, 2] %}
9
-
10
- {% set values = values|merge(['apple', 'orange']) %}
11
-
12
- {# values now contains [1, 2, 'apple', 'orange'] #}
13
-
14
- New values are added at the end of the existing ones.
15
-
16
- The ``merge`` filter also works on hashes:
17
-
18
- .. code-block:: jinja
19
-
20
- {% set items = { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'unknown' } %}
21
-
22
- {% set items = items|merge({ 'peugeot': 'car', 'renault': 'car' }) %}
23
-
24
- {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'renault': 'car' } #}
25
-
26
- For hashes, the merging process occurs on the keys: if the key does not
27
- already exist, it is added but if the key already exists, its value is
28
- overridden.
29
-
30
- .. tip::
31
-
32
- If you want to ensure that some values are defined in an array (by given
33
- default values), reverse the two elements in the call:
34
-
35
- .. code-block:: jinja
36
-
37
- {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
38
-
39
- {% set items = { 'apple': 'unknown' }|merge(items) %}
40
-
41
- {# items now contains { 'apple': 'fruit', 'orange': 'fruit' } #}
42
-
43
- .. note::
44
-
45
- Internally, Twig uses the PHP `array_merge`_ function. It supports
46
- Traversable objects by transforming those to arrays.
47
-
48
- .. _`array_merge`: http://php.net/array_merge
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/nl2br.rst DELETED
@@ -1,22 +0,0 @@
1
- ``nl2br``
2
- =========
3
-
4
- .. versionadded:: 1.5
5
- The ``nl2br`` filter was added in Twig 1.5.
6
-
7
- The ``nl2br`` filter inserts HTML line breaks before all newlines in a string:
8
-
9
- .. code-block:: jinja
10
-
11
- {{ "I like Twig.\nYou will like it too."|nl2br }}
12
- {# outputs
13
-
14
- I like Twig.<br />
15
- You will like it too.
16
-
17
- #}
18
-
19
- .. note::
20
-
21
- The ``nl2br`` filter pre-escapes the input before applying the
22
- transformation.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/number_format.rst DELETED
@@ -1,48 +0,0 @@
1
- ``number_format``
2
- =================
3
-
4
- .. versionadded:: 1.5
5
- The ``number_format`` filter was added in Twig 1.5
6
-
7
- The ``number_format`` filter formats numbers. It is a wrapper around PHP's
8
- `number_format`_ function:
9
-
10
- .. code-block:: jinja
11
-
12
- {{ 200.35|number_format }}
13
-
14
- You can control the number of decimal places, decimal point, and thousands
15
- separator using the additional arguments:
16
-
17
- .. code-block:: jinja
18
-
19
- {{ 9800.333|number_format(2, '.', ',') }}
20
-
21
- If no formatting options are provided then Twig will use the default formatting
22
- options of:
23
-
24
- * 0 decimal places.
25
- * ``.`` as the decimal point.
26
- * ``,`` as the thousands separator.
27
-
28
- These defaults can be easily changed through the core extension:
29
-
30
- .. code-block:: php
31
-
32
- $twig = new Twig_Environment($loader);
33
- $twig->getExtension('Twig_Extension_Core')->setNumberFormat(3, '.', ',');
34
-
35
- // before Twig 1.26
36
- $twig->getExtension('core')->setNumberFormat(3, '.', ',');
37
-
38
- The defaults set for ``number_format`` can be over-ridden upon each call using the
39
- additional parameters.
40
-
41
- Arguments
42
- ---------
43
-
44
- * ``decimal``: The number of decimal points to display
45
- * ``decimal_point``: The character(s) to use for the decimal point
46
- * ``thousand_sep``: The character(s) to use for the thousands separator
47
-
48
- .. _`number_format`: http://php.net/number_format
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/raw.rst DELETED
@@ -1,36 +0,0 @@
1
- ``raw``
2
- =======
3
-
4
- The ``raw`` filter marks the value as being "safe", which means that in an
5
- environment with automatic escaping enabled this variable will not be escaped
6
- if ``raw`` is the last filter applied to it:
7
-
8
- .. code-block:: jinja
9
-
10
- {% autoescape %}
11
- {{ var|raw }} {# var won't be escaped #}
12
- {% endautoescape %}
13
-
14
- .. note::
15
-
16
- Be careful when using the ``raw`` filter inside expressions:
17
-
18
- .. code-block:: jinja
19
-
20
- {% autoescape %}
21
- {% set hello = '<strong>Hello</strong>' %}
22
- {% set hola = '<strong>Hola</strong>' %}
23
-
24
- {{ false ? '<strong>Hola</strong>' : hello|raw }}
25
- does not render the same as
26
- {{ false ? hola : hello|raw }}
27
- but renders the same as
28
- {{ (false ? hola : hello)|raw }}
29
- {% endautoescape %}
30
-
31
- The first ternary statement is not escaped: ``hello`` is marked as being
32
- safe and Twig does not escape static values (see
33
- :doc:`escape<../tags/autoescape>`). In the second ternary statement, even
34
- if ``hello`` is marked as safe, ``hola`` remains unsafe and so is the whole
35
- expression. The third ternary statement is marked as safe and the result is
36
- not escaped.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/replace.rst DELETED
@@ -1,19 +0,0 @@
1
- ``replace``
2
- ===========
3
-
4
- The ``replace`` filter formats a given string by replacing the placeholders
5
- (placeholders are free-form):
6
-
7
- .. code-block:: jinja
8
-
9
- {{ "I like %this% and %that%."|replace({'%this%': foo, '%that%': "bar"}) }}
10
-
11
- {# outputs I like foo and bar
12
- if the foo parameter equals to the foo string. #}
13
-
14
- Arguments
15
- ---------
16
-
17
- * ``from``: The placeholder values
18
-
19
- .. seealso:: :doc:`format<format>`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/reverse.rst DELETED
@@ -1,47 +0,0 @@
1
- ``reverse``
2
- ===========
3
-
4
- .. versionadded:: 1.6
5
- Support for strings has been added in Twig 1.6.
6
-
7
- The ``reverse`` filter reverses a sequence, a mapping, or a string:
8
-
9
- .. code-block:: jinja
10
-
11
- {% for user in users|reverse %}
12
- ...
13
- {% endfor %}
14
-
15
- {{ '1234'|reverse }}
16
-
17
- {# outputs 4321 #}
18
-
19
- .. tip::
20
-
21
- For sequences and mappings, numeric keys are not preserved. To reverse
22
- them as well, pass ``true`` as an argument to the ``reverse`` filter:
23
-
24
- .. code-block:: jinja
25
-
26
- {% for key, value in {1: "a", 2: "b", 3: "c"}|reverse %}
27
- {{ key }}: {{ value }}
28
- {%- endfor %}
29
-
30
- {# output: 0: c 1: b 2: a #}
31
-
32
- {% for key, value in {1: "a", 2: "b", 3: "c"}|reverse(true) %}
33
- {{ key }}: {{ value }}
34
- {%- endfor %}
35
-
36
- {# output: 3: c 2: b 1: a #}
37
-
38
- .. note::
39
-
40
- It also works with objects implementing the `Traversable`_ interface.
41
-
42
- Arguments
43
- ---------
44
-
45
- * ``preserve_keys``: Preserve keys when reversing a mapping or a sequence.
46
-
47
- .. _`Traversable`: http://php.net/Traversable
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/round.rst DELETED
@@ -1,37 +0,0 @@
1
- ``round``
2
- =========
3
-
4
- .. versionadded:: 1.15.0
5
- The ``round`` filter was added in Twig 1.15.0.
6
-
7
- The ``round`` filter rounds a number to a given precision:
8
-
9
- .. code-block:: jinja
10
-
11
- {{ 42.55|round }}
12
- {# outputs 43 #}
13
-
14
- {{ 42.55|round(1, 'floor') }}
15
- {# outputs 42.5 #}
16
-
17
- The ``round`` filter takes two optional arguments; the first one specifies the
18
- precision (default is ``0``) and the second the rounding method (default is
19
- ``common``):
20
-
21
- * ``common`` rounds either up or down (rounds the value up to precision decimal
22
- places away from zero, when it is half way there -- making 1.5 into 2 and
23
- -1.5 into -2);
24
-
25
- * ``ceil`` always rounds up;
26
-
27
- * ``floor`` always rounds down.
28
-
29
- .. note::
30
-
31
- The ``//`` operator is equivalent to ``|round(0, 'floor')``.
32
-
33
- Arguments
34
- ---------
35
-
36
- * ``precision``: The rounding precision
37
- * ``method``: The rounding method
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/slice.rst DELETED
@@ -1,71 +0,0 @@
1
- ``slice``
2
- ===========
3
-
4
- .. versionadded:: 1.6
5
- The ``slice`` filter was added in Twig 1.6.
6
-
7
- The ``slice`` filter extracts a slice of a sequence, a mapping, or a string:
8
-
9
- .. code-block:: jinja
10
-
11
- {% for i in [1, 2, 3, 4, 5]|slice(1, 2) %}
12
- {# will iterate over 2 and 3 #}
13
- {% endfor %}
14
-
15
- {{ '12345'|slice(1, 2) }}
16
-
17
- {# outputs 23 #}
18
-
19
- You can use any valid expression for both the start and the length:
20
-
21
- .. code-block:: jinja
22
-
23
- {% for i in [1, 2, 3, 4, 5]|slice(start, length) %}
24
- {# ... #}
25
- {% endfor %}
26
-
27
- As syntactic sugar, you can also use the ``[]`` notation:
28
-
29
- .. code-block:: jinja
30
-
31
- {% for i in [1, 2, 3, 4, 5][start:length] %}
32
- {# ... #}
33
- {% endfor %}
34
-
35
- {{ '12345'[1:2] }} {# will display "23" #}
36
-
37
- {# you can omit the first argument -- which is the same as 0 #}
38
- {{ '12345'[:2] }} {# will display "12" #}
39
-
40
- {# you can omit the last argument -- which will select everything till the end #}
41
- {{ '12345'[2:] }} {# will display "345" #}
42
-
43
- The ``slice`` filter works as the `array_slice`_ PHP function for arrays and
44
- `mb_substr`_ for strings with a fallback to `substr`_.
45
-
46
- If the start is non-negative, the sequence will start at that start in the
47
- variable. If start is negative, the sequence will start that far from the end
48
- of the variable.
49
-
50
- If length is given and is positive, then the sequence will have up to that
51
- many elements in it. If the variable is shorter than the length, then only the
52
- available variable elements will be present. If length is given and is
53
- negative then the sequence will stop that many elements from the end of the
54
- variable. If it is omitted, then the sequence will have everything from offset
55
- up until the end of the variable.
56
-
57
- .. note::
58
-
59
- It also works with objects implementing the `Traversable`_ interface.
60
-
61
- Arguments
62
- ---------
63
-
64
- * ``start``: The start of the slice
65
- * ``length``: The size of the slice
66
- * ``preserve_keys``: Whether to preserve key or not (when the input is an array)
67
-
68
- .. _`Traversable`: http://php.net/manual/en/class.traversable.php
69
- .. _`array_slice`: http://php.net/array_slice
70
- .. _`mb_substr` : http://php.net/mb-substr
71
- .. _`substr`: http://php.net/substr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/sort.rst DELETED
@@ -1,18 +0,0 @@
1
- ``sort``
2
- ========
3
-
4
- The ``sort`` filter sorts an array:
5
-
6
- .. code-block:: jinja
7
-
8
- {% for user in users|sort %}
9
- ...
10
- {% endfor %}
11
-
12
- .. note::
13
-
14
- Internally, Twig uses the PHP `asort`_ function to maintain index
15
- association. It supports Traversable objects by transforming
16
- those to arrays.
17
-
18
- .. _`asort`: http://php.net/asort
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/split.rst DELETED
@@ -1,53 +0,0 @@
1
- ``split``
2
- =========
3
-
4
- .. versionadded:: 1.10.3
5
- The ``split`` filter was added in Twig 1.10.3.
6
-
7
- The ``split`` filter splits a string by the given delimiter and returns a list
8
- of strings:
9
-
10
- .. code-block:: jinja
11
-
12
- {% set foo = "one,two,three"|split(',') %}
13
- {# foo contains ['one', 'two', 'three'] #}
14
-
15
- You can also pass a ``limit`` argument:
16
-
17
- * If ``limit`` is positive, the returned array will contain a maximum of
18
- limit elements with the last element containing the rest of string;
19
-
20
- * If ``limit`` is negative, all components except the last -limit are
21
- returned;
22
-
23
- * If ``limit`` is zero, then this is treated as 1.
24
-
25
- .. code-block:: jinja
26
-
27
- {% set foo = "one,two,three,four,five"|split(',', 3) %}
28
- {# foo contains ['one', 'two', 'three,four,five'] #}
29
-
30
- If the ``delimiter`` is an empty string, then value will be split by equal
31
- chunks. Length is set by the ``limit`` argument (one character by default).
32
-
33
- .. code-block:: jinja
34
-
35
- {% set foo = "123"|split('') %}
36
- {# foo contains ['1', '2', '3'] #}
37
-
38
- {% set bar = "aabbcc"|split('', 2) %}
39
- {# bar contains ['aa', 'bb', 'cc'] #}
40
-
41
- .. note::
42
-
43
- Internally, Twig uses the PHP `explode`_ or `str_split`_ (if delimiter is
44
- empty) functions for string splitting.
45
-
46
- Arguments
47
- ---------
48
-
49
- * ``delimiter``: The delimiter
50
- * ``limit``: The limit argument
51
-
52
- .. _`explode`: http://php.net/explode
53
- .. _`str_split`: http://php.net/str_split
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/striptags.rst DELETED
@@ -1,29 +0,0 @@
1
- ``striptags``
2
- =============
3
-
4
- The ``striptags`` filter strips SGML/XML tags and replace adjacent whitespace
5
- by one space:
6
-
7
- .. code-block:: jinja
8
-
9
- {{ some_html|striptags }}
10
-
11
- You can also provide tags which should not be stripped:
12
-
13
- .. code-block:: jinja
14
-
15
- {{ some_html|striptags('<br><p>') }}
16
-
17
- In this example, the ``<br/>``, ``<br>``, ``<p>``, and ``</p>`` tags won't be
18
- removed from the string.
19
-
20
- .. note::
21
-
22
- Internally, Twig uses the PHP `strip_tags`_ function.
23
-
24
- Arguments
25
- ---------
26
-
27
- * ``allowable_tags``: Tags which should not be stripped
28
-
29
- .. _`strip_tags`: http://php.net/strip_tags
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/title.rst DELETED
@@ -1,11 +0,0 @@
1
- ``title``
2
- =========
3
-
4
- The ``title`` filter returns a titlecased version of the value. Words will
5
- start with uppercase letters, all remaining characters are lowercase:
6
-
7
- .. code-block:: jinja
8
-
9
- {{ 'my first car'|title }}
10
-
11
- {# outputs 'My First Car' #}
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/trim.rst DELETED
@@ -1,45 +0,0 @@
1
- ``trim``
2
- ========
3
-
4
- .. versionadded:: 1.32
5
- The ``side`` argument was added in Twig 1.32.
6
-
7
- .. versionadded:: 1.6.2
8
- The ``trim`` filter was added in Twig 1.6.2.
9
-
10
- The ``trim`` filter strips whitespace (or other characters) from the beginning
11
- and end of a string:
12
-
13
- .. code-block:: jinja
14
-
15
- {{ ' I like Twig. '|trim }}
16
-
17
- {# outputs 'I like Twig.' #}
18
-
19
- {{ ' I like Twig.'|trim('.') }}
20
-
21
- {# outputs ' I like Twig' #}
22
-
23
- {{ ' I like Twig. '|trim(side='left') }}
24
-
25
- {# outputs 'I like Twig. ' #}
26
-
27
- {{ ' I like Twig. '|trim(' ', 'right') }}
28
-
29
- {# outputs ' I like Twig.' #}
30
-
31
- .. note::
32
-
33
- Internally, Twig uses the PHP `trim`_, `ltrim`_, and `rtrim`_ functions.
34
-
35
- Arguments
36
- ---------
37
-
38
- * ``character_mask``: The characters to strip
39
-
40
- * ``side``: The default is to strip from the left and the right (`both`) sides, but `left`
41
- and `right` will strip from either the left side or right side only
42
-
43
- .. _`trim`: http://php.net/trim
44
- .. _`ltrim`: http://php.net/ltrim
45
- .. _`rtrim`: http://php.net/rtrim
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/upper.rst DELETED
@@ -1,10 +0,0 @@
1
- ``upper``
2
- =========
3
-
4
- The ``upper`` filter converts a value to uppercase:
5
-
6
- .. code-block:: jinja
7
-
8
- {{ 'welcome'|upper }}
9
-
10
- {# outputs 'WELCOME' #}
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/filters/url_encode.rst DELETED
@@ -1,34 +0,0 @@
1
- ``url_encode``
2
- ==============
3
-
4
- .. versionadded:: 1.12.3
5
- Support for encoding an array as query string was added in Twig 1.12.3.
6
-
7
- .. versionadded:: 1.16.0
8
- The ``raw`` argument was removed in Twig 1.16.0. Twig now always encodes
9
- according to RFC 3986.
10
-
11
- The ``url_encode`` filter percent encodes a given string as URL segment
12
- or an array as query string:
13
-
14
- .. code-block:: jinja
15
-
16
- {{ "path-seg*ment"|url_encode }}
17
- {# outputs "path-seg%2Ament" #}
18
-
19
- {{ "string with spaces"|url_encode }}
20
- {# outputs "string%20with%20spaces" #}
21
-
22
- {{ {'param': 'value', 'foo': 'bar'}|url_encode }}
23
- {# outputs "param=value&foo=bar" #}
24
-
25
- .. note::
26
-
27
- Internally, Twig uses the PHP `urlencode`_ (or `rawurlencode`_ if you pass
28
- ``true`` as the first parameter) or the `http_build_query`_ function. Note
29
- that as of Twig 1.16.0, ``urlencode`` **always** uses ``rawurlencode`` (the
30
- ``raw`` argument was removed.)
31
-
32
- .. _`urlencode`: http://php.net/urlencode
33
- .. _`rawurlencode`: http://php.net/rawurlencode
34
- .. _`http_build_query`: http://php.net/http_build_query
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/attribute.rst DELETED
@@ -1,26 +0,0 @@
1
- ``attribute``
2
- =============
3
-
4
- .. versionadded:: 1.2
5
- The ``attribute`` function was added in Twig 1.2.
6
-
7
- The ``attribute`` function can be used to access a "dynamic" attribute of a
8
- variable:
9
-
10
- .. code-block:: jinja
11
-
12
- {{ attribute(object, method) }}
13
- {{ attribute(object, method, arguments) }}
14
- {{ attribute(array, item) }}
15
-
16
- In addition, the ``defined`` test can check for the existence of a dynamic
17
- attribute:
18
-
19
- .. code-block:: jinja
20
-
21
- {{ attribute(object, method) is defined ? 'Method exists' : 'Method does not exist' }}
22
-
23
- .. note::
24
-
25
- The resolution algorithm is the same as the one used for the ``.``
26
- notation, except that the item can be any valid expression.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/block.rst DELETED
@@ -1,41 +0,0 @@
1
- ``block``
2
- =========
3
-
4
- .. versionadded: 1.28
5
- Using ``block`` with the ``defined`` test was added in Twig 1.28.
6
-
7
- .. versionadded: 1.28
8
- Support for the template argument was added in Twig 1.28.
9
-
10
- When a template uses inheritance and if you want to print a block multiple
11
- times, use the ``block`` function:
12
-
13
- .. code-block:: jinja
14
-
15
- <title>{% block title %}{% endblock %}</title>
16
-
17
- <h1>{{ block('title') }}</h1>
18
-
19
- {% block body %}{% endblock %}
20
-
21
- The ``block`` function can also be used to display one block of another
22
- template:
23
-
24
- .. code-block:: jinja
25
-
26
- {{ block("title", "common_blocks.twig") }}
27
-
28
- Use the ``defined`` test to check if a block exists in the context of the
29
- current template:
30
-
31
- .. code-block:: jinja
32
-
33
- {% if block("footer") is defined %}
34
- ...
35
- {% endif %}
36
-
37
- {% if block("footer", "common_blocks.twig") is defined %}
38
- ...
39
- {% endif %}
40
-
41
- .. seealso:: :doc:`extends<../tags/extends>`, :doc:`parent<../functions/parent>`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/constant.rst DELETED
@@ -1,29 +0,0 @@
1
- ``constant``
2
- ============
3
-
4
- .. versionadded: 1.12.1
5
- constant now accepts object instances as the second argument.
6
-
7
- .. versionadded: 1.28
8
- Using ``constant`` with the ``defined`` test was added in Twig 1.28.
9
-
10
- ``constant`` returns the constant value for a given string:
11
-
12
- .. code-block:: jinja
13
-
14
- {{ some_date|date(constant('DATE_W3C')) }}
15
- {{ constant('Namespace\\Classname::CONSTANT_NAME') }}
16
-
17
- As of 1.12.1 you can read constants from object instances as well:
18
-
19
- .. code-block:: jinja
20
-
21
- {{ constant('RSS', date) }}
22
-
23
- Use the ``defined`` test to check if a constant is defined:
24
-
25
- .. code-block:: jinja
26
-
27
- {% if constant('SOME_CONST') is defined %}
28
- ...
29
- {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/cycle.rst DELETED
@@ -1,28 +0,0 @@
1
- ``cycle``
2
- =========
3
-
4
- The ``cycle`` function cycles on an array of values:
5
-
6
- .. code-block:: jinja
7
-
8
- {% set start_year = date() | date('Y') %}
9
- {% set end_year = start_year + 5 %}
10
-
11
- {% for year in start_year..end_year %}
12
- {{ cycle(['odd', 'even'], loop.index0) }}
13
- {% endfor %}
14
-
15
- The array can contain any number of values:
16
-
17
- .. code-block:: jinja
18
-
19
- {% set fruits = ['apple', 'orange', 'citrus'] %}
20
-
21
- {% for i in 0..10 %}
22
- {{ cycle(fruits, i) }}
23
- {% endfor %}
24
-
25
- Arguments
26
- ---------
27
-
28
- * ``position``: The cycle position
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/date.rst DELETED
@@ -1,55 +0,0 @@
1
- ``date``
2
- ========
3
-
4
- .. versionadded:: 1.6
5
- The date function has been added in Twig 1.6.
6
-
7
- .. versionadded:: 1.6.1
8
- The default timezone support has been added in Twig 1.6.1.
9
-
10
- Converts an argument to a date to allow date comparison:
11
-
12
- .. code-block:: jinja
13
-
14
- {% if date(user.created_at) < date('-2days') %}
15
- {# do something #}
16
- {% endif %}
17
-
18
- The argument must be in one of PHP’s supported `date and time formats`_.
19
-
20
- You can pass a timezone as the second argument:
21
-
22
- .. code-block:: jinja
23
-
24
- {% if date(user.created_at) < date('-2days', 'Europe/Paris') %}
25
- {# do something #}
26
- {% endif %}
27
-
28
- If no argument is passed, the function returns the current date:
29
-
30
- .. code-block:: jinja
31
-
32
- {% if date(user.created_at) < date() %}
33
- {# always! #}
34
- {% endif %}
35
-
36
- .. note::
37
-
38
- You can set the default timezone globally by calling ``setTimezone()`` on
39
- the ``core`` extension instance:
40
-
41
- .. code-block:: php
42
-
43
- $twig = new Twig_Environment($loader);
44
- $twig->getExtension('Twig_Extension_Core')->setTimezone('Europe/Paris');
45
-
46
- // before Twig 1.26
47
- $twig->getExtension('core')->setTimezone('Europe/Paris');
48
-
49
- Arguments
50
- ---------
51
-
52
- * ``date``: The date
53
- * ``timezone``: The timezone
54
-
55
- .. _`date and time formats`: http://php.net/manual/en/datetime.formats.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/dump.rst DELETED
@@ -1,69 +0,0 @@
1
- ``dump``
2
- ========
3
-
4
- .. versionadded:: 1.5
5
- The ``dump`` function was added in Twig 1.5.
6
-
7
- The ``dump`` function dumps information about a template variable. This is
8
- mostly useful to debug a template that does not behave as expected by
9
- introspecting its variables:
10
-
11
- .. code-block:: jinja
12
-
13
- {{ dump(user) }}
14
-
15
- .. note::
16
-
17
- The ``dump`` function is not available by default. You must add the
18
- ``Twig_Extension_Debug`` extension explicitly when creating your Twig
19
- environment::
20
-
21
- $twig = new Twig_Environment($loader, array(
22
- 'debug' => true,
23
- // ...
24
- ));
25
- $twig->addExtension(new Twig_Extension_Debug());
26
-
27
- Even when enabled, the ``dump`` function won't display anything if the
28
- ``debug`` option on the environment is not enabled (to avoid leaking debug
29
- information on a production server).
30
-
31
- In an HTML context, wrap the output with a ``pre`` tag to make it easier to
32
- read:
33
-
34
- .. code-block:: jinja
35
-
36
- <pre>
37
- {{ dump(user) }}
38
- </pre>
39
-
40
- .. tip::
41
-
42
- Using a ``pre`` tag is not needed when `XDebug`_ is enabled and
43
- ``html_errors`` is ``on``; as a bonus, the output is also nicer with
44
- XDebug enabled.
45
-
46
- You can debug several variables by passing them as additional arguments:
47
-
48
- .. code-block:: jinja
49
-
50
- {{ dump(user, categories) }}
51
-
52
- If you don't pass any value, all variables from the current context are
53
- dumped:
54
-
55
- .. code-block:: jinja
56
-
57
- {{ dump() }}
58
-
59
- .. note::
60
-
61
- Internally, Twig uses the PHP `var_dump`_ function.
62
-
63
- Arguments
64
- ---------
65
-
66
- * ``context``: The context to dump
67
-
68
- .. _`XDebug`: http://xdebug.org/docs/display
69
- .. _`var_dump`: http://php.net/var_dump
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/include.rst DELETED
@@ -1,84 +0,0 @@
1
- ``include``
2
- ===========
3
-
4
- .. versionadded:: 1.12
5
- The ``include`` function was added in Twig 1.12.
6
-
7
- The ``include`` function returns the rendered content of a template:
8
-
9
- .. code-block:: jinja
10
-
11
- {{ include('template.html') }}
12
- {{ include(some_var) }}
13
-
14
- Included templates have access to the variables of the active context.
15
-
16
- If you are using the filesystem loader, the templates are looked for in the
17
- paths defined by it.
18
-
19
- The context is passed by default to the template but you can also pass
20
- additional variables:
21
-
22
- .. code-block:: jinja
23
-
24
- {# template.html will have access to the variables from the current context and the additional ones provided #}
25
- {{ include('template.html', {foo: 'bar'}) }}
26
-
27
- You can disable access to the context by setting ``with_context`` to
28
- ``false``:
29
-
30
- .. code-block:: jinja
31
-
32
- {# only the foo variable will be accessible #}
33
- {{ include('template.html', {foo: 'bar'}, with_context = false) }}
34
-
35
- .. code-block:: jinja
36
-
37
- {# no variables will be accessible #}
38
- {{ include('template.html', with_context = false) }}
39
-
40
- And if the expression evaluates to a ``Twig_Template`` or a
41
- ``Twig_TemplateWrapper`` instance, Twig will use it directly::
42
-
43
- // {{ include(template) }}
44
-
45
- // deprecated as of Twig 1.28
46
- $template = $twig->loadTemplate('some_template.twig');
47
-
48
- // as of Twig 1.28
49
- $template = $twig->load('some_template.twig');
50
-
51
- $twig->display('template.twig', array('template' => $template));
52
-
53
- When you set the ``ignore_missing`` flag, Twig will return an empty string if
54
- the template does not exist:
55
-
56
- .. code-block:: jinja
57
-
58
- {{ include('sidebar.html', ignore_missing = true) }}
59
-
60
- You can also provide a list of templates that are checked for existence before
61
- inclusion. The first template that exists will be rendered:
62
-
63
- .. code-block:: jinja
64
-
65
- {{ include(['page_detailed.html', 'page.html']) }}
66
-
67
- If ``ignore_missing`` is set, it will fall back to rendering nothing if none
68
- of the templates exist, otherwise it will throw an exception.
69
-
70
- When including a template created by an end user, you should consider
71
- sandboxing it:
72
-
73
- .. code-block:: jinja
74
-
75
- {{ include('page.html', sandboxed = true) }}
76
-
77
- Arguments
78
- ---------
79
-
80
- * ``template``: The template to render
81
- * ``variables``: The variables to pass to the template
82
- * ``with_context``: Whether to pass the current context variables or not
83
- * ``ignore_missing``: Whether to ignore missing templates or not
84
- * ``sandboxed``: Whether to sandbox the template or not
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/index.rst DELETED
@@ -1,20 +0,0 @@
1
- Functions
2
- =========
3
-
4
- .. toctree::
5
- :maxdepth: 1
6
-
7
- attribute
8
- block
9
- constant
10
- cycle
11
- date
12
- dump
13
- include
14
- max
15
- min
16
- parent
17
- random
18
- range
19
- source
20
- template_from_string
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/max.rst DELETED
@@ -1,20 +0,0 @@
1
- ``max``
2
- =======
3
-
4
- .. versionadded:: 1.15
5
- The ``max`` function was added in Twig 1.15.
6
-
7
- ``max`` returns the biggest value of a sequence or a set of values:
8
-
9
- .. code-block:: jinja
10
-
11
- {{ max(1, 3, 2) }}
12
- {{ max([1, 3, 2]) }}
13
-
14
- When called with a mapping, max ignores keys and only compares values:
15
-
16
- .. code-block:: jinja
17
-
18
- {{ max({2: "e", 1: "a", 3: "b", 5: "d", 4: "c"}) }}
19
- {# returns "e" #}
20
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/min.rst DELETED
@@ -1,20 +0,0 @@
1
- ``min``
2
- =======
3
-
4
- .. versionadded:: 1.15
5
- The ``min`` function was added in Twig 1.15.
6
-
7
- ``min`` returns the lowest value of a sequence or a set of values:
8
-
9
- .. code-block:: jinja
10
-
11
- {{ min(1, 3, 2) }}
12
- {{ min([1, 3, 2]) }}
13
-
14
- When called with a mapping, min ignores keys and only compares values:
15
-
16
- .. code-block:: jinja
17
-
18
- {{ min({2: "e", 3: "a", 1: "b", 5: "d", 4: "c"}) }}
19
- {# returns "a" #}
20
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/parent.rst DELETED
@@ -1,20 +0,0 @@
1
- ``parent``
2
- ==========
3
-
4
- When a template uses inheritance, it's possible to render the contents of the
5
- parent block when overriding a block by using the ``parent`` function:
6
-
7
- .. code-block:: jinja
8
-
9
- {% extends "base.html" %}
10
-
11
- {% block sidebar %}
12
- <h3>Table Of Contents</h3>
13
- ...
14
- {{ parent() }}
15
- {% endblock %}
16
-
17
- The ``parent()`` call will return the content of the ``sidebar`` block as
18
- defined in the ``base.html`` template.
19
-
20
- .. seealso:: :doc:`extends<../tags/extends>`, :doc:`block<../functions/block>`, :doc:`block<../tags/block>`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/random.rst DELETED
@@ -1,29 +0,0 @@
1
- ``random``
2
- ==========
3
-
4
- .. versionadded:: 1.5
5
- The ``random`` function was added in Twig 1.5.
6
-
7
- .. versionadded:: 1.6
8
- String and integer handling was added in Twig 1.6.
9
-
10
- The ``random`` function returns a random value depending on the supplied
11
- parameter type:
12
-
13
- * a random item from a sequence;
14
- * a random character from a string;
15
- * a random integer between 0 and the integer parameter (inclusive).
16
-
17
- .. code-block:: jinja
18
-
19
- {{ random(['apple', 'orange', 'citrus']) }} {# example output: orange #}
20
- {{ random('ABC') }} {# example output: C #}
21
- {{ random() }} {# example output: 15386094 (works as the native PHP mt_rand function) #}
22
- {{ random(5) }} {# example output: 3 #}
23
-
24
- Arguments
25
- ---------
26
-
27
- * ``values``: The values
28
-
29
- .. _`mt_rand`: http://php.net/mt_rand
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/range.rst DELETED
@@ -1,58 +0,0 @@
1
- ``range``
2
- =========
3
-
4
- Returns a list containing an arithmetic progression of integers:
5
-
6
- .. code-block:: jinja
7
-
8
- {% for i in range(0, 3) %}
9
- {{ i }},
10
- {% endfor %}
11
-
12
- {# outputs 0, 1, 2, 3, #}
13
-
14
- When step is given (as the third parameter), it specifies the increment (or
15
- decrement for negative values):
16
-
17
- .. code-block:: jinja
18
-
19
- {% for i in range(0, 6, 2) %}
20
- {{ i }},
21
- {% endfor %}
22
-
23
- {# outputs 0, 2, 4, 6, #}
24
-
25
- .. note::
26
-
27
- Note that if the start is greater than the end, ``range`` assumes a step of
28
- ``-1``:
29
-
30
- .. code-block:: jinja
31
-
32
- {% for i in range(3, 0) %}
33
- {{ i }},
34
- {% endfor %}
35
-
36
- {# outputs 3, 2, 1, 0, #}
37
-
38
- The Twig built-in ``..`` operator is just syntactic sugar for the ``range``
39
- function (with a step of ``1``, or ``-1`` if the start is greater than the end):
40
-
41
- .. code-block:: jinja
42
-
43
- {% for i in 0..3 %}
44
- {{ i }},
45
- {% endfor %}
46
-
47
- .. tip::
48
-
49
- The ``range`` function works as the native PHP `range`_ function.
50
-
51
- Arguments
52
- ---------
53
-
54
- * ``low``: The first value of the sequence.
55
- * ``high``: The highest possible value of the sequence.
56
- * ``step``: The increment between elements of the sequence.
57
-
58
- .. _`range`: http://php.net/range
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/source.rst DELETED
@@ -1,32 +0,0 @@
1
- ``source``
2
- ==========
3
-
4
- .. versionadded:: 1.15
5
- The ``source`` function was added in Twig 1.15.
6
-
7
- .. versionadded:: 1.18.3
8
- The ``ignore_missing`` flag was added in Twig 1.18.3.
9
-
10
- The ``source`` function returns the content of a template without rendering it:
11
-
12
- .. code-block:: jinja
13
-
14
- {{ source('template.html') }}
15
- {{ source(some_var) }}
16
-
17
- When you set the ``ignore_missing`` flag, Twig will return an empty string if
18
- the template does not exist:
19
-
20
- .. code-block:: jinja
21
-
22
- {{ source('template.html', ignore_missing = true) }}
23
-
24
- The function uses the same template loaders as the ones used to include
25
- templates. So, if you are using the filesystem loader, the templates are looked
26
- for in the paths defined by it.
27
-
28
- Arguments
29
- ---------
30
-
31
- * ``name``: The name of the template to read
32
- * ``ignore_missing``: Whether to ignore missing templates or not
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/functions/template_from_string.rst DELETED
@@ -1,32 +0,0 @@
1
- ``template_from_string``
2
- ========================
3
-
4
- .. versionadded:: 1.11
5
- The ``template_from_string`` function was added in Twig 1.11.
6
-
7
- The ``template_from_string`` function loads a template from a string:
8
-
9
- .. code-block:: jinja
10
-
11
- {{ include(template_from_string("Hello {{ name }}")) }}
12
- {{ include(template_from_string(page.template)) }}
13
-
14
- .. note::
15
-
16
- The ``template_from_string`` function is not available by default. You
17
- must add the ``Twig_Extension_StringLoader`` extension explicitly when
18
- creating your Twig environment::
19
-
20
- $twig = new Twig_Environment(...);
21
- $twig->addExtension(new Twig_Extension_StringLoader());
22
-
23
- .. note::
24
-
25
- Even if you will probably always use the ``template_from_string`` function
26
- with the ``include`` function, you can use it with any tag or function that
27
- takes a template as an argument (like the ``embed`` or ``extends`` tags).
28
-
29
- Arguments
30
- ---------
31
-
32
- * ``template``: The template
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/index.rst DELETED
@@ -1,19 +0,0 @@
1
- Twig
2
- ====
3
-
4
- .. toctree::
5
- :maxdepth: 2
6
-
7
- intro
8
- installation
9
- templates
10
- api
11
- advanced
12
- internals
13
- deprecated
14
- recipes
15
- coding_standards
16
- tags/index
17
- filters/index
18
- functions/index
19
- tests/index
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/installation.rst DELETED
@@ -1,116 +0,0 @@
1
- Installation
2
- ============
3
-
4
- You have multiple ways to install Twig.
5
-
6
- Installing the Twig PHP package
7
- -------------------------------
8
-
9
- Installing via Composer (recommended)
10
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11
-
12
- Install `Composer`_ and run the following command to get the latest version:
13
-
14
- .. code-block:: bash
15
-
16
- composer require twig/twig:~1.0
17
-
18
- Installing from the tarball release
19
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
20
-
21
- 1. Download the most recent tarball from the `download page`_
22
- 2. Verify the integrity of the tarball http://fabien.potencier.org/article/73/signing-project-releases
23
- 3. Unpack the tarball
24
- 4. Move the files somewhere in your project
25
-
26
- Installing the development version
27
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
28
-
29
- .. code-block:: bash
30
-
31
- git clone git://github.com/twigphp/Twig.git
32
-
33
- Installing the PEAR package
34
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
35
-
36
- .. note::
37
-
38
- Using PEAR for installing Twig is deprecated and Twig 1.15.1 was the last
39
- version published on the PEAR channel; use Composer instead.
40
-
41
- .. code-block:: bash
42
-
43
- pear channel-discover pear.twig-project.org
44
- pear install twig/Twig
45
-
46
- Installing the C extension
47
- --------------------------
48
-
49
- .. versionadded:: 1.4
50
- The C extension was added in Twig 1.4.
51
-
52
- .. note::
53
-
54
- The C extension is **optional** but it brings some nice performance
55
- improvements. Note that the extension is not a replacement for the PHP
56
- code; it only implements a small part of the PHP code to improve the
57
- performance at runtime; you must still install the regular PHP code.
58
-
59
- Twig comes with a C extension that enhances the performance of the Twig
60
- runtime engine; install it like any other PHP extensions:
61
-
62
- .. code-block:: bash
63
-
64
- cd ext/twig
65
- phpize
66
- ./configure
67
- make
68
- make install
69
-
70
- .. note::
71
-
72
- You can also install the C extension via PEAR (note that this method is
73
- deprecated and newer versions of Twig are not available on the PEAR
74
- channel):
75
-
76
- .. code-block:: bash
77
-
78
- pear channel-discover pear.twig-project.org
79
- pear install twig/CTwig
80
-
81
- For Windows:
82
-
83
- 1. Setup the build environment following the `PHP documentation`_
84
- 2. Put Twig's C extension source code into ``C:\php-sdk\phpdev\vcXX\x86\php-source-directory\ext\twig``
85
- 3. Use the ``configure --disable-all --enable-cli --enable-twig=shared`` command instead of step 14
86
- 4. ``nmake``
87
- 5. Copy the ``C:\php-sdk\phpdev\vcXX\x86\php-source-directory\Release_TS\php_twig.dll`` file to your PHP setup.
88
-
89
- .. tip::
90
-
91
- For Windows ZendServer, ZTS is not enabled as mentioned in `Zend Server
92
- FAQ`_.
93
-
94
- You have to use ``configure --disable-all --disable-zts --enable-cli
95
- --enable-twig=shared`` to be able to build the twig C extension for
96
- ZendServer.
97
-
98
- The built DLL will be available in
99
- ``C:\\php-sdk\\phpdev\\vcXX\\x86\\php-source-directory\\Release``
100
-
101
- Finally, enable the extension in your ``php.ini`` configuration file:
102
-
103
- .. code-block:: ini
104
-
105
- extension=twig.so #For Unix systems
106
- extension=php_twig.dll #For Windows systems
107
-
108
- And from now on, Twig will automatically compile your templates to take
109
- advantage of the C extension. Note that this extension does not replace the
110
- PHP code but only provides an optimized version of the
111
- ``Twig_Template::getAttribute()`` method.
112
-
113
- .. _`download page`: https://github.com/twigphp/Twig/tags
114
- .. _`Composer`: https://getcomposer.org/download/
115
- .. _`PHP documentation`: https://wiki.php.net/internals/windows/stepbystepbuild
116
- .. _`Zend Server FAQ`: http://www.zend.com/en/products/server/faq#faqD6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/internals.rst DELETED
@@ -1,142 +0,0 @@
1
- Twig Internals
2
- ==============
3
-
4
- Twig is very extensible and you can easily hack it. Keep in mind that you
5
- should probably try to create an extension before hacking the core, as most
6
- features and enhancements can be handled with extensions. This chapter is also
7
- useful for people who want to understand how Twig works under the hood.
8
-
9
- How does Twig work?
10
- -------------------
11
-
12
- The rendering of a Twig template can be summarized into four key steps:
13
-
14
- * **Load** the template: If the template is already compiled, load it and go
15
- to the *evaluation* step, otherwise:
16
-
17
- * First, the **lexer** tokenizes the template source code into small pieces
18
- for easier processing;
19
- * Then, the **parser** converts the token stream into a meaningful tree
20
- of nodes (the Abstract Syntax Tree);
21
- * Eventually, the *compiler* transforms the AST into PHP code.
22
-
23
- * **Evaluate** the template: It basically means calling the ``display()``
24
- method of the compiled template and passing it the context.
25
-
26
- The Lexer
27
- ---------
28
-
29
- The lexer tokenizes a template source code into a token stream (each token is
30
- an instance of ``Twig_Token``, and the stream is an instance of
31
- ``Twig_TokenStream``). The default lexer recognizes 13 different token types:
32
-
33
- * ``Twig_Token::BLOCK_START_TYPE``, ``Twig_Token::BLOCK_END_TYPE``: Delimiters for blocks (``{% %}``)
34
- * ``Twig_Token::VAR_START_TYPE``, ``Twig_Token::VAR_END_TYPE``: Delimiters for variables (``{{ }}``)
35
- * ``Twig_Token::TEXT_TYPE``: A text outside an expression;
36
- * ``Twig_Token::NAME_TYPE``: A name in an expression;
37
- * ``Twig_Token::NUMBER_TYPE``: A number in an expression;
38
- * ``Twig_Token::STRING_TYPE``: A string in an expression;
39
- * ``Twig_Token::OPERATOR_TYPE``: An operator;
40
- * ``Twig_Token::PUNCTUATION_TYPE``: A punctuation sign;
41
- * ``Twig_Token::INTERPOLATION_START_TYPE``, ``Twig_Token::INTERPOLATION_END_TYPE`` (as of Twig 1.5): Delimiters for string interpolation;
42
- * ``Twig_Token::EOF_TYPE``: Ends of template.
43
-
44
- You can manually convert a source code into a token stream by calling the
45
- ``tokenize()`` method of an environment::
46
-
47
- $stream = $twig->tokenize(new Twig_Source($source, $identifier));
48
-
49
- .. versionadded:: 1.27
50
- ``Twig_Source`` was introduced in version 1.27, pass the source and the
51
- identifier directly on previous versions.
52
-
53
- As the stream has a ``__toString()`` method, you can have a textual
54
- representation of it by echoing the object::
55
-
56
- echo $stream."\n";
57
-
58
- Here is the output for the ``Hello {{ name }}`` template:
59
-
60
- .. code-block:: text
61
-
62
- TEXT_TYPE(Hello )
63
- VAR_START_TYPE()
64
- NAME_TYPE(name)
65
- VAR_END_TYPE()
66
- EOF_TYPE()
67
-
68
- .. note::
69
-
70
- The default lexer (``Twig_Lexer``) can be changed by calling
71
- the ``setLexer()`` method::
72
-
73
- $twig->setLexer($lexer);
74
-
75
- The Parser
76
- ----------
77
-
78
- The parser converts the token stream into an AST (Abstract Syntax Tree), or a
79
- node tree (an instance of ``Twig_Node_Module``). The core extension defines
80
- the basic nodes like: ``for``, ``if``, ... and the expression nodes.
81
-
82
- You can manually convert a token stream into a node tree by calling the
83
- ``parse()`` method of an environment::
84
-
85
- $nodes = $twig->parse($stream);
86
-
87
- Echoing the node object gives you a nice representation of the tree::
88
-
89
- echo $nodes."\n";
90
-
91
- Here is the output for the ``Hello {{ name }}`` template:
92
-
93
- .. code-block:: text
94
-
95
- Twig_Node_Module(
96
- Twig_Node_Text(Hello )
97
- Twig_Node_Print(
98
- Twig_Node_Expression_Name(name)
99
- )
100
- )
101
-
102
- .. note::
103
-
104
- The default parser (``Twig_TokenParser``) can be changed by calling the
105
- ``setParser()`` method::
106
-
107
- $twig->setParser($parser);
108
-
109
- The Compiler
110
- ------------
111
-
112
- The last step is done by the compiler. It takes a node tree as an input and
113
- generates PHP code usable for runtime execution of the template.
114
-
115
- You can manually compile a node tree to PHP code with the ``compile()`` method
116
- of an environment::
117
-
118
- $php = $twig->compile($nodes);
119
-
120
- The generated template for a ``Hello {{ name }}`` template reads as follows
121
- (the actual output can differ depending on the version of Twig you are
122
- using)::
123
-
124
- /* Hello {{ name }} */
125
- class __TwigTemplate_1121b6f109fe93ebe8c6e22e3712bceb extends Twig_Template
126
- {
127
- protected function doDisplay(array $context, array $blocks = array())
128
- {
129
- // line 1
130
- echo "Hello ";
131
- echo twig_escape_filter($this->env, isset($context["name"]) ? $context["name"] : null), "html", null, true);
132
- }
133
-
134
- // some more code
135
- }
136
-
137
- .. note::
138
-
139
- The default compiler (``Twig_Compiler``) can be changed by calling the
140
- ``setCompiler()`` method::
141
-
142
- $twig->setCompiler($compiler);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/intro.rst DELETED
@@ -1,85 +0,0 @@
1
- Introduction
2
- ============
3
-
4
- This is the documentation for Twig, the flexible, fast, and secure template
5
- engine for PHP.
6
-
7
- If you have any exposure to other text-based template languages, such as
8
- Smarty, Django, or Jinja, you should feel right at home with Twig. It's both
9
- designer and developer friendly by sticking to PHP's principles and adding
10
- functionality useful for templating environments.
11
-
12
- The key-features are...
13
-
14
- * *Fast*: Twig compiles templates down to plain optimized PHP code. The
15
- overhead compared to regular PHP code was reduced to the very minimum.
16
-
17
- * *Secure*: Twig has a sandbox mode to evaluate untrusted template code. This
18
- allows Twig to be used as a template language for applications where users
19
- may modify the template design.
20
-
21
- * *Flexible*: Twig is powered by a flexible lexer and parser. This allows the
22
- developer to define their own custom tags and filters, and to create their own DSL.
23
-
24
- Twig is used by many Open-Source projects like Symfony, Drupal8, eZPublish,
25
- phpBB, Piwik, OroCRM; and many frameworks have support for it as well like
26
- Slim, Yii, Laravel, Codeigniter and Kohana — just to name a few.
27
-
28
- Prerequisites
29
- -------------
30
-
31
- Twig needs at least **PHP 5.2.7** to run.
32
-
33
- Installation
34
- ------------
35
-
36
- The recommended way to install Twig is via Composer:
37
-
38
- .. code-block:: bash
39
-
40
- composer require "twig/twig:~1.0"
41
-
42
- .. note::
43
-
44
- To learn more about the other installation methods, read the
45
- :doc:`installation<installation>` chapter; it also explains how to install
46
- the Twig C extension.
47
-
48
- Basic API Usage
49
- ---------------
50
-
51
- This section gives you a brief introduction to the PHP API for Twig.
52
-
53
- .. code-block:: php
54
-
55
- require_once '/path/to/vendor/autoload.php';
56
-
57
- $loader = new Twig_Loader_Array(array(
58
- 'index' => 'Hello {{ name }}!',
59
- ));
60
- $twig = new Twig_Environment($loader);
61
-
62
- echo $twig->render('index', array('name' => 'Fabien'));
63
-
64
- Twig uses a loader (``Twig_Loader_Array``) to locate templates, and an
65
- environment (``Twig_Environment``) to store the configuration.
66
-
67
- The ``render()`` method loads the template passed as a first argument and
68
- renders it with the variables passed as a second argument.
69
-
70
- As templates are generally stored on the filesystem, Twig also comes with a
71
- filesystem loader::
72
-
73
- $loader = new Twig_Loader_Filesystem('/path/to/templates');
74
- $twig = new Twig_Environment($loader, array(
75
- 'cache' => '/path/to/compilation_cache',
76
- ));
77
-
78
- echo $twig->render('index.html', array('name' => 'Fabien'));
79
-
80
- .. tip::
81
-
82
- If you are not using Composer, use the Twig built-in autoloader::
83
-
84
- require_once '/path/to/lib/Twig/Autoloader.php';
85
- Twig_Autoloader::register();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/recipes.rst DELETED
@@ -1,568 +0,0 @@
1
- Recipes
2
- =======
3
-
4
- .. _deprecation-notices:
5
-
6
- Displaying Deprecation Notices
7
- ------------------------------
8
-
9
- .. versionadded:: 1.21
10
- This works as of Twig 1.21.
11
-
12
- Deprecated features generate deprecation notices (via a call to the
13
- ``trigger_error()`` PHP function). By default, they are silenced and never
14
- displayed nor logged.
15
-
16
- To easily remove all deprecated feature usages from your templates, write and
17
- run a script along the lines of the following::
18
-
19
- require_once __DIR__.'/vendor/autoload.php';
20
-
21
- $twig = create_your_twig_env();
22
-
23
- $deprecations = new Twig_Util_DeprecationCollector($twig);
24
-
25
- print_r($deprecations->collectDir(__DIR__.'/templates'));
26
-
27
- The ``collectDir()`` method compiles all templates found in a directory,
28
- catches deprecation notices, and return them.
29
-
30
- .. tip::
31
-
32
- If your templates are not stored on the filesystem, use the ``collect()``
33
- method instead. ``collect()`` takes a ``Traversable`` which must return
34
- template names as keys and template contents as values (as done by
35
- ``Twig_Util_TemplateDirIterator``).
36
-
37
- However, this code won't find all deprecations (like using deprecated some Twig
38
- classes). To catch all notices, register a custom error handler like the one
39
- below::
40
-
41
- $deprecations = array();
42
- set_error_handler(function ($type, $msg) use (&$deprecations) {
43
- if (E_USER_DEPRECATED === $type) {
44
- $deprecations[] = $msg;
45
- }
46
- });
47
-
48
- // run your application
49
-
50
- print_r($deprecations);
51
-
52
- Note that most deprecation notices are triggered during **compilation**, so
53
- they won't be generated when templates are already cached.
54
-
55
- .. tip::
56
-
57
- If you want to manage the deprecation notices from your PHPUnit tests, have
58
- a look at the `symfony/phpunit-bridge
59
- <https://github.com/symfony/phpunit-bridge>`_ package, which eases the
60
- process a lot.
61
-
62
- Making a Layout conditional
63
- ---------------------------
64
-
65
- Working with Ajax means that the same content is sometimes displayed as is,
66
- and sometimes decorated with a layout. As Twig layout template names can be
67
- any valid expression, you can pass a variable that evaluates to ``true`` when
68
- the request is made via Ajax and choose the layout accordingly:
69
-
70
- .. code-block:: jinja
71
-
72
- {% extends request.ajax ? "base_ajax.html" : "base.html" %}
73
-
74
- {% block content %}
75
- This is the content to be displayed.
76
- {% endblock %}
77
-
78
- Making an Include dynamic
79
- -------------------------
80
-
81
- When including a template, its name does not need to be a string. For
82
- instance, the name can depend on the value of a variable:
83
-
84
- .. code-block:: jinja
85
-
86
- {% include var ~ '_foo.html' %}
87
-
88
- If ``var`` evaluates to ``index``, the ``index_foo.html`` template will be
89
- rendered.
90
-
91
- As a matter of fact, the template name can be any valid expression, such as
92
- the following:
93
-
94
- .. code-block:: jinja
95
-
96
- {% include var|default('index') ~ '_foo.html' %}
97
-
98
- Overriding a Template that also extends itself
99
- ----------------------------------------------
100
-
101
- A template can be customized in two different ways:
102
-
103
- * *Inheritance*: A template *extends* a parent template and overrides some
104
- blocks;
105
-
106
- * *Replacement*: If you use the filesystem loader, Twig loads the first
107
- template it finds in a list of configured directories; a template found in a
108
- directory *replaces* another one from a directory further in the list.
109
-
110
- But how do you combine both: *replace* a template that also extends itself
111
- (aka a template in a directory further in the list)?
112
-
113
- Let's say that your templates are loaded from both ``.../templates/mysite``
114
- and ``.../templates/default`` in this order. The ``page.twig`` template,
115
- stored in ``.../templates/default`` reads as follows:
116
-
117
- .. code-block:: jinja
118
-
119
- {# page.twig #}
120
- {% extends "layout.twig" %}
121
-
122
- {% block content %}
123
- {% endblock %}
124
-
125
- You can replace this template by putting a file with the same name in
126
- ``.../templates/mysite``. And if you want to extend the original template, you
127
- might be tempted to write the following:
128
-
129
- .. code-block:: jinja
130
-
131
- {# page.twig in .../templates/mysite #}
132
- {% extends "page.twig" %} {# from .../templates/default #}
133
-
134
- Of course, this will not work as Twig will always load the template from
135
- ``.../templates/mysite``.
136
-
137
- It turns out it is possible to get this to work, by adding a directory right
138
- at the end of your template directories, which is the parent of all of the
139
- other directories: ``.../templates`` in our case. This has the effect of
140
- making every template file within our system uniquely addressable. Most of the
141
- time you will use the "normal" paths, but in the special case of wanting to
142
- extend a template with an overriding version of itself we can reference its
143
- parent's full, unambiguous template path in the extends tag:
144
-
145
- .. code-block:: jinja
146
-
147
- {# page.twig in .../templates/mysite #}
148
- {% extends "default/page.twig" %} {# from .../templates #}
149
-
150
- .. note::
151
-
152
- This recipe was inspired by the following Django wiki page:
153
- http://code.djangoproject.com/wiki/ExtendingTemplates
154
-
155
- Customizing the Syntax
156
- ----------------------
157
-
158
- Twig allows some syntax customization for the block delimiters. It's not
159
- recommended to use this feature as templates will be tied with your custom
160
- syntax. But for specific projects, it can make sense to change the defaults.
161
-
162
- To change the block delimiters, you need to create your own lexer object::
163
-
164
- $twig = new Twig_Environment();
165
-
166
- $lexer = new Twig_Lexer($twig, array(
167
- 'tag_comment' => array('{#', '#}'),
168
- 'tag_block' => array('{%', '%}'),
169
- 'tag_variable' => array('{{', '}}'),
170
- 'interpolation' => array('#{', '}'),
171
- ));
172
- $twig->setLexer($lexer);
173
-
174
- Here are some configuration example that simulates some other template engines
175
- syntax::
176
-
177
- // Ruby erb syntax
178
- $lexer = new Twig_Lexer($twig, array(
179
- 'tag_comment' => array('<%#', '%>'),
180
- 'tag_block' => array('<%', '%>'),
181
- 'tag_variable' => array('<%=', '%>'),
182
- ));
183
-
184
- // SGML Comment Syntax
185
- $lexer = new Twig_Lexer($twig, array(
186
- 'tag_comment' => array('<!--#', '-->'),
187
- 'tag_block' => array('<!--', '-->'),
188
- 'tag_variable' => array('${', '}'),
189
- ));
190
-
191
- // Smarty like
192
- $lexer = new Twig_Lexer($twig, array(
193
- 'tag_comment' => array('{*', '*}'),
194
- 'tag_block' => array('{', '}'),
195
- 'tag_variable' => array('{$', '}'),
196
- ));
197
-
198
- Using dynamic Object Properties
199
- -------------------------------
200
-
201
- When Twig encounters a variable like ``article.title``, it tries to find a
202
- ``title`` public property in the ``article`` object.
203
-
204
- It also works if the property does not exist but is rather defined dynamically
205
- thanks to the magic ``__get()`` method; you just need to also implement the
206
- ``__isset()`` magic method like shown in the following snippet of code::
207
-
208
- class Article
209
- {
210
- public function __get($name)
211
- {
212
- if ('title' == $name) {
213
- return 'The title';
214
- }
215
-
216
- // throw some kind of error
217
- }
218
-
219
- public function __isset($name)
220
- {
221
- if ('title' == $name) {
222
- return true;
223
- }
224
-
225
- return false;
226
- }
227
- }
228
-
229
- Accessing the parent Context in Nested Loops
230
- --------------------------------------------
231
-
232
- Sometimes, when using nested loops, you need to access the parent context. The
233
- parent context is always accessible via the ``loop.parent`` variable. For
234
- instance, if you have the following template data::
235
-
236
- $data = array(
237
- 'topics' => array(
238
- 'topic1' => array('Message 1 of topic 1', 'Message 2 of topic 1'),
239
- 'topic2' => array('Message 1 of topic 2', 'Message 2 of topic 2'),
240
- ),
241
- );
242
-
243
- And the following template to display all messages in all topics:
244
-
245
- .. code-block:: jinja
246
-
247
- {% for topic, messages in topics %}
248
- * {{ loop.index }}: {{ topic }}
249
- {% for message in messages %}
250
- - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}
251
- {% endfor %}
252
- {% endfor %}
253
-
254
- The output will be similar to:
255
-
256
- .. code-block:: text
257
-
258
- * 1: topic1
259
- - 1.1: The message 1 of topic 1
260
- - 1.2: The message 2 of topic 1
261
- * 2: topic2
262
- - 2.1: The message 1 of topic 2
263
- - 2.2: The message 2 of topic 2
264
-
265
- In the inner loop, the ``loop.parent`` variable is used to access the outer
266
- context. So, the index of the current ``topic`` defined in the outer for loop
267
- is accessible via the ``loop.parent.loop.index`` variable.
268
-
269
- Defining undefined Functions and Filters on the Fly
270
- ---------------------------------------------------
271
-
272
- When a function (or a filter) is not defined, Twig defaults to throw a
273
- ``Twig_Error_Syntax`` exception. However, it can also call a `callback`_ (any
274
- valid PHP callable) which should return a function (or a filter).
275
-
276
- For filters, register callbacks with ``registerUndefinedFilterCallback()``.
277
- For functions, use ``registerUndefinedFunctionCallback()``::
278
-
279
- // auto-register all native PHP functions as Twig functions
280
- // don't try this at home as it's not secure at all!
281
- $twig->registerUndefinedFunctionCallback(function ($name) {
282
- if (function_exists($name)) {
283
- return new Twig_SimpleFunction($name, $name);
284
- }
285
-
286
- return false;
287
- });
288
-
289
- If the callable is not able to return a valid function (or filter), it must
290
- return ``false``.
291
-
292
- If you register more than one callback, Twig will call them in turn until one
293
- does not return ``false``.
294
-
295
- .. tip::
296
-
297
- As the resolution of functions and filters is done during compilation,
298
- there is no overhead when registering these callbacks.
299
-
300
- Validating the Template Syntax
301
- ------------------------------
302
-
303
- When template code is provided by a third-party (through a web interface for
304
- instance), it might be interesting to validate the template syntax before
305
- saving it. If the template code is stored in a `$template` variable, here is
306
- how you can do it::
307
-
308
- try {
309
- $twig->parse($twig->tokenize(new Twig_Source($template)));
310
-
311
- // the $template is valid
312
- } catch (Twig_Error_Syntax $e) {
313
- // $template contains one or more syntax errors
314
- }
315
-
316
- If you iterate over a set of files, you can pass the filename to the
317
- ``tokenize()`` method to get the filename in the exception message::
318
-
319
- foreach ($files as $file) {
320
- try {
321
- $twig->parse($twig->tokenize(new Twig_Source($template, $file->getFilename(), $file)));
322
-
323
- // the $template is valid
324
- } catch (Twig_Error_Syntax $e) {
325
- // $template contains one or more syntax errors
326
- }
327
- }
328
-
329
- .. versionadded:: 1.27
330
- ``Twig_Source`` was introduced in version 1.27, pass the source and the
331
- identifier directly on previous versions.
332
-
333
- .. note::
334
-
335
- This method won't catch any sandbox policy violations because the policy
336
- is enforced during template rendering (as Twig needs the context for some
337
- checks like allowed methods on objects).
338
-
339
- Refreshing modified Templates when OPcache or APC is enabled
340
- ------------------------------------------------------------
341
-
342
- When using OPcache with ``opcache.validate_timestamps`` set to ``0`` or APC
343
- with ``apc.stat`` set to ``0`` and Twig cache enabled, clearing the template
344
- cache won't update the cache.
345
-
346
- To get around this, force Twig to invalidate the bytecode cache::
347
-
348
- $twig = new Twig_Environment($loader, array(
349
- 'cache' => new Twig_Cache_Filesystem('/some/cache/path', Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION),
350
- // ...
351
- ));
352
-
353
- .. note::
354
-
355
- Before Twig 1.22, you should extend ``Twig_Environment`` instead::
356
-
357
- class OpCacheAwareTwigEnvironment extends Twig_Environment
358
- {
359
- protected function writeCacheFile($file, $content)
360
- {
361
- parent::writeCacheFile($file, $content);
362
-
363
- // Compile cached file into bytecode cache
364
- if (function_exists('opcache_invalidate')) {
365
- opcache_invalidate($file, true);
366
- } elseif (function_exists('apc_compile_file')) {
367
- apc_compile_file($file);
368
- }
369
- }
370
- }
371
-
372
- Reusing a stateful Node Visitor
373
- -------------------------------
374
-
375
- When attaching a visitor to a ``Twig_Environment`` instance, Twig uses it to
376
- visit *all* templates it compiles. If you need to keep some state information
377
- around, you probably want to reset it when visiting a new template.
378
-
379
- This can be easily achieved with the following code::
380
-
381
- protected $someTemplateState = array();
382
-
383
- public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
384
- {
385
- if ($node instanceof Twig_Node_Module) {
386
- // reset the state as we are entering a new template
387
- $this->someTemplateState = array();
388
- }
389
-
390
- // ...
391
-
392
- return $node;
393
- }
394
-
395
- Using a Database to store Templates
396
- -----------------------------------
397
-
398
- If you are developing a CMS, templates are usually stored in a database. This
399
- recipe gives you a simple PDO template loader you can use as a starting point
400
- for your own.
401
-
402
- First, let's create a temporary in-memory SQLite3 database to work with::
403
-
404
- $dbh = new PDO('sqlite::memory:');
405
- $dbh->exec('CREATE TABLE templates (name STRING, source STRING, last_modified INTEGER)');
406
- $base = '{% block content %}{% endblock %}';
407
- $index = '
408
- {% extends "base.twig" %}
409
- {% block content %}Hello {{ name }}{% endblock %}
410
- ';
411
- $now = time();
412
- $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('base.twig', '$base', $now)");
413
- $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('index.twig', '$index', $now)");
414
-
415
- We have created a simple ``templates`` table that hosts two templates:
416
- ``base.twig`` and ``index.twig``.
417
-
418
- Now, let's define a loader able to use this database::
419
-
420
- class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface
421
- {
422
- protected $dbh;
423
-
424
- public function __construct(PDO $dbh)
425
- {
426
- $this->dbh = $dbh;
427
- }
428
-
429
- public function getSource($name)
430
- {
431
- if (false === $source = $this->getValue('source', $name)) {
432
- throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
433
- }
434
-
435
- return $source;
436
- }
437
-
438
- // Twig_SourceContextLoaderInterface as of Twig 1.27
439
- public function getSourceContext($name)
440
- {
441
- if (false === $source = $this->getValue('source', $name)) {
442
- throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
443
- }
444
-
445
- return new Twig_Source($source, $name);
446
- }
447
-
448
- // Twig_ExistsLoaderInterface as of Twig 1.11
449
- public function exists($name)
450
- {
451
- return $name === $this->getValue('name', $name);
452
- }
453
-
454
- public function getCacheKey($name)
455
- {
456
- return $name;
457
- }
458
-
459
- public function isFresh($name, $time)
460
- {
461
- if (false === $lastModified = $this->getValue('last_modified', $name)) {
462
- return false;
463
- }
464
-
465
- return $lastModified <= $time;
466
- }
467
-
468
- protected function getValue($column, $name)
469
- {
470
- $sth = $this->dbh->prepare('SELECT '.$column.' FROM templates WHERE name = :name');
471
- $sth->execute(array(':name' => (string) $name));
472
-
473
- return $sth->fetchColumn();
474
- }
475
- }
476
-
477
- Finally, here is an example on how you can use it::
478
-
479
- $loader = new DatabaseTwigLoader($dbh);
480
- $twig = new Twig_Environment($loader);
481
-
482
- echo $twig->render('index.twig', array('name' => 'Fabien'));
483
-
484
- Using different Template Sources
485
- --------------------------------
486
-
487
- This recipe is the continuation of the previous one. Even if you store the
488
- contributed templates in a database, you might want to keep the original/base
489
- templates on the filesystem. When templates can be loaded from different
490
- sources, you need to use the ``Twig_Loader_Chain`` loader.
491
-
492
- As you can see in the previous recipe, we reference the template in the exact
493
- same way as we would have done it with a regular filesystem loader. This is
494
- the key to be able to mix and match templates coming from the database, the
495
- filesystem, or any other loader for that matter: the template name should be a
496
- logical name, and not the path from the filesystem::
497
-
498
- $loader1 = new DatabaseTwigLoader($dbh);
499
- $loader2 = new Twig_Loader_Array(array(
500
- 'base.twig' => '{% block content %}{% endblock %}',
501
- ));
502
- $loader = new Twig_Loader_Chain(array($loader1, $loader2));
503
-
504
- $twig = new Twig_Environment($loader);
505
-
506
- echo $twig->render('index.twig', array('name' => 'Fabien'));
507
-
508
- Now that the ``base.twig`` templates is defined in an array loader, you can
509
- remove it from the database, and everything else will still work as before.
510
-
511
- Loading a Template from a String
512
- --------------------------------
513
-
514
- From a template, you can easily load a template stored in a string via the
515
- ``template_from_string`` function (available as of Twig 1.11 via the
516
- ``Twig_Extension_StringLoader`` extension):
517
-
518
- .. code-block:: jinja
519
-
520
- {{ include(template_from_string("Hello {{ name }}")) }}
521
-
522
- From PHP, it's also possible to load a template stored in a string via
523
- ``Twig_Environment::createTemplate()`` (available as of Twig 1.18)::
524
-
525
- $template = $twig->createTemplate('hello {{ name }}');
526
- echo $template->render(array('name' => 'Fabien'));
527
-
528
- .. note::
529
-
530
- Never use the ``Twig_Loader_String`` loader, which has severe limitations.
531
-
532
- Using Twig and AngularJS in the same Templates
533
- ----------------------------------------------
534
-
535
- Mixing different template syntaxes in the same file is not a recommended
536
- practice as both AngularJS and Twig use the same delimiters in their syntax:
537
- ``{{`` and ``}}``.
538
-
539
- Still, if you want to use AngularJS and Twig in the same template, there are
540
- two ways to make it work depending on the amount of AngularJS you need to
541
- include in your templates:
542
-
543
- * Escaping the AngularJS delimiters by wrapping AngularJS sections with the
544
- ``{% verbatim %}`` tag or by escaping each delimiter via ``{{ '{{' }}`` and
545
- ``{{ '}}' }}``;
546
-
547
- * Changing the delimiters of one of the template engines (depending on which
548
- engine you introduced last):
549
-
550
- * For AngularJS, change the interpolation tags using the
551
- ``interpolateProvider`` service, for instance at the module initialization
552
- time:
553
-
554
- .. code-block:: javascript
555
-
556
- angular.module('myApp', []).config(function($interpolateProvider) {
557
- $interpolateProvider.startSymbol('{[').endSymbol(']}');
558
- });
559
-
560
- * For Twig, change the delimiters via the ``tag_variable`` Lexer option:
561
-
562
- .. code-block:: php
563
-
564
- $env->setLexer(new Twig_Lexer($env, array(
565
- 'tag_variable' => array('{[', ']}'),
566
- )));
567
-
568
- .. _callback: http://www.php.net/manual/en/function.is-callable.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/autoescape.rst DELETED
@@ -1,81 +0,0 @@
1
- ``autoescape``
2
- ==============
3
-
4
- Whether automatic escaping is enabled or not, you can mark a section of a
5
- template to be escaped or not by using the ``autoescape`` tag:
6
-
7
- .. code-block:: jinja
8
-
9
- {% autoescape %}
10
- Everything will be automatically escaped in this block
11
- using the HTML strategy
12
- {% endautoescape %}
13
-
14
- {% autoescape 'html' %}
15
- Everything will be automatically escaped in this block
16
- using the HTML strategy
17
- {% endautoescape %}
18
-
19
- {% autoescape 'js' %}
20
- Everything will be automatically escaped in this block
21
- using the js escaping strategy
22
- {% endautoescape %}
23
-
24
- {% autoescape false %}
25
- Everything will be outputted as is in this block
26
- {% endautoescape %}
27
-
28
- .. note::
29
-
30
- Before Twig 1.8, the syntax was different:
31
-
32
- .. code-block:: jinja
33
-
34
- {% autoescape true %}
35
- Everything will be automatically escaped in this block
36
- using the HTML strategy
37
- {% endautoescape %}
38
-
39
- {% autoescape false %}
40
- Everything will be outputted as is in this block
41
- {% endautoescape %}
42
-
43
- {% autoescape true js %}
44
- Everything will be automatically escaped in this block
45
- using the js escaping strategy
46
- {% endautoescape %}
47
-
48
- When automatic escaping is enabled everything is escaped by default except for
49
- values explicitly marked as safe. Those can be marked in the template by using
50
- the :doc:`raw<../filters/raw>` filter:
51
-
52
- .. code-block:: jinja
53
-
54
- {% autoescape %}
55
- {{ safe_value|raw }}
56
- {% endautoescape %}
57
-
58
- Functions returning template data (like :doc:`macros<macro>` and
59
- :doc:`parent<../functions/parent>`) always return safe markup.
60
-
61
- .. note::
62
-
63
- Twig is smart enough to not escape an already escaped value by the
64
- :doc:`escape<../filters/escape>` filter.
65
-
66
- .. note::
67
-
68
- Twig does not escape static expressions:
69
-
70
- .. code-block:: jinja
71
-
72
- {% set hello = "<strong>Hello</strong>" %}
73
- {{ hello }}
74
- {{ "<strong>world</strong>" }}
75
-
76
- Will be rendered "<strong>Hello</strong> **world**".
77
-
78
- .. note::
79
-
80
- The chapter :doc:`Twig for Developers<../api>` gives more information
81
- about when and how automatic escaping is applied.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/block.rst DELETED
@@ -1,11 +0,0 @@
1
- ``block``
2
- =========
3
-
4
- Blocks are used for inheritance and act as placeholders and replacements at
5
- the same time. They are documented in detail in the documentation for the
6
- :doc:`extends<../tags/extends>` tag.
7
-
8
- Block names should consist of alphanumeric characters, and underscores. Dashes
9
- are not permitted.
10
-
11
- .. seealso:: :doc:`block<../functions/block>`, :doc:`parent<../functions/parent>`, :doc:`use<../tags/use>`, :doc:`extends<../tags/extends>`
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/do.rst DELETED
@@ -1,12 +0,0 @@
1
- ``do``
2
- ======
3
-
4
- .. versionadded:: 1.5
5
- The ``do`` tag was added in Twig 1.5.
6
-
7
- The ``do`` tag works exactly like the regular variable expression (``{{ ...
8
- }}``) just that it doesn't print anything:
9
-
10
- .. code-block:: jinja
11
-
12
- {% do 1 + 2 %}
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/embed.rst DELETED
@@ -1,178 +0,0 @@
1
- ``embed``
2
- =========
3
-
4
- .. versionadded:: 1.8
5
- The ``embed`` tag was added in Twig 1.8.
6
-
7
- The ``embed`` tag combines the behaviour of :doc:`include<include>` and
8
- :doc:`extends<extends>`.
9
- It allows you to include another template's contents, just like ``include``
10
- does. But it also allows you to override any block defined inside the
11
- included template, like when extending a template.
12
-
13
- Think of an embedded template as a "micro layout skeleton".
14
-
15
- .. code-block:: jinja
16
-
17
- {% embed "teasers_skeleton.twig" %}
18
- {# These blocks are defined in "teasers_skeleton.twig" #}
19
- {# and we override them right here: #}
20
- {% block left_teaser %}
21
- Some content for the left teaser box
22
- {% endblock %}
23
- {% block right_teaser %}
24
- Some content for the right teaser box
25
- {% endblock %}
26
- {% endembed %}
27
-
28
- The ``embed`` tag takes the idea of template inheritance to the level of
29
- content fragments. While template inheritance allows for "document skeletons",
30
- which are filled with life by child templates, the ``embed`` tag allows you to
31
- create "skeletons" for smaller units of content and re-use and fill them
32
- anywhere you like.
33
-
34
- Since the use case may not be obvious, let's look at a simplified example.
35
- Imagine a base template shared by multiple HTML pages, defining a single block
36
- named "content":
37
-
38
- .. code-block:: text
39
-
40
- ┌─── page layout ─────────────────────┐
41
- │ │
42
- │ ┌── block "content" ──┐ │
43
- │ │ │ │
44
- │ │ │ │
45
- │ │ (child template to │ │
46
- │ │ put content here) │ │
47
- │ │ │ │
48
- │ │ │ │
49
- │ └─────────────────────┘ │
50
- │ │
51
- └─────────────────────────────────────┘
52
-
53
- Some pages ("foo" and "bar") share the same content structure -
54
- two vertically stacked boxes:
55
-
56
- .. code-block:: text
57
-
58
- ┌─── page layout ─────────────────────┐
59
- │ │
60
- │ ┌── block "content" ──┐ │
61
- │ │ ┌─ block "top" ───┐ │ │
62
- │ │ │ │ │ │
63
- │ │ └─────────────────┘ │ │
64
- │ │ ┌─ block "bottom" ┐ │ │
65
- │ │ │ │ │ │
66
- │ │ └─────────────────┘ │ │
67
- │ └─────────────────────┘ │
68
- │ │
69
- └─────────────────────────────────────┘
70
-
71
- While other pages ("boom" and "baz") share a different content structure -
72
- two boxes side by side:
73
-
74
- .. code-block:: text
75
-
76
- ┌─── page layout ─────────────────────┐
77
- │ │
78
- │ ┌── block "content" ──┐ │
79
- │ │ │ │
80
- │ │ ┌ block ┐ ┌ block ┐ │ │
81
- │ │ │"left" │ │"right"│ │ │
82
- │ │ │ │ │ │ │ │
83
- │ │ │ │ │ │ │ │
84
- │ │ └───────┘ └───────┘ │ │
85
- │ └─────────────────────┘ │
86
- │ │
87
- └─────────────────────────────────────┘
88
-
89
- Without the ``embed`` tag, you have two ways to design your templates:
90
-
91
- * Create two "intermediate" base templates that extend the master layout
92
- template: one with vertically stacked boxes to be used by the "foo" and
93
- "bar" pages and another one with side-by-side boxes for the "boom" and
94
- "baz" pages.
95
-
96
- * Embed the markup for the top/bottom and left/right boxes into each page
97
- template directly.
98
-
99
- These two solutions do not scale well because they each have a major drawback:
100
-
101
- * The first solution may indeed work for this simplified example. But imagine
102
- we add a sidebar, which may again contain different, recurring structures
103
- of content. Now we would need to create intermediate base templates for
104
- all occurring combinations of content structure and sidebar structure...
105
- and so on.
106
-
107
- * The second solution involves duplication of common code with all its negative
108
- consequences: any change involves finding and editing all affected copies
109
- of the structure, correctness has to be verified for each copy, copies may
110
- go out of sync by careless modifications etc.
111
-
112
- In such a situation, the ``embed`` tag comes in handy. The common layout
113
- code can live in a single base template, and the two different content structures,
114
- let's call them "micro layouts" go into separate templates which are embedded
115
- as necessary:
116
-
117
- Page template ``foo.twig``:
118
-
119
- .. code-block:: jinja
120
-
121
- {% extends "layout_skeleton.twig" %}
122
-
123
- {% block content %}
124
- {% embed "vertical_boxes_skeleton.twig" %}
125
- {% block top %}
126
- Some content for the top box
127
- {% endblock %}
128
-
129
- {% block bottom %}
130
- Some content for the bottom box
131
- {% endblock %}
132
- {% endembed %}
133
- {% endblock %}
134
-
135
- And here is the code for ``vertical_boxes_skeleton.twig``:
136
-
137
- .. code-block:: html+jinja
138
-
139
- <div class="top_box">
140
- {% block top %}
141
- Top box default content
142
- {% endblock %}
143
- </div>
144
-
145
- <div class="bottom_box">
146
- {% block bottom %}
147
- Bottom box default content
148
- {% endblock %}
149
- </div>
150
-
151
- The goal of the ``vertical_boxes_skeleton.twig`` template being to factor
152
- out the HTML markup for the boxes.
153
-
154
- The ``embed`` tag takes the exact same arguments as the ``include`` tag:
155
-
156
- .. code-block:: jinja
157
-
158
- {% embed "base" with {'foo': 'bar'} %}
159
- ...
160
- {% endembed %}
161
-
162
- {% embed "base" with {'foo': 'bar'} only %}
163
- ...
164
- {% endembed %}
165
-
166
- {% embed "base" ignore missing %}
167
- ...
168
- {% endembed %}
169
-
170
- .. warning::
171
-
172
- As embedded templates do not have "names", auto-escaping strategies based
173
- on the template name won't work as expected if you change the context (for
174
- instance, if you embed a CSS/JavaScript template into an HTML one). In that
175
- case, explicitly set the default auto-escaping strategy with the
176
- ``autoescape`` tag.
177
-
178
- .. seealso:: :doc:`include<../tags/include>`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/extends.rst DELETED
@@ -1,272 +0,0 @@
1
- ``extends``
2
- ===========
3
-
4
- The ``extends`` tag can be used to extend a template from another one.
5
-
6
- .. note::
7
-
8
- Like PHP, Twig does not support multiple inheritance. So you can only have
9
- one extends tag called per rendering. However, Twig supports horizontal
10
- :doc:`reuse<use>`.
11
-
12
- Let's define a base template, ``base.html``, which defines a simple HTML
13
- skeleton document:
14
-
15
- .. code-block:: html+jinja
16
-
17
- <!DOCTYPE html>
18
- <html>
19
- <head>
20
- {% block head %}
21
- <link rel="stylesheet" href="style.css" />
22
- <title>{% block title %}{% endblock %} - My Webpage</title>
23
- {% endblock %}
24
- </head>
25
- <body>
26
- <div id="content">{% block content %}{% endblock %}</div>
27
- <div id="footer">
28
- {% block footer %}
29
- &copy; Copyright 2011 by <a href="http://domain.invalid/">you</a>.
30
- {% endblock %}
31
- </div>
32
- </body>
33
- </html>
34
-
35
- In this example, the :doc:`block<block>` tags define four blocks that child
36
- templates can fill in.
37
-
38
- All the ``block`` tag does is to tell the template engine that a child
39
- template may override those portions of the template.
40
-
41
- Child Template
42
- --------------
43
-
44
- A child template might look like this:
45
-
46
- .. code-block:: jinja
47
-
48
- {% extends "base.html" %}
49
-
50
- {% block title %}Index{% endblock %}
51
- {% block head %}
52
- {{ parent() }}
53
- <style type="text/css">
54
- .important { color: #336699; }
55
- </style>
56
- {% endblock %}
57
- {% block content %}
58
- <h1>Index</h1>
59
- <p class="important">
60
- Welcome on my awesome homepage.
61
- </p>
62
- {% endblock %}
63
-
64
- The ``extends`` tag is the key here. It tells the template engine that this
65
- template "extends" another template. When the template system evaluates this
66
- template, first it locates the parent. The extends tag should be the first tag
67
- in the template.
68
-
69
- Note that since the child template doesn't define the ``footer`` block, the
70
- value from the parent template is used instead.
71
-
72
- You can't define multiple ``block`` tags with the same name in the same
73
- template. This limitation exists because a block tag works in "both"
74
- directions. That is, a block tag doesn't just provide a hole to fill - it also
75
- defines the content that fills the hole in the *parent*. If there were two
76
- similarly-named ``block`` tags in a template, that template's parent wouldn't
77
- know which one of the blocks' content to use.
78
-
79
- If you want to print a block multiple times you can however use the
80
- ``block`` function:
81
-
82
- .. code-block:: jinja
83
-
84
- <title>{% block title %}{% endblock %}</title>
85
- <h1>{{ block('title') }}</h1>
86
- {% block body %}{% endblock %}
87
-
88
- Parent Blocks
89
- -------------
90
-
91
- It's possible to render the contents of the parent block by using the
92
- :doc:`parent<../functions/parent>` function. This gives back the results of
93
- the parent block:
94
-
95
- .. code-block:: jinja
96
-
97
- {% block sidebar %}
98
- <h3>Table Of Contents</h3>
99
- ...
100
- {{ parent() }}
101
- {% endblock %}
102
-
103
- Named Block End-Tags
104
- --------------------
105
-
106
- Twig allows you to put the name of the block after the end tag for better
107
- readability:
108
-
109
- .. code-block:: jinja
110
-
111
- {% block sidebar %}
112
- {% block inner_sidebar %}
113
- ...
114
- {% endblock inner_sidebar %}
115
- {% endblock sidebar %}
116
-
117
- Of course, the name after the ``endblock`` word must match the block name.
118
-
119
- Block Nesting and Scope
120
- -----------------------
121
-
122
- Blocks can be nested for more complex layouts. Per default, blocks have access
123
- to variables from outer scopes:
124
-
125
- .. code-block:: jinja
126
-
127
- {% for item in seq %}
128
- <li>{% block loop_item %}{{ item }}{% endblock %}</li>
129
- {% endfor %}
130
-
131
- Block Shortcuts
132
- ---------------
133
-
134
- For blocks with little content, it's possible to use a shortcut syntax. The
135
- following constructs do the same thing:
136
-
137
- .. code-block:: jinja
138
-
139
- {% block title %}
140
- {{ page_title|title }}
141
- {% endblock %}
142
-
143
- .. code-block:: jinja
144
-
145
- {% block title page_title|title %}
146
-
147
- Dynamic Inheritance
148
- -------------------
149
-
150
- Twig supports dynamic inheritance by using a variable as the base template:
151
-
152
- .. code-block:: jinja
153
-
154
- {% extends some_var %}
155
-
156
- If the variable evaluates to a ``Twig_Template`` or a ``Twig_TemplateWrapper``
157
- instance, Twig will use it as the parent template::
158
-
159
- // {% extends layout %}
160
-
161
- // deprecated as of Twig 1.28
162
- $layout = $twig->loadTemplate('some_layout_template.twig');
163
-
164
- // as of Twig 1.28
165
- $layout = $twig->load('some_layout_template.twig');
166
-
167
- $twig->display('template.twig', array('layout' => $layout));
168
-
169
- .. versionadded:: 1.2
170
- The possibility to pass an array of templates has been added in Twig 1.2.
171
-
172
- You can also provide a list of templates that are checked for existence. The
173
- first template that exists will be used as a parent:
174
-
175
- .. code-block:: jinja
176
-
177
- {% extends ['layout.html', 'base_layout.html'] %}
178
-
179
- Conditional Inheritance
180
- -----------------------
181
-
182
- As the template name for the parent can be any valid Twig expression, it's
183
- possible to make the inheritance mechanism conditional:
184
-
185
- .. code-block:: jinja
186
-
187
- {% extends standalone ? "minimum.html" : "base.html" %}
188
-
189
- In this example, the template will extend the "minimum.html" layout template
190
- if the ``standalone`` variable evaluates to ``true``, and "base.html"
191
- otherwise.
192
-
193
- How do blocks work?
194
- -------------------
195
-
196
- A block provides a way to change how a certain part of a template is rendered
197
- but it does not interfere in any way with the logic around it.
198
-
199
- Let's take the following example to illustrate how a block works and more
200
- importantly, how it does not work:
201
-
202
- .. code-block:: jinja
203
-
204
- {# base.twig #}
205
-
206
- {% for post in posts %}
207
- {% block post %}
208
- <h1>{{ post.title }}</h1>
209
- <p>{{ post.body }}</p>
210
- {% endblock %}
211
- {% endfor %}
212
-
213
- If you render this template, the result would be exactly the same with or
214
- without the ``block`` tag. The ``block`` inside the ``for`` loop is just a way
215
- to make it overridable by a child template:
216
-
217
- .. code-block:: jinja
218
-
219
- {# child.twig #}
220
-
221
- {% extends "base.twig" %}
222
-
223
- {% block post %}
224
- <article>
225
- <header>{{ post.title }}</header>
226
- <section>{{ post.text }}</section>
227
- </article>
228
- {% endblock %}
229
-
230
- Now, when rendering the child template, the loop is going to use the block
231
- defined in the child template instead of the one defined in the base one; the
232
- executed template is then equivalent to the following one:
233
-
234
- .. code-block:: jinja
235
-
236
- {% for post in posts %}
237
- <article>
238
- <header>{{ post.title }}</header>
239
- <section>{{ post.text }}</section>
240
- </article>
241
- {% endfor %}
242
-
243
- Let's take another example: a block included within an ``if`` statement:
244
-
245
- .. code-block:: jinja
246
-
247
- {% if posts is empty %}
248
- {% block head %}
249
- {{ parent() }}
250
-
251
- <meta name="robots" content="noindex, follow">
252
- {% endblock head %}
253
- {% endif %}
254
-
255
- Contrary to what you might think, this template does not define a block
256
- conditionally; it just makes overridable by a child template the output of
257
- what will be rendered when the condition is ``true``.
258
-
259
- If you want the output to be displayed conditionally, use the following
260
- instead:
261
-
262
- .. code-block:: jinja
263
-
264
- {% block head %}
265
- {{ parent() }}
266
-
267
- {% if posts is empty %}
268
- <meta name="robots" content="noindex, follow">
269
- {% endif %}
270
- {% endblock head %}
271
-
272
- .. seealso:: :doc:`block<../functions/block>`, :doc:`block<../tags/block>`, :doc:`parent<../functions/parent>`, :doc:`use<../tags/use>`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/filter.rst DELETED
@@ -1,21 +0,0 @@
1
- ``filter``
2
- ==========
3
-
4
- Filter sections allow you to apply regular Twig filters on a block of template
5
- data. Just wrap the code in the special ``filter`` section:
6
-
7
- .. code-block:: jinja
8
-
9
- {% filter upper %}
10
- This text becomes uppercase
11
- {% endfilter %}
12
-
13
- You can also chain filters:
14
-
15
- .. code-block:: jinja
16
-
17
- {% filter lower|escape %}
18
- <strong>SOME TEXT</strong>
19
- {% endfilter %}
20
-
21
- {# outputs "&lt;strong&gt;some text&lt;/strong&gt;" #}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/flush.rst DELETED
@@ -1,17 +0,0 @@
1
- ``flush``
2
- =========
3
-
4
- .. versionadded:: 1.5
5
- The flush tag was added in Twig 1.5.
6
-
7
- The ``flush`` tag tells Twig to flush the output buffer:
8
-
9
- .. code-block:: jinja
10
-
11
- {% flush %}
12
-
13
- .. note::
14
-
15
- Internally, Twig uses the PHP `flush`_ function.
16
-
17
- .. _`flush`: http://php.net/flush
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/for.rst DELETED
@@ -1,172 +0,0 @@
1
- ``for``
2
- =======
3
-
4
- Loop over each item in a sequence. For example, to display a list of users
5
- provided in a variable called ``users``:
6
-
7
- .. code-block:: jinja
8
-
9
- <h1>Members</h1>
10
- <ul>
11
- {% for user in users %}
12
- <li>{{ user.username|e }}</li>
13
- {% endfor %}
14
- </ul>
15
-
16
- .. note::
17
-
18
- A sequence can be either an array or an object implementing the
19
- ``Traversable`` interface.
20
-
21
- If you do need to iterate over a sequence of numbers, you can use the ``..``
22
- operator:
23
-
24
- .. code-block:: jinja
25
-
26
- {% for i in 0..10 %}
27
- * {{ i }}
28
- {% endfor %}
29
-
30
- The above snippet of code would print all numbers from 0 to 10.
31
-
32
- It can be also useful with letters:
33
-
34
- .. code-block:: jinja
35
-
36
- {% for letter in 'a'..'z' %}
37
- * {{ letter }}
38
- {% endfor %}
39
-
40
- The ``..`` operator can take any expression at both sides:
41
-
42
- .. code-block:: jinja
43
-
44
- {% for letter in 'a'|upper..'z'|upper %}
45
- * {{ letter }}
46
- {% endfor %}
47
-
48
- .. tip:
49
-
50
- If you need a step different from 1, you can use the ``range`` function
51
- instead.
52
-
53
- The `loop` variable
54
- -------------------
55
-
56
- Inside of a ``for`` loop block you can access some special variables:
57
-
58
- ===================== =============================================================
59
- Variable Description
60
- ===================== =============================================================
61
- ``loop.index`` The current iteration of the loop. (1 indexed)
62
- ``loop.index0`` The current iteration of the loop. (0 indexed)
63
- ``loop.revindex`` The number of iterations from the end of the loop (1 indexed)
64
- ``loop.revindex0`` The number of iterations from the end of the loop (0 indexed)
65
- ``loop.first`` True if first iteration
66
- ``loop.last`` True if last iteration
67
- ``loop.length`` The number of items in the sequence
68
- ``loop.parent`` The parent context
69
- ===================== =============================================================
70
-
71
- .. code-block:: jinja
72
-
73
- {% for user in users %}
74
- {{ loop.index }} - {{ user.username }}
75
- {% endfor %}
76
-
77
- .. note::
78
-
79
- The ``loop.length``, ``loop.revindex``, ``loop.revindex0``, and
80
- ``loop.last`` variables are only available for PHP arrays, or objects that
81
- implement the ``Countable`` interface. They are also not available when
82
- looping with a condition.
83
-
84
- .. versionadded:: 1.2
85
- The ``if`` modifier support has been added in Twig 1.2.
86
-
87
- Adding a condition
88
- ------------------
89
-
90
- Unlike in PHP, it's not possible to ``break`` or ``continue`` in a loop. You
91
- can however filter the sequence during iteration which allows you to skip
92
- items. The following example skips all the users which are not active:
93
-
94
- .. code-block:: jinja
95
-
96
- <ul>
97
- {% for user in users if user.active %}
98
- <li>{{ user.username|e }}</li>
99
- {% endfor %}
100
- </ul>
101
-
102
- The advantage is that the special loop variable will count correctly thus not
103
- counting the users not iterated over. Keep in mind that properties like
104
- ``loop.last`` will not be defined when using loop conditions.
105
-
106
- .. note::
107
-
108
- Using the ``loop`` variable within the condition is not recommended as it
109
- will probably not be doing what you expect it to. For instance, adding a
110
- condition like ``loop.index > 4`` won't work as the index is only
111
- incremented when the condition is true (so the condition will never
112
- match).
113
-
114
- The `else` Clause
115
- -----------------
116
-
117
- If no iteration took place because the sequence was empty, you can render a
118
- replacement block by using ``else``:
119
-
120
- .. code-block:: jinja
121
-
122
- <ul>
123
- {% for user in users %}
124
- <li>{{ user.username|e }}</li>
125
- {% else %}
126
- <li><em>no user found</em></li>
127
- {% endfor %}
128
- </ul>
129
-
130
- Iterating over Keys
131
- -------------------
132
-
133
- By default, a loop iterates over the values of the sequence. You can iterate
134
- on keys by using the ``keys`` filter:
135
-
136
- .. code-block:: jinja
137
-
138
- <h1>Members</h1>
139
- <ul>
140
- {% for key in users|keys %}
141
- <li>{{ key }}</li>
142
- {% endfor %}
143
- </ul>
144
-
145
- Iterating over Keys and Values
146
- ------------------------------
147
-
148
- You can also access both keys and values:
149
-
150
- .. code-block:: jinja
151
-
152
- <h1>Members</h1>
153
- <ul>
154
- {% for key, user in users %}
155
- <li>{{ key }}: {{ user.username|e }}</li>
156
- {% endfor %}
157
- </ul>
158
-
159
- Iterating over a Subset
160
- -----------------------
161
-
162
- You might want to iterate over a subset of values. This can be achieved using
163
- the :doc:`slice <../filters/slice>` filter:
164
-
165
- .. code-block:: jinja
166
-
167
- <h1>Top Ten Members</h1>
168
- <ul>
169
- {% for user in users|slice(0, 10) %}
170
- <li>{{ user.username|e }}</li>
171
- {% endfor %}
172
- </ul>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/from.rst DELETED
@@ -1,8 +0,0 @@
1
- ``from``
2
- ========
3
-
4
- The ``from`` tag imports :doc:`macro<../tags/macro>` names into the current
5
- namespace. The tag is documented in detail in the documentation for the
6
- :doc:`import<../tags/import>` tag.
7
-
8
- .. seealso:: :doc:`macro<../tags/macro>`, :doc:`import<../tags/import>`
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/if.rst DELETED
@@ -1,76 +0,0 @@
1
- ``if``
2
- ======
3
-
4
- The ``if`` statement in Twig is comparable with the if statements of PHP.
5
-
6
- In the simplest form you can use it to test if an expression evaluates to
7
- ``true``:
8
-
9
- .. code-block:: jinja
10
-
11
- {% if online == false %}
12
- <p>Our website is in maintenance mode. Please, come back later.</p>
13
- {% endif %}
14
-
15
- You can also test if an array is not empty:
16
-
17
- .. code-block:: jinja
18
-
19
- {% if users %}
20
- <ul>
21
- {% for user in users %}
22
- <li>{{ user.username|e }}</li>
23
- {% endfor %}
24
- </ul>
25
- {% endif %}
26
-
27
- .. note::
28
-
29
- If you want to test if the variable is defined, use ``if users is
30
- defined`` instead.
31
-
32
- You can also use ``not`` to check for values that evaluate to ``false``:
33
-
34
- .. code-block:: jinja
35
-
36
- {% if not user.subscribed %}
37
- <p>You are not subscribed to our mailing list.</p>
38
- {% endif %}
39
-
40
- For multiple conditions, ``and`` and ``or`` can be used:
41
-
42
- .. code-block:: jinja
43
-
44
- {% if temperature > 18 and temperature < 27 %}
45
- <p>It's a nice day for a walk in the park.</p>
46
- {% endif %}
47
-
48
- For multiple branches ``elseif`` and ``else`` can be used like in PHP. You can
49
- use more complex ``expressions`` there too:
50
-
51
- .. code-block:: jinja
52
-
53
- {% if kenny.sick %}
54
- Kenny is sick.
55
- {% elseif kenny.dead %}
56
- You killed Kenny! You bastard!!!
57
- {% else %}
58
- Kenny looks okay --- so far
59
- {% endif %}
60
-
61
- .. note::
62
-
63
- The rules to determine if an expression is ``true`` or ``false`` are the
64
- same as in PHP; here are the edge cases rules:
65
-
66
- ====================== ====================
67
- Value Boolean evaluation
68
- ====================== ====================
69
- empty string false
70
- numeric zero false
71
- whitespace-only string true
72
- empty array false
73
- null false
74
- non-empty array true
75
- object true
76
- ====================== ====================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/import.rst DELETED
@@ -1,57 +0,0 @@
1
- ``import``
2
- ==========
3
-
4
- Twig supports putting often used code into :doc:`macros<../tags/macro>`. These
5
- macros can go into different templates and get imported from there.
6
-
7
- There are two ways to import templates. You can import the complete template
8
- into a variable or request specific macros from it.
9
-
10
- Imagine we have a helper module that renders forms (called ``forms.html``):
11
-
12
- .. code-block:: jinja
13
-
14
- {% macro input(name, value, type, size) %}
15
- <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
16
- {% endmacro %}
17
-
18
- {% macro textarea(name, value, rows, cols) %}
19
- <textarea name="{{ name }}" rows="{{ rows|default(10) }}" cols="{{ cols|default(40) }}">{{ value|e }}</textarea>
20
- {% endmacro %}
21
-
22
- The easiest and most flexible is importing the whole module into a variable.
23
- That way you can access the attributes:
24
-
25
- .. code-block:: jinja
26
-
27
- {% import 'forms.html' as forms %}
28
-
29
- <dl>
30
- <dt>Username</dt>
31
- <dd>{{ forms.input('username') }}</dd>
32
- <dt>Password</dt>
33
- <dd>{{ forms.input('password', null, 'password') }}</dd>
34
- </dl>
35
- <p>{{ forms.textarea('comment') }}</p>
36
-
37
- Alternatively you can import names from the template into the current
38
- namespace:
39
-
40
- .. code-block:: jinja
41
-
42
- {% from 'forms.html' import input as input_field, textarea %}
43
-
44
- <dl>
45
- <dt>Username</dt>
46
- <dd>{{ input_field('username') }}</dd>
47
- <dt>Password</dt>
48
- <dd>{{ input_field('password', '', 'password') }}</dd>
49
- </dl>
50
- <p>{{ textarea('comment') }}</p>
51
-
52
- .. tip::
53
-
54
- To import macros from the current file, use the special ``_self`` variable
55
- for the source.
56
-
57
- .. seealso:: :doc:`macro<../tags/macro>`, :doc:`from<../tags/from>`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/include.rst DELETED
@@ -1,90 +0,0 @@
1
- ``include``
2
- ===========
3
-
4
- The ``include`` statement includes a template and returns the rendered content
5
- of that file into the current namespace:
6
-
7
- .. code-block:: jinja
8
-
9
- {% include 'header.html' %}
10
- Body
11
- {% include 'footer.html' %}
12
-
13
- Included templates have access to the variables of the active context.
14
-
15
- If you are using the filesystem loader, the templates are looked for in the
16
- paths defined by it.
17
-
18
- You can add additional variables by passing them after the ``with`` keyword:
19
-
20
- .. code-block:: jinja
21
-
22
- {# template.html will have access to the variables from the current context and the additional ones provided #}
23
- {% include 'template.html' with {'foo': 'bar'} %}
24
-
25
- {% set vars = {'foo': 'bar'} %}
26
- {% include 'template.html' with vars %}
27
-
28
- You can disable access to the context by appending the ``only`` keyword:
29
-
30
- .. code-block:: jinja
31
-
32
- {# only the foo variable will be accessible #}
33
- {% include 'template.html' with {'foo': 'bar'} only %}
34
-
35
- .. code-block:: jinja
36
-
37
- {# no variables will be accessible #}
38
- {% include 'template.html' only %}
39
-
40
- .. tip::
41
-
42
- When including a template created by an end user, you should consider
43
- sandboxing it. More information in the :doc:`Twig for Developers<../api>`
44
- chapter and in the :doc:`sandbox<../tags/sandbox>` tag documentation.
45
-
46
- The template name can be any valid Twig expression:
47
-
48
- .. code-block:: jinja
49
-
50
- {% include some_var %}
51
- {% include ajax ? 'ajax.html' : 'not_ajax.html' %}
52
-
53
- And if the expression evaluates to a ``Twig_Template`` or a
54
- ``Twig_TemplateWrapper`` instance, Twig will use it directly::
55
-
56
- // {% include template %}
57
-
58
- // deprecated as of Twig 1.28
59
- $template = $twig->loadTemplate('some_template.twig');
60
-
61
- // as of Twig 1.28
62
- $template = $twig->load('some_template.twig');
63
-
64
- $twig->display('template.twig', array('template' => $template));
65
-
66
- .. versionadded:: 1.2
67
- The ``ignore missing`` feature has been added in Twig 1.2.
68
-
69
- You can mark an include with ``ignore missing`` in which case Twig will ignore
70
- the statement if the template to be included does not exist. It has to be
71
- placed just after the template name. Here some valid examples:
72
-
73
- .. code-block:: jinja
74
-
75
- {% include 'sidebar.html' ignore missing %}
76
- {% include 'sidebar.html' ignore missing with {'foo': 'bar'} %}
77
- {% include 'sidebar.html' ignore missing only %}
78
-
79
- .. versionadded:: 1.2
80
- The possibility to pass an array of templates has been added in Twig 1.2.
81
-
82
- You can also provide a list of templates that are checked for existence before
83
- inclusion. The first template that exists will be included:
84
-
85
- .. code-block:: jinja
86
-
87
- {% include ['page_detailed.html', 'page.html'] %}
88
-
89
- If ``ignore missing`` is given, it will fall back to rendering nothing if none
90
- of the templates exist, otherwise it will throw an exception.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/index.rst DELETED
@@ -1,25 +0,0 @@
1
- Tags
2
- ====
3
-
4
- .. toctree::
5
- :maxdepth: 1
6
-
7
- autoescape
8
- block
9
- do
10
- embed
11
- extends
12
- filter
13
- flush
14
- for
15
- from
16
- if
17
- import
18
- include
19
- macro
20
- sandbox
21
- set
22
- spaceless
23
- use
24
- verbatim
25
- with
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/macro.rst DELETED
@@ -1,103 +0,0 @@
1
- ``macro``
2
- =========
3
-
4
- Macros are comparable with functions in regular programming languages. They
5
- are useful to put often used HTML idioms into reusable elements to not repeat
6
- yourself.
7
-
8
- Here is a small example of a macro that renders a form element:
9
-
10
- .. code-block:: jinja
11
-
12
- {% macro input(name, value, type, size) %}
13
- <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
14
- {% endmacro %}
15
-
16
- Macros differ from native PHP functions in a few ways:
17
-
18
- * Default argument values are defined by using the ``default`` filter in the
19
- macro body;
20
-
21
- * Arguments of a macro are always optional.
22
-
23
- * If extra positional arguments are passed to a macro, they end up in the
24
- special ``varargs`` variable as a list of values.
25
-
26
- But as with PHP functions, macros don't have access to the current template
27
- variables.
28
-
29
- .. tip::
30
-
31
- You can pass the whole context as an argument by using the special
32
- ``_context`` variable.
33
-
34
- Import
35
- ------
36
-
37
- Macros can be defined in any template, and need to be "imported" before being
38
- used (see the documentation for the :doc:`import<../tags/import>` tag for more
39
- information):
40
-
41
- .. code-block:: jinja
42
-
43
- {% import "forms.html" as forms %}
44
-
45
- The above ``import`` call imports the "forms.html" file (which can contain only
46
- macros, or a template and some macros), and import the functions as items of
47
- the ``forms`` variable.
48
-
49
- The macro can then be called at will:
50
-
51
- .. code-block:: jinja
52
-
53
- <p>{{ forms.input('username') }}</p>
54
- <p>{{ forms.input('password', null, 'password') }}</p>
55
-
56
- If macros are defined and used in the same template, you can use the
57
- special ``_self`` variable to import them:
58
-
59
- .. code-block:: jinja
60
-
61
- {% import _self as forms %}
62
-
63
- <p>{{ forms.input('username') }}</p>
64
-
65
- .. warning::
66
-
67
- When you define a macro in the template where you are going to use it, you
68
- might be tempted to call the macro directly via ``_self.input()`` instead
69
- of importing it; even if seems to work, this is just a side-effect of the
70
- current implementation and it won't work anymore in Twig 2.x.
71
-
72
- When you want to use a macro in another macro from the same file, you need to
73
- import it locally:
74
-
75
- .. code-block:: jinja
76
-
77
- {% macro input(name, value, type, size) %}
78
- <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
79
- {% endmacro %}
80
-
81
- {% macro wrapped_input(name, value, type, size) %}
82
- {% import _self as forms %}
83
-
84
- <div class="field">
85
- {{ forms.input(name, value, type, size) }}
86
- </div>
87
- {% endmacro %}
88
-
89
- Named Macro End-Tags
90
- --------------------
91
-
92
- Twig allows you to put the name of the macro after the end tag for better
93
- readability:
94
-
95
- .. code-block:: jinja
96
-
97
- {% macro input() %}
98
- ...
99
- {% endmacro input %}
100
-
101
- Of course, the name after the ``endmacro`` word must match the macro name.
102
-
103
- .. seealso:: :doc:`from<../tags/from>`, :doc:`import<../tags/import>`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/sandbox.rst DELETED
@@ -1,30 +0,0 @@
1
- ``sandbox``
2
- ===========
3
-
4
- The ``sandbox`` tag can be used to enable the sandboxing mode for an included
5
- template, when sandboxing is not enabled globally for the Twig environment:
6
-
7
- .. code-block:: jinja
8
-
9
- {% sandbox %}
10
- {% include 'user.html' %}
11
- {% endsandbox %}
12
-
13
- .. warning::
14
-
15
- The ``sandbox`` tag is only available when the sandbox extension is
16
- enabled (see the :doc:`Twig for Developers<../api>` chapter).
17
-
18
- .. note::
19
-
20
- The ``sandbox`` tag can only be used to sandbox an include tag and it
21
- cannot be used to sandbox a section of a template. The following example
22
- won't work:
23
-
24
- .. code-block:: jinja
25
-
26
- {% sandbox %}
27
- {% for i in 1..2 %}
28
- {{ i }}
29
- {% endfor %}
30
- {% endsandbox %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/set.rst DELETED
@@ -1,78 +0,0 @@
1
- ``set``
2
- =======
3
-
4
- Inside code blocks you can also assign values to variables. Assignments use
5
- the ``set`` tag and can have multiple targets.
6
-
7
- Here is how you can assign the ``bar`` value to the ``foo`` variable:
8
-
9
- .. code-block:: jinja
10
-
11
- {% set foo = 'bar' %}
12
-
13
- After the ``set`` call, the ``foo`` variable is available in the template like
14
- any other ones:
15
-
16
- .. code-block:: jinja
17
-
18
- {# displays bar #}
19
- {{ foo }}
20
-
21
- The assigned value can be any valid :ref:`Twig expressions
22
- <twig-expressions>`:
23
-
24
- .. code-block:: jinja
25
-
26
- {% set foo = [1, 2] %}
27
- {% set foo = {'foo': 'bar'} %}
28
- {% set foo = 'foo' ~ 'bar' %}
29
-
30
- Several variables can be assigned in one block:
31
-
32
- .. code-block:: jinja
33
-
34
- {% set foo, bar = 'foo', 'bar' %}
35
-
36
- {# is equivalent to #}
37
-
38
- {% set foo = 'foo' %}
39
- {% set bar = 'bar' %}
40
-
41
- The ``set`` tag can also be used to 'capture' chunks of text:
42
-
43
- .. code-block:: jinja
44
-
45
- {% set foo %}
46
- <div id="pagination">
47
- ...
48
- </div>
49
- {% endset %}
50
-
51
- .. caution::
52
-
53
- If you enable automatic output escaping, Twig will only consider the
54
- content to be safe when capturing chunks of text.
55
-
56
- .. note::
57
-
58
- Note that loops are scoped in Twig; therefore a variable declared inside a
59
- ``for`` loop is not accessible outside the loop itself:
60
-
61
- .. code-block:: jinja
62
-
63
- {% for item in list %}
64
- {% set foo = item %}
65
- {% endfor %}
66
-
67
- {# foo is NOT available #}
68
-
69
- If you want to access the variable, just declare it before the loop:
70
-
71
- .. code-block:: jinja
72
-
73
- {% set foo = "" %}
74
- {% for item in list %}
75
- {% set foo = item %}
76
- {% endfor %}
77
-
78
- {# foo is available #}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/spaceless.rst DELETED
@@ -1,37 +0,0 @@
1
- ``spaceless``
2
- =============
3
-
4
- Use the ``spaceless`` tag to remove whitespace *between HTML tags*, not
5
- whitespace within HTML tags or whitespace in plain text:
6
-
7
- .. code-block:: jinja
8
-
9
- {% spaceless %}
10
- <div>
11
- <strong>foo</strong>
12
- </div>
13
- {% endspaceless %}
14
-
15
- {# output will be <div><strong>foo</strong></div> #}
16
-
17
- This tag is not meant to "optimize" the size of the generated HTML content but
18
- merely to avoid extra whitespace between HTML tags to avoid browser rendering
19
- quirks under some circumstances.
20
-
21
- .. tip::
22
-
23
- If you want to optimize the size of the generated HTML content, gzip
24
- compress the output instead.
25
-
26
- .. tip::
27
-
28
- If you want to create a tag that actually removes all extra whitespace in
29
- an HTML string, be warned that this is not as easy as it seems to be
30
- (think of ``textarea`` or ``pre`` tags for instance). Using a third-party
31
- library like Tidy is probably a better idea.
32
-
33
- .. tip::
34
-
35
- For more information on whitespace control, read the
36
- :ref:`dedicated section <templates-whitespace-control>` of the documentation and learn how
37
- you can also use the whitespace control modifier on your tags.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
library/twig/twig/doc/tags/use.rst DELETED
@@ -1,124 +0,0 @@
1
- ``use``
2
- =======
3
-
4
- .. versionadded:: 1.1
5
- Horizontal reuse was added in Twig 1.1.
6
-
7
- .. note::
8
-
9
- Horizontal reuse is an advanced Twig feature that is hardly ever needed in
10
- regular templates. It is mainly used by projects that need to make
11
- template blocks reusable without using inheritance.
12
-
13
- Template inheritance is one of the most powerful features of Twig but it is
14
- limited to single inheritance; a template can only extend one other template.
15
- This limitation makes template inheritance simple to understand and easy to
16
- debug:
17
-
18
- .. code-block:: jinja
19
-
20
- {% extends "base.html" %}
21
-
22
- {% block title %}{% endblock %}
23
- {% block content %}{% endblock %}
24
-
25
- Horizontal reuse is a way to achieve the same goal as multiple inheritance,
26
- but without the associated complexity:
27
-
28
- .. code-block:: jinja
29
-
30
- {% extends "base.html" %}
31
-
32
- {% use "blocks.html" %}
33
-
34
- {% block title %}{% endblock %}
35
- {% block content %}{% endblock %}
36
-
37
- The ``use`` statement tells Twig to import the blocks defined in
38
- ``blocks.html`` into the current template (it's like macros, but for blocks):
39
-
40
- .. code-block:: jinja
41
-
42
- {# blocks.html #}
43
-
44
- {% block sidebar %}{% endblock %}
45
-
46
- In this example, the ``use`` statement imports the ``sidebar`` block into the
47
- main template. The code is mostly equivalent to the following one (the
48
- imported blocks are not outputted automatically):
49
-
50
- .. code-block:: jinja
51
-
52
- {% extends "base.html" %}
53
-
54
- {% block sidebar %}{% endblock %}
55
- {% block title %}{% endblock %}
56
- {% block content %}{% endblock %}
57
-
58
- .. note::
59
-
60
- The ``use`` tag only imports a template if it does not extend another
61
- template, if it does not define macros, and if the body is empty. But it
62
- can *use* other templates.
63
-
64
- .. note::
65
-
66
- Because ``use`` statements are resolved independently of the context
67
- passed to the template, the template reference cannot be an expression.
68
-
69
- The main template can also override any imported block. If the template
70
- already defines the ``sidebar`` block, then the one defined in ``blocks.html``
71
- is ignored. To avoid name conflicts, you can rename imported blocks:
72
-
73
- .. code-block:: jinja
74
-
75
- {% extends "base.html" %}
76
-
77
- {% use "blocks.html" with sidebar as base_sid