Code Snippets - Version 1.6

Version Description

  • Updated code editor to use CodeMirror 3
  • Improved compatibility with Clean Options plugin
  • Code improvements and optimization
  • Changed namespace from cs to code_snippets
  • Move css and js under assets
  • Organized CodeMirror scripts
  • Improved updating process
  • Current line of code editor is now highlighted
  • Highlight matches of selected text in code editor
  • Only create snippet tables when needed
  • Store multisite only options in site options table
  • Fixed compatibility bugs with WordPress 3.5
Download this release

Release Info

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

Code changes from version 1.5 to 1.6

assets/codemirror.css ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* BASICS */
2
+
3
+ .CodeMirror {
4
+ /* Set height, width, borders, and global font properties here */
5
+ font-family: monospace;
6
+ height: auto;
7
+ min-height: 300px;
8
+ border: 1px solid #eee;
9
+ -moz-border-radius: 3px; -webkit-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px;
10
+ }
11
+ .CodeMirror-scroll {
12
+ /* Set scrolling behaviour here */
13
+ overflow-y: hidden;
14
+ overflow-x: auto;
15
+ }
16
+
17
+ .CodeMirror-sizer {
18
+ min-height: 300px !important;
19
+ }
20
+
21
+ /* PADDING */
22
+
23
+ .CodeMirror-lines {
24
+ padding: 4px 0; /* Vertical padding around content */
25
+ }
26
+ .CodeMirror pre {
27
+ padding: 0 4px; /* Horizontal padding of content */
28
+ }
29
+
30
+ .CodeMirror-scrollbar-filler {
31
+ background-color: white; /* The little square between H and V scrollbars */
32
+ }
33
+
34
+ /* GUTTER */
35
+
36
+ .CodeMirror-gutters {
37
+ border-right: 1px solid #ddd;
38
+ background-color: #f7f7f7;
39
+ }
40
+ .CodeMirror-linenumbers {}
41
+ .CodeMirror-linenumber {
42
+ padding: 0 3px 0 5px;
43
+ min-width: 20px;
44
+ text-align: right;
45
+ color: #999;
46
+ }
47
+
48
+ /* CURSOR */
49
+
50
+ .CodeMirror pre.CodeMirror-cursor {
51
+ border-left: 1px solid black;
52
+ }
53
+ /* Shown when moving in bi-directional text */
54
+ .CodeMirror pre.CodeMirror-secondarycursor {
55
+ border-left: 1px solid silver;
56
+ }
57
+ .cm-keymap-fat-cursor pre.CodeMirror-cursor {
58
+ width: auto;
59
+ border: 0;
60
+ background: transparent;
61
+ background: rgba(0, 200, 0, .4);
62
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800);
63
+ }
64
+ /* Kludge to turn off filter in ie9+, which also accepts rgba */
65
+ .cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) {
66
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
67
+ }
68
+ /* Can style cursor different in overwrite (non-insert) mode */
69
+ .CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
70
+
71
+ /* DEFAULT THEME */
72
+
73
+ .cm-s-default .cm-keyword {color: #708;}
74
+ .cm-s-default .cm-atom {color: #219;}
75
+ .cm-s-default .cm-number {color: #164;}
76
+ .cm-s-default .cm-def {color: #00f;}
77
+ .cm-s-default .cm-variable {color: black;}
78
+ .cm-s-default .cm-variable-2 {color: #05a;}
79
+ .cm-s-default .cm-variable-3 {color: #085;}
80
+ .cm-s-default .cm-property {color: black;}
81
+ .cm-s-default .cm-operator {color: black;}
82
+ .cm-s-default .cm-comment {color: #a50;}
83
+ .cm-s-default .cm-string {color: #a11;}
84
+ .cm-s-default .cm-string-2 {color: #f50;}
85
+ .cm-s-default .cm-meta {color: #555;}
86
+ .cm-s-default .cm-error {color: #f00;}
87
+ .cm-s-default .cm-qualifier {color: #555;}
88
+ .cm-s-default .cm-builtin {color: #30a;}
89
+ .cm-s-default .cm-bracket {color: #997;}
90
+ .cm-s-default .cm-tag {color: #170;}
91
+ .cm-s-default .cm-attribute {color: #00c;}
92
+ .cm-s-default .cm-header {color: blue;}
93
+ .cm-s-default .cm-quote {color: #090;}
94
+ .cm-s-default .cm-hr {color: #999;}
95
+ .cm-s-default .cm-link {color: #00c;}
96
+
97
+ .cm-negative {color: #d44;}
98
+ .cm-positive {color: #292;}
99
+ .cm-header, .cm-strong {font-weight: bold;}
100
+ .cm-em {font-style: italic;}
101
+ .cm-emstrong {font-style: italic; font-weight: bold;}
102
+ .cm-link {text-decoration: underline;}
103
+
104
+ .cm-invalidchar {color: #f00;}
105
+
106
+ div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
107
+ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
108
+
109
+ .activeline { background: #e8f2ff !important; }
110
+
111
+ span.CodeMirror-matchhighlight { background: #e9e9e9 }
112
+ .CodeMirror-focused span.CodeMirror-matchhighlight { background: #e7e4ff; !important }
113
+
114
+ /* STOP */
115
+
116
+ /* The rest of this file contains styles related to the mechanics of
117
+ the editor. You probably shouldn't touch them. */
118
+
119
+ .CodeMirror {
120
+ line-height: 1;
121
+ position: relative;
122
+ overflow: hidden;
123
+ }
124
+
125
+ .CodeMirror-scroll {
126
+ /* 30px is the magic margin used to hide the element's real scrollbars */
127
+ /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */
128
+ margin-bottom: -30px; margin-right: -30px;
129
+ padding-bottom: 30px; padding-right: 30px;
130
+ height: 100%;
131
+ outline: none; /* Prevent dragging from highlighting the element */
132
+ position: relative;
133
+ }
134
+ .CodeMirror-sizer {
135
+ position: relative;
136
+ }
137
+
138
+ /* The fake, visible scrollbars. Used to force redraw during scrolling
139
+ before actuall scrolling happens, thus preventing shaking and
140
+ flickering artifacts. */
141
+ .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler {
142
+ position: absolute;
143
+ z-index: 6;
144
+ display: none;
145
+ }
146
+ .CodeMirror-vscrollbar {
147
+ right: 0; top: 0;
148
+ overflow-x: hidden;
149
+ overflow-y: scroll;
150
+ }
151
+ .CodeMirror-hscrollbar {
152
+ bottom: 0; left: 0;
153
+ overflow-y: hidden;
154
+ overflow-x: scroll;
155
+ }
156
+ .CodeMirror-scrollbar-filler {
157
+ right: 0; bottom: 0;
158
+ z-index: 6;
159
+ }
160
+
161
+ .CodeMirror-gutters {
162
+ position: absolute; left: 0; top: 0;
163
+ height: 100%;
164
+ z-index: 3;
165
+ }
166
+ .CodeMirror-gutter {
167
+ height: 100%;
168
+ display: inline-block;
169
+ /* Hack to make IE7 behave */
170
+ *zoom:1;
171
+ *display:inline;
172
+ }
173
+ .CodeMirror-gutter-elt {
174
+ position: absolute;
175
+ cursor: default;
176
+ z-index: 4;
177
+ }
178
+
179
+ .CodeMirror-lines {
180
+ cursor: text;
181
+ }
182
+ .CodeMirror pre {
183
+ /* Reset some styles that the rest of the page might have set */
184
+ -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0;
185
+ border-width: 0;
186
+ background: transparent;
187
+ font-family: inherit;
188
+ font-size: inherit;
189
+ margin: 0;
190
+ white-space: pre;
191
+ word-wrap: normal;
192
+ line-height: inherit;
193
+ color: inherit;
194
+ z-index: 2;
195
+ position: relative;
196
+ overflow: visible;
197
+ }
198
+ .CodeMirror-wrap pre {
199
+ word-wrap: break-word;
200
+ white-space: pre-wrap;
201
+ word-break: normal;
202
+ }
203
+ .CodeMirror-linebackground {
204
+ position: absolute;
205
+ left: 0; right: 0; top: 0; bottom: 0;
206
+ z-index: 0;
207
+ }
208
+
209
+ .CodeMirror-linewidget {
210
+ position: relative;
211
+ z-index: 2;
212
+ }
213
+
214
+ .CodeMirror-wrap .CodeMirror-scroll {
215
+ overflow-x: hidden;
216
+ }
217
+
218
+ .CodeMirror-measure {
219
+ position: absolute;
220
+ width: 100%; height: 0px;
221
+ overflow: hidden;
222
+ visibility: hidden;
223
+ }
224
+ .CodeMirror-measure pre { position: static; }
225
+
226
+ .CodeMirror pre.CodeMirror-cursor {
227
+ position: absolute;
228
+ visibility: hidden;
229
+ border-right: none;
230
+ width: 0;
231
+ }
232
+ .CodeMirror-focused pre.CodeMirror-cursor {
233
+ visibility: visible;
234
+ }
235
+
236
+ .CodeMirror-selected { background: #d9d9d9; }
237
+ .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
238
+
239
+ .CodeMirror-searching {
240
+ background: #ffa;
241
+ background: rgba(255, 255, 0, .4);
242
+ }
243
+
244
+ /* IE7 hack to prevent it from returning funny offsetTops on the spans */
245
+ .CodeMirror span { *vertical-align: text-bottom; }
246
+
247
+ @media print {
248
+ /* Hide the cursor when printing */
249
+ .CodeMirror pre.CodeMirror-cursor {
250
+ visibility: hidden;
251
+ }
252
+ }
assets/codemirror.js ADDED
@@ -0,0 +1,4553 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // CodeMirror version 3.0
2
+ //
3
+ // CodeMirror is the only global var we claim
4
+ window.CodeMirror = (function() {
5
+ "use strict";
6
+
7
+ // BROWSER SNIFFING
8
+
9
+ // Crude, but necessary to handle a number of hard-to-feature-detect
10
+ // bugs and behavior differences.
11
+ var gecko = /gecko\/\d/i.test(navigator.userAgent);
12
+ var ie = /MSIE \d/.test(navigator.userAgent);
13
+ var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
14
+ var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
15
+ var webkit = /WebKit\//.test(navigator.userAgent);
16
+ var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
17
+ var chrome = /Chrome\//.test(navigator.userAgent);
18
+ var opera = /Opera\//.test(navigator.userAgent);
19
+ var safari = /Apple Computer/.test(navigator.vendor);
20
+ var khtml = /KHTML\//.test(navigator.userAgent);
21
+ var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent);
22
+ var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
23
+ var phantom = /PhantomJS/.test(navigator.userAgent);
24
+
25
+ var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
26
+ // This is woefully incomplete. Suggestions for alternative methods welcome.
27
+ var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent);
28
+ var mac = ios || /Mac/.test(navigator.platform);
29
+
30
+ // Optimize some code when these features are not used
31
+ var sawReadOnlySpans = false, sawCollapsedSpans = false;
32
+
33
+ // CONSTRUCTOR
34
+
35
+ function CodeMirror(place, options) {
36
+ if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
37
+
38
+ this.options = options = options || {};
39
+ // Determine effective options based on given values and defaults.
40
+ for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
41
+ options[opt] = defaults[opt];
42
+ setGuttersForLineNumbers(options);
43
+
44
+ var display = this.display = makeDisplay(place);
45
+ display.wrapper.CodeMirror = this;
46
+ updateGutters(this);
47
+ if (options.autofocus && !mobile) focusInput(this);
48
+
49
+ this.view = makeView(new BranchChunk([new LeafChunk([makeLine("", null, textHeight(display))])]));
50
+ this.nextOpId = 0;
51
+ loadMode(this);
52
+ themeChanged(this);
53
+ if (options.lineWrapping)
54
+ this.display.wrapper.className += " CodeMirror-wrap";
55
+
56
+ // Initialize the content.
57
+ this.setValue(options.value || "");
58
+ // Override magic textarea content restore that IE sometimes does
59
+ // on our hidden textarea on reload
60
+ if (ie) setTimeout(bind(resetInput, this, true), 20);
61
+ this.view.history = makeHistory();
62
+
63
+ registerEventHandlers(this);
64
+ // IE throws unspecified error in certain cases, when
65
+ // trying to access activeElement before onload
66
+ var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { }
67
+ if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20);
68
+ else onBlur(this);
69
+
70
+ operation(this, function() {
71
+ for (var opt in optionHandlers)
72
+ if (optionHandlers.propertyIsEnumerable(opt))
73
+ optionHandlers[opt](this, options[opt], Init);
74
+ for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
75
+ })();
76
+ }
77
+
78
+ // DISPLAY CONSTRUCTOR
79
+
80
+ function makeDisplay(place) {
81
+ var d = {};
82
+ var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;");
83
+ input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off");
84
+ // Wraps and hides input textarea
85
+ d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
86
+ // The actual fake scrollbars.
87
+ d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
88
+ d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
89
+ d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
90
+ // DIVs containing the selection and the actual code
91
+ d.lineDiv = elt("div");
92
+ d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
93
+ // Blinky cursor, and element used to ensure cursor fits at the end of a line
94
+ d.cursor = elt("pre", "\u00a0", "CodeMirror-cursor");
95
+ // Secondary cursor, shown when on a 'jump' in bi-directional text
96
+ d.otherCursor = elt("pre", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
97
+ // Used to measure text size
98
+ d.measure = elt("div", null, "CodeMirror-measure");
99
+ // Wraps everything that needs to exist inside the vertically-padded coordinate system
100
+ d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor],
101
+ null, "position: relative; outline: none");
102
+ // Moved around its parent to cover visible view
103
+ d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
104
+ // Set to the height of the text, causes scrolling
105
+ d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
106
+ // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
107
+ d.heightForcer = elt("div", "\u00a0", null, "position: absolute; height: " + scrollerCutOff + "px");
108
+ // Will contain the gutters, if any
109
+ d.gutters = elt("div", null, "CodeMirror-gutters");
110
+ d.lineGutter = null;
111
+ // Helper element to properly size the gutter backgrounds
112
+ var scrollerInner = elt("div", [d.sizer, d.heightForcer, d.gutters], null, "position: relative; min-height: 100%");
113
+ // Provides scrolling
114
+ d.scroller = elt("div", [scrollerInner], "CodeMirror-scroll");
115
+ d.scroller.setAttribute("tabIndex", "-1");
116
+ // The element in which the editor lives.
117
+ d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
118
+ d.scrollbarFiller, d.scroller], "CodeMirror");
119
+ // Work around IE7 z-index bug
120
+ if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
121
+ if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
122
+
123
+ // Needed to hide big blue blinking cursor on Mobile Safari
124
+ if (ios) input.style.width = "0px";
125
+ if (!webkit) d.scroller.draggable = true;
126
+ // Needed to handle Tab key in KHTML
127
+ if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
128
+ // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
129
+ else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px";
130
+
131
+ // Current visible range (may be bigger than the view window).
132
+ d.viewOffset = d.showingFrom = d.showingTo = d.lastSizeC = 0;
133
+
134
+ // Used to only resize the line number gutter when necessary (when
135
+ // the amount of lines crosses a boundary that makes its width change)
136
+ d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
137
+ // See readInput and resetInput
138
+ d.prevInput = "";
139
+ // Set to true when a non-horizontal-scrolling widget is added. As
140
+ // an optimization, widget aligning is skipped when d is false.
141
+ d.alignWidgets = false;
142
+ // Flag that indicates whether we currently expect input to appear
143
+ // (after some event like 'keypress' or 'input') and are polling
144
+ // intensively.
145
+ d.pollingFast = false;
146
+ // Self-resetting timeout for the poller
147
+ d.poll = new Delayed();
148
+ // True when a drag from the editor is active
149
+ d.draggingText = false;
150
+
151
+ d.cachedCharWidth = d.cachedTextHeight = null;
152
+ d.measureLineCache = [];
153
+ d.measureLineCachePos = 0;
154
+
155
+ // Tracks when resetInput has punted to just putting a short
156
+ // string instead of the (large) selection.
157
+ d.inaccurateSelection = false;
158
+
159
+ // Used to adjust overwrite behaviour when a paste has been
160
+ // detected
161
+ d.pasteIncoming = false;
162
+
163
+ return d;
164
+ }
165
+
166
+ // VIEW CONSTRUCTOR
167
+
168
+ function makeView(doc) {
169
+ var selPos = {line: 0, ch: 0};
170
+ return {
171
+ doc: doc,
172
+ // frontier is the point up to which the content has been parsed,
173
+ frontier: 0, highlight: new Delayed(),
174
+ sel: {from: selPos, to: selPos, head: selPos, anchor: selPos, shift: false, extend: false},
175
+ scrollTop: 0, scrollLeft: 0,
176
+ overwrite: false, focused: false,
177
+ // Tracks the maximum line length so that
178
+ // the horizontal scrollbar can be kept
179
+ // static when scrolling.
180
+ maxLine: getLine(doc, 0),
181
+ maxLineLength: 0,
182
+ maxLineChanged: false,
183
+ suppressEdits: false,
184
+ goalColumn: null,
185
+ cantEdit: false,
186
+ keyMaps: []
187
+ };
188
+ }
189
+
190
+ // STATE UPDATES
191
+
192
+ // Used to get the editor into a consistent state again when options change.
193
+
194
+ function loadMode(cm) {
195
+ var doc = cm.view.doc;
196
+ cm.view.mode = CodeMirror.getMode(cm.options, cm.options.mode);
197
+ doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
198
+ cm.view.frontier = 0;
199
+ startWorker(cm, 100);
200
+ }
201
+
202
+ function wrappingChanged(cm) {
203
+ var doc = cm.view.doc, th = textHeight(cm.display);
204
+ if (cm.options.lineWrapping) {
205
+ cm.display.wrapper.className += " CodeMirror-wrap";
206
+ var perLine = cm.display.scroller.clientWidth / charWidth(cm.display) - 3;
207
+ doc.iter(0, doc.size, function(line) {
208
+ if (line.height == 0) return;
209
+ var guess = Math.ceil(line.text.length / perLine) || 1;
210
+ if (guess != 1) updateLineHeight(line, guess * th);
211
+ });
212
+ cm.display.sizer.style.minWidth = "";
213
+ } else {
214
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
215
+ computeMaxLength(cm.view);
216
+ doc.iter(0, doc.size, function(line) {
217
+ if (line.height != 0) updateLineHeight(line, th);
218
+ });
219
+ }
220
+ regChange(cm, 0, doc.size);
221
+ clearCaches(cm);
222
+ setTimeout(function(){updateScrollbars(cm.display, cm.view.doc.height);}, 100);
223
+ }
224
+
225
+ function keyMapChanged(cm) {
226
+ var style = keyMap[cm.options.keyMap].style;
227
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
228
+ (style ? " cm-keymap-" + style : "");
229
+ }
230
+
231
+ function themeChanged(cm) {
232
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
233
+ cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
234
+ clearCaches(cm);
235
+ }
236
+
237
+ function guttersChanged(cm) {
238
+ updateGutters(cm);
239
+ updateDisplay(cm, true);
240
+ }
241
+
242
+ function updateGutters(cm) {
243
+ var gutters = cm.display.gutters, specs = cm.options.gutters;
244
+ removeChildren(gutters);
245
+ for (var i = 0; i < specs.length; ++i) {
246
+ var gutterClass = specs[i];
247
+ var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
248
+ if (gutterClass == "CodeMirror-linenumbers") {
249
+ cm.display.lineGutter = gElt;
250
+ gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
251
+ }
252
+ }
253
+ gutters.style.display = i ? "" : "none";
254
+ }
255
+
256
+ function lineLength(doc, line) {
257
+ if (line.height == 0) return 0;
258
+ var len = line.text.length, merged, cur = line;
259
+ while (merged = collapsedSpanAtStart(cur)) {
260
+ var found = merged.find();
261
+ cur = getLine(doc, found.from.line);
262
+ len += found.from.ch - found.to.ch;
263
+ }
264
+ cur = line;
265
+ while (merged = collapsedSpanAtEnd(cur)) {
266
+ var found = merged.find();
267
+ len -= cur.text.length - found.from.ch;
268
+ cur = getLine(doc, found.to.line);
269
+ len += cur.text.length - found.to.ch;
270
+ }
271
+ return len;
272
+ }
273
+
274
+ function computeMaxLength(view) {
275
+ view.maxLine = getLine(view.doc, 0);
276
+ view.maxLineLength = lineLength(view.doc, view.maxLine);
277
+ view.maxLineChanged = true;
278
+ view.doc.iter(1, view.doc.size, function(line) {
279
+ var len = lineLength(view.doc, line);
280
+ if (len > view.maxLineLength) {
281
+ view.maxLineLength = len;
282
+ view.maxLine = line;
283
+ }
284
+ });
285
+ }
286
+
287
+ // Make sure the gutters options contains the element
288
+ // "CodeMirror-linenumbers" when the lineNumbers option is true.
289
+ function setGuttersForLineNumbers(options) {
290
+ var found = false;
291
+ for (var i = 0; i < options.gutters.length; ++i) {
292
+ if (options.gutters[i] == "CodeMirror-linenumbers") {
293
+ if (options.lineNumbers) found = true;
294
+ else options.gutters.splice(i--, 1);
295
+ }
296
+ }
297
+ if (!found && options.lineNumbers)
298
+ options.gutters.push("CodeMirror-linenumbers");
299
+ }
300
+
301
+ // SCROLLBARS
302
+
303
+ // Re-synchronize the fake scrollbars with the actual size of the
304
+ // content. Optionally force a scrollTop.
305
+ function updateScrollbars(d /* display */, docHeight) {
306
+ var totalHeight = docHeight + 2 * paddingTop(d);
307
+ d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
308
+ var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
309
+ var needsH = d.scroller.scrollWidth > d.scroller.clientWidth;
310
+ var needsV = scrollHeight > d.scroller.clientHeight;
311
+ if (needsV) {
312
+ d.scrollbarV.style.display = "block";
313
+ d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
314
+ d.scrollbarV.firstChild.style.height =
315
+ (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
316
+ } else d.scrollbarV.style.display = "";
317
+ if (needsH) {
318
+ d.scrollbarH.style.display = "block";
319
+ d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
320
+ d.scrollbarH.firstChild.style.width =
321
+ (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px";
322
+ } else d.scrollbarH.style.display = "";
323
+ if (needsH && needsV) {
324
+ d.scrollbarFiller.style.display = "block";
325
+ d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
326
+ } else d.scrollbarFiller.style.display = "";
327
+
328
+ if (mac_geLion && scrollbarWidth(d.measure) === 0)
329
+ d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
330
+ }
331
+
332
+ function visibleLines(display, doc, viewPort) {
333
+ var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;
334
+ if (typeof viewPort == "number") top = viewPort;
335
+ else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}
336
+ top = Math.floor(top - paddingTop(display));
337
+ var bottom = Math.ceil(top + height);
338
+ return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};
339
+ }
340
+
341
+ // LINE NUMBERS
342
+
343
+ function alignHorizontally(cm) {
344
+ var display = cm.display;
345
+ if (!display.alignWidgets && !display.gutters.firstChild) return;
346
+ var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.view.scrollLeft;
347
+ var gutterW = display.gutters.offsetWidth, l = comp + "px";
348
+ for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
349
+ for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
350
+ }
351
+ display.gutters.style.left = (comp + gutterW) + "px";
352
+ }
353
+
354
+ function maybeUpdateLineNumberWidth(cm) {
355
+ if (!cm.options.lineNumbers) return false;
356
+ var doc = cm.view.doc, last = lineNumberFor(cm.options, doc.size - 1), display = cm.display;
357
+ if (last.length != display.lineNumChars) {
358
+ var test = display.measure.appendChild(elt("div", [elt("div", last)],
359
+ "CodeMirror-linenumber CodeMirror-gutter-elt"));
360
+ var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
361
+ display.lineGutter.style.width = "";
362
+ display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
363
+ display.lineNumWidth = display.lineNumInnerWidth + padding;
364
+ display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
365
+ display.lineGutter.style.width = display.lineNumWidth + "px";
366
+ return true;
367
+ }
368
+ return false;
369
+ }
370
+
371
+ function lineNumberFor(options, i) {
372
+ return String(options.lineNumberFormatter(i + options.firstLineNumber));
373
+ }
374
+ function compensateForHScroll(display) {
375
+ return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left;
376
+ }
377
+
378
+ // DISPLAY DRAWING
379
+
380
+ function updateDisplay(cm, changes, viewPort) {
381
+ var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo;
382
+ var updated = updateDisplayInner(cm, changes, viewPort);
383
+ if (updated) {
384
+ signalLater(cm, cm, "update", cm);
385
+ if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
386
+ signalLater(cm, cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
387
+ }
388
+ updateSelection(cm);
389
+ updateScrollbars(cm.display, cm.view.doc.height);
390
+
391
+ return updated;
392
+ }
393
+
394
+ // Uses a set of changes plus the current scroll position to
395
+ // determine which DOM updates have to be made, and makes the
396
+ // updates.
397
+ function updateDisplayInner(cm, changes, viewPort) {
398
+ var display = cm.display, doc = cm.view.doc;
399
+ if (!display.wrapper.clientWidth) {
400
+ display.showingFrom = display.showingTo = display.viewOffset = 0;
401
+ return;
402
+ }
403
+
404
+ // Compute the new visible window
405
+ // If scrollTop is specified, use that to determine which lines
406
+ // to render instead of the current scrollbar position.
407
+ var visible = visibleLines(display, doc, viewPort);
408
+ // Bail out if the visible area is already rendered and nothing changed.
409
+ if (changes !== true && changes.length == 0 &&
410
+ visible.from > display.showingFrom && visible.to < display.showingTo)
411
+ return;
412
+
413
+ if (changes && maybeUpdateLineNumberWidth(cm))
414
+ changes = true;
415
+ display.sizer.style.marginLeft = display.scrollbarH.style.left = display.gutters.offsetWidth + "px";
416
+
417
+ // When merged lines are present, the line that needs to be
418
+ // redrawn might not be the one that was changed.
419
+ if (changes !== true && sawCollapsedSpans)
420
+ for (var i = 0; i < changes.length; ++i) {
421
+ var ch = changes[i], merged;
422
+ while (merged = collapsedSpanAtStart(getLine(doc, ch.from))) {
423
+ var from = merged.find().from.line;
424
+ if (ch.diff) ch.diff -= ch.from - from;
425
+ ch.from = from;
426
+ }
427
+ }
428
+
429
+ // Used to determine which lines need their line numbers updated
430
+ var positionsChangedFrom = changes === true ? 0 : Infinity;
431
+ if (cm.options.lineNumbers && changes && changes !== true)
432
+ for (var i = 0; i < changes.length; ++i)
433
+ if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; }
434
+
435
+ var from = Math.max(visible.from - cm.options.viewportMargin, 0);
436
+ var to = Math.min(doc.size, visible.to + cm.options.viewportMargin);
437
+ if (display.showingFrom < from && from - display.showingFrom < 20) from = display.showingFrom;
438
+ if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(doc.size, display.showingTo);
439
+ if (sawCollapsedSpans) {
440
+ from = lineNo(visualLine(doc, getLine(doc, from)));
441
+ while (to < doc.size && lineIsHidden(getLine(doc, to))) ++to;
442
+ }
443
+
444
+ // Create a range of theoretically intact lines, and punch holes
445
+ // in that using the change info.
446
+ var intact = changes === true ? [] :
447
+ computeIntact([{from: display.showingFrom, to: display.showingTo}], changes);
448
+ // Clip off the parts that won't be visible
449
+ var intactLines = 0;
450
+ for (var i = 0; i < intact.length; ++i) {
451
+ var range = intact[i];
452
+ if (range.from < from) range.from = from;
453
+ if (range.to > to) range.to = to;
454
+ if (range.from >= range.to) intact.splice(i--, 1);
455
+ else intactLines += range.to - range.from;
456
+ }
457
+ if (intactLines == to - from && from == display.showingFrom && to == display.showingTo)
458
+ return;
459
+ intact.sort(function(a, b) {return a.from - b.from;});
460
+
461
+ if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
462
+ patchDisplay(cm, from, to, intact, positionsChangedFrom);
463
+ display.lineDiv.style.display = "";
464
+
465
+ var different = from != display.showingFrom || to != display.showingTo ||
466
+ display.lastSizeC != display.wrapper.clientHeight;
467
+ // This is just a bogus formula that detects when the editor is
468
+ // resized or the font size changes.
469
+ if (different) display.lastSizeC = display.wrapper.clientHeight;
470
+ display.showingFrom = from; display.showingTo = to;
471
+ startWorker(cm, 100);
472
+
473
+ var prevBottom = display.lineDiv.offsetTop;
474
+ for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
475
+ if (ie_lt8) {
476
+ var bot = node.offsetTop + node.offsetHeight;
477
+ height = bot - prevBottom;
478
+ prevBottom = bot;
479
+ } else {
480
+ var box = node.getBoundingClientRect();
481
+ height = box.bottom - box.top;
482
+ }
483
+ var diff = node.lineObj.height - height;
484
+ if (height < 2) height = textHeight(display);
485
+ if (diff > .001 || diff < -.001)
486
+ updateLineHeight(node.lineObj, height);
487
+ }
488
+ display.viewOffset = heightAtLine(cm, getLine(doc, from));
489
+ // Position the mover div to align with the current virtual scroll position
490
+ display.mover.style.top = display.viewOffset + "px";
491
+ return true;
492
+ }
493
+
494
+ function computeIntact(intact, changes) {
495
+ for (var i = 0, l = changes.length || 0; i < l; ++i) {
496
+ var change = changes[i], intact2 = [], diff = change.diff || 0;
497
+ for (var j = 0, l2 = intact.length; j < l2; ++j) {
498
+ var range = intact[j];
499
+ if (change.to <= range.from && change.diff) {
500
+ intact2.push({from: range.from + diff, to: range.to + diff});
501
+ } else if (change.to <= range.from || change.from >= range.to) {
502
+ intact2.push(range);
503
+ } else {
504
+ if (change.from > range.from)
505
+ intact2.push({from: range.from, to: change.from});
506
+ if (change.to < range.to)
507
+ intact2.push({from: change.to + diff, to: range.to + diff});
508
+ }
509
+ }
510
+ intact = intact2;
511
+ }
512
+ return intact;
513
+ }
514
+
515
+ function getDimensions(cm) {
516
+ var d = cm.display, left = {}, width = {};
517
+ for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
518
+ left[cm.options.gutters[i]] = n.offsetLeft;
519
+ width[cm.options.gutters[i]] = n.offsetWidth;
520
+ }
521
+ return {fixedPos: compensateForHScroll(d),
522
+ gutterTotalWidth: d.gutters.offsetWidth,
523
+ gutterLeft: left,
524
+ gutterWidth: width,
525
+ wrapperWidth: d.wrapper.clientWidth};
526
+ }
527
+
528
+ function patchDisplay(cm, from, to, intact, updateNumbersFrom) {
529
+ var dims = getDimensions(cm);
530
+ var display = cm.display, lineNumbers = cm.options.lineNumbers;
531
+ // IE does bad things to nodes when .innerHTML = "" is used on a parent
532
+ // we still need widgets and markers intact to add back to the new content later
533
+ if (!intact.length && !ie && (!webkit || !cm.display.currentWheelTarget))
534
+ removeChildren(display.lineDiv);
535
+ var container = display.lineDiv, cur = container.firstChild;
536
+
537
+ function rm(node) {
538
+ var next = node.nextSibling;
539
+ if (webkit && mac && cm.display.currentWheelTarget == node) {
540
+ node.style.display = "none";
541
+ node.lineObj = null;
542
+ } else {
543
+ container.removeChild(node);
544
+ }
545
+ return next;
546
+ }
547
+
548
+ var nextIntact = intact.shift(), lineNo = from;
549
+ cm.view.doc.iter(from, to, function(line) {
550
+ if (nextIntact && nextIntact.to == lineNo) nextIntact = intact.shift();
551
+ if (lineIsHidden(line)) {
552
+ if (line.height != 0) updateLineHeight(line, 0);
553
+ } else if (nextIntact && nextIntact.from <= lineNo && nextIntact.to > lineNo) {
554
+ // This line is intact. Skip to the actual node. Update its
555
+ // line number if needed.
556
+ while (cur.lineObj != line) cur = rm(cur);
557
+ if (lineNumbers && updateNumbersFrom <= lineNo && cur.lineNumber)
558
+ setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineNo));
559
+ cur = cur.nextSibling;
560
+ } else {
561
+ // This line needs to be generated.
562
+ var lineNode = buildLineElement(cm, line, lineNo, dims);
563
+ container.insertBefore(lineNode, cur);
564
+ lineNode.lineObj = line;
565
+ }
566
+ ++lineNo;
567
+ });
568
+ while (cur) cur = rm(cur);
569
+ }
570
+
571
+ function buildLineElement(cm, line, lineNo, dims) {
572
+ var lineElement = lineContent(cm, line);
573
+ var markers = line.gutterMarkers, display = cm.display;
574
+
575
+ if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass &&
576
+ (!line.widgets || !line.widgets.length)) return lineElement;
577
+
578
+ // Lines with gutter elements or a background class need
579
+ // to be wrapped again, and have the extra elements added
580
+ // to the wrapper div
581
+
582
+ var wrap = elt("div", null, line.wrapClass, "position: relative");
583
+ if (cm.options.lineNumbers || markers) {
584
+ var gutterWrap = wrap.appendChild(elt("div", null, null, "position: absolute; left: " +
585
+ dims.fixedPos + "px"));
586
+ wrap.alignable = [gutterWrap];
587
+ if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
588
+ wrap.lineNumber = gutterWrap.appendChild(
589
+ elt("div", lineNumberFor(cm.options, lineNo),
590
+ "CodeMirror-linenumber CodeMirror-gutter-elt",
591
+ "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
592
+ + display.lineNumInnerWidth + "px"));
593
+ if (markers)
594
+ for (var k = 0; k < cm.options.gutters.length; ++k) {
595
+ var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
596
+ if (found)
597
+ gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
598
+ dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
599
+ }
600
+ }
601
+ // Kludge to make sure the styled element lies behind the selection (by z-index)
602
+ if (line.bgClass)
603
+ wrap.appendChild(elt("div", "\u00a0", line.bgClass + " CodeMirror-linebackground"));
604
+ wrap.appendChild(lineElement);
605
+ if (line.widgets)
606
+ for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
607
+ var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
608
+ node.widget = widget;
609
+ if (widget.noHScroll) {
610
+ (wrap.alignable || (wrap.alignable = [])).push(node);
611
+ var width = dims.wrapperWidth;
612
+ node.style.left = dims.fixedPos + "px";
613
+ if (!widget.coverGutter) {
614
+ width -= dims.gutterTotalWidth;
615
+ node.style.paddingLeft = dims.gutterTotalWidth + "px";
616
+ }
617
+ node.style.width = width + "px";
618
+ }
619
+ if (widget.coverGutter) {
620
+ node.style.zIndex = 5;
621
+ node.style.position = "relative";
622
+ if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
623
+ }
624
+ if (widget.above)
625
+ wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
626
+ else
627
+ wrap.appendChild(node);
628
+ }
629
+
630
+ if (ie_lt8) wrap.style.zIndex = 2;
631
+ return wrap;
632
+ }
633
+
634
+ // SELECTION / CURSOR
635
+
636
+ function updateSelection(cm) {
637
+ var display = cm.display;
638
+ var collapsed = posEq(cm.view.sel.from, cm.view.sel.to);
639
+ if (collapsed || cm.options.showCursorWhenSelecting)
640
+ updateSelectionCursor(cm);
641
+ else
642
+ display.cursor.style.display = display.otherCursor.style.display = "none";
643
+ if (!collapsed)
644
+ updateSelectionRange(cm);
645
+ else
646
+ display.selectionDiv.style.display = "none";
647
+
648
+ // Move the hidden textarea near the cursor to prevent scrolling artifacts
649
+ var headPos = cursorCoords(cm, cm.view.sel.head, "div");
650
+ var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
651
+ display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
652
+ headPos.top + lineOff.top - wrapOff.top)) + "px";
653
+ display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
654
+ headPos.left + lineOff.left - wrapOff.left)) + "px";
655
+ }
656
+
657
+ // No selection, plain cursor
658
+ function updateSelectionCursor(cm) {
659
+ var display = cm.display, pos = cursorCoords(cm, cm.view.sel.head, "div");
660
+ display.cursor.style.left = pos.left + "px";
661
+ display.cursor.style.top = pos.top + "px";
662
+ display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
663
+ display.cursor.style.display = "";
664
+
665
+ if (pos.other) {
666
+ display.otherCursor.style.display = "";
667
+ display.otherCursor.style.left = pos.other.left + "px";
668
+ display.otherCursor.style.top = pos.other.top + "px";
669
+ display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
670
+ } else { display.otherCursor.style.display = "none"; }
671
+ }
672
+
673
+ // Highlight selection
674
+ function updateSelectionRange(cm) {
675
+ var display = cm.display, doc = cm.view.doc, sel = cm.view.sel;
676
+ var fragment = document.createDocumentFragment();
677
+ var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);
678
+
679
+ function add(left, top, width, bottom) {
680
+ if (top < 0) top = 0;
681
+ fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
682
+ "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) +
683
+ "px; height: " + (bottom - top) + "px"));
684
+ }
685
+
686
+ function drawForLine(line, fromArg, toArg, retTop) {
687
+ var lineObj = getLine(doc, line);
688
+ var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity;
689
+ function coords(ch) {
690
+ return charCoords(cm, {line: line, ch: ch}, "div", lineObj);
691
+ }
692
+
693
+ iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
694
+ var leftPos = coords(dir == "rtl" ? to - 1 : from);
695
+ var rightPos = coords(dir == "rtl" ? from : to - 1);
696
+ var left = leftPos.left, right = rightPos.right;
697
+ if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
698
+ add(left, leftPos.top, null, leftPos.bottom);
699
+ left = pl;
700
+ if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
701
+ }
702
+ if (toArg == null && to == lineLen) right = clientWidth;
703
+ if (fromArg == null && from == 0) left = pl;
704
+ rVal = retTop ? Math.min(rightPos.top, rVal) : Math.max(rightPos.bottom, rVal);
705
+ if (left < pl + 1) left = pl;
706
+ add(left, rightPos.top, right - left, rightPos.bottom);
707
+ });
708
+ return rVal;
709
+ }
710
+
711
+ if (sel.from.line == sel.to.line) {
712
+ drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
713
+ } else {
714
+ var fromObj = getLine(doc, sel.from.line);
715
+ var cur = fromObj, merged, path = [sel.from.line, sel.from.ch], singleLine;
716
+ while (merged = collapsedSpanAtEnd(cur)) {
717
+ var found = merged.find();
718
+ path.push(found.from.ch, found.to.line, found.to.ch);
719
+ if (found.to.line == sel.to.line) {
720
+ path.push(sel.to.ch);
721
+ singleLine = true;
722
+ break;
723
+ }
724
+ cur = getLine(doc, found.to.line);
725
+ }
726
+
727
+ // This is a single, merged line
728
+ if (singleLine) {
729
+ for (var i = 0; i < path.length; i += 3)
730
+ drawForLine(path[i], path[i+1], path[i+2]);
731
+ } else {
732
+ var middleTop, middleBot, toObj = getLine(doc, sel.to.line);
733
+ if (sel.from.ch)
734
+ // Draw the first line of selection.
735
+ middleTop = drawForLine(sel.from.line, sel.from.ch, null, false);
736
+ else
737
+ // Simply include it in the middle block.
738
+ middleTop = heightAtLine(cm, fromObj) - display.viewOffset;
739
+
740
+ if (!sel.to.ch)
741
+ middleBot = heightAtLine(cm, toObj) - display.viewOffset;
742
+ else
743
+ middleBot = drawForLine(sel.to.line, collapsedSpanAtStart(toObj) ? null : 0, sel.to.ch, true);
744
+
745
+ if (middleTop < middleBot) add(pl, middleTop, null, middleBot);
746
+ }
747
+ }
748
+
749
+ removeChildrenAndAdd(display.selectionDiv, fragment);
750
+ display.selectionDiv.style.display = "";
751
+ }
752
+
753
+ // Cursor-blinking
754
+ function restartBlink(cm) {
755
+ var display = cm.display;
756
+ clearInterval(display.blinker);
757
+ var on = true;
758
+ display.cursor.style.visibility = display.otherCursor.style.visibility = "";
759
+ display.blinker = setInterval(function() {
760
+ if (!display.cursor.offsetHeight) return;
761
+ display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
762
+ }, cm.options.cursorBlinkRate);
763
+ }
764
+
765
+ // HIGHLIGHT WORKER
766
+
767
+ function startWorker(cm, time) {
768
+ if (cm.view.frontier < cm.display.showingTo)
769
+ cm.view.highlight.set(time, bind(highlightWorker, cm));
770
+ }
771
+
772
+ function highlightWorker(cm) {
773
+ var view = cm.view, doc = view.doc;
774
+ if (view.frontier >= cm.display.showingTo) return;
775
+ var end = +new Date + cm.options.workTime;
776
+ var state = copyState(view.mode, getStateBefore(cm, view.frontier));
777
+ var changed = [], prevChange;
778
+ doc.iter(view.frontier, Math.min(doc.size, cm.display.showingTo + 500), function(line) {
779
+ if (view.frontier >= cm.display.showingFrom) { // Visible
780
+ if (highlightLine(cm, line, state) && view.frontier >= cm.display.showingFrom) {
781
+ if (prevChange && prevChange.end == view.frontier) prevChange.end++;
782
+ else changed.push(prevChange = {start: view.frontier, end: view.frontier + 1});
783
+ }
784
+ line.stateAfter = copyState(view.mode, state);
785
+ } else {
786
+ processLine(cm, line, state);
787
+ line.stateAfter = view.frontier % 5 == 0 ? copyState(view.mode, state) : null;
788
+ }
789
+ ++view.frontier;
790
+ if (+new Date > end) {
791
+ startWorker(cm, cm.options.workDelay);
792
+ return true;
793
+ }
794
+ });
795
+ if (changed.length)
796
+ operation(cm, function() {
797
+ for (var i = 0; i < changed.length; ++i)
798
+ regChange(this, changed[i].start, changed[i].end);
799
+ })();
800
+ }
801
+
802
+ // Finds the line to start with when starting a parse. Tries to
803
+ // find a line with a stateAfter, so that it can start with a
804
+ // valid state. If that fails, it returns the line with the
805
+ // smallest indentation, which tends to need the least context to
806
+ // parse correctly.
807
+ function findStartLine(cm, n) {
808
+ var minindent, minline, doc = cm.view.doc;
809
+ for (var search = n, lim = n - 100; search > lim; --search) {
810
+ if (search == 0) return 0;
811
+ var line = getLine(doc, search-1);
812
+ if (line.stateAfter) return search;
813
+ var indented = countColumn(line.text, null, cm.options.tabSize);
814
+ if (minline == null || minindent > indented) {
815
+ minline = search - 1;
816
+ minindent = indented;
817
+ }
818
+ }
819
+ return minline;
820
+ }
821
+
822
+ function getStateBefore(cm, n) {
823
+ var view = cm.view;
824
+ var pos = findStartLine(cm, n), state = pos && getLine(view.doc, pos-1).stateAfter;
825
+ if (!state) state = startState(view.mode);
826
+ else state = copyState(view.mode, state);
827
+ view.doc.iter(pos, n, function(line) {
828
+ processLine(cm, line, state);
829
+ var save = pos == n - 1 || pos % 5 == 0 || pos >= view.showingFrom && pos < view.showingTo;
830
+ line.stateAfter = save ? copyState(view.mode, state) : null;
831
+ ++pos;
832
+ });
833
+ return state;
834
+ }
835
+
836
+ // POSITION MEASUREMENT
837
+
838
+ function paddingTop(display) {return display.lineSpace.offsetTop;}
839
+ function paddingLeft(display) {
840
+ var e = removeChildrenAndAdd(display.measure, elt("pre")).appendChild(elt("span", "x"));
841
+ return e.offsetLeft;
842
+ }
843
+
844
+ function measureChar(cm, line, ch, data) {
845
+ var data = data || measureLine(cm, line), dir = -1;
846
+ for (var pos = ch;; pos += dir) {
847
+ var r = data[pos];
848
+ if (r) break;
849
+ if (dir < 0 && pos == 0) dir = 1;
850
+ }
851
+ return {left: pos < ch ? r.right : r.left,
852
+ right: pos > ch ? r.left : r.right,
853
+ top: r.top, bottom: r.bottom};
854
+ }
855
+
856
+ function measureLine(cm, line) {
857
+ // First look in the cache
858
+ var display = cm.display, cache = cm.display.measureLineCache;
859
+ for (var i = 0; i < cache.length; ++i) {
860
+ var memo = cache[i];
861
+ if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
862
+ display.scroller.clientWidth == memo.width)
863
+ return memo.measure;
864
+ }
865
+
866
+ var measure = measureLineInner(cm, line);
867
+ // Store result in the cache
868
+ var memo = {text: line.text, width: display.scroller.clientWidth,
869
+ markedSpans: line.markedSpans, measure: measure};
870
+ if (cache.length == 16) cache[++display.measureLineCachePos % 16] = memo;
871
+ else cache.push(memo);
872
+ return measure;
873
+ }
874
+
875
+ function measureLineInner(cm, line) {
876
+ var display = cm.display, measure = emptyArray(line.text.length);
877
+ var pre = lineContent(cm, line, measure);
878
+
879
+ // IE does not cache element positions of inline elements between
880
+ // calls to getBoundingClientRect. This makes the loop below,
881
+ // which gathers the positions of all the characters on the line,
882
+ // do an amount of layout work quadratic to the number of
883
+ // characters. When line wrapping is off, we try to improve things
884
+ // by first subdividing the line into a bunch of inline blocks, so
885
+ // that IE can reuse most of the layout information from caches
886
+ // for those blocks. This does interfere with line wrapping, so it
887
+ // doesn't work when wrapping is on, but in that case the
888
+ // situation is slightly better, since IE does cache line-wrapping
889
+ // information and only recomputes per-line.
890
+ if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
891
+ var fragment = document.createDocumentFragment();
892
+ var chunk = 10, n = pre.childNodes.length;
893
+ for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {
894
+ var wrap = elt("div", null, null, "display: inline-block");
895
+ for (var j = 0; j < chunk && n; ++j) {
896
+ wrap.appendChild(pre.firstChild);
897
+ --n;
898
+ }
899
+ fragment.appendChild(wrap);
900
+ }
901
+ pre.appendChild(fragment);
902
+ }
903
+
904
+ removeChildrenAndAdd(display.measure, pre);
905
+
906
+ var outer = display.lineDiv.getBoundingClientRect();
907
+ var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
908
+ for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
909
+ var size = cur.getBoundingClientRect();
910
+ var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot);
911
+ for (var j = 0; j < vranges.length; j += 2) {
912
+ var rtop = vranges[j], rbot = vranges[j+1];
913
+ if (rtop > bot || rbot < top) continue;
914
+ if (rtop <= top && rbot >= bot ||
915
+ top <= rtop && bot >= rbot ||
916
+ Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
917
+ vranges[j] = Math.min(top, rtop);
918
+ vranges[j+1] = Math.max(bot, rbot);
919
+ break;
920
+ }
921
+ }
922
+ if (j == vranges.length) vranges.push(top, bot);
923
+ data[i] = {left: size.left - outer.left, right: size.right - outer.left, top: j};
924
+ }
925
+ for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
926
+ var vr = cur.top;
927
+ cur.top = vranges[vr]; cur.bottom = vranges[vr+1];
928
+ }
929
+ return data;
930
+ }
931
+
932
+ function clearCaches(cm) {
933
+ cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
934
+ cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
935
+ cm.view.maxLineChanged = true;
936
+ }
937
+
938
+ // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
939
+ function intoCoordSystem(cm, lineObj, rect, context) {
940
+ if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
941
+ var size = lineObj.widgets[i].node.offsetHeight;
942
+ rect.top += size; rect.bottom += size;
943
+ }
944
+ if (context == "line") return rect;
945
+ if (!context) context = "local";
946
+ var yOff = heightAtLine(cm, lineObj);
947
+ if (context != "local") yOff -= cm.display.viewOffset;
948
+ if (context == "page") {
949
+ var lOff = cm.display.lineSpace.getBoundingClientRect();
950
+ yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop);
951
+ var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft);
952
+ rect.left += xOff; rect.right += xOff;
953
+ }
954
+ rect.top += yOff; rect.bottom += yOff;
955
+ return rect;
956
+ }
957
+
958
+ function charCoords(cm, pos, context, lineObj) {
959
+ if (!lineObj) lineObj = getLine(cm.view.doc, pos.line);
960
+ return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context);
961
+ }
962
+
963
+ function cursorCoords(cm, pos, context, lineObj, measurement) {
964
+ lineObj = lineObj || getLine(cm.view.doc, pos.line);
965
+ if (!measurement) measurement = measureLine(cm, lineObj);
966
+ function get(ch, right) {
967
+ var m = measureChar(cm, lineObj, ch, measurement);
968
+ if (right) m.left = m.right; else m.right = m.left;
969
+ return intoCoordSystem(cm, lineObj, m, context);
970
+ }
971
+ var order = getOrder(lineObj), ch = pos.ch;
972
+ if (!order) return get(ch);
973
+ var main, other, linedir = order[0].level;
974
+ for (var i = 0; i < order.length; ++i) {
975
+ var part = order[i], rtl = part.level % 2, nb, here;
976
+ if (part.from < ch && part.to > ch) return get(ch, rtl);
977
+ var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to;
978
+ if (left == ch) {
979
+ // Opera and IE return bogus offsets and widths for edges
980
+ // where the direction flips, but only for the side with the
981
+ // lower level. So we try to use the side with the higher
982
+ // level.
983
+ if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true);
984
+ else here = get(rtl && part.from != part.to ? ch - 1 : ch);
985
+ if (rtl == linedir) main = here; else other = here;
986
+ } else if (right == ch) {
987
+ var nb = i < order.length - 1 && order[i+1];
988
+ if (!rtl && nb && nb.from == nb.to) continue;
989
+ if (nb && part.level < nb.level) here = get(nb.level % 2 ? nb.to - 1 : nb.from);
990
+ else here = get(rtl ? ch : ch - 1, true);
991
+ if (rtl == linedir) main = here; else other = here;
992
+ }
993
+ }
994
+ if (linedir && !ch) other = get(order[0].to - 1);
995
+ if (!main) return other;
996
+ if (other) main.other = other;
997
+ return main;
998
+ }
999
+
1000
+ // Coords must be lineSpace-local
1001
+ function coordsChar(cm, x, y) {
1002
+ var doc = cm.view.doc;
1003
+ y += cm.display.viewOffset;
1004
+ if (y < 0) return {line: 0, ch: 0, outside: true};
1005
+ var lineNo = lineAtHeight(doc, y);
1006
+ if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc, doc.size - 1).text.length};
1007
+ if (x < 0) x = 0;
1008
+
1009
+ for (;;) {
1010
+ var lineObj = getLine(doc, lineNo);
1011
+ var found = coordsCharInner(cm, lineObj, lineNo, x, y);
1012
+ var merged = collapsedSpanAtEnd(lineObj);
1013
+ if (merged && found.ch == lineRight(lineObj))
1014
+ lineNo = merged.find().to.line;
1015
+ else
1016
+ return found;
1017
+ }
1018
+ }
1019
+
1020
+ function coordsCharInner(cm, lineObj, lineNo, x, y) {
1021
+ var innerOff = y - heightAtLine(cm, lineObj);
1022
+ var wrongLine = false, cWidth = cm.display.wrapper.clientWidth;
1023
+ var measurement = measureLine(cm, lineObj);
1024
+
1025
+ function getX(ch) {
1026
+ var sp = cursorCoords(cm, {line: lineNo, ch: ch}, "line",
1027
+ lineObj, measurement);
1028
+ wrongLine = true;
1029
+ if (innerOff > sp.bottom) return Math.max(0, sp.left - cWidth);
1030
+ else if (innerOff < sp.top) return sp.left + cWidth;
1031
+ else wrongLine = false;
1032
+ return sp.left;
1033
+ }
1034
+
1035
+ var bidi = getOrder(lineObj), dist = lineObj.text.length;
1036
+ var from = lineLeft(lineObj), to = lineRight(lineObj);
1037
+ var fromX = paddingLeft(cm.display), toX = getX(to);
1038
+
1039
+ if (x > toX) return {line: lineNo, ch: to, outside: wrongLine};
1040
+ // Do a binary search between these bounds.
1041
+ for (;;) {
1042
+ if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
1043
+ var after = x - fromX < toX - x, ch = after ? from : to;
1044
+ while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
1045
+ return {line: lineNo, ch: ch, after: after, outside: wrongLine};
1046
+ }
1047
+ var step = Math.ceil(dist / 2), middle = from + step;
1048
+ if (bidi) {
1049
+ middle = from;
1050
+ for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
1051
+ }
1052
+ var middleX = getX(middle);
1053
+ if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; dist -= step;}
1054
+ else {from = middle; fromX = middleX; dist = step;}
1055
+ }
1056
+ }
1057
+
1058
+ var measureText;
1059
+ function textHeight(display) {
1060
+ if (display.cachedTextHeight != null) return display.cachedTextHeight;
1061
+ if (measureText == null) {
1062
+ measureText = elt("pre");
1063
+ // Measure a bunch of lines, for browsers that compute
1064
+ // fractional heights.
1065
+ for (var i = 0; i < 49; ++i) {
1066
+ measureText.appendChild(document.createTextNode("x"));
1067
+ measureText.appendChild(elt("br"));
1068
+ }
1069
+ measureText.appendChild(document.createTextNode("x"));
1070
+ }
1071
+ removeChildrenAndAdd(display.measure, measureText);
1072
+ var height = measureText.offsetHeight / 50;
1073
+ if (height > 3) display.cachedTextHeight = height;
1074
+ removeChildren(display.measure);
1075
+ return height || 1;
1076
+ }
1077
+
1078
+ function charWidth(display) {
1079
+ if (display.cachedCharWidth != null) return display.cachedCharWidth;
1080
+ var anchor = elt("span", "x");
1081
+ var pre = elt("pre", [anchor]);
1082
+ removeChildrenAndAdd(display.measure, pre);
1083
+ var width = anchor.offsetWidth;
1084
+ if (width > 2) display.cachedCharWidth = width;
1085
+ return width || 10;
1086
+ }
1087
+
1088
+ // OPERATIONS
1089
+
1090
+ // Operations are used to wrap changes in such a way that each
1091
+ // change won't have to update the cursor and display (which would
1092
+ // be awkward, slow, and error-prone), but instead updates are
1093
+ // batched and then all combined and executed at once.
1094
+
1095
+ function startOperation(cm) {
1096
+ if (cm.curOp) ++cm.curOp.depth;
1097
+ else cm.curOp = {
1098
+ // Nested operations delay update until the outermost one
1099
+ // finishes.
1100
+ depth: 1,
1101
+ // An array of ranges of lines that have to be updated. See
1102
+ // updateDisplay.
1103
+ changes: [],
1104
+ delayedCallbacks: [],
1105
+ updateInput: null,
1106
+ userSelChange: null,
1107
+ textChanged: null,
1108
+ selectionChanged: false,
1109
+ updateMaxLine: false,
1110
+ id: ++cm.nextOpId
1111
+ };
1112
+ }
1113
+
1114
+ function endOperation(cm) {
1115
+ var op = cm.curOp;
1116
+ if (--op.depth) return;
1117
+ cm.curOp = null;
1118
+ var view = cm.view, display = cm.display;
1119
+ if (op.updateMaxLine) computeMaxLength(view);
1120
+ if (view.maxLineChanged && !cm.options.lineWrapping) {
1121
+ var width = measureChar(cm, view.maxLine, view.maxLine.text.length).right;
1122
+ display.sizer.style.minWidth = (width + 3 + scrollerCutOff) + "px";
1123
+ view.maxLineChanged = false;
1124
+ }
1125
+ var newScrollPos, updated;
1126
+ if (op.selectionChanged) {
1127
+ var coords = cursorCoords(cm, view.sel.head);
1128
+ newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
1129
+ }
1130
+ if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null)
1131
+ updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop);
1132
+ if (!updated && op.selectionChanged) updateSelection(cm);
1133
+ if (newScrollPos) scrollCursorIntoView(cm);
1134
+ if (op.selectionChanged) restartBlink(cm);
1135
+
1136
+ if (view.focused && op.updateInput)
1137
+ resetInput(cm, op.userSelChange);
1138
+
1139
+ if (op.textChanged)
1140
+ signal(cm, "change", cm, op.textChanged);
1141
+ if (op.selectionChanged) signal(cm, "cursorActivity", cm);
1142
+ for (var i = 0; i < op.delayedCallbacks.length; ++i) op.delayedCallbacks[i](cm);
1143
+ }
1144
+
1145
+ // Wraps a function in an operation. Returns the wrapped function.
1146
+ function operation(cm1, f) {
1147
+ return function() {
1148
+ var cm = cm1 || this;
1149
+ startOperation(cm);
1150
+ try {var result = f.apply(cm, arguments);}
1151
+ finally {endOperation(cm);}
1152
+ return result;
1153
+ };
1154
+ }
1155
+
1156
+ function regChange(cm, from, to, lendiff) {
1157
+ cm.curOp.changes.push({from: from, to: to, diff: lendiff});
1158
+ }
1159
+
1160
+ // INPUT HANDLING
1161
+
1162
+ function slowPoll(cm) {
1163
+ if (cm.view.pollingFast) return;
1164
+ cm.display.poll.set(cm.options.pollInterval, function() {
1165
+ readInput(cm);
1166
+ if (cm.view.focused) slowPoll(cm);
1167
+ });
1168
+ }
1169
+
1170
+ function fastPoll(cm) {
1171
+ var missed = false;
1172
+ cm.display.pollingFast = true;
1173
+ function p() {
1174
+ var changed = readInput(cm);
1175
+ if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
1176
+ else {cm.display.pollingFast = false; slowPoll(cm);}
1177
+ }
1178
+ cm.display.poll.set(20, p);
1179
+ }
1180
+
1181
+ // prevInput is a hack to work with IME. If we reset the textarea
1182
+ // on every change, that breaks IME. So we look for changes
1183
+ // compared to the previous content instead. (Modern browsers have
1184
+ // events that indicate IME taking place, but these are not widely
1185
+ // supported or compatible enough yet to rely on.)
1186
+ function readInput(cm) {
1187
+ var input = cm.display.input, prevInput = cm.display.prevInput, view = cm.view, sel = view.sel;
1188
+ if (!view.focused || hasSelection(input) || isReadOnly(cm)) return false;
1189
+ var text = input.value;
1190
+ if (text == prevInput && posEq(sel.from, sel.to)) return false;
1191
+ startOperation(cm);
1192
+ view.sel.shift = false;
1193
+ var same = 0, l = Math.min(prevInput.length, text.length);
1194
+ while (same < l && prevInput[same] == text[same]) ++same;
1195
+ var from = sel.from, to = sel.to;
1196
+ if (same < prevInput.length)
1197
+ from = {line: from.line, ch: from.ch - (prevInput.length - same)};
1198
+ else if (view.overwrite && posEq(from, to) && !cm.display.pasteIncoming)
1199
+ to = {line: to.line, ch: Math.min(getLine(cm.view.doc, to.line).text.length, to.ch + (text.length - same))};
1200
+ var updateInput = cm.curOp.updateInput;
1201
+ updateDoc(cm, from, to, splitLines(text.slice(same)), "end",
1202
+ cm.display.pasteIncoming ? "paste" : "input", {from: from, to: to});
1203
+ cm.curOp.updateInput = updateInput;
1204
+ if (text.length > 1000) input.value = cm.display.prevInput = "";
1205
+ else cm.display.prevInput = text;
1206
+ endOperation(cm);
1207
+ cm.display.pasteIncoming = false;
1208
+ return true;
1209
+ }
1210
+
1211
+ function resetInput(cm, user) {
1212
+ var view = cm.view, minimal, selected;
1213
+ if (!posEq(view.sel.from, view.sel.to)) {
1214
+ cm.display.prevInput = "";
1215
+ minimal = hasCopyEvent &&
1216
+ (view.sel.to.line - view.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
1217
+ if (minimal) cm.display.input.value = "-";
1218
+ else cm.display.input.value = selected || cm.getSelection();
1219
+ if (view.focused) selectInput(cm.display.input);
1220
+ } else if (user) cm.display.prevInput = cm.display.input.value = "";
1221
+ cm.display.inaccurateSelection = minimal;
1222
+ }
1223
+
1224
+ function focusInput(cm) {
1225
+ if (cm.options.readOnly != "nocursor" && (ie || document.activeElement != cm.display.input))
1226
+ cm.display.input.focus();
1227
+ }
1228
+
1229
+ function isReadOnly(cm) {
1230
+ return cm.options.readOnly || cm.view.cantEdit;
1231
+ }
1232
+
1233
+ // EVENT HANDLERS
1234
+
1235
+ function registerEventHandlers(cm) {
1236
+ var d = cm.display;
1237
+ on(d.scroller, "mousedown", operation(cm, onMouseDown));
1238
+ on(d.scroller, "dblclick", operation(cm, e_preventDefault));
1239
+ on(d.lineSpace, "selectstart", function(e) {
1240
+ if (!mouseEventInWidget(d, e)) e_preventDefault(e);
1241
+ });
1242
+ // Gecko browsers fire contextmenu *after* opening the menu, at
1243
+ // which point we can't mess with it anymore. Context menu is
1244
+ // handled in onMouseDown for Gecko.
1245
+ if (!gecko) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
1246
+
1247
+ on(d.scroller, "scroll", function() {
1248
+ setScrollTop(cm, d.scroller.scrollTop);
1249
+ setScrollLeft(cm, d.scroller.scrollLeft, true);
1250
+ signal(cm, "scroll", cm);
1251
+ });
1252
+ on(d.scrollbarV, "scroll", function() {
1253
+ setScrollTop(cm, d.scrollbarV.scrollTop);
1254
+ });
1255
+ on(d.scrollbarH, "scroll", function() {
1256
+ setScrollLeft(cm, d.scrollbarH.scrollLeft);
1257
+ });
1258
+
1259
+ on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
1260
+ on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
1261
+
1262
+ function reFocus() { if (cm.view.focused) setTimeout(bind(focusInput, cm), 0); }
1263
+ on(d.scrollbarH, "mousedown", reFocus);
1264
+ on(d.scrollbarV, "mousedown", reFocus);
1265
+ // Prevent wrapper from ever scrolling
1266
+ on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
1267
+ on(window, "resize", function resizeHandler() {
1268
+ // Might be a text scaling operation, clear size caches.
1269
+ d.cachedCharWidth = d.cachedTextHeight = null;
1270
+ clearCaches(cm);
1271
+ if (d.wrapper.parentNode) updateDisplay(cm, true);
1272
+ else off(window, "resize", resizeHandler);
1273
+ });
1274
+
1275
+ on(d.input, "keyup", operation(cm, function(e) {
1276
+ if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1277
+ if (e_prop(e, "keyCode") == 16) cm.view.sel.shift = false;
1278
+ }));
1279
+ on(d.input, "input", bind(fastPoll, cm));
1280
+ on(d.input, "keydown", operation(cm, onKeyDown));
1281
+ on(d.input, "keypress", operation(cm, onKeyPress));
1282
+ on(d.input, "focus", bind(onFocus, cm));
1283
+ on(d.input, "blur", bind(onBlur, cm));
1284
+
1285
+ function drag_(e) {
1286
+ if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1287
+ e_stop(e);
1288
+ }
1289
+ if (cm.options.dragDrop) {
1290
+ on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
1291
+ on(d.scroller, "dragenter", drag_);
1292
+ on(d.scroller, "dragover", drag_);
1293
+ on(d.scroller, "drop", operation(cm, onDrop));
1294
+ }
1295
+ on(d.scroller, "paste", function(){focusInput(cm); fastPoll(cm);});
1296
+ on(d.input, "paste", function() {
1297
+ d.pasteIncoming = true;
1298
+ fastPoll(cm);
1299
+ });
1300
+
1301
+ function prepareCopy() {
1302
+ if (d.inaccurateSelection) {
1303
+ d.prevInput = "";
1304
+ d.inaccurateSelection = false;
1305
+ d.input.value = cm.getSelection();
1306
+ selectInput(d.input);
1307
+ }
1308
+ }
1309
+ on(d.input, "cut", prepareCopy);
1310
+ on(d.input, "copy", prepareCopy);
1311
+
1312
+ // Needed to handle Tab key in KHTML
1313
+ if (khtml) on(d.sizer, "mouseup", function() {
1314
+ if (document.activeElement == d.input) d.input.blur();
1315
+ focusInput(cm);
1316
+ });
1317
+ }
1318
+
1319
+ function mouseEventInWidget(display, e) {
1320
+ for (var n = e_target(e); n != display.wrapper; n = n.parentNode)
1321
+ if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) ||
1322
+ n.parentNode == display.sizer && n != display.mover) return true;
1323
+ }
1324
+
1325
+ function posFromMouse(cm, e, liberal) {
1326
+ var display = cm.display;
1327
+ if (!liberal) {
1328
+ var target = e_target(e);
1329
+ if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
1330
+ target == display.scrollbarV || target == display.scrollbarV.firstChild ||
1331
+ target == display.scrollbarFiller) return null;
1332
+ }
1333
+ var x, y, space = display.lineSpace.getBoundingClientRect();
1334
+ // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1335
+ try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1336
+ return coordsChar(cm, x - space.left, y - space.top);
1337
+ }
1338
+
1339
+ var lastClick, lastDoubleClick;
1340
+ function onMouseDown(e) {
1341
+ var cm = this, display = cm.display, view = cm.view, sel = view.sel, doc = view.doc;
1342
+ sel.shift = e_prop(e, "shiftKey");
1343
+
1344
+ if (mouseEventInWidget(display, e)) {
1345
+ if (!webkit) {
1346
+ display.scroller.draggable = false;
1347
+ setTimeout(function(){display.scroller.draggable = true;}, 100);
1348
+ }
1349
+ return;
1350
+ }
1351
+ if (clickInGutter(cm, e)) return;
1352
+ var start = posFromMouse(cm, e);
1353
+
1354
+ switch (e_button(e)) {
1355
+ case 3:
1356
+ if (gecko) onContextMenu.call(cm, cm, e);
1357
+ return;
1358
+ case 2:
1359
+ if (start) extendSelection(cm, start);
1360
+ setTimeout(bind(focusInput, cm), 20);
1361
+ e_preventDefault(e);
1362
+ return;
1363
+ }
1364
+ // For button 1, if it was clicked inside the editor
1365
+ // (posFromMouse returning non-null), we have to adjust the
1366
+ // selection.
1367
+ if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}
1368
+
1369
+ if (!view.focused) onFocus(cm);
1370
+
1371
+ var now = +new Date, type = "single";
1372
+ if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
1373
+ type = "triple";
1374
+ e_preventDefault(e);
1375
+ setTimeout(bind(focusInput, cm), 20);
1376
+ selectLine(cm, start.line);
1377
+ } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
1378
+ type = "double";
1379
+ lastDoubleClick = {time: now, pos: start};
1380
+ e_preventDefault(e);
1381
+ var word = findWordAt(getLine(doc, start.line).text, start);
1382
+ extendSelection(cm, word.from, word.to);
1383
+ } else { lastClick = {time: now, pos: start}; }
1384
+
1385
+ var last = start;
1386
+ if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) &&
1387
+ !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
1388
+ var dragEnd = operation(cm, function(e2) {
1389
+ if (webkit) display.scroller.draggable = false;
1390
+ view.draggingText = false;
1391
+ off(document, "mouseup", dragEnd);
1392
+ off(display.scroller, "drop", dragEnd);
1393
+ if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
1394
+ e_preventDefault(e2);
1395
+ extendSelection(cm, start);
1396
+ focusInput(cm);
1397
+ }
1398
+ });
1399
+ // Let the drag handler handle this.
1400
+ if (webkit) display.scroller.draggable = true;
1401
+ view.draggingText = dragEnd;
1402
+ // IE's approach to draggable
1403
+ if (display.scroller.dragDrop) display.scroller.dragDrop();
1404
+ on(document, "mouseup", dragEnd);
1405
+ on(display.scroller, "drop", dragEnd);
1406
+ return;
1407
+ }
1408
+ e_preventDefault(e);
1409
+ if (type == "single") extendSelection(cm, clipPos(doc, start));
1410
+
1411
+ var startstart = sel.from, startend = sel.to;
1412
+
1413
+ function doSelect(cur) {
1414
+ if (type == "single") {
1415
+ extendSelection(cm, clipPos(doc, start), cur);
1416
+ return;
1417
+ }
1418
+
1419
+ startstart = clipPos(doc, startstart);
1420
+ startend = clipPos(doc, startend);
1421
+ if (type == "double") {
1422
+ var word = findWordAt(getLine(doc, cur.line).text, cur);
1423
+ if (posLess(cur, startstart)) extendSelection(cm, word.from, startend);
1424
+ else extendSelection(cm, startstart, word.to);
1425
+ } else if (type == "triple") {
1426
+ if (posLess(cur, startstart)) extendSelection(cm, startend, clipPos(doc, {line: cur.line, ch: 0}));
1427
+ else extendSelection(cm, startstart, clipPos(doc, {line: cur.line + 1, ch: 0}));
1428
+ }
1429
+ }
1430
+
1431
+ var editorSize = display.wrapper.getBoundingClientRect();
1432
+ // Used to ensure timeout re-tries don't fire when another extend
1433
+ // happened in the meantime (clearTimeout isn't reliable -- at
1434
+ // least on Chrome, the timeouts still happen even when cleared,
1435
+ // if the clear happens after their scheduled firing time).
1436
+ var counter = 0;
1437
+
1438
+ function extend(e) {
1439
+ var curCount = ++counter;
1440
+ var cur = posFromMouse(cm, e, true);
1441
+ if (!cur) return;
1442
+ if (!posEq(cur, last)) {
1443
+ if (!view.focused) onFocus(cm);
1444
+ last = cur;
1445
+ doSelect(cur);
1446
+ var visible = visibleLines(display, doc);
1447
+ if (cur.line >= visible.to || cur.line < visible.from)
1448
+ setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
1449
+ } else {
1450
+ var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
1451
+ if (outside) setTimeout(operation(cm, function() {
1452
+ if (counter != curCount) return;
1453
+ display.scroller.scrollTop += outside;
1454
+ extend(e);
1455
+ }), 50);
1456
+ }
1457
+ }
1458
+
1459
+ function done(e) {
1460
+ counter = Infinity;
1461
+ var cur = posFromMouse(cm, e);
1462
+ if (cur) doSelect(cur);
1463
+ e_preventDefault(e);
1464
+ focusInput(cm);
1465
+ off(document, "mousemove", move);
1466
+ off(document, "mouseup", up);
1467
+ }
1468
+
1469
+ var move = operation(cm, function(e) {
1470
+ if (!ie && !e_button(e)) done(e);
1471
+ else extend(e);
1472
+ });
1473
+ var up = operation(cm, done);
1474
+ on(document, "mousemove", move);
1475
+ on(document, "mouseup", up);
1476
+ }
1477
+
1478
+ function onDrop(e) {
1479
+ var cm = this;
1480
+ if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1481
+ e_preventDefault(e);
1482
+ var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
1483
+ if (!pos || isReadOnly(cm)) return;
1484
+ if (files && files.length && window.FileReader && window.File) {
1485
+ var n = files.length, text = Array(n), read = 0;
1486
+ var loadFile = function(file, i) {
1487
+ var reader = new FileReader;
1488
+ reader.onload = function() {
1489
+ text[i] = reader.result;
1490
+ if (++read == n) {
1491
+ pos = clipPos(cm.view.doc, pos);
1492
+ operation(cm, function() {
1493
+ var end = replaceRange(cm, text.join(""), pos, pos, "paste");
1494
+ setSelection(cm, pos, end);
1495
+ })();
1496
+ }
1497
+ };
1498
+ reader.readAsText(file);
1499
+ };
1500
+ for (var i = 0; i < n; ++i) loadFile(files[i], i);
1501
+ } else {
1502
+ // Don't do a replace if the drop happened inside of the selected text.
1503
+ if (cm.view.draggingText && !(posLess(pos, cm.view.sel.from) || posLess(cm.view.sel.to, pos))) {
1504
+ cm.view.draggingText(e);
1505
+ if (ie) setTimeout(bind(focusInput, cm), 50);
1506
+ return;
1507
+ }
1508
+ try {
1509
+ var text = e.dataTransfer.getData("Text");
1510
+ if (text) {
1511
+ var curFrom = cm.view.sel.from, curTo = cm.view.sel.to;
1512
+ setSelection(cm, pos, pos);
1513
+ if (cm.view.draggingText) replaceRange(cm, "", curFrom, curTo, "paste");
1514
+ cm.replaceSelection(text, null, "paste");
1515
+ focusInput(cm);
1516
+ onFocus(cm);
1517
+ }
1518
+ }
1519
+ catch(e){}
1520
+ }
1521
+ }
1522
+
1523
+ function clickInGutter(cm, e) {
1524
+ var display = cm.display;
1525
+ try { var mX = e.clientX, mY = e.clientY; }
1526
+ catch(e) { return false; }
1527
+
1528
+ if (mX >= Math.floor(display.gutters.getBoundingClientRect().right)) return false;
1529
+ e_preventDefault(e);
1530
+ if (!hasHandler(cm, "gutterClick")) return true;
1531
+
1532
+ var lineBox = display.lineDiv.getBoundingClientRect();
1533
+ if (mY > lineBox.bottom) return true;
1534
+ mY -= lineBox.top - display.viewOffset;
1535
+
1536
+ for (var i = 0; i < cm.options.gutters.length; ++i) {
1537
+ var g = display.gutters.childNodes[i];
1538
+ if (g && g.getBoundingClientRect().right >= mX) {
1539
+ var line = lineAtHeight(cm.view.doc, mY);
1540
+ var gutter = cm.options.gutters[i];
1541
+ signalLater(cm, cm, "gutterClick", cm, line, gutter, e);
1542
+ break;
1543
+ }
1544
+ }
1545
+ return true;
1546
+ }
1547
+
1548
+ function onDragStart(cm, e) {
1549
+ var txt = cm.getSelection();
1550
+ e.dataTransfer.setData("Text", txt);
1551
+
1552
+ // Use dummy image instead of default browsers image.
1553
+ // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
1554
+ if (e.dataTransfer.setDragImage && !safari)
1555
+ e.dataTransfer.setDragImage(elt('img'), 0, 0);
1556
+ }
1557
+
1558
+ function setScrollTop(cm, val) {
1559
+ if (Math.abs(cm.view.scrollTop - val) < 2) return;
1560
+ cm.view.scrollTop = val;
1561
+ if (!gecko) updateDisplay(cm, [], val);
1562
+ if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
1563
+ if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
1564
+ if (gecko) updateDisplay(cm, []);
1565
+ }
1566
+ function setScrollLeft(cm, val, isScroller) {
1567
+ if (isScroller ? val == cm.view.scrollLeft : Math.abs(cm.view.scrollLeft - val) < 2) return;
1568
+ cm.view.scrollLeft = val;
1569
+ alignHorizontally(cm);
1570
+ if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
1571
+ if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
1572
+ }
1573
+
1574
+ // Since the delta values reported on mouse wheel events are
1575
+ // unstandardized between browsers and even browser versions, and
1576
+ // generally horribly unpredictable, this code starts by measuring
1577
+ // the scroll effect that the first few mouse wheel events have,
1578
+ // and, from that, detects the way it can convert deltas to pixel
1579
+ // offsets afterwards.
1580
+ //
1581
+ // The reason we want to know the amount a wheel event will scroll
1582
+ // is that it gives us a chance to update the display before the
1583
+ // actual scrolling happens, reducing flickering.
1584
+
1585
+ var wheelSamples = 0, wheelDX, wheelDY, wheelStartX, wheelStartY, wheelPixelsPerUnit = null;
1586
+ // Fill in a browser-detected starting value on browsers where we
1587
+ // know one. These don't have to be accurate -- the result of them
1588
+ // being wrong would just be a slight flicker on the first wheel
1589
+ // scroll (if it is large enough).
1590
+ if (ie) wheelPixelsPerUnit = -.53;
1591
+ else if (gecko) wheelPixelsPerUnit = 15;
1592
+ else if (chrome) wheelPixelsPerUnit = -.7;
1593
+ else if (safari) wheelPixelsPerUnit = -1/3;
1594
+
1595
+ function onScrollWheel(cm, e) {
1596
+ var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
1597
+ if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
1598
+ if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
1599
+ else if (dy == null) dy = e.wheelDelta;
1600
+
1601
+ // Webkit browsers on OS X abort momentum scrolls when the target
1602
+ // of the scroll event is removed from the scrollable element.
1603
+ // This hack (see related code in patchDisplay) makes sure the
1604
+ // element is kept around.
1605
+ if (dy && mac && webkit) {
1606
+ for (var cur = e.target; cur != scroll; cur = cur.parentNode) {
1607
+ if (cur.lineObj) {
1608
+ cm.display.currentWheelTarget = cur;
1609
+ break;
1610
+ }
1611
+ }
1612
+ }
1613
+
1614
+ var scroll = cm.display.scroller;
1615
+ // On some browsers, horizontal scrolling will cause redraws to
1616
+ // happen before the gutter has been realigned, causing it to
1617
+ // wriggle around in a most unseemly way. When we have an
1618
+ // estimated pixels/delta value, we just handle horizontal
1619
+ // scrolling entirely here. It'll be slightly off from native, but
1620
+ // better than glitching out.
1621
+ if (dx && !gecko && !opera && wheelPixelsPerUnit != null) {
1622
+ if (dy)
1623
+ setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
1624
+ setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
1625
+ e_preventDefault(e);
1626
+ wheelStartX = null; // Abort measurement, if in progress
1627
+ return;
1628
+ }
1629
+
1630
+ if (dy && wheelPixelsPerUnit != null) {
1631
+ var pixels = dy * wheelPixelsPerUnit;
1632
+ var top = cm.view.scrollTop, bot = top + cm.display.wrapper.clientHeight;
1633
+ if (pixels < 0) top = Math.max(0, top + pixels - 50);
1634
+ else bot = Math.min(cm.view.doc.height, bot + pixels + 50);
1635
+ updateDisplay(cm, [], {top: top, bottom: bot});
1636
+ }
1637
+
1638
+ if (wheelSamples < 20) {
1639
+ if (wheelStartX == null) {
1640
+ wheelStartX = scroll.scrollLeft; wheelStartY = scroll.scrollTop;
1641
+ wheelDX = dx; wheelDY = dy;
1642
+ setTimeout(function() {
1643
+ if (wheelStartX == null) return;
1644
+ var movedX = scroll.scrollLeft - wheelStartX;
1645
+ var movedY = scroll.scrollTop - wheelStartY;
1646
+ var sample = (movedY && wheelDY && movedY / wheelDY) ||
1647
+ (movedX && wheelDX && movedX / wheelDX);
1648
+ wheelStartX = wheelStartY = null;
1649
+ if (!sample) return;
1650
+ wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
1651
+ ++wheelSamples;
1652
+ }, 200);
1653
+ } else {
1654
+ wheelDX += dx; wheelDY += dy;
1655
+ }
1656
+ }
1657
+ }
1658
+
1659
+ function doHandleBinding(cm, bound, dropShift) {
1660
+ if (typeof bound == "string") {
1661
+ bound = commands[bound];
1662
+ if (!bound) return false;
1663
+ }
1664
+ // Ensure previous input has been read, so that the handler sees a
1665
+ // consistent view of the document
1666
+ if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
1667
+ var view = cm.view, prevShift = view.sel.shift;
1668
+ try {
1669
+ if (isReadOnly(cm)) view.suppressEdits = true;
1670
+ if (dropShift) view.sel.shift = false;
1671
+ bound(cm);
1672
+ } catch(e) {
1673
+ if (e != Pass) throw e;
1674
+ return false;
1675
+ } finally {
1676
+ view.sel.shift = prevShift;
1677
+ view.suppressEdits = false;
1678
+ }
1679
+ return true;
1680
+ }
1681
+
1682
+ function allKeyMaps(cm) {
1683
+ var maps = cm.view.keyMaps.slice(0);
1684
+ maps.push(cm.options.keyMap);
1685
+ if (cm.options.extraKeys) maps.unshift(cm.options.extraKeys);
1686
+ return maps;
1687
+ }
1688
+
1689
+ var maybeTransition;
1690
+ function handleKeyBinding(cm, e) {
1691
+ // Handle auto keymap transitions
1692
+ var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
1693
+ clearTimeout(maybeTransition);
1694
+ if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
1695
+ if (getKeyMap(cm.options.keyMap) == startMap)
1696
+ cm.options.keyMap = (next.call ? next.call(null, cm) : next);
1697
+ }, 50);
1698
+
1699
+ var name = keyNames[e_prop(e, "keyCode")], handled = false;
1700
+ var flipCtrlCmd = mac && (opera || qtwebkit);
1701
+ if (name == null || e.altGraphKey) return false;
1702
+ if (e_prop(e, "altKey")) name = "Alt-" + name;
1703
+ if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name;
1704
+ if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name;
1705
+
1706
+ var stopped = false;
1707
+ function stop() { stopped = true; }
1708
+ var keymaps = allKeyMaps(cm);
1709
+
1710
+ if (e_prop(e, "shiftKey")) {
1711
+ handled = lookupKey("Shift-" + name, keymaps,
1712
+ function(b) {return doHandleBinding(cm, b, true);}, stop)
1713
+ || lookupKey(name, keymaps, function(b) {
1714
+ if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b);
1715
+ }, stop);
1716
+ } else {
1717
+ handled = lookupKey(name, keymaps,
1718
+ function(b) { return doHandleBinding(cm, b); }, stop);
1719
+ }
1720
+ if (stopped) handled = false;
1721
+ if (handled) {
1722
+ e_preventDefault(e);
1723
+ restartBlink(cm);
1724
+ if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
1725
+ }
1726
+ return handled;
1727
+ }
1728
+
1729
+ function handleCharBinding(cm, e, ch) {
1730
+ var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
1731
+ function(b) { return doHandleBinding(cm, b, true); });
1732
+ if (handled) {
1733
+ e_preventDefault(e);
1734
+ restartBlink(cm);
1735
+ }
1736
+ return handled;
1737
+ }
1738
+
1739
+ var lastStoppedKey = null;
1740
+ function onKeyDown(e) {
1741
+ var cm = this;
1742
+ if (!cm.view.focused) onFocus(cm);
1743
+ if (ie && e.keyCode == 27) { e.returnValue = false; }
1744
+ if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1745
+ var code = e_prop(e, "keyCode");
1746
+ // IE does strange things with escape.
1747
+ cm.view.sel.shift = code == 16 || e_prop(e, "shiftKey");
1748
+ // First give onKeyEvent option a chance to handle this.
1749
+ var handled = handleKeyBinding(cm, e);
1750
+ if (opera) {
1751
+ lastStoppedKey = handled ? code : null;
1752
+ // Opera has no cut event... we try to at least catch the key combo
1753
+ if (!handled && code == 88 && !hasCopyEvent && e_prop(e, mac ? "metaKey" : "ctrlKey"))
1754
+ cm.replaceSelection("");
1755
+ }
1756
+ }
1757
+
1758
+ function onKeyPress(e) {
1759
+ var cm = this;
1760
+ if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1761
+ var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
1762
+ if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
1763
+ if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
1764
+ var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
1765
+ if (this.options.electricChars && this.view.mode.electricChars &&
1766
+ this.options.smartIndent && !isReadOnly(this) &&
1767
+ this.view.mode.electricChars.indexOf(ch) > -1)
1768
+ setTimeout(operation(cm, function() {indentLine(cm, cm.view.sel.to.line, "smart");}), 75);
1769
+ if (handleCharBinding(cm, e, ch)) return;
1770
+ fastPoll(cm);
1771
+ }
1772
+
1773
+ function onFocus(cm) {
1774
+ if (cm.options.readOnly == "nocursor") return;
1775
+ if (!cm.view.focused) {
1776
+ signal(cm, "focus", cm);
1777
+ cm.view.focused = true;
1778
+ if (cm.display.scroller.className.search(/\bCodeMirror-focused\b/) == -1)
1779
+ cm.display.scroller.className += " CodeMirror-focused";
1780
+ resetInput(cm, true);
1781
+ }
1782
+ slowPoll(cm);
1783
+ restartBlink(cm);
1784
+ }
1785
+ function onBlur(cm) {
1786
+ if (cm.view.focused) {
1787
+ signal(cm, "blur", cm);
1788
+ cm.view.focused = false;
1789
+ cm.display.scroller.className = cm.display.scroller.className.replace(" CodeMirror-focused", "");
1790
+ }
1791
+ clearInterval(cm.display.blinker);
1792
+ setTimeout(function() {if (!cm.view.focused) cm.view.sel.shift = false;}, 150);
1793
+ }
1794
+
1795
+ var detectingSelectAll;
1796
+ function onContextMenu(cm, e) {
1797
+ var display = cm.display, sel = cm.view.sel;
1798
+ var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
1799
+ if (!pos || opera) return; // Opera is difficult.
1800
+ if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
1801
+ operation(cm, setSelection)(cm, pos, pos);
1802
+
1803
+ var oldCSS = display.input.style.cssText;
1804
+ display.inputDiv.style.position = "absolute";
1805
+ display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
1806
+ "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" +
1807
+ "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1808
+ focusInput(cm);
1809
+ resetInput(cm, true);
1810
+ // Adds "Select all" to context menu in FF
1811
+ if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
1812
+
1813
+ function rehide() {
1814
+ display.inputDiv.style.position = "relative";
1815
+ display.input.style.cssText = oldCSS;
1816
+ if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
1817
+ slowPoll(cm);
1818
+
1819
+ // Try to detect the user choosing select-all
1820
+ if (display.input.selectionStart != null) {
1821
+ clearTimeout(detectingSelectAll);
1822
+ var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value), i = 0;
1823
+ display.prevInput = " ";
1824
+ display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
1825
+ detectingSelectAll = setTimeout(function poll(){
1826
+ if (display.prevInput == " " && display.input.selectionStart == 0)
1827
+ operation(cm, commands.selectAll)(cm);
1828
+ else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
1829
+ else resetInput(cm);
1830
+ }, 200);
1831
+ }
1832
+ }
1833
+
1834
+ if (gecko) {
1835
+ e_stop(e);
1836
+ on(window, "mouseup", function mouseup() {
1837
+ off(window, "mouseup", mouseup);
1838
+ setTimeout(rehide, 20);
1839
+ });
1840
+ } else {
1841
+ setTimeout(rehide, 50);
1842
+ }
1843
+ }
1844
+
1845
+ // UPDATING
1846
+
1847
+ // Replace the range from from to to by the strings in newText.
1848
+ // Afterwards, set the selection to selFrom, selTo.
1849
+ function updateDoc(cm, from, to, newText, selUpdate, origin) {
1850
+ // Possibly split or suppress the update based on the presence
1851
+ // of read-only spans in its range.
1852
+ var split = sawReadOnlySpans &&
1853
+ removeReadOnlyRanges(cm.view.doc, from, to);
1854
+ if (split) {
1855
+ for (var i = split.length - 1; i >= 1; --i)
1856
+ updateDocInner(cm, split[i].from, split[i].to, [""], origin);
1857
+ if (split.length)
1858
+ return updateDocInner(cm, split[0].from, split[0].to, newText, selUpdate, origin);
1859
+ } else {
1860
+ return updateDocInner(cm, from, to, newText, selUpdate, origin);
1861
+ }
1862
+ }
1863
+
1864
+ function updateDocInner(cm, from, to, newText, selUpdate, origin) {
1865
+ if (cm.view.suppressEdits) return;
1866
+
1867
+ var view = cm.view, doc = view.doc, old = [];
1868
+ doc.iter(from.line, to.line + 1, function(line) {
1869
+ old.push(newHL(line.text, line.markedSpans));
1870
+ });
1871
+ var startSelFrom = view.sel.from, startSelTo = view.sel.to;
1872
+ var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText);
1873
+ var retval = updateDocNoUndo(cm, from, to, lines, selUpdate, origin);
1874
+ if (view.history) addChange(cm, from.line, newText.length, old, origin,
1875
+ startSelFrom, startSelTo, view.sel.from, view.sel.to);
1876
+ return retval;
1877
+ }
1878
+
1879
+ function unredoHelper(cm, type) {
1880
+ var doc = cm.view.doc, hist = cm.view.history;
1881
+ var set = (type == "undo" ? hist.done : hist.undone).pop();
1882
+ if (!set) return;
1883
+ var anti = {events: [], fromBefore: set.fromAfter, toBefore: set.toAfter,
1884
+ fromAfter: set.fromBefore, toAfter: set.toBefore};
1885
+ for (var i = set.events.length - 1; i >= 0; i -= 1) {
1886
+ hist.dirtyCounter += type == "undo" ? -1 : 1;
1887
+ var change = set.events[i];
1888
+ var replaced = [], end = change.start + change.added;
1889
+ doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); });
1890
+ anti.events.push({start: change.start, added: change.old.length, old: replaced});
1891
+ var selPos = i ? null : {from: set.fromBefore, to: set.toBefore};
1892
+ updateDocNoUndo(cm, {line: change.start, ch: 0}, {line: end - 1, ch: getLine(doc, end-1).text.length},
1893
+ change.old, selPos, type);
1894
+ }
1895
+ (type == "undo" ? hist.undone : hist.done).push(anti);
1896
+ }
1897
+
1898
+ function updateDocNoUndo(cm, from, to, lines, selUpdate, origin) {
1899
+ var view = cm.view, doc = view.doc, display = cm.display;
1900
+ if (view.suppressEdits) return;
1901
+
1902
+ var nlines = to.line - from.line, firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
1903
+ var recomputeMaxLength = false, checkWidthStart = from.line;
1904
+ if (!cm.options.lineWrapping) {
1905
+ checkWidthStart = lineNo(visualLine(doc, firstLine));
1906
+ doc.iter(checkWidthStart, to.line + 1, function(line) {
1907
+ if (lineLength(doc, line) == view.maxLineLength) {
1908
+ recomputeMaxLength = true;
1909
+ return true;
1910
+ }
1911
+ });
1912
+ }
1913
+
1914
+ var lastHL = lst(lines), th = textHeight(display);
1915
+
1916
+ // First adjust the line structure
1917
+ if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") {
1918
+ // This is a whole-line replace. Treated specially to make
1919
+ // sure line objects move the way they are supposed to.
1920
+ var added = [];
1921
+ for (var i = 0, e = lines.length - 1; i < e; ++i)
1922
+ added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th));
1923
+ updateLine(cm, lastLine, lastLine.text, hlSpans(lastHL));
1924
+ if (nlines) doc.remove(from.line, nlines, cm);
1925
+ if (added.length) doc.insert(from.line, added);
1926
+ } else if (firstLine == lastLine) {
1927
+ if (lines.length == 1) {
1928
+ updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]) +
1929
+ firstLine.text.slice(to.ch), hlSpans(lines[0]));
1930
+ } else {
1931
+ for (var added = [], i = 1, e = lines.length - 1; i < e; ++i)
1932
+ added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th));
1933
+ added.push(makeLine(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL), th));
1934
+ updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]));
1935
+ doc.insert(from.line + 1, added);
1936
+ }
1937
+ } else if (lines.length == 1) {
1938
+ updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]) +
1939
+ lastLine.text.slice(to.ch), hlSpans(lines[0]));
1940
+ doc.remove(from.line + 1, nlines, cm);
1941
+ } else {
1942
+ var added = [];
1943
+ updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]));
1944
+ updateLine(cm, lastLine, hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL));
1945
+ for (var i = 1, e = lines.length - 1; i < e; ++i)
1946
+ added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th));
1947
+ if (nlines > 1) doc.remove(from.line + 1, nlines - 1, cm);
1948
+ doc.insert(from.line + 1, added);
1949
+ }
1950
+
1951
+ if (cm.options.lineWrapping) {
1952
+ var perLine = Math.max(5, display.scroller.clientWidth / charWidth(display) - 3);
1953
+ doc.iter(from.line, from.line + lines.length, function(line) {
1954
+ if (line.height == 0) return;
1955
+ var guess = (Math.ceil(line.text.length / perLine) || 1) * th;
1956
+ if (guess != line.height) updateLineHeight(line, guess);
1957
+ });
1958
+ } else {
1959
+ doc.iter(checkWidthStart, from.line + lines.length, function(line) {
1960
+ var len = lineLength(doc, line);
1961
+ if (len > view.maxLineLength) {
1962
+ view.maxLine = line;
1963
+ view.maxLineLength = len;
1964
+ view.maxLineChanged = true;
1965
+ recomputeMaxLength = false;
1966
+ }
1967
+ });
1968
+ if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
1969
+ }
1970
+
1971
+ // Adjust frontier, schedule worker
1972
+ view.frontier = Math.min(view.frontier, from.line);
1973
+ startWorker(cm, 400);
1974
+
1975
+ var lendiff = lines.length - nlines - 1;
1976
+ // Remember that these lines changed, for updating the display
1977
+ regChange(cm, from.line, to.line + 1, lendiff);
1978
+ if (hasHandler(cm, "change")) {
1979
+ // Normalize lines to contain only strings, since that's what
1980
+ // the change event handler expects
1981
+ for (var i = 0; i < lines.length; ++i)
1982
+ if (typeof lines[i] != "string") lines[i] = lines[i].text;
1983
+ var changeObj = {from: from, to: to, text: lines, origin: origin};
1984
+ if (cm.curOp.textChanged) {
1985
+ for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}
1986
+ cur.next = changeObj;
1987
+ } else cm.curOp.textChanged = changeObj;
1988
+ }
1989
+
1990
+ // Update the selection
1991
+ var newSelFrom, newSelTo, end = {line: from.line + lines.length - 1,
1992
+ ch: hlText(lastHL).length + (lines.length == 1 ? from.ch : 0)};
1993
+ if (selUpdate && typeof selUpdate != "string") {
1994
+ if (selUpdate.from) { newSelFrom = selUpdate.from; newSelTo = selUpdate.to; }
1995
+ else newSelFrom = newSelTo = selUpdate;
1996
+ } else if (selUpdate == "end") {
1997
+ newSelFrom = newSelTo = end;
1998
+ } else if (selUpdate == "start") {
1999
+ newSelFrom = newSelTo = from;
2000
+ } else if (selUpdate == "around") {
2001
+ newSelFrom = from; newSelTo = end;
2002
+ } else {
2003
+ var adjustPos = function(pos) {
2004
+ if (posLess(pos, from)) return pos;
2005
+ if (!posLess(to, pos)) return end;
2006
+ var line = pos.line + lendiff;
2007
+ var ch = pos.ch;
2008
+ if (pos.line == to.line)
2009
+ ch += hlText(lastHL).length - (to.ch - (to.line == from.line ? from.ch : 0));
2010
+ return {line: line, ch: ch};
2011
+ };
2012
+ newSelFrom = adjustPos(view.sel.from);
2013
+ newSelTo = adjustPos(view.sel.to);
2014
+ }
2015
+ setSelection(cm, newSelFrom, newSelTo, null, true);
2016
+ return end;
2017
+ }
2018
+
2019
+ function replaceRange(cm, code, from, to, origin) {
2020
+ if (!to) to = from;
2021
+ if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }
2022
+ return updateDoc(cm, from, to, splitLines(code), null, origin);
2023
+ }
2024
+
2025
+ // SELECTION
2026
+
2027
+ function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
2028
+ function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
2029
+ function copyPos(x) {return {line: x.line, ch: x.ch};}
2030
+
2031
+ function clipLine(doc, n) {return Math.max(0, Math.min(n, doc.size-1));}
2032
+ function clipPos(doc, pos) {
2033
+ if (pos.line < 0) return {line: 0, ch: 0};
2034
+ if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc, doc.size-1).text.length};
2035
+ var ch = pos.ch, linelen = getLine(doc, pos.line).text.length;
2036
+ if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
2037
+ else if (ch < 0) return {line: pos.line, ch: 0};
2038
+ else return pos;
2039
+ }
2040
+ function isLine(doc, l) {return l >= 0 && l < doc.size;}
2041
+
2042
+ // If shift is held, this will move the selection anchor. Otherwise,
2043
+ // it'll set the whole selection.
2044
+ function extendSelection(cm, pos, other, bias) {
2045
+ var sel = cm.view.sel;
2046
+ if (sel.shift || sel.extend) {
2047
+ var anchor = sel.anchor;
2048
+ if (other) {
2049
+ var posBefore = posLess(pos, anchor);
2050
+ if (posBefore != posLess(other, anchor)) {
2051
+ anchor = pos;
2052
+ pos = other;
2053
+ } else if (posBefore != posLess(pos, other)) {
2054
+ pos = other;
2055
+ }
2056
+ }
2057
+ setSelection(cm, anchor, pos, bias);
2058
+ } else {
2059
+ setSelection(cm, pos, other || pos, bias);
2060
+ }
2061
+ cm.curOp.userSelChange = true;
2062
+ }
2063
+
2064
+ // Update the selection. Last two args are only used by
2065
+ // updateDoc, since they have to be expressed in the line
2066
+ // numbers before the update.
2067
+ function setSelection(cm, anchor, head, bias, checkAtomic) {
2068
+ cm.view.goalColumn = null;
2069
+ var sel = cm.view.sel;
2070
+ // Skip over atomic spans.
2071
+ if (checkAtomic || !posEq(anchor, sel.anchor))
2072
+ anchor = skipAtomic(cm, anchor, bias, checkAtomic != "push");
2073
+ if (checkAtomic || !posEq(head, sel.head))
2074
+ head = skipAtomic(cm, head, bias, checkAtomic != "push");
2075
+
2076
+ if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return;
2077
+
2078
+ sel.anchor = anchor; sel.head = head;
2079
+ var inv = posLess(head, anchor);
2080
+ sel.from = inv ? head : anchor;
2081
+ sel.to = inv ? anchor : head;
2082
+
2083
+ cm.curOp.updateInput = true;
2084
+ cm.curOp.selectionChanged = true;
2085
+ }
2086
+
2087
+ function reCheckSelection(cm) {
2088
+ setSelection(cm, cm.view.sel.from, cm.view.sel.to, null, "push");
2089
+ }
2090
+
2091
+ function skipAtomic(cm, pos, bias, mayClear) {
2092
+ var doc = cm.view.doc, flipped = false, curPos = pos;
2093
+ var dir = bias || 1;
2094
+ cm.view.cantEdit = false;
2095
+ search: for (;;) {
2096
+ var line = getLine(doc, curPos.line), toClear;
2097
+ if (line.markedSpans) {
2098
+ for (var i = 0; i < line.markedSpans.length; ++i) {
2099
+ var sp = line.markedSpans[i], m = sp.marker;
2100
+ if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
2101
+ (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
2102
+ if (mayClear && m.clearOnEnter) {
2103
+ (toClear || (toClear = [])).push(m);
2104
+ continue;
2105
+ } else if (!m.atomic) continue;
2106
+ var newPos = m.find()[dir < 0 ? "from" : "to"];
2107
+ if (posEq(newPos, curPos)) {
2108
+ newPos.ch += dir;
2109
+ if (newPos.ch < 0) {
2110
+ if (newPos.line) newPos = clipPos(doc, {line: newPos.line - 1});
2111
+ else newPos = null;
2112
+ } else if (newPos.ch > line.text.length) {
2113
+ if (newPos.line < doc.size - 1) newPos = {line: newPos.line + 1, ch: 0};
2114
+ else newPos = null;
2115
+ }
2116
+ if (!newPos) {
2117
+ if (flipped) {
2118
+ // Driven in a corner -- no valid cursor position found at all
2119
+ // -- try again *with* clearing, if we didn't already
2120
+ if (!mayClear) return skipAtomic(cm, pos, bias, true);
2121
+ // Otherwise, turn off editing until further notice, and return the start of the doc
2122
+ cm.view.cantEdit = true;
2123
+ return {line: 0, ch: 0};
2124
+ }
2125
+ flipped = true; newPos = pos; dir = -dir;
2126
+ }
2127
+ }
2128
+ curPos = newPos;
2129
+ continue search;
2130
+ }
2131
+ }
2132
+ if (toClear) for (var i = 0; i < toClear.length; ++i) toClear[i].clear();
2133
+ }
2134
+ return curPos;
2135
+ }
2136
+ }
2137
+
2138
+ // SCROLLING
2139
+
2140
+ function scrollCursorIntoView(cm) {
2141
+ var view = cm.view;
2142
+ var coords = scrollPosIntoView(cm, view.sel.head);
2143
+ if (!view.focused) return;
2144
+ var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
2145
+ if (coords.top + box.top < 0) doScroll = true;
2146
+ else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
2147
+ if (doScroll != null && !phantom) {
2148
+ var hidden = display.cursor.style.display == "none";
2149
+ if (hidden) {
2150
+ display.cursor.style.display = "";
2151
+ display.cursor.style.left = coords.left + "px";
2152
+ display.cursor.style.top = (coords.top - display.viewOffset) + "px";
2153
+ }
2154
+ display.cursor.scrollIntoView(doScroll);
2155
+ if (hidden) display.cursor.style.display = "none";
2156
+ }
2157
+ }
2158
+
2159
+ function scrollPosIntoView(cm, pos) {
2160
+ for (;;) {
2161
+ var changed = false, coords = cursorCoords(cm, pos);
2162
+ var scrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
2163
+ var startTop = cm.view.scrollTop, startLeft = cm.view.scrollLeft;
2164
+ if (scrollPos.scrollTop != null) {
2165
+ setScrollTop(cm, scrollPos.scrollTop);
2166
+ if (Math.abs(cm.view.scrollTop - startTop) > 1) changed = true;
2167
+ }
2168
+ if (scrollPos.scrollLeft != null) {
2169
+ setScrollLeft(cm, scrollPos.scrollLeft);
2170
+ if (Math.abs(cm.view.scrollLeft - startLeft) > 1) changed = true;
2171
+ }
2172
+ if (!changed) return coords;
2173
+ }
2174
+ }
2175
+
2176
+ function scrollIntoView(cm, x1, y1, x2, y2) {
2177
+ var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
2178
+ if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
2179
+ if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
2180
+ }
2181
+
2182
+ function calculateScrollPos(cm, x1, y1, x2, y2) {
2183
+ var display = cm.display, pt = paddingTop(display);
2184
+ y1 += pt; y2 += pt;
2185
+ var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
2186
+ var docBottom = cm.view.doc.height + 2 * pt;
2187
+ var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;
2188
+ if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
2189
+ else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
2190
+
2191
+ var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
2192
+ x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
2193
+ var gutterw = display.gutters.offsetWidth;
2194
+ var atLeft = x1 < gutterw + 10;
2195
+ if (x1 < screenleft + gutterw || atLeft) {
2196
+ if (atLeft) x1 = 0;
2197
+ result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
2198
+ } else if (x2 > screenw + screenleft - 3) {
2199
+ result.scrollLeft = x2 + 10 - screenw;
2200
+ }
2201
+ return result;
2202
+ }
2203
+
2204
+ // API UTILITIES
2205
+
2206
+ function indentLine(cm, n, how, aggressive) {
2207
+ var doc = cm.view.doc;
2208
+ if (!how) how = "add";
2209
+ if (how == "smart") {
2210
+ if (!cm.view.mode.indent) how = "prev";
2211
+ else var state = getStateBefore(cm, n);
2212
+ }
2213
+
2214
+ var tabSize = cm.options.tabSize;
2215
+ var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
2216
+ var curSpaceString = line.text.match(/^\s*/)[0], indentation;
2217
+ if (how == "smart") {
2218
+ indentation = cm.view.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
2219
+ if (indentation == Pass) {
2220
+ if (!aggressive) return;
2221
+ how = "prev";
2222
+ }
2223
+ }
2224
+ if (how == "prev") {
2225
+ if (n) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
2226
+ else indentation = 0;
2227
+ }
2228
+ else if (how == "add") indentation = curSpace + cm.options.indentUnit;
2229
+ else if (how == "subtract") indentation = curSpace - cm.options.indentUnit;
2230
+ indentation = Math.max(0, indentation);
2231
+
2232
+ var indentString = "", pos = 0;
2233
+ if (cm.options.indentWithTabs)
2234
+ for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
2235
+ if (pos < indentation) indentString += spaceStr(indentation - pos);
2236
+
2237
+ if (indentString != curSpaceString)
2238
+ replaceRange(cm, indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}, "input");
2239
+ line.stateAfter = null;
2240
+ }
2241
+
2242
+ function changeLine(cm, handle, op) {
2243
+ var no = handle, line = handle, doc = cm.view.doc;
2244
+ if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
2245
+ else no = lineNo(handle);
2246
+ if (no == null) return null;
2247
+ if (op(line, no)) regChange(cm, no, no + 1);
2248
+ else return null;
2249
+ return line;
2250
+ }
2251
+
2252
+ function findPosH(cm, dir, unit, visually) {
2253
+ var doc = cm.view.doc, end = cm.view.sel.head, line = end.line, ch = end.ch;
2254
+ var lineObj = getLine(doc, line);
2255
+ function findNextLine() {
2256
+ var l = line + dir;
2257
+ if (l < 0 || l == doc.size) return false;
2258
+ line = l;
2259
+ return lineObj = getLine(doc, l);
2260
+ }
2261
+ function moveOnce(boundToLine) {
2262
+ var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
2263
+ if (next == null) {
2264
+ if (!boundToLine && findNextLine()) {
2265
+ if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
2266
+ else ch = dir < 0 ? lineObj.text.length : 0;
2267
+ } else return false;
2268
+ } else ch = next;
2269
+ return true;
2270
+ }
2271
+ if (unit == "char") moveOnce();
2272
+ else if (unit == "column") moveOnce(true);
2273
+ else if (unit == "word") {
2274
+ var sawWord = false;
2275
+ for (;;) {
2276
+ if (dir < 0) if (!moveOnce()) break;
2277
+ if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
2278
+ else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
2279
+ if (dir > 0) if (!moveOnce()) break;
2280
+ }
2281
+ }
2282
+ return skipAtomic(cm, {line: line, ch: ch}, dir, true);
2283
+ }
2284
+
2285
+ function findWordAt(line, pos) {
2286
+ var start = pos.ch, end = pos.ch;
2287
+ if (line) {
2288
+ if (pos.after === false || end == line.length) --start; else ++end;
2289
+ var startChar = line.charAt(start);
2290
+ var check = isWordChar(startChar) ? isWordChar :
2291
+ /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} :
2292
+ function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
2293
+ while (start > 0 && check(line.charAt(start - 1))) --start;
2294
+ while (end < line.length && check(line.charAt(end))) ++end;
2295
+ }
2296
+ return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}};
2297
+ }
2298
+
2299
+ function selectLine(cm, line) {
2300
+ extendSelection(cm, {line: line, ch: 0}, clipPos(cm.view.doc, {line: line + 1, ch: 0}));
2301
+ }
2302
+
2303
+ // PROTOTYPE
2304
+
2305
+ // The publicly visible API. Note that operation(null, f) means
2306
+ // 'wrap f in an operation, performed on its `this` parameter'
2307
+
2308
+ CodeMirror.prototype = {
2309
+ getValue: function(lineSep) {
2310
+ var text = [], doc = this.view.doc;
2311
+ doc.iter(0, doc.size, function(line) { text.push(line.text); });
2312
+ return text.join(lineSep || "\n");
2313
+ },
2314
+
2315
+ setValue: operation(null, function(code) {
2316
+ var doc = this.view.doc, top = {line: 0, ch: 0}, lastLen = getLine(doc, doc.size-1).text.length;
2317
+ updateDocInner(this, top, {line: doc.size - 1, ch: lastLen}, splitLines(code), top, top, "setValue");
2318
+ }),
2319
+
2320
+ getSelection: function(lineSep) { return this.getRange(this.view.sel.from, this.view.sel.to, lineSep); },
2321
+
2322
+ replaceSelection: operation(null, function(code, collapse, origin) {
2323
+ var sel = this.view.sel;
2324
+ updateDoc(this, sel.from, sel.to, splitLines(code), collapse || "around", origin);
2325
+ }),
2326
+
2327
+ focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},
2328
+
2329
+ setOption: function(option, value) {
2330
+ var options = this.options, old = options[option];
2331
+ if (options[option] == value && option != "mode") return;
2332
+ options[option] = value;
2333
+ if (optionHandlers.hasOwnProperty(option))
2334
+ operation(this, optionHandlers[option])(this, value, old);
2335
+ },
2336
+
2337
+ getOption: function(option) {return this.options[option];},
2338
+
2339
+ getMode: function() {return this.view.mode;},
2340
+
2341
+ addKeyMap: function(map) {
2342
+ this.view.keyMaps.push(map);
2343
+ },
2344
+
2345
+ removeKeyMap: function(map) {
2346
+ var maps = this.view.keyMaps;
2347
+ for (var i = 0; i < maps.length; ++i)
2348
+ if ((typeof map == "string" ? maps[i].name : maps[i]) == map) {
2349
+ maps.splice(i, 1);
2350
+ return true;
2351
+ }
2352
+ },
2353
+
2354
+ undo: operation(null, function() {unredoHelper(this, "undo");}),
2355
+ redo: operation(null, function() {unredoHelper(this, "redo");}),
2356
+
2357
+ indentLine: operation(null, function(n, dir, aggressive) {
2358
+ if (typeof dir != "string") {
2359
+ if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
2360
+ else dir = dir ? "add" : "subtract";
2361
+ }
2362
+ if (isLine(this.view.doc, n)) indentLine(this, n, dir, aggressive);
2363
+ }),
2364
+
2365
+ indentSelection: operation(null, function(how) {
2366
+ var sel = this.view.sel;
2367
+ if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);
2368
+ var e = sel.to.line - (sel.to.ch ? 0 : 1);
2369
+ for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
2370
+ }),
2371
+
2372
+ historySize: function() {
2373
+ var hist = this.view.history;
2374
+ return {undo: hist.done.length, redo: hist.undone.length};
2375
+ },
2376
+
2377
+ clearHistory: function() {this.view.history = makeHistory();},
2378
+
2379
+ markClean: function() {
2380
+ this.view.history.dirtyCounter = 0;
2381
+ this.view.history.lastOp = this.view.history.lastOrigin = null;
2382
+ },
2383
+
2384
+ isClean: function () {return this.view.history.dirtyCounter == 0;},
2385
+
2386
+ getHistory: function() {
2387
+ var hist = this.view.history;
2388
+ function cp(arr) {
2389
+ for (var i = 0, nw = [], nwelt; i < arr.length; ++i) {
2390
+ var set = arr[i];
2391
+ nw.push({events: nwelt = [], fromBefore: set.fromBefore, toBefore: set.toBefore,
2392
+ fromAfter: set.fromAfter, toAfter: set.toAfter});
2393
+ for (var j = 0, elt = set.events; j < elt.length; ++j) {
2394
+ var old = [], cur = elt[j];
2395
+ nwelt.push({start: cur.start, added: cur.added, old: old});
2396
+ for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k]));
2397
+ }
2398
+ }
2399
+ return nw;
2400
+ }
2401
+ return {done: cp(hist.done), undone: cp(hist.undone)};
2402
+ },
2403
+
2404
+ setHistory: function(histData) {
2405
+ var hist = this.view.history = makeHistory();
2406
+ hist.done = histData.done;
2407
+ hist.undone = histData.undone;
2408
+ },
2409
+
2410
+ // Fetch the parser token for a given character. Useful for hacks
2411
+ // that want to inspect the mode state (say, for completion).
2412
+ getTokenAt: function(pos) {
2413
+ var doc = this.view.doc;
2414
+ pos = clipPos(doc, pos);
2415
+ var state = getStateBefore(this, pos.line), mode = this.view.mode;
2416
+ var line = getLine(doc, pos.line);
2417
+ var stream = new StringStream(line.text, this.options.tabSize);
2418
+ while (stream.pos < pos.ch && !stream.eol()) {
2419
+ stream.start = stream.pos;
2420
+ var style = mode.token(stream, state);
2421
+ }
2422
+ return {start: stream.start,
2423
+ end: stream.pos,
2424
+ string: stream.current(),
2425
+ className: style || null, // Deprecated, use 'type' instead
2426
+ type: style || null,
2427
+ state: state};
2428
+ },
2429
+
2430
+ getStateAfter: function(line) {
2431
+ var doc = this.view.doc;
2432
+ line = clipLine(doc, line == null ? doc.size - 1: line);
2433
+ return getStateBefore(this, line + 1);
2434
+ },
2435
+
2436
+ cursorCoords: function(start, mode) {
2437
+ var pos, sel = this.view.sel;
2438
+ if (start == null) pos = sel.head;
2439
+ else if (typeof start == "object") pos = clipPos(this.view.doc, start);
2440
+ else pos = start ? sel.from : sel.to;
2441
+ return cursorCoords(this, pos, mode || "page");
2442
+ },
2443
+
2444
+ charCoords: function(pos, mode) {
2445
+ return charCoords(this, clipPos(this.view.doc, pos), mode || "page");
2446
+ },
2447
+
2448
+ coordsChar: function(coords) {
2449
+ var off = this.display.lineSpace.getBoundingClientRect();
2450
+ return coordsChar(this, coords.left - off.left, coords.top - off.top);
2451
+ },
2452
+
2453
+ defaultTextHeight: function() { return textHeight(this.display); },
2454
+
2455
+ markText: operation(null, function(from, to, options) {
2456
+ return markText(this, clipPos(this.view.doc, from), clipPos(this.view.doc, to),
2457
+ options, "range");
2458
+ }),
2459
+
2460
+ setBookmark: operation(null, function(pos, widget) {
2461
+ pos = clipPos(this.view.doc, pos);
2462
+ return markText(this, pos, pos, widget ? {replacedWith: widget} : {}, "bookmark");
2463
+ }),
2464
+
2465
+ findMarksAt: function(pos) {
2466
+ var doc = this.view.doc;
2467
+ pos = clipPos(doc, pos);
2468
+ var markers = [], spans = getLine(doc, pos.line).markedSpans;
2469
+ if (spans) for (var i = 0; i < spans.length; ++i) {
2470
+ var span = spans[i];
2471
+ if ((span.from == null || span.from <= pos.ch) &&
2472
+ (span.to == null || span.to >= pos.ch))
2473
+ markers.push(span.marker);
2474
+ }
2475
+ return markers;
2476
+ },
2477
+
2478
+ setGutterMarker: operation(null, function(line, gutterID, value) {
2479
+ return changeLine(this, line, function(line) {
2480
+ var markers = line.gutterMarkers || (line.gutterMarkers = {});
2481
+ markers[gutterID] = value;
2482
+ if (!value && isEmpty(markers)) line.gutterMarkers = null;
2483
+ return true;
2484
+ });
2485
+ }),
2486
+
2487
+ clearGutter: operation(null, function(gutterID) {
2488
+ var i = 0, cm = this, doc = cm.view.doc;
2489
+ doc.iter(0, doc.size, function(line) {
2490
+ if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
2491
+ line.gutterMarkers[gutterID] = null;
2492
+ regChange(cm, i, i + 1);
2493
+ if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
2494
+ }
2495
+ ++i;
2496
+ });
2497
+ }),
2498
+
2499
+ addLineClass: operation(null, function(handle, where, cls) {
2500
+ return changeLine(this, handle, function(line) {
2501
+ var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
2502
+ if (!line[prop]) line[prop] = cls;
2503
+ else if (new RegExp("\\b" + cls + "\\b").test(line[prop])) return false;
2504
+ else line[prop] += " " + cls;
2505
+ return true;
2506
+ });
2507
+ }),
2508
+
2509
+ removeLineClass: operation(null, function(handle, where, cls) {
2510
+ return changeLine(this, handle, function(line) {
2511
+ var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
2512
+ var cur = line[prop];
2513
+ if (!cur) return false;
2514
+ else if (cls == null) line[prop] = null;
2515
+ else {
2516
+ var upd = cur.replace(new RegExp("^" + cls + "\\b\\s*|\\s*\\b" + cls + "\\b"), "");
2517
+ if (upd == cur) return false;
2518
+ line[prop] = upd || null;
2519
+ }
2520
+ return true;
2521
+ });
2522
+ }),
2523
+
2524
+ addLineWidget: operation(null, function(handle, node, options) {
2525
+ var widget = options || {};
2526
+ widget.node = node;
2527
+ if (widget.noHScroll) this.display.alignWidgets = true;
2528
+ changeLine(this, handle, function(line) {
2529
+ (line.widgets || (line.widgets = [])).push(widget);
2530
+ widget.line = line;
2531
+ return true;
2532
+ });
2533
+ return widget;
2534
+ }),
2535
+
2536
+ removeLineWidget: operation(null, function(widget) {
2537
+ var ws = widget.line.widgets, no = lineNo(widget.line);
2538
+ if (no == null) return;
2539
+ for (var i = 0; i < ws.length; ++i) if (ws[i] == widget) ws.splice(i--, 1);
2540
+ regChange(this, no, no + 1);
2541
+ }),
2542
+
2543
+ lineInfo: function(line) {
2544
+ if (typeof line == "number") {
2545
+ if (!isLine(this.view.doc, line)) return null;
2546
+ var n = line;
2547
+ line = getLine(this.view.doc, line);
2548
+ if (!line) return null;
2549
+ } else {
2550
+ var n = lineNo(line);
2551
+ if (n == null) return null;
2552
+ }
2553
+ return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
2554
+ textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
2555
+ widgets: line.widgets};
2556
+ },
2557
+
2558
+ getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};},
2559
+
2560
+ addWidget: function(pos, node, scroll, vert, horiz) {
2561
+ var display = this.display;
2562
+ pos = cursorCoords(this, clipPos(this.view.doc, pos));
2563
+ var top = pos.top, left = pos.left;
2564
+ node.style.position = "absolute";
2565
+ display.sizer.appendChild(node);
2566
+ if (vert == "over") top = pos.top;
2567
+ else if (vert == "near") {
2568
+ var vspace = Math.max(display.wrapper.clientHeight, this.view.doc.height),
2569
+ hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
2570
+ if (pos.bottom + node.offsetHeight > vspace && pos.top > node.offsetHeight)
2571
+ top = pos.top - node.offsetHeight;
2572
+ if (left + node.offsetWidth > hspace)
2573
+ left = hspace - node.offsetWidth;
2574
+ }
2575
+ node.style.top = (top + paddingTop(display)) + "px";
2576
+ node.style.left = node.style.right = "";
2577
+ if (horiz == "right") {
2578
+ left = display.sizer.clientWidth - node.offsetWidth;
2579
+ node.style.right = "0px";
2580
+ } else {
2581
+ if (horiz == "left") left = 0;
2582
+ else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2;
2583
+ node.style.left = left + "px";
2584
+ }
2585
+ if (scroll)
2586
+ scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
2587
+ },
2588
+
2589
+ lineCount: function() {return this.view.doc.size;},
2590
+
2591
+ clipPos: function(pos) {return clipPos(this.view.doc, pos);},
2592
+
2593
+ getCursor: function(start) {
2594
+ var sel = this.view.sel, pos;
2595
+ if (start == null || start == "head") pos = sel.head;
2596
+ else if (start == "anchor") pos = sel.anchor;
2597
+ else if (start == "end" || start === false) pos = sel.to;
2598
+ else pos = sel.from;
2599
+ return copyPos(pos);
2600
+ },
2601
+
2602
+ somethingSelected: function() {return !posEq(this.view.sel.from, this.view.sel.to);},
2603
+
2604
+ setCursor: operation(null, function(line, ch, extend) {
2605
+ var pos = clipPos(this.view.doc, typeof line == "number" ? {line: line, ch: ch || 0} : line);
2606
+ if (extend) extendSelection(this, pos);
2607
+ else setSelection(this, pos, pos);
2608
+ }),
2609
+
2610
+ setSelection: operation(null, function(anchor, head) {
2611
+ var doc = this.view.doc;
2612
+ setSelection(this, clipPos(doc, anchor), clipPos(doc, head || anchor));
2613
+ }),
2614
+
2615
+ extendSelection: operation(null, function(from, to) {
2616
+ var doc = this.view.doc;
2617
+ extendSelection(this, clipPos(doc, from), to && clipPos(doc, to));
2618
+ }),
2619
+
2620
+ setExtending: function(val) {this.view.sel.extend = val;},
2621
+
2622
+ getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
2623
+
2624
+ getLineHandle: function(line) {
2625
+ var doc = this.view.doc;
2626
+ if (isLine(doc, line)) return getLine(doc, line);
2627
+ },
2628
+
2629
+ getLineNumber: function(line) {return lineNo(line);},
2630
+
2631
+ setLine: operation(null, function(line, text) {
2632
+ if (isLine(this.view.doc, line))
2633
+ replaceRange(this, text, {line: line, ch: 0}, {line: line, ch: getLine(this.view.doc, line).text.length});
2634
+ }),
2635
+
2636
+ removeLine: operation(null, function(line) {
2637
+ if (isLine(this.view.doc, line))
2638
+ replaceRange(this, "", {line: line, ch: 0}, clipPos(this.view.doc, {line: line+1, ch: 0}));
2639
+ }),
2640
+
2641
+ replaceRange: operation(null, function(code, from, to) {
2642
+ var doc = this.view.doc;
2643
+ from = clipPos(doc, from);
2644
+ to = to ? clipPos(doc, to) : from;
2645
+ return replaceRange(this, code, from, to);
2646
+ }),
2647
+
2648
+ getRange: function(from, to, lineSep) {
2649
+ var doc = this.view.doc;
2650
+ from = clipPos(doc, from); to = clipPos(doc, to);
2651
+ var l1 = from.line, l2 = to.line;
2652
+ if (l1 == l2) return getLine(doc, l1).text.slice(from.ch, to.ch);
2653
+ var code = [getLine(doc, l1).text.slice(from.ch)];
2654
+ doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
2655
+ code.push(getLine(doc, l2).text.slice(0, to.ch));
2656
+ return code.join(lineSep || "\n");
2657
+ },
2658
+
2659
+ triggerOnKeyDown: operation(null, onKeyDown),
2660
+
2661
+ execCommand: function(cmd) {return commands[cmd](this);},
2662
+
2663
+ // Stuff used by commands, probably not much use to outside code.
2664
+ moveH: operation(null, function(dir, unit) {
2665
+ var sel = this.view.sel, pos = dir < 0 ? sel.from : sel.to;
2666
+ if (sel.shift || sel.extend || posEq(sel.from, sel.to)) pos = findPosH(this, dir, unit, true);
2667
+ extendSelection(this, pos, pos, dir);
2668
+ }),
2669
+
2670
+ deleteH: operation(null, function(dir, unit) {
2671
+ var sel = this.view.sel;
2672
+ if (!posEq(sel.from, sel.to)) replaceRange(this, "", sel.from, sel.to, "delete");
2673
+ else replaceRange(this, "", sel.from, findPosH(this, dir, unit, false), "delete");
2674
+ this.curOp.userSelChange = true;
2675
+ }),
2676
+
2677
+ moveV: operation(null, function(dir, unit) {
2678
+ var view = this.view, doc = view.doc, display = this.display;
2679
+ var cur = view.sel.head, pos = cursorCoords(this, cur, "div");
2680
+ var x = pos.left, y;
2681
+ if (view.goalColumn != null) x = view.goalColumn;
2682
+ if (unit == "page") {
2683
+ var pageSize = Math.min(display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
2684
+ y = pos.top + dir * pageSize;
2685
+ } else if (unit == "line") {
2686
+ y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
2687
+ }
2688
+ do {
2689
+ var target = coordsChar(this, x, y);
2690
+ y += dir * 5;
2691
+ } while (target.outside && (dir < 0 ? y > 0 : y < doc.height));
2692
+
2693
+ if (unit == "page") display.scrollbarV.scrollTop += charCoords(this, target, "div").top - pos.top;
2694
+ extendSelection(this, target, target, dir);
2695
+ view.goalColumn = x;
2696
+ }),
2697
+
2698
+ toggleOverwrite: function() {
2699
+ if (this.view.overwrite = !this.view.overwrite)
2700
+ this.display.cursor.className += " CodeMirror-overwrite";
2701
+ else
2702
+ this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", "");
2703
+ },
2704
+
2705
+ posFromIndex: function(off) {
2706
+ var lineNo = 0, ch, doc = this.view.doc;
2707
+ doc.iter(0, doc.size, function(line) {
2708
+ var sz = line.text.length + 1;
2709
+ if (sz > off) { ch = off; return true; }
2710
+ off -= sz;
2711
+ ++lineNo;
2712
+ });
2713
+ return clipPos(doc, {line: lineNo, ch: ch});
2714
+ },
2715
+ indexFromPos: function (coords) {
2716
+ if (coords.line < 0 || coords.ch < 0) return 0;
2717
+ var index = coords.ch;
2718
+ this.view.doc.iter(0, coords.line, function (line) {
2719
+ index += line.text.length + 1;
2720
+ });
2721
+ return index;
2722
+ },
2723
+
2724
+ scrollTo: function(x, y) {
2725
+ if (x != null) this.display.scrollbarH.scrollLeft = this.display.scroller.scrollLeft = x;
2726
+ if (y != null) this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = y;
2727
+ updateDisplay(this, []);
2728
+ },
2729
+ getScrollInfo: function() {
2730
+ var scroller = this.display.scroller, co = scrollerCutOff;
2731
+ return {left: scroller.scrollLeft, top: scroller.scrollTop,
2732
+ height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
2733
+ clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
2734
+ },
2735
+
2736
+ scrollIntoView: function(pos) {
2737
+ if (typeof pos == "number") pos = {line: pos, ch: 0};
2738
+ pos = pos ? clipPos(this.view.doc, pos) : this.view.sel.head;
2739
+ scrollPosIntoView(this, pos);
2740
+ },
2741
+
2742
+ setSize: function(width, height) {
2743
+ function interpret(val) {
2744
+ return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
2745
+ }
2746
+ if (width != null) this.display.wrapper.style.width = interpret(width);
2747
+ if (height != null) this.display.wrapper.style.height = interpret(height);
2748
+ this.refresh();
2749
+ },
2750
+
2751
+ on: function(type, f) {on(this, type, f);},
2752
+ off: function(type, f) {off(this, type, f);},
2753
+
2754
+ operation: function(f){return operation(this, f)();},
2755
+
2756
+ refresh: function() {
2757
+ clearCaches(this);
2758
+ if (this.display.scroller.scrollHeight > this.view.scrollTop)
2759
+ this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = this.view.scrollTop;
2760
+ updateDisplay(this, true);
2761
+ },
2762
+
2763
+ getInputField: function(){return this.display.input;},
2764
+ getWrapperElement: function(){return this.display.wrapper;},
2765
+ getScrollerElement: function(){return this.display.scroller;},
2766
+ getGutterElement: function(){return this.display.gutters;}
2767
+ };
2768
+
2769
+ // OPTION DEFAULTS
2770
+
2771
+ var optionHandlers = CodeMirror.optionHandlers = {};
2772
+
2773
+ // The default configuration options.
2774
+ var defaults = CodeMirror.defaults = {};
2775
+
2776
+ function option(name, deflt, handle, notOnInit) {
2777
+ CodeMirror.defaults[name] = deflt;
2778
+ if (handle) optionHandlers[name] =
2779
+ notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;
2780
+ }
2781
+
2782
+ var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};
2783
+
2784
+ // These two are, on init, called from the constructor because they
2785
+ // have to be initialized before the editor can start at all.
2786
+ option("value", "", function(cm, val) {cm.setValue(val);}, true);
2787
+ option("mode", null, loadMode, true);
2788
+
2789
+ option("indentUnit", 2, loadMode, true);
2790
+ option("indentWithTabs", false);
2791
+ option("smartIndent", true);
2792
+ option("tabSize", 4, function(cm) {
2793
+ loadMode(cm);
2794
+ clearCaches(cm);
2795
+ updateDisplay(cm, true);
2796
+ }, true);
2797
+ option("electricChars", true);
2798
+
2799
+ option("theme", "default", function(cm) {
2800
+ themeChanged(cm);
2801
+ guttersChanged(cm);
2802
+ }, true);
2803
+ option("keyMap", "default", keyMapChanged);
2804
+ option("extraKeys", null);
2805
+
2806
+ option("onKeyEvent", null);
2807
+ option("onDragEvent", null);
2808
+
2809
+ option("lineWrapping", false, wrappingChanged, true);
2810
+ option("gutters", [], function(cm) {
2811
+ setGuttersForLineNumbers(cm.options);
2812
+ guttersChanged(cm);
2813
+ }, true);
2814
+ option("lineNumbers", false, function(cm) {
2815
+ setGuttersForLineNumbers(cm.options);
2816
+ guttersChanged(cm);
2817
+ }, true);
2818
+ option("firstLineNumber", 1, guttersChanged, true);
2819
+ option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
2820
+ option("showCursorWhenSelecting", false, updateSelection, true);
2821
+
2822
+ option("readOnly", false, function(cm, val) {
2823
+ if (val == "nocursor") {onBlur(cm); cm.display.input.blur();}
2824
+ else if (!val) resetInput(cm, true);
2825
+ });
2826
+ option("dragDrop", true);
2827
+
2828
+ option("cursorBlinkRate", 530);
2829
+ option("cursorHeight", 1);
2830
+ option("workTime", 100);
2831
+ option("workDelay", 100);
2832
+ option("flattenSpans", true);
2833
+ option("pollInterval", 100);
2834
+ option("undoDepth", 40);
2835
+ option("viewportMargin", 10, function(cm){cm.refresh();}, true);
2836
+
2837
+ option("tabindex", null, function(cm, val) {
2838
+ cm.display.input.tabIndex = val || "";
2839
+ });
2840
+ option("autofocus", null);
2841
+
2842
+ // MODE DEFINITION AND QUERYING
2843
+
2844
+ // Known modes, by name and by MIME
2845
+ var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
2846
+
2847
+ CodeMirror.defineMode = function(name, mode) {
2848
+ if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
2849
+ if (arguments.length > 2) {
2850
+ mode.dependencies = [];
2851
+ for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
2852
+ }
2853
+ modes[name] = mode;
2854
+ };
2855
+
2856
+ CodeMirror.defineMIME = function(mime, spec) {
2857
+ mimeModes[mime] = spec;
2858
+ };
2859
+
2860
+ CodeMirror.resolveMode = function(spec) {
2861
+ if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
2862
+ spec = mimeModes[spec];
2863
+ else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
2864
+ return CodeMirror.resolveMode("application/xml");
2865
+ if (typeof spec == "string") return {name: spec};
2866
+ else return spec || {name: "null"};
2867
+ };
2868
+
2869
+ CodeMirror.getMode = function(options, spec) {
2870
+ var spec = CodeMirror.resolveMode(spec);
2871
+ var mfactory = modes[spec.name];
2872
+ if (!mfactory) return CodeMirror.getMode(options, "text/plain");
2873
+ var modeObj = mfactory(options, spec);
2874
+ if (modeExtensions.hasOwnProperty(spec.name)) {
2875
+ var exts = modeExtensions[spec.name];
2876
+ for (var prop in exts) {
2877
+ if (!exts.hasOwnProperty(prop)) continue;
2878
+ if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
2879
+ modeObj[prop] = exts[prop];
2880
+ }
2881
+ }
2882
+ modeObj.name = spec.name;
2883
+ return modeObj;
2884
+ };
2885
+
2886
+ CodeMirror.defineMode("null", function() {
2887
+ return {token: function(stream) {stream.skipToEnd();}};
2888
+ });
2889
+ CodeMirror.defineMIME("text/plain", "null");
2890
+
2891
+ var modeExtensions = CodeMirror.modeExtensions = {};
2892
+ CodeMirror.extendMode = function(mode, properties) {
2893
+ var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
2894
+ for (var prop in properties) if (properties.hasOwnProperty(prop))
2895
+ exts[prop] = properties[prop];
2896
+ };
2897
+
2898
+ // EXTENSIONS
2899
+
2900
+ CodeMirror.defineExtension = function(name, func) {
2901
+ CodeMirror.prototype[name] = func;
2902
+ };
2903
+
2904
+ CodeMirror.defineOption = option;
2905
+
2906
+ var initHooks = [];
2907
+ CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
2908
+
2909
+ // MODE STATE HANDLING
2910
+
2911
+ // Utility functions for working with state. Exported because modes
2912
+ // sometimes need to do this.
2913
+ function copyState(mode, state) {
2914
+ if (state === true) return state;
2915
+ if (mode.copyState) return mode.copyState(state);
2916
+ var nstate = {};
2917
+ for (var n in state) {
2918
+ var val = state[n];
2919
+ if (val instanceof Array) val = val.concat([]);
2920
+ nstate[n] = val;
2921
+ }
2922
+ return nstate;
2923
+ }
2924
+ CodeMirror.copyState = copyState;
2925
+
2926
+ function startState(mode, a1, a2) {
2927
+ return mode.startState ? mode.startState(a1, a2) : true;
2928
+ }
2929
+ CodeMirror.startState = startState;
2930
+
2931
+ CodeMirror.innerMode = function(mode, state) {
2932
+ while (mode.innerMode) {
2933
+ var info = mode.innerMode(state);
2934
+ state = info.state;
2935
+ mode = info.mode;
2936
+ }
2937
+ return info || {mode: mode, state: state};
2938
+ };
2939
+
2940
+ // STANDARD COMMANDS
2941
+
2942
+ var commands = CodeMirror.commands = {
2943
+ selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
2944
+ killLine: function(cm) {
2945
+ var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
2946
+ if (!sel && cm.getLine(from.line).length == from.ch)
2947
+ cm.replaceRange("", from, {line: from.line + 1, ch: 0}, "delete");
2948
+ else cm.replaceRange("", from, sel ? to : {line: from.line}, "delete");
2949
+ },
2950
+ deleteLine: function(cm) {
2951
+ var l = cm.getCursor().line;
2952
+ cm.replaceRange("", {line: l, ch: 0}, {line: l}, "delete");
2953
+ },
2954
+ undo: function(cm) {cm.undo();},
2955
+ redo: function(cm) {cm.redo();},
2956
+ goDocStart: function(cm) {cm.extendSelection({line: 0, ch: 0});},
2957
+ goDocEnd: function(cm) {cm.extendSelection({line: cm.lineCount() - 1});},
2958
+ goLineStart: function(cm) {
2959
+ cm.extendSelection(lineStart(cm, cm.getCursor().line));
2960
+ },
2961
+ goLineStartSmart: function(cm) {
2962
+ var cur = cm.getCursor(), start = lineStart(cm, cur.line);
2963
+ var line = cm.getLineHandle(start.line);
2964
+ var order = getOrder(line);
2965
+ if (!order || order[0].level == 0) {
2966
+ var firstNonWS = Math.max(0, line.text.search(/\S/));
2967
+ var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch;
2968
+ cm.extendSelection({line: start.line, ch: inWS ? 0 : firstNonWS});
2969
+ } else cm.extendSelection(start);
2970
+ },
2971
+ goLineEnd: function(cm) {
2972
+ cm.extendSelection(lineEnd(cm, cm.getCursor().line));
2973
+ },
2974
+ goLineUp: function(cm) {cm.moveV(-1, "line");},
2975
+ goLineDown: function(cm) {cm.moveV(1, "line");},
2976
+ goPageUp: function(cm) {cm.moveV(-1, "page");},
2977
+ goPageDown: function(cm) {cm.moveV(1, "page");},
2978
+ goCharLeft: function(cm) {cm.moveH(-1, "char");},
2979
+ goCharRight: function(cm) {cm.moveH(1, "char");},
2980
+ goColumnLeft: function(cm) {cm.moveH(-1, "column");},
2981
+ goColumnRight: function(cm) {cm.moveH(1, "column");},
2982
+ goWordLeft: function(cm) {cm.moveH(-1, "word");},
2983
+ goWordRight: function(cm) {cm.moveH(1, "word");},
2984
+ delCharBefore: function(cm) {cm.deleteH(-1, "char");},
2985
+ delCharAfter: function(cm) {cm.deleteH(1, "char");},
2986
+ delWordBefore: function(cm) {cm.deleteH(-1, "word");},
2987
+ delWordAfter: function(cm) {cm.deleteH(1, "word");},
2988
+ indentAuto: function(cm) {cm.indentSelection("smart");},
2989
+ indentMore: function(cm) {cm.indentSelection("add");},
2990
+ indentLess: function(cm) {cm.indentSelection("subtract");},
2991
+ insertTab: function(cm) {cm.replaceSelection("\t", "end", "input");},
2992
+ defaultTab: function(cm) {
2993
+ if (cm.somethingSelected()) cm.indentSelection("add");
2994
+ else cm.replaceSelection("\t", "end", "input");
2995
+ },
2996
+ transposeChars: function(cm) {
2997
+ var cur = cm.getCursor(), line = cm.getLine(cur.line);
2998
+ if (cur.ch > 0 && cur.ch < line.length - 1)
2999
+ cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
3000
+ {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
3001
+ },
3002
+ newlineAndIndent: function(cm) {
3003
+ operation(cm, function() {
3004
+ cm.replaceSelection("\n", "end", "input");
3005
+ cm.indentLine(cm.getCursor().line, null, true);
3006
+ })();
3007
+ },
3008
+ toggleOverwrite: function(cm) {cm.toggleOverwrite();}
3009
+ };
3010
+
3011
+ // STANDARD KEYMAPS
3012
+
3013
+ var keyMap = CodeMirror.keyMap = {};
3014
+ keyMap.basic = {
3015
+ "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
3016
+ "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
3017
+ "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
3018
+ "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
3019
+ };
3020
+ // Note that the save and find-related commands aren't defined by
3021
+ // default. Unknown commands are simply ignored.
3022
+ keyMap.pcDefault = {
3023
+ "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
3024
+ "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
3025
+ "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
3026
+ "Ctrl-Backspace": "delWordBefore", "Ctrl-Delete": "delWordAfter", "Ctrl-S": "save", "Ctrl-F": "find",
3027
+ "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
3028
+ "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
3029
+ fallthrough: "basic"
3030
+ };
3031
+ keyMap.macDefault = {
3032
+ "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
3033
+ "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
3034
+ "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordBefore",
3035
+ "Ctrl-Alt-Backspace": "delWordAfter", "Alt-Delete": "delWordAfter", "Cmd-S": "save", "Cmd-F": "find",
3036
+ "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
3037
+ "Cmd-[": "indentLess", "Cmd-]": "indentMore",
3038
+ fallthrough: ["basic", "emacsy"]
3039
+ };
3040
+ keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
3041
+ keyMap.emacsy = {
3042
+ "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
3043
+ "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
3044
+ "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
3045
+ "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
3046
+ };
3047
+
3048
+ // KEYMAP DISPATCH
3049
+
3050
+ function getKeyMap(val) {
3051
+ if (typeof val == "string") return keyMap[val];
3052
+ else return val;
3053
+ }
3054
+
3055
+ function lookupKey(name, maps, handle, stop) {
3056
+ function lookup(map) {
3057
+ map = getKeyMap(map);
3058
+ var found = map[name];
3059
+ if (found === false) {
3060
+ if (stop) stop();
3061
+ return true;
3062
+ }
3063
+ if (found != null && handle(found)) return true;
3064
+ if (map.nofallthrough) {
3065
+ if (stop) stop();
3066
+ return true;
3067
+ }
3068
+ var fallthrough = map.fallthrough;
3069
+ if (fallthrough == null) return false;
3070
+ if (Object.prototype.toString.call(fallthrough) != "[object Array]")
3071
+ return lookup(fallthrough);
3072
+ for (var i = 0, e = fallthrough.length; i < e; ++i) {
3073
+ if (lookup(fallthrough[i])) return true;
3074
+ }
3075
+ return false;
3076
+ }
3077
+
3078
+ for (var i = 0; i < maps.length; ++i)
3079
+ if (lookup(maps[i])) return true;
3080
+ }
3081
+ function isModifierKey(event) {
3082
+ var name = keyNames[e_prop(event, "keyCode")];
3083
+ return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
3084
+ }
3085
+ CodeMirror.isModifierKey = isModifierKey;
3086
+
3087
+ // FROMTEXTAREA
3088
+
3089
+ CodeMirror.fromTextArea = function(textarea, options) {
3090
+ if (!options) options = {};
3091
+ options.value = textarea.value;
3092
+ if (!options.tabindex && textarea.tabindex)
3093
+ options.tabindex = textarea.tabindex;
3094
+ // Set autofocus to true if this textarea is focused, or if it has
3095
+ // autofocus and no other element is focused.
3096
+ if (options.autofocus == null) {
3097
+ var hasFocus = document.body;
3098
+ // doc.activeElement occasionally throws on IE
3099
+ try { hasFocus = document.activeElement; } catch(e) {}
3100
+ options.autofocus = hasFocus == textarea ||
3101
+ textarea.getAttribute("autofocus") != null && hasFocus == document.body;
3102
+ }
3103
+
3104
+ function save() {textarea.value = cm.getValue();}
3105
+ if (textarea.form) {
3106
+ // Deplorable hack to make the submit method do the right thing.
3107
+ on(textarea.form, "submit", save);
3108
+ var form = textarea.form, realSubmit = form.submit;
3109
+ try {
3110
+ form.submit = function wrappedSubmit() {
3111
+ save();
3112
+ form.submit = realSubmit;
3113
+ form.submit();
3114
+ form.submit = wrappedSubmit;
3115
+ };
3116
+ } catch(e) {}
3117
+ }
3118
+
3119
+ textarea.style.display = "none";
3120
+ var cm = CodeMirror(function(node) {
3121
+ textarea.parentNode.insertBefore(node, textarea.nextSibling);
3122
+ }, options);
3123
+ cm.save = save;
3124
+ cm.getTextArea = function() { return textarea; };
3125
+ cm.toTextArea = function() {
3126
+ save();
3127
+ textarea.parentNode.removeChild(cm.getWrapperElement());
3128
+ textarea.style.display = "";
3129
+ if (textarea.form) {
3130
+ off(textarea.form, "submit", save);
3131
+ if (typeof textarea.form.submit == "function")
3132
+ textarea.form.submit = realSubmit;
3133
+ }
3134
+ };
3135
+ return cm;
3136
+ };
3137
+
3138
+ // STRING STREAM
3139
+
3140
+ // Fed to the mode parsers, provides helper functions to make
3141
+ // parsers more succinct.
3142
+
3143
+ // The character stream used by a mode's parser.
3144
+ function StringStream(string, tabSize) {
3145
+ this.pos = this.start = 0;
3146
+ this.string = string;
3147
+ this.tabSize = tabSize || 8;
3148
+ }
3149
+
3150
+ StringStream.prototype = {
3151
+ eol: function() {return this.pos >= this.string.length;},
3152
+ sol: function() {return this.pos == 0;},
3153
+ peek: function() {return this.string.charAt(this.pos) || undefined;},
3154
+ next: function() {
3155
+ if (this.pos < this.string.length)
3156
+ return this.string.charAt(this.pos++);
3157
+ },
3158
+ eat: function(match) {
3159
+ var ch = this.string.charAt(this.pos);
3160
+ if (typeof match == "string") var ok = ch == match;
3161
+ else var ok = ch && (match.test ? match.test(ch) : match(ch));
3162
+ if (ok) {++this.pos; return ch;}
3163
+ },
3164
+ eatWhile: function(match) {
3165
+ var start = this.pos;
3166
+ while (this.eat(match)){}
3167
+ return this.pos > start;
3168
+ },
3169
+ eatSpace: function() {
3170
+ var start = this.pos;
3171
+ while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
3172
+ return this.pos > start;
3173
+ },
3174
+ skipToEnd: function() {this.pos = this.string.length;},
3175
+ skipTo: function(ch) {
3176
+ var found = this.string.indexOf(ch, this.pos);
3177
+ if (found > -1) {this.pos = found; return true;}
3178
+ },
3179
+ backUp: function(n) {this.pos -= n;},
3180
+ column: function() {return countColumn(this.string, this.start, this.tabSize);},
3181
+ indentation: function() {return countColumn(this.string, null, this.tabSize);},
3182
+ match: function(pattern, consume, caseInsensitive) {
3183
+ if (typeof pattern == "string") {
3184
+ var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
3185
+ if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
3186
+ if (consume !== false) this.pos += pattern.length;
3187
+ return true;
3188
+ }
3189
+ } else {
3190
+ var match = this.string.slice(this.pos).match(pattern);
3191
+ if (match && match.index > 0) return null;
3192
+ if (match && consume !== false) this.pos += match[0].length;
3193
+ return match;
3194
+ }
3195
+ },
3196
+ current: function(){return this.string.slice(this.start, this.pos);}
3197
+ };
3198
+ CodeMirror.StringStream = StringStream;
3199
+
3200
+ // TEXTMARKERS
3201
+
3202
+ function TextMarker(cm, type) {
3203
+ this.lines = [];
3204
+ this.type = type;
3205
+ this.cm = cm;
3206
+ }
3207
+
3208
+ TextMarker.prototype.clear = function() {
3209
+ if (this.explicitlyCleared) return;
3210
+ startOperation(this.cm);
3211
+ var min = null, max = null;
3212
+ for (var i = 0; i < this.lines.length; ++i) {
3213
+ var line = this.lines[i];
3214
+ var span = getMarkedSpanFor(line.markedSpans, this);
3215
+ if (span.to != null) max = lineNo(line);
3216
+ line.markedSpans = removeMarkedSpan(line.markedSpans, span);
3217
+ if (span.from != null)
3218
+ min = lineNo(line);
3219
+ else if (this.collapsed && !lineIsHidden(line))
3220
+ updateLineHeight(line, textHeight(this.cm.display));
3221
+ }
3222
+ if (min != null) regChange(this.cm, min, max + 1);
3223
+ this.lines.length = 0;
3224
+ this.explicitlyCleared = true;
3225
+ if (this.collapsed && this.cm.view.cantEdit) {
3226
+ this.cm.view.cantEdit = false;
3227
+ reCheckSelection(this.cm);
3228
+ }
3229
+ endOperation(this.cm);
3230
+ signalLater(this.cm, this, "clear");
3231
+ };
3232
+
3233
+ TextMarker.prototype.find = function() {
3234
+ var from, to;
3235
+ for (var i = 0; i < this.lines.length; ++i) {
3236
+ var line = this.lines[i];
3237
+ var span = getMarkedSpanFor(line.markedSpans, this);
3238
+ if (span.from != null || span.to != null) {
3239
+ var found = lineNo(line);
3240
+ if (span.from != null) from = {line: found, ch: span.from};
3241
+ if (span.to != null) to = {line: found, ch: span.to};
3242
+ }
3243
+ }
3244
+ if (this.type == "bookmark") return from;
3245
+ return from && {from: from, to: to};
3246
+ };
3247
+
3248
+ function markText(cm, from, to, options, type) {
3249
+ var doc = cm.view.doc;
3250
+ var marker = new TextMarker(cm, type);
3251
+ if (type == "range" && !posLess(from, to)) return marker;
3252
+ if (options) for (var opt in options) if (options.hasOwnProperty(opt))
3253
+ marker[opt] = options[opt];
3254
+ if (marker.replacedWith) {
3255
+ marker.collapsed = true;
3256
+ marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
3257
+ }
3258
+ if (marker.collapsed) sawCollapsedSpans = true;
3259
+
3260
+ var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd;
3261
+ doc.iter(curLine, to.line + 1, function(line) {
3262
+ var span = {from: null, to: null, marker: marker};
3263
+ size += line.text.length;
3264
+ if (curLine == from.line) {span.from = from.ch; size -= from.ch;}
3265
+ if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;}
3266
+ if (marker.collapsed) {
3267
+ if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch);
3268
+ if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch);
3269
+ else updateLineHeight(line, 0);
3270
+ }
3271
+ addMarkedSpan(line, span);
3272
+ if (marker.collapsed && curLine == from.line && lineIsHidden(line))
3273
+ updateLineHeight(line, 0);
3274
+ ++curLine;
3275
+ });
3276
+
3277
+ if (marker.readOnly) {
3278
+ sawReadOnlySpans = true;
3279
+ if (cm.view.history.done.length || cm.view.history.undone.length)
3280
+ cm.clearHistory();
3281
+ }
3282
+ if (marker.collapsed) {
3283
+ if (collapsedAtStart != collapsedAtEnd)
3284
+ throw new Error("Inserting collapsed marker overlapping an existing one");
3285
+ marker.size = size;
3286
+ marker.atomic = true;
3287
+ }
3288
+ if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed)
3289
+ regChange(cm, from.line, to.line + 1);
3290
+ if (marker.atomic) reCheckSelection(cm);
3291
+ return marker;
3292
+ }
3293
+
3294
+ // TEXTMARKER SPANS
3295
+
3296
+ function getMarkedSpanFor(spans, marker) {
3297
+ if (spans) for (var i = 0; i < spans.length; ++i) {
3298
+ var span = spans[i];
3299
+ if (span.marker == marker) return span;
3300
+ }
3301
+ }
3302
+ function removeMarkedSpan(spans, span) {
3303
+ for (var r, i = 0; i < spans.length; ++i)
3304
+ if (spans[i] != span) (r || (r = [])).push(spans[i]);
3305
+ return r;
3306
+ }
3307
+ function addMarkedSpan(line, span) {
3308
+ line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
3309
+ span.marker.lines.push(line);
3310
+ }
3311
+
3312
+ function markedSpansBefore(old, startCh) {
3313
+ if (old) for (var i = 0, nw; i < old.length; ++i) {
3314
+ var span = old[i], marker = span.marker;
3315
+ var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
3316
+ if (startsBefore || marker.type == "bookmark" && span.from == startCh) {
3317
+ var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
3318
+ (nw || (nw = [])).push({from: span.from,
3319
+ to: endsAfter ? null : span.to,
3320
+ marker: marker});
3321
+ }
3322
+ }
3323
+ return nw;
3324
+ }
3325
+
3326
+ function markedSpansAfter(old, startCh, endCh) {
3327
+ if (old) for (var i = 0, nw; i < old.length; ++i) {
3328
+ var span = old[i], marker = span.marker;
3329
+ var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
3330
+ if (endsAfter || marker.type == "bookmark" && span.from == endCh && span.from != startCh) {
3331
+ var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
3332
+ (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
3333
+ to: span.to == null ? null : span.to - endCh,
3334
+ marker: marker});
3335
+ }
3336
+ }
3337
+ return nw;
3338
+ }
3339
+
3340
+ function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) {
3341
+ if (!oldFirst && !oldLast) return newText;
3342
+ // Get the spans that 'stick out' on both sides
3343
+ var first = markedSpansBefore(oldFirst, startCh);
3344
+ var last = markedSpansAfter(oldLast, startCh, endCh);
3345
+
3346
+ // Next, merge those two ends
3347
+ var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0);
3348
+ if (first) {
3349
+ // Fix up .to properties of first
3350
+ for (var i = 0; i < first.length; ++i) {
3351
+ var span = first[i];
3352
+ if (span.to == null) {
3353
+ var found = getMarkedSpanFor(last, span.marker);
3354
+ if (!found) span.to = startCh;
3355
+ else if (sameLine) span.to = found.to == null ? null : found.to + offset;
3356
+ }
3357
+ }
3358
+ }
3359
+ if (last) {
3360
+ // Fix up .from in last (or move them into first in case of sameLine)
3361
+ for (var i = 0; i < last.length; ++i) {
3362
+ var span = last[i];
3363
+ if (span.to != null) span.to += offset;
3364
+ if (span.from == null) {
3365
+ var found = getMarkedSpanFor(first, span.marker);
3366
+ if (!found) {
3367
+ span.from = offset;
3368
+ if (sameLine) (first || (first = [])).push(span);
3369
+ }
3370
+ } else {
3371
+ span.from += offset;
3372
+ if (sameLine) (first || (first = [])).push(span);
3373
+ }
3374
+ }
3375
+ }
3376
+
3377
+ var newMarkers = [newHL(newText[0], first)];
3378
+ if (!sameLine) {
3379
+ // Fill gap with whole-line-spans
3380
+ var gap = newText.length - 2, gapMarkers;
3381
+ if (gap > 0 && first)
3382
+ for (var i = 0; i < first.length; ++i)
3383
+ if (first[i].to == null)
3384
+ (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker});
3385
+ for (var i = 0; i < gap; ++i)
3386
+ newMarkers.push(newHL(newText[i+1], gapMarkers));
3387
+ newMarkers.push(newHL(lst(newText), last));
3388
+ }
3389
+ return newMarkers;
3390
+ }
3391
+
3392
+ function removeReadOnlyRanges(doc, from, to) {
3393
+ var markers = null;
3394
+ doc.iter(from.line, to.line + 1, function(line) {
3395
+ if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
3396
+ var mark = line.markedSpans[i].marker;
3397
+ if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
3398
+ (markers || (markers = [])).push(mark);
3399
+ }
3400
+ });
3401
+ if (!markers) return null;
3402
+ var parts = [{from: from, to: to}];
3403
+ for (var i = 0; i < markers.length; ++i) {
3404
+ var m = markers[i].find();
3405
+ for (var j = 0; j < parts.length; ++j) {
3406
+ var p = parts[j];
3407
+ if (!posLess(m.from, p.to) || posLess(m.to, p.from)) continue;
3408
+ var newParts = [j, 1];
3409
+ if (posLess(p.from, m.from)) newParts.push({from: p.from, to: m.from});
3410
+ if (posLess(m.to, p.to)) newParts.push({from: m.to, to: p.to});
3411
+ parts.splice.apply(parts, newParts);
3412
+ j += newParts.length - 1;
3413
+ }
3414
+ }
3415
+ return parts;
3416
+ }
3417
+
3418
+ function collapsedSpanAt(line, ch) {
3419
+ var sps = sawCollapsedSpans && line.markedSpans, found;
3420
+ if (sps) for (var sp, i = 0; i < sps.length; ++i) {
3421
+ sp = sps[i];
3422
+ if (!sp.marker.collapsed) continue;
3423
+ if ((sp.from == null || sp.from < ch) &&
3424
+ (sp.to == null || sp.to > ch) &&
3425
+ (!found || found.width < sp.marker.width))
3426
+ found = sp.marker;
3427
+ }
3428
+ return found;
3429
+ }
3430
+ function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); }
3431
+ function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); }
3432
+
3433
+ function visualLine(doc, line) {
3434
+ var merged;
3435
+ while (merged = collapsedSpanAtStart(line))
3436
+ line = getLine(doc, merged.find().from.line);
3437
+ return line;
3438
+ }
3439
+
3440
+ function lineIsHidden(line) {
3441
+ var sps = sawCollapsedSpans && line.markedSpans;
3442
+ if (sps) for (var sp, i = 0; i < sps.length; ++i) {
3443
+ sp = sps[i];
3444
+ if (!sp.marker.collapsed) continue;
3445
+ if (sp.from == null) return true;
3446
+ if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(line, sp))
3447
+ return true;
3448
+ }
3449
+ }
3450
+ window.lineIsHidden = lineIsHidden;
3451
+ function lineIsHiddenInner(line, span) {
3452
+ if (span.to == null || span.marker.inclusiveRight && span.to == line.text.length)
3453
+ return true;
3454
+ for (var sp, i = 0; i < line.markedSpans.length; ++i) {
3455
+ sp = line.markedSpans[i];
3456
+ if (sp.marker.collapsed && sp.from == span.to &&
3457
+ (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
3458
+ lineIsHiddenInner(line, sp)) return true;
3459
+ }
3460
+ }
3461
+
3462
+ // hl stands for history-line, a data structure that can be either a
3463
+ // string (line without markers) or a {text, markedSpans} object.
3464
+ function hlText(val) { return typeof val == "string" ? val : val.text; }
3465
+ function hlSpans(val) {
3466
+ if (typeof val == "string") return null;
3467
+ var spans = val.markedSpans, out = null;
3468
+ for (var i = 0; i < spans.length; ++i) {
3469
+ if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
3470
+ else if (out) out.push(spans[i]);
3471
+ }
3472
+ return !out ? spans : out.length ? out : null;
3473
+ }
3474
+ function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; }
3475
+
3476
+ function detachMarkedSpans(line) {
3477
+ var spans = line.markedSpans;
3478
+ if (!spans) return;
3479
+ for (var i = 0; i < spans.length; ++i) {
3480
+ var lines = spans[i].marker.lines;
3481
+ var ix = indexOf(lines, line);
3482
+ lines.splice(ix, 1);
3483
+ }
3484
+ line.markedSpans = null;
3485
+ }
3486
+
3487
+ function attachMarkedSpans(line, spans) {
3488
+ if (!spans) return;
3489
+ for (var i = 0; i < spans.length; ++i)
3490
+ spans[i].marker.lines.push(line);
3491
+ line.markedSpans = spans;
3492
+ }
3493
+
3494
+ // LINE DATA STRUCTURE
3495
+
3496
+ // Line objects. These hold state related to a line, including
3497
+ // highlighting info (the styles array).
3498
+ function makeLine(text, markedSpans, height) {
3499
+ var line = {text: text, height: height};
3500
+ attachMarkedSpans(line, markedSpans);
3501
+ if (lineIsHidden(line)) line.height = 0;
3502
+ return line;
3503
+ }
3504
+
3505
+ function updateLine(cm, line, text, markedSpans) {
3506
+ line.text = text;
3507
+ line.stateAfter = line.styles = null;
3508
+ if (line.order != null) line.order = null;
3509
+ detachMarkedSpans(line);
3510
+ attachMarkedSpans(line, markedSpans);
3511
+ if (lineIsHidden(line)) line.height = 0;
3512
+ else if (!line.height) line.height = textHeight(cm.display);
3513
+ signalLater(cm, line, "change");
3514
+ }
3515
+
3516
+ function cleanUpLine(line) {
3517
+ line.parent = null;
3518
+ detachMarkedSpans(line);
3519
+ }
3520
+
3521
+ // Run the given mode's parser over a line, update the styles
3522
+ // array, which contains alternating fragments of text and CSS
3523
+ // classes.
3524
+ function highlightLine(cm, line, state) {
3525
+ var mode = cm.view.mode, flattenSpans = cm.options.flattenSpans;
3526
+ var changed = !line.styles, pos = 0, curText = "", curStyle = null;
3527
+ var stream = new StringStream(line.text, cm.options.tabSize), st = line.styles || (line.styles = []);
3528
+ if (line.text == "" && mode.blankLine) mode.blankLine(state);
3529
+ while (!stream.eol()) {
3530
+ var style = mode.token(stream, state), substr = stream.current();
3531
+ stream.start = stream.pos;
3532
+ if (!flattenSpans || curStyle != style) {
3533
+ if (curText) {
3534
+ changed = changed || pos >= st.length || curText != st[pos] || curStyle != st[pos+1];
3535
+ st[pos++] = curText; st[pos++] = curStyle;
3536
+ }
3537
+ curText = substr; curStyle = style;
3538
+ } else curText = curText + substr;
3539
+ // Give up when line is ridiculously long
3540
+ if (stream.pos > 5000) break;
3541
+ }
3542
+ if (curText) {
3543
+ changed = changed || pos >= st.length || curText != st[pos] || curStyle != st[pos+1];
3544
+ st[pos++] = curText; st[pos++] = curStyle;
3545
+ }
3546
+ if (stream.pos > 5000) { st[pos++] = line.text.slice(stream.pos); st[pos++] = null; }
3547
+ if (pos != st.length) { st.length = pos; changed = true; }
3548
+ return changed;
3549
+ }
3550
+
3551
+ // Lightweight form of highlight -- proceed over this line and
3552
+ // update state, but don't save a style array.
3553
+ function processLine(cm, line, state) {
3554
+ var mode = cm.view.mode;
3555
+ var stream = new StringStream(line.text, cm.options.tabSize);
3556
+ if (line.text == "" && mode.blankLine) mode.blankLine(state);
3557
+ while (!stream.eol() && stream.pos <= 5000) {
3558
+ mode.token(stream, state);
3559
+ stream.start = stream.pos;
3560
+ }
3561
+ }
3562
+
3563
+ var styleToClassCache = {};
3564
+ function styleToClass(style) {
3565
+ if (!style) return null;
3566
+ return styleToClassCache[style] ||
3567
+ (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-"));
3568
+ }
3569
+
3570
+ function lineContent(cm, realLine, measure) {
3571
+ var merged, line = realLine, lineBefore, sawBefore, simple = true;
3572
+ while (merged = collapsedSpanAtStart(line)) {
3573
+ simple = false;
3574
+ line = getLine(cm.view.doc, merged.find().from.line);
3575
+ if (!lineBefore) lineBefore = line;
3576
+ }
3577
+
3578
+ var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure,
3579
+ measure: null, addedOne: false, cm: cm};
3580
+ if (line.textClass) builder.pre.className = line.textClass;
3581
+
3582
+ do {
3583
+ if (!line.styles)
3584
+ highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
3585
+ builder.measure = line == realLine && measure;
3586
+ builder.pos = 0;
3587
+ builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
3588
+ if (measure && sawBefore && line != realLine && !builder.addedOne) {
3589
+ measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
3590
+ builder.addedOne = true;
3591
+ }
3592
+ var next = insertLineContent(line, builder);
3593
+ sawBefore = line == lineBefore;
3594
+ if (next) {
3595
+ line = getLine(cm.view.doc, next.to.line);
3596
+ simple = false;
3597
+ }
3598
+ } while (next);
3599
+
3600
+ if (measure && !builder.addedOne)
3601
+ measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
3602
+ if (!builder.pre.firstChild && !lineIsHidden(realLine))
3603
+ builder.pre.appendChild(document.createTextNode("\u00a0"));
3604
+
3605
+ return builder.pre;
3606
+ }
3607
+
3608
+ var tokenSpecialChars = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g;
3609
+ function buildToken(builder, text, style, startStyle, endStyle) {
3610
+ if (!text) return;
3611
+ if (!tokenSpecialChars.test(text)) {
3612
+ builder.col += text.length;
3613
+ var content = document.createTextNode(text);
3614
+ } else {
3615
+ var content = document.createDocumentFragment(), pos = 0;
3616
+ while (true) {
3617
+ tokenSpecialChars.lastIndex = pos;
3618
+ var m = tokenSpecialChars.exec(text);
3619
+ var skipped = m ? m.index - pos : text.length - pos;
3620
+ if (skipped) {
3621
+ content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
3622
+ builder.col += skipped;
3623
+ }
3624
+ if (!m) break;
3625
+ pos += skipped + 1;
3626
+ if (m[0] == "\t") {
3627
+ var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
3628
+ content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
3629
+ builder.col += tabWidth;
3630
+ } else {
3631
+ var token = elt("span", "\u2022", "cm-invalidchar");
3632
+ token.title = "\\u" + m[0].charCodeAt(0).toString(16);
3633
+ content.appendChild(token);
3634
+ builder.col += 1;
3635
+ }
3636
+ }
3637
+ }
3638
+ if (style || startStyle || endStyle || builder.measure) {
3639
+ var fullStyle = style || "";
3640
+ if (startStyle) fullStyle += startStyle;
3641
+ if (endStyle) fullStyle += endStyle;
3642
+ return builder.pre.appendChild(elt("span", [content], fullStyle));
3643
+ }
3644
+ builder.pre.appendChild(content);
3645
+ }
3646
+
3647
+ function buildTokenMeasure(builder, text, style, startStyle, endStyle) {
3648
+ for (var i = 0; i < text.length; ++i) {
3649
+ if (i && i < text.length - 1 &&
3650
+ builder.cm.options.lineWrapping &&
3651
+ spanAffectsWrapping.test(text.slice(i - 1, i + 1)))
3652
+ builder.pre.appendChild(elt("wbr"));
3653
+ builder.measure[builder.pos++] =
3654
+ buildToken(builder, text.charAt(i), style,
3655
+ i == 0 && startStyle, i == text.length - 1 && endStyle);
3656
+ }
3657
+ if (text.length) builder.addedOne = true;
3658
+ }
3659
+
3660
+ function buildCollapsedSpan(builder, size, widget) {
3661
+ if (widget) {
3662
+ if (!builder.display) widget = widget.cloneNode(true);
3663
+ builder.pre.appendChild(widget);
3664
+ if (builder.measure && size) {
3665
+ builder.measure[builder.pos] = widget;
3666
+ builder.addedOne = true;
3667
+ }
3668
+ }
3669
+ builder.pos += size;
3670
+ }
3671
+
3672
+ // Outputs a number of spans to make up a line, taking highlighting
3673
+ // and marked text into account.
3674
+ function insertLineContent(line, builder) {
3675
+ var st = line.styles, spans = line.markedSpans;
3676
+ if (!spans) {
3677
+ for (var i = 0; i < st.length; i+=2)
3678
+ builder.addToken(builder, st[i], styleToClass(st[i+1]));
3679
+ return;
3680
+ }
3681
+
3682
+ var allText = line.text, len = allText.length;
3683
+ var pos = 0, i = 0, text = "", style;
3684
+ var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed;
3685
+ for (;;) {
3686
+ if (nextChange == pos) { // Update current marker set
3687
+ spanStyle = spanEndStyle = spanStartStyle = "";
3688
+ collapsed = null; nextChange = Infinity;
3689
+ var foundBookmark = null;
3690
+ for (var j = 0; j < spans.length; ++j) {
3691
+ var sp = spans[j], m = sp.marker;
3692
+ if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
3693
+ if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
3694
+ if (m.className) spanStyle += " " + m.className;
3695
+ if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
3696
+ if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
3697
+ if (m.collapsed && (!collapsed || collapsed.marker.width < m.width))
3698
+ collapsed = sp;
3699
+ } else if (sp.from > pos && nextChange > sp.from) {
3700
+ nextChange = sp.from;
3701
+ }
3702
+ if (m.type == "bookmark" && sp.from == pos && m.replacedWith)
3703
+ foundBookmark = m.replacedWith;
3704
+ }
3705
+ if (collapsed && (collapsed.from || 0) == pos) {
3706
+ buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos,
3707
+ collapsed.from != null && collapsed.marker.replacedWith);
3708
+ if (collapsed.to == null) return collapsed.marker.find();
3709
+ }
3710
+ if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark);
3711
+ }
3712
+ if (pos >= len) break;
3713
+
3714
+ var upto = Math.min(len, nextChange);
3715
+ while (true) {
3716
+ if (text) {
3717
+ var end = pos + text.length;
3718
+ if (!collapsed) {
3719
+ var tokenText = end > upto ? text.slice(0, upto - pos) : text;
3720
+ builder.addToken(builder, tokenText, style + spanStyle,
3721
+ spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "");
3722
+ }
3723
+ if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
3724
+ pos = end;
3725
+ spanStartStyle = "";
3726
+ }
3727
+ text = st[i++]; style = styleToClass(st[i++]);
3728
+ }
3729
+ }
3730
+ }
3731
+
3732
+ // DOCUMENT DATA STRUCTURE
3733
+
3734
+ function LeafChunk(lines) {
3735
+ this.lines = lines;
3736
+ this.parent = null;
3737
+ for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
3738
+ lines[i].parent = this;
3739
+ height += lines[i].height;
3740
+ }
3741
+ this.height = height;
3742
+ }
3743
+
3744
+ LeafChunk.prototype = {
3745
+ chunkSize: function() { return this.lines.length; },
3746
+ remove: function(at, n, cm) {
3747
+ for (var i = at, e = at + n; i < e; ++i) {
3748
+ var line = this.lines[i];
3749
+ this.height -= line.height;
3750
+ cleanUpLine(line);
3751
+ signalLater(cm, line, "delete");
3752
+ }
3753
+ this.lines.splice(at, n);
3754
+ },
3755
+ collapse: function(lines) {
3756
+ lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
3757
+ },
3758
+ insertHeight: function(at, lines, height) {
3759
+ this.height += height;
3760
+ this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
3761
+ for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
3762
+ },
3763
+ iterN: function(at, n, op) {
3764
+ for (var e = at + n; at < e; ++at)
3765
+ if (op(this.lines[at])) return true;
3766
+ }
3767
+ };
3768
+
3769
+ function BranchChunk(children) {
3770
+ this.children = children;
3771
+ var size = 0, height = 0;
3772
+ for (var i = 0, e = children.length; i < e; ++i) {
3773
+ var ch = children[i];
3774
+ size += ch.chunkSize(); height += ch.height;
3775
+ ch.parent = this;
3776
+ }
3777
+ this.size = size;
3778
+ this.height = height;
3779
+ this.parent = null;
3780
+ }
3781
+
3782
+ BranchChunk.prototype = {
3783
+ chunkSize: function() { return this.size; },
3784
+ remove: function(at, n, callbacks) {
3785
+ this.size -= n;
3786
+ for (var i = 0; i < this.children.length; ++i) {
3787
+ var child = this.children[i], sz = child.chunkSize();
3788
+ if (at < sz) {
3789
+ var rm = Math.min(n, sz - at), oldHeight = child.height;
3790
+ child.remove(at, rm, callbacks);
3791
+ this.height -= oldHeight - child.height;
3792
+ if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
3793
+ if ((n -= rm) == 0) break;
3794
+ at = 0;
3795
+ } else at -= sz;
3796
+ }
3797
+ if (this.size - n < 25) {
3798
+ var lines = [];
3799
+ this.collapse(lines);
3800
+ this.children = [new LeafChunk(lines)];
3801
+ this.children[0].parent = this;
3802
+ }
3803
+ },
3804
+ collapse: function(lines) {
3805
+ for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
3806
+ },
3807
+ insert: function(at, lines) {
3808
+ var height = 0;
3809
+ for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
3810
+ this.insertHeight(at, lines, height);
3811
+ },
3812
+ insertHeight: function(at, lines, height) {
3813
+ this.size += lines.length;
3814
+ this.height += height;
3815
+ for (var i = 0, e = this.children.length; i < e; ++i) {
3816
+ var child = this.children[i], sz = child.chunkSize();
3817
+ if (at <= sz) {
3818
+ child.insertHeight(at, lines, height);
3819
+ if (child.lines && child.lines.length > 50) {
3820
+ while (child.lines.length > 50) {
3821
+ var spilled = child.lines.splice(child.lines.length - 25, 25);
3822
+ var newleaf = new LeafChunk(spilled);
3823
+ child.height -= newleaf.height;
3824
+ this.children.splice(i + 1, 0, newleaf);
3825
+ newleaf.parent = this;
3826
+ }
3827
+ this.maybeSpill();
3828
+ }
3829
+ break;
3830
+ }
3831
+ at -= sz;
3832
+ }
3833
+ },
3834
+ maybeSpill: function() {
3835
+ if (this.children.length <= 10) return;
3836
+ var me = this;
3837
+ do {
3838
+ var spilled = me.children.splice(me.children.length - 5, 5);
3839
+ var sibling = new BranchChunk(spilled);
3840
+ if (!me.parent) { // Become the parent node
3841
+ var copy = new BranchChunk(me.children);
3842
+ copy.parent = me;
3843
+ me.children = [copy, sibling];
3844
+ me = copy;
3845
+ } else {
3846
+ me.size -= sibling.size;
3847
+ me.height -= sibling.height;
3848
+ var myIndex = indexOf(me.parent.children, me);
3849
+ me.parent.children.splice(myIndex + 1, 0, sibling);
3850
+ }
3851
+ sibling.parent = me.parent;
3852
+ } while (me.children.length > 10);
3853
+ me.parent.maybeSpill();
3854
+ },
3855
+ iter: function(from, to, op) { this.iterN(from, to - from, op); },
3856
+ iterN: function(at, n, op) {
3857
+ for (var i = 0, e = this.children.length; i < e; ++i) {
3858
+ var child = this.children[i], sz = child.chunkSize();
3859
+ if (at < sz) {
3860
+ var used = Math.min(n, sz - at);
3861
+ if (child.iterN(at, used, op)) return true;
3862
+ if ((n -= used) == 0) break;
3863
+ at = 0;
3864
+ } else at -= sz;
3865
+ }
3866
+ }
3867
+ };
3868
+
3869
+ // LINE UTILITIES
3870
+
3871
+ function getLine(chunk, n) {
3872
+ while (!chunk.lines) {
3873
+ for (var i = 0;; ++i) {
3874
+ var child = chunk.children[i], sz = child.chunkSize();
3875
+ if (n < sz) { chunk = child; break; }
3876
+ n -= sz;
3877
+ }
3878
+ }
3879
+ return chunk.lines[n];
3880
+ }
3881
+
3882
+ function updateLineHeight(line, height) {
3883
+ var diff = height - line.height;
3884
+ for (var n = line; n; n = n.parent) n.height += diff;
3885
+ }
3886
+
3887
+ function lineNo(line) {
3888
+ if (line.parent == null) return null;
3889
+ var cur = line.parent, no = indexOf(cur.lines, line);
3890
+ for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
3891
+ for (var i = 0;; ++i) {
3892
+ if (chunk.children[i] == cur) break;
3893
+ no += chunk.children[i].chunkSize();
3894
+ }
3895
+ }
3896
+ return no;
3897
+ }
3898
+
3899
+ function lineAtHeight(chunk, h) {
3900
+ var n = 0;
3901
+ outer: do {
3902
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
3903
+ var child = chunk.children[i], ch = child.height;
3904
+ if (h < ch) { chunk = child; continue outer; }
3905
+ h -= ch;
3906
+ n += child.chunkSize();
3907
+ }
3908
+ return n;
3909
+ } while (!chunk.lines);
3910
+ for (var i = 0, e = chunk.lines.length; i < e; ++i) {
3911
+ var line = chunk.lines[i], lh = line.height;
3912
+ if (h < lh) break;
3913
+ h -= lh;
3914
+ }
3915
+ return n + i;
3916
+ }
3917
+
3918
+ function heightAtLine(cm, lineObj) {
3919
+ lineObj = visualLine(cm.view.doc, lineObj);
3920
+
3921
+ var h = 0, chunk = lineObj.parent;
3922
+ for (var i = 0; i < chunk.lines.length; ++i) {
3923
+ var line = chunk.lines[i];
3924
+ if (line == lineObj) break;
3925
+ else h += line.height;
3926
+ }
3927
+ for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
3928
+ for (var i = 0; i < p.children.length; ++i) {
3929
+ var cur = p.children[i];
3930
+ if (cur == chunk) break;
3931
+ else h += cur.height;
3932
+ }
3933
+ }
3934
+ return h;
3935
+ }
3936
+
3937
+ function getOrder(line) {
3938
+ var order = line.order;
3939
+ if (order == null) order = line.order = bidiOrdering(line.text);
3940
+ return order;
3941
+ }
3942
+
3943
+ // HISTORY
3944
+
3945
+ function makeHistory() {
3946
+ return {
3947
+ // Arrays of history events. Doing something adds an event to
3948
+ // done and clears undo. Undoing moves events from done to
3949
+ // undone, redoing moves them in the other direction.
3950
+ done: [], undone: [],
3951
+ // Used to track when changes can be merged into a single undo
3952
+ // event
3953
+ lastTime: 0, lastOp: null, lastOrigin: null,
3954
+ // Used by the isClean() method
3955
+ dirtyCounter: 0
3956
+ };
3957
+ }
3958
+
3959
+ function addChange(cm, start, added, old, origin, fromBefore, toBefore, fromAfter, toAfter) {
3960
+ var history = cm.view.history;
3961
+ history.undone.length = 0;
3962
+ var time = +new Date, cur = lst(history.done);
3963
+
3964
+ if (cur &&
3965
+ (history.lastOp == cm.curOp.id ||
3966
+ history.lastOrigin == origin && (origin == "input" || origin == "delete") &&
3967
+ history.lastTime > time - 600)) {
3968
+ // Merge this change into the last event
3969
+ var last = lst(cur.events);
3970
+ if (last.start > start + old.length || last.start + last.added < start) {
3971
+ // Doesn't intersect with last sub-event, add new sub-event
3972
+ cur.events.push({start: start, added: added, old: old});
3973
+ } else {
3974
+ // Patch up the last sub-event
3975
+ var startBefore = Math.max(0, last.start - start),
3976
+ endAfter = Math.max(0, (start + old.length) - (last.start + last.added));
3977
+ for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]);
3978
+ for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]);
3979
+ if (startBefore) last.start = start;
3980
+ last.added += added - (old.length - startBefore - endAfter);
3981
+ }
3982
+ cur.fromAfter = fromAfter; cur.toAfter = toAfter;
3983
+ } else {
3984
+ // Can not be merged, start a new event.
3985
+ cur = {events: [{start: start, added: added, old: old}],
3986
+ fromBefore: fromBefore, toBefore: toBefore, fromAfter: fromAfter, toAfter: toAfter};
3987
+ history.done.push(cur);
3988
+ while (history.done.length > cm.options.undoDepth)
3989
+ history.done.shift();
3990
+ if (history.dirtyCounter < 0)
3991
+ // The user has made a change after undoing past the last clean state.
3992
+ // We can never get back to a clean state now until markClean() is called.
3993
+ history.dirtyCounter = NaN;
3994
+ else
3995
+ history.dirtyCounter++;
3996
+ }
3997
+ history.lastTime = time;
3998
+ history.lastOp = cm.curOp.id;
3999
+ history.lastOrigin = origin;
4000
+ }
4001
+
4002
+ // EVENT OPERATORS
4003
+
4004
+ function stopMethod() {e_stop(this);}
4005
+ // Ensure an event has a stop method.
4006
+ function addStop(event) {
4007
+ if (!event.stop) event.stop = stopMethod;
4008
+ return event;
4009
+ }
4010
+
4011
+ function e_preventDefault(e) {
4012
+ if (e.preventDefault) e.preventDefault();
4013
+ else e.returnValue = false;
4014
+ }
4015
+ function e_stopPropagation(e) {
4016
+ if (e.stopPropagation) e.stopPropagation();
4017
+ else e.cancelBubble = true;
4018
+ }
4019
+ function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
4020
+ CodeMirror.e_stop = e_stop;
4021
+ CodeMirror.e_preventDefault = e_preventDefault;
4022
+ CodeMirror.e_stopPropagation = e_stopPropagation;
4023
+
4024
+ function e_target(e) {return e.target || e.srcElement;}
4025
+ function e_button(e) {
4026
+ var b = e.which;
4027
+ if (b == null) {
4028
+ if (e.button & 1) b = 1;
4029
+ else if (e.button & 2) b = 3;
4030
+ else if (e.button & 4) b = 2;
4031
+ }
4032
+ if (mac && e.ctrlKey && b == 1) b = 3;
4033
+ return b;
4034
+ }
4035
+
4036
+ // Allow 3rd-party code to override event properties by adding an override
4037
+ // object to an event object.
4038
+ function e_prop(e, prop) {
4039
+ var overridden = e.override && e.override.hasOwnProperty(prop);
4040
+ return overridden ? e.override[prop] : e[prop];
4041
+ }
4042
+
4043
+ // EVENT HANDLING
4044
+
4045
+ function on(emitter, type, f) {
4046
+ if (emitter.addEventListener)
4047
+ emitter.addEventListener(type, f, false);
4048
+ else if (emitter.attachEvent)
4049
+ emitter.attachEvent("on" + type, f);
4050
+ else {
4051
+ var map = emitter._handlers || (emitter._handlers = {});
4052
+ var arr = map[type] || (map[type] = []);
4053
+ arr.push(f);
4054
+ }
4055
+ }
4056
+
4057
+ function off(emitter, type, f) {
4058
+ if (emitter.removeEventListener)
4059
+ emitter.removeEventListener(type, f, false);
4060
+ else if (emitter.detachEvent)
4061
+ emitter.detachEvent("on" + type, f);
4062
+ else {
4063
+ var arr = emitter._handlers && emitter._handlers[type];
4064
+ if (!arr) return;
4065
+ for (var i = 0; i < arr.length; ++i)
4066
+ if (arr[i] == f) { arr.splice(i, 1); break; }
4067
+ }
4068
+ }
4069
+
4070
+ function signal(emitter, type /*, values...*/) {
4071
+ var arr = emitter._handlers && emitter._handlers[type];
4072
+ if (!arr) return;
4073
+ var args = Array.prototype.slice.call(arguments, 2);
4074
+ for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
4075
+ }
4076
+
4077
+ function signalLater(cm, emitter, type /*, values...*/) {
4078
+ var arr = emitter._handlers && emitter._handlers[type];
4079
+ if (!arr) return;
4080
+ var args = Array.prototype.slice.call(arguments, 3), flist = cm.curOp && cm.curOp.delayedCallbacks;
4081
+ function bnd(f) {return function(){f.apply(null, args);};};
4082
+ for (var i = 0; i < arr.length; ++i)
4083
+ if (flist) flist.push(bnd(arr[i]));
4084
+ else arr[i].apply(null, args);
4085
+ }
4086
+
4087
+ function hasHandler(emitter, type) {
4088
+ var arr = emitter._handlers && emitter._handlers[type];
4089
+ return arr && arr.length > 0;
4090
+ }
4091
+
4092
+ CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal;
4093
+
4094
+ // MISC UTILITIES
4095
+
4096
+ // Number of pixels added to scroller and sizer to hide scrollbar
4097
+ var scrollerCutOff = 30;
4098
+
4099
+ // Returned or thrown by various protocols to signal 'I'm not
4100
+ // handling this'.
4101
+ var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
4102
+
4103
+ function Delayed() {this.id = null;}
4104
+ Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
4105
+
4106
+ // Counts the column offset in a string, taking tabs into account.
4107
+ // Used mostly to find indentation.
4108
+ function countColumn(string, end, tabSize) {
4109
+ if (end == null) {
4110
+ end = string.search(/[^\s\u00a0]/);
4111
+ if (end == -1) end = string.length;
4112
+ }
4113
+ for (var i = 0, n = 0; i < end; ++i) {
4114
+ if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
4115
+ else ++n;
4116
+ }
4117
+ return n;
4118
+ }
4119
+ CodeMirror.countColumn = countColumn;
4120
+
4121
+ var spaceStrs = [""];
4122
+ function spaceStr(n) {
4123
+ while (spaceStrs.length <= n)
4124
+ spaceStrs.push(lst(spaceStrs) + " ");
4125
+ return spaceStrs[n];
4126
+ }
4127
+
4128
+ function lst(arr) { return arr[arr.length-1]; }
4129
+
4130
+ function selectInput(node) {
4131
+ if (ios) { // Mobile Safari apparently has a bug where select() is broken.
4132
+ node.selectionStart = 0;
4133
+ node.selectionEnd = node.value.length;
4134
+ } else node.select();
4135
+ }
4136
+
4137
+ function indexOf(collection, elt) {
4138
+ if (collection.indexOf) return collection.indexOf(elt);
4139
+ for (var i = 0, e = collection.length; i < e; ++i)
4140
+ if (collection[i] == elt) return i;
4141
+ return -1;
4142
+ }
4143
+
4144
+ function emptyArray(size) {
4145
+ for (var a = [], i = 0; i < size; ++i) a.push(undefined);
4146
+ return a;
4147
+ }
4148
+
4149
+ function bind(f) {
4150
+ var args = Array.prototype.slice.call(arguments, 1);
4151
+ return function(){return f.apply(null, args);};
4152
+ }
4153
+
4154
+ var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/;
4155
+ function isWordChar(ch) {
4156
+ return /\w/.test(ch) || ch > "\x80" &&
4157
+ (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
4158
+ }
4159
+
4160
+ function isEmpty(obj) {
4161
+ var c = 0;
4162
+ for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) ++c;
4163
+ return !c;
4164
+ }
4165
+
4166
+ var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F]/;
4167
+
4168
+ // DOM UTILITIES
4169
+
4170
+ function elt(tag, content, className, style) {
4171
+ var e = document.createElement(tag);
4172
+ if (className) e.className = className;
4173
+ if (style) e.style.cssText = style;
4174
+ if (typeof content == "string") setTextContent(e, content);
4175
+ else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
4176
+ return e;
4177
+ }
4178
+
4179
+ function removeChildren(e) {
4180
+ e.innerHTML = "";
4181
+ return e;
4182
+ }
4183
+
4184
+ function removeChildrenAndAdd(parent, e) {
4185
+ return removeChildren(parent).appendChild(e);
4186
+ }
4187
+
4188
+ function setTextContent(e, str) {
4189
+ if (ie_lt9) {
4190
+ e.innerHTML = "";
4191
+ e.appendChild(document.createTextNode(str));
4192
+ } else e.textContent = str;
4193
+ }
4194
+
4195
+ // FEATURE DETECTION
4196
+
4197
+ // Detect drag-and-drop
4198
+ var dragAndDrop = function() {
4199
+ // There is *some* kind of drag-and-drop support in IE6-8, but I
4200
+ // couldn't get it to work yet.
4201
+ if (ie_lt9) return false;
4202
+ var div = elt('div');
4203
+ return "draggable" in div || "dragDrop" in div;
4204
+ }();
4205
+
4206
+ // For a reason I have yet to figure out, some browsers disallow
4207
+ // word wrapping between certain characters *only* if a new inline
4208
+ // element is started between them. This makes it hard to reliably
4209
+ // measure the position of things, since that requires inserting an
4210
+ // extra span. This terribly fragile set of regexps matches the
4211
+ // character combinations that suffer from this phenomenon on the
4212
+ // various browsers.
4213
+ var spanAffectsWrapping = /^$/; // Won't match any two-character string
4214
+ if (gecko) spanAffectsWrapping = /$'/;
4215
+ else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
4216
+ else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
4217
+
4218
+ var knownScrollbarWidth;
4219
+ function scrollbarWidth(measure) {
4220
+ if (knownScrollbarWidth != null) return knownScrollbarWidth;
4221
+ var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
4222
+ removeChildrenAndAdd(measure, test);
4223
+ if (test.offsetWidth)
4224
+ knownScrollbarWidth = test.offsetHeight - test.clientHeight;
4225
+ return knownScrollbarWidth || 0;
4226
+ }
4227
+
4228
+ var zwspSupported;
4229
+ function zeroWidthElement(measure) {
4230
+ if (zwspSupported == null) {
4231
+ var test = elt("span", "\u200b");
4232
+ removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
4233
+ if (measure.firstChild.offsetHeight != 0)
4234
+ zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8;
4235
+ }
4236
+ if (zwspSupported) return elt("span", "\u200b");
4237
+ else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
4238
+ }
4239
+
4240
+ // See if "".split is the broken IE version, if so, provide an
4241
+ // alternative way to split lines.
4242
+ var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
4243
+ var pos = 0, result = [], l = string.length;
4244
+ while (pos <= l) {
4245
+ var nl = string.indexOf("\n", pos);
4246
+ if (nl == -1) nl = string.length;
4247
+ var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
4248
+ var rt = line.indexOf("\r");
4249
+ if (rt != -1) {
4250
+ result.push(line.slice(0, rt));
4251
+ pos += rt + 1;
4252
+ } else {
4253
+ result.push(line);
4254
+ pos = nl + 1;
4255
+ }
4256
+ }
4257
+ return result;
4258
+ } : function(string){return string.split(/\r\n?|\n/);};
4259
+ CodeMirror.splitLines = splitLines;
4260
+
4261
+ var hasSelection = window.getSelection ? function(te) {
4262
+ try { return te.selectionStart != te.selectionEnd; }
4263
+ catch(e) { return false; }
4264
+ } : function(te) {
4265
+ try {var range = te.ownerDocument.selection.createRange();}
4266
+ catch(e) {}
4267
+ if (!range || range.parentElement() != te) return false;
4268
+ return range.compareEndPoints("StartToEnd", range) != 0;
4269
+ };
4270
+
4271
+ var hasCopyEvent = (function() {
4272
+ var e = elt("div");
4273
+ if ("oncopy" in e) return true;
4274
+ e.setAttribute("oncopy", "return;");
4275
+ return typeof e.oncopy == 'function';
4276
+ })();
4277
+
4278
+ // KEY NAMING
4279
+
4280
+ var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
4281
+ 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
4282
+ 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
4283
+ 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
4284
+ 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
4285
+ 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
4286
+ 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
4287
+ CodeMirror.keyNames = keyNames;
4288
+ (function() {
4289
+ // Number keys
4290
+ for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
4291
+ // Alphabetic keys
4292
+ for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
4293
+ // Function keys
4294
+ for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
4295
+ })();
4296
+
4297
+ // BIDI HELPERS
4298
+
4299
+ function iterateBidiSections(order, from, to, f) {
4300
+ if (!order) return f(from, to, "ltr");
4301
+ for (var i = 0; i < order.length; ++i) {
4302
+ var part = order[i];
4303
+ if (part.from < to && part.to > from)
4304
+ f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
4305
+ }
4306
+ }
4307
+
4308
+ function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
4309
+ function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
4310
+
4311
+ function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }
4312
+ function lineRight(line) {
4313
+ var order = getOrder(line);
4314
+ if (!order) return line.text.length;
4315
+ return bidiRight(lst(order));
4316
+ }
4317
+
4318
+ function lineStart(cm, lineN) {
4319
+ var line = getLine(cm.view.doc, lineN);
4320
+ var visual = visualLine(cm.view.doc, line);
4321
+ if (visual != line) lineN = lineNo(visual);
4322
+ var order = getOrder(visual);
4323
+ var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);
4324
+ return {line: lineN, ch: ch};
4325
+ }
4326
+ function lineEnd(cm, lineNo) {
4327
+ var merged, line;
4328
+ while (merged = collapsedSpanAtEnd(line = getLine(cm.view.doc, lineNo)))
4329
+ lineNo = merged.find().to.line;
4330
+ var order = getOrder(line);
4331
+ var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
4332
+ return {line: lineNo, ch: ch};
4333
+ }
4334
+
4335
+ // This is somewhat involved. It is needed in order to move
4336
+ // 'visually' through bi-directional text -- i.e., pressing left
4337
+ // should make the cursor go left, even when in RTL text. The
4338
+ // tricky part is the 'jumps', where RTL and LTR text touch each
4339
+ // other. This often requires the cursor offset to move more than
4340
+ // one unit, in order to visually move one unit.
4341
+ function moveVisually(line, start, dir, byUnit) {
4342
+ var bidi = getOrder(line);
4343
+ if (!bidi) return moveLogically(line, start, dir, byUnit);
4344
+ var moveOneUnit = byUnit ? function(pos, dir) {
4345
+ do pos += dir;
4346
+ while (pos > 0 && isExtendingChar.test(line.text.charAt(pos)));
4347
+ return pos;
4348
+ } : function(pos, dir) { return pos + dir; };
4349
+ var linedir = bidi[0].level;
4350
+ for (var i = 0; i < bidi.length; ++i) {
4351
+ var part = bidi[i], sticky = part.level % 2 == linedir;
4352
+ if ((part.from < start && part.to > start) ||
4353
+ (sticky && (part.from == start || part.to == start))) break;
4354
+ }
4355
+ var target = moveOneUnit(start, part.level % 2 ? -dir : dir);
4356
+
4357
+ while (target != null) {
4358
+ if (part.level % 2 == linedir) {
4359
+ if (target < part.from || target > part.to) {
4360
+ part = bidi[i += dir];
4361
+ target = part && (dir > 0 == part.level % 2 ? moveOneUnit(part.to, -1) : moveOneUnit(part.from, 1));
4362
+ } else break;
4363
+ } else {
4364
+ if (target == bidiLeft(part)) {
4365
+ part = bidi[--i];
4366
+ target = part && bidiRight(part);
4367
+ } else if (target == bidiRight(part)) {
4368
+ part = bidi[++i];
4369
+ target = part && bidiLeft(part);
4370
+ } else break;
4371
+ }
4372
+ }
4373
+
4374
+ return target < 0 || target > line.text.length ? null : target;
4375
+ }
4376
+
4377
+ function moveLogically(line, start, dir, byUnit) {
4378
+ var target = start + dir;
4379
+ if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir;
4380
+ return target < 0 || target > line.text.length ? null : target;
4381
+ }
4382
+
4383
+ // Bidirectional ordering algorithm
4384
+ // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
4385
+ // that this (partially) implements.
4386
+
4387
+ // One-char codes used for character types:
4388
+ // L (L): Left-to-Right
4389
+ // R (R): Right-to-Left
4390
+ // r (AL): Right-to-Left Arabic
4391
+ // 1 (EN): European Number
4392
+ // + (ES): European Number Separator
4393
+ // % (ET): European Number Terminator
4394
+ // n (AN): Arabic Number
4395
+ // , (CS): Common Number Separator
4396
+ // m (NSM): Non-Spacing Mark
4397
+ // b (BN): Boundary Neutral
4398
+ // s (B): Paragraph Separator
4399
+ // t (S): Segment Separator
4400
+ // w (WS): Whitespace
4401
+ // N (ON): Other Neutrals
4402
+
4403
+ // Returns null if characters are ordered as they appear
4404
+ // (left-to-right), or an array of sections ({from, to, level}
4405
+ // objects) in the order in which they occur visually.
4406
+ var bidiOrdering = (function() {
4407
+ // Character types for codepoints 0 to 0xff
4408
+ var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL";
4409
+ // Character types for codepoints 0x600 to 0x6ff
4410
+ var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr";
4411
+ function charType(code) {
4412
+ if (code <= 0xff) return lowTypes.charAt(code);
4413
+ else if (0x590 <= code && code <= 0x5f4) return "R";
4414
+ else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600);
4415
+ else if (0x700 <= code && code <= 0x8ac) return "r";
4416
+ else return "L";
4417
+ }
4418
+
4419
+ var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
4420
+ var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
4421
+
4422
+ return function charOrdering(str) {
4423
+ if (!bidiRE.test(str)) return false;
4424
+ var len = str.length, types = [], startType = null;
4425
+ for (var i = 0, type; i < len; ++i) {
4426
+ types.push(type = charType(str.charCodeAt(i)));
4427
+ if (startType == null) {
4428
+ if (type == "L") startType = "L";
4429
+ else if (type == "R" || type == "r") startType = "R";
4430
+ }
4431
+ }
4432
+ if (startType == null) startType = "L";
4433
+
4434
+ // W1. Examine each non-spacing mark (NSM) in the level run, and
4435
+ // change the type of the NSM to the type of the previous
4436
+ // character. If the NSM is at the start of the level run, it will
4437
+ // get the type of sor.
4438
+ for (var i = 0, prev = startType; i < len; ++i) {
4439
+ var type = types[i];
4440
+ if (type == "m") types[i] = prev;
4441
+ else prev = type;
4442
+ }
4443
+
4444
+ // W2. Search backwards from each instance of a European number
4445
+ // until the first strong type (R, L, AL, or sor) is found. If an
4446
+ // AL is found, change the type of the European number to Arabic
4447
+ // number.
4448
+ // W3. Change all ALs to R.
4449
+ for (var i = 0, cur = startType; i < len; ++i) {
4450
+ var type = types[i];
4451
+ if (type == "1" && cur == "r") types[i] = "n";
4452
+ else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
4453
+ }
4454
+
4455
+ // W4. A single European separator between two European numbers
4456
+ // changes to a European number. A single common separator between
4457
+ // two numbers of the same type changes to that type.
4458
+ for (var i = 1, prev = types[0]; i < len - 1; ++i) {
4459
+ var type = types[i];
4460
+ if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1";
4461
+ else if (type == "," && prev == types[i+1] &&
4462
+ (prev == "1" || prev == "n")) types[i] = prev;
4463
+ prev = type;
4464
+ }
4465
+
4466
+ // W5. A sequence of European terminators adjacent to European
4467
+ // numbers changes to all European numbers.
4468
+ // W6. Otherwise, separators and terminators change to Other
4469
+ // Neutral.
4470
+ for (var i = 0; i < len; ++i) {
4471
+ var type = types[i];
4472
+ if (type == ",") types[i] = "N";
4473
+ else if (type == "%") {
4474
+ for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
4475
+ var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N";
4476
+ for (var j = i; j < end; ++j) types[j] = replace;
4477
+ i = end - 1;
4478
+ }
4479
+ }
4480
+
4481
+ // W7. Search backwards from each instance of a European number
4482
+ // until the first strong type (R, L, or sor) is found. If an L is
4483
+ // found, then change the type of the European number to L.
4484
+ for (var i = 0, cur = startType; i < len; ++i) {
4485
+ var type = types[i];
4486
+ if (cur == "L" && type == "1") types[i] = "L";
4487
+ else if (isStrong.test(type)) cur = type;
4488
+ }
4489
+
4490
+ // N1. A sequence of neutrals takes the direction of the
4491
+ // surrounding strong text if the text on both sides has the same
4492
+ // direction. European and Arabic numbers act as if they were R in
4493
+ // terms of their influence on neutrals. Start-of-level-run (sor)
4494
+ // and end-of-level-run (eor) are used at level run boundaries.
4495
+ // N2. Any remaining neutrals take the embedding direction.
4496
+ for (var i = 0; i < len; ++i) {
4497
+ if (isNeutral.test(types[i])) {
4498
+ for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
4499
+ var before = (i ? types[i-1] : startType) == "L";
4500
+ var after = (end < len - 1 ? types[end] : startType) == "L";
4501
+ var replace = before || after ? "L" : "R";
4502
+ for (var j = i; j < end; ++j) types[j] = replace;
4503
+ i = end - 1;
4504
+ }
4505
+ }
4506
+
4507
+ // Here we depart from the documented algorithm, in order to avoid
4508
+ // building up an actual levels array. Since there are only three
4509
+ // levels (0, 1, 2) in an implementation that doesn't take
4510
+ // explicit embedding into account, we can build up the order on
4511
+ // the fly, without following the level-based algorithm.
4512
+ var order = [], m;
4513
+ for (var i = 0; i < len;) {
4514
+ if (countsAsLeft.test(types[i])) {
4515
+ var start = i;
4516
+ for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
4517
+ order.push({from: start, to: i, level: 0});
4518
+ } else {
4519
+ var pos = i, at = order.length;
4520
+ for (++i; i < len && types[i] != "L"; ++i) {}
4521
+ for (var j = pos; j < i;) {
4522
+ if (countsAsNum.test(types[j])) {
4523
+ if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1});
4524
+ var nstart = j;
4525
+ for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
4526
+ order.splice(at, 0, {from: nstart, to: j, level: 2});
4527
+ pos = j;
4528
+ } else ++j;
4529
+ }
4530
+ if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1});
4531
+ }
4532
+ }
4533
+ if (order[0].level == 1 && (m = str.match(/^\s+/))) {
4534
+ order[0].from = m[0].length;
4535
+ order.unshift({from: 0, to: m[0].length, level: 0});
4536
+ }
4537
+ if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
4538
+ lst(order).to -= m[0].length;
4539
+ order.push({from: len - m[0].length, to: len, level: 0});
4540
+ }
4541
+ if (order[0].level != lst(order).level)
4542
+ order.push({from: len, to: len, level: order[0].level});
4543
+
4544
+ return order;
4545
+ };
4546
+ })();
4547
+
4548
+ // THE END
4549
+
4550
+ CodeMirror.version = "3.0";
4551
+
4552
+ return CodeMirror;
4553
+ })();
assets/icon.css ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ #icon-snippets.icon32 {
2
+ background: url('../images/icon32.png') no-repeat scroll transparent;
3
+ }
{js → assets/mode}/clike.js RENAMED
@@ -1,5 +1,6 @@
1
  CodeMirror.defineMode("clike", function(config, parserConfig) {
2
  var indentUnit = config.indentUnit,
 
3
  keywords = parserConfig.keywords || {},
4
  builtin = parserConfig.builtin || {},
5
  blockKeywords = parserConfig.blockKeywords || {},
@@ -89,7 +90,10 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
89
  this.prev = prev;
90
  }
91
  function pushContext(state, col, type) {
92
- return state.context = new Context(state.indented, col, type, null, state.context);
 
 
 
93
  }
94
  function popContext(state) {
95
  var t = state.context.type;
@@ -123,7 +127,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
123
  if (style == "comment" || style == "meta") return style;
124
  if (ctx.align == null) ctx.align = true;
125
 
126
- if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
127
  else if (curPunc == "{") pushContext(state, stream.column(), "}");
128
  else if (curPunc == "[") pushContext(state, stream.column(), "]");
129
  else if (curPunc == "(") pushContext(state, stream.column(), ")");
@@ -133,18 +137,18 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
133
  while (ctx.type == "statement") ctx = popContext(state);
134
  }
135
  else if (curPunc == ctx.type) popContext(state);
136
- else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
137
  pushContext(state, stream.column(), "statement");
138
  state.startOfLine = false;
139
  return style;
140
  },
141
 
142
  indent: function(state, textAfter) {
143
- if (state.tokenize != tokenBase && state.tokenize != null) return 0;
144
  var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
145
  if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
146
  var closing = firstChar == ctx.type;
147
- if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit);
148
  else if (ctx.align) return ctx.column + (closing ? 0 : 1);
149
  else return ctx.indented + (closing ? 0 : indentUnit);
150
  },
@@ -165,7 +169,19 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
165
 
166
  function cppHook(stream, state) {
167
  if (!state.startOfLine) return false;
168
- stream.skipToEnd();
 
 
 
 
 
 
 
 
 
 
 
 
169
  return "meta";
170
  }
171
 
@@ -212,7 +228,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
212
  blockKeywords: words("catch class do else finally for if switch try while"),
213
  atoms: words("true false null"),
214
  hooks: {
215
- "@": function(stream, state) {
216
  stream.eatWhile(/[\w\$_]/);
217
  return "meta";
218
  }
@@ -275,7 +291,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
275
  blockKeywords: words("catch class do else finally for forSome if match switch try while"),
276
  atoms: words("true false null"),
277
  hooks: {
278
- "@": function(stream, state) {
279
  stream.eatWhile(/[\w\$_]/);
280
  return "meta";
281
  }
1
  CodeMirror.defineMode("clike", function(config, parserConfig) {
2
  var indentUnit = config.indentUnit,
3
+ statementIndentUnit = parserConfig.statementIndentUnit || indentUnit,
4
  keywords = parserConfig.keywords || {},
5
  builtin = parserConfig.builtin || {},
6
  blockKeywords = parserConfig.blockKeywords || {},
90
  this.prev = prev;
91
  }
92
  function pushContext(state, col, type) {
93
+ var indent = state.indented;
94
+ if (state.context && state.context.type == "statement")
95
+ indent = state.context.indented;
96
+ return state.context = new Context(indent, col, type, null, state.context);
97
  }
98
  function popContext(state) {
99
  var t = state.context.type;
127
  if (style == "comment" || style == "meta") return style;
128
  if (ctx.align == null) ctx.align = true;
129
 
130
+ if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement") popContext(state);
131
  else if (curPunc == "{") pushContext(state, stream.column(), "}");
132
  else if (curPunc == "[") pushContext(state, stream.column(), "]");
133
  else if (curPunc == "(") pushContext(state, stream.column(), ")");
137
  while (ctx.type == "statement") ctx = popContext(state);
138
  }
139
  else if (curPunc == ctx.type) popContext(state);
140
+ else if (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement"))
141
  pushContext(state, stream.column(), "statement");
142
  state.startOfLine = false;
143
  return style;
144
  },
145
 
146
  indent: function(state, textAfter) {
147
+ if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;
148
  var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
149
  if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
150
  var closing = firstChar == ctx.type;
151
+ if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
152
  else if (ctx.align) return ctx.column + (closing ? 0 : 1);
153
  else return ctx.indented + (closing ? 0 : indentUnit);
154
  },
169
 
170
  function cppHook(stream, state) {
171
  if (!state.startOfLine) return false;
172
+ for (;;) {
173
+ if (stream.skipTo("\\")) {
174
+ stream.next();
175
+ if (stream.eol()) {
176
+ state.tokenize = cppHook;
177
+ break;
178
+ }
179
+ } else {
180
+ stream.skipToEnd();
181
+ state.tokenize = null;
182
+ break;
183
+ }
184
+ }
185
  return "meta";
186
  }
187
 
228
  blockKeywords: words("catch class do else finally for if switch try while"),
229
  atoms: words("true false null"),
230
  hooks: {
231
+ "@": function(stream) {
232
  stream.eatWhile(/[\w\$_]/);
233
  return "meta";
234
  }
291
  blockKeywords: words("catch class do else finally for forSome if match switch try while"),
292
  atoms: words("true false null"),
293
  hooks: {
294
+ "@": function(stream) {
295
  stream.eatWhile(/[\w\$_]/);
296
  return "meta";
297
  }
assets/mode/css.js ADDED
@@ -0,0 +1,465 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.defineMode("css", function(config) {
2
+ var indentUnit = config.indentUnit, type;
3
+
4
+ var atMediaTypes = keySet([
5
+ "all", "aural", "braille", "handheld", "print", "projection", "screen",
6
+ "tty", "tv", "embossed"
7
+ ]);
8
+
9
+ var atMediaFeatures = keySet([
10
+ "width", "min-width", "max-width", "height", "min-height", "max-height",
11
+ "device-width", "min-device-width", "max-device-width", "device-height",
12
+ "min-device-height", "max-device-height", "aspect-ratio",
13
+ "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
14
+ "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
15
+ "max-color", "color-index", "min-color-index", "max-color-index",
16
+ "monochrome", "min-monochrome", "max-monochrome", "resolution",
17
+ "min-resolution", "max-resolution", "scan", "grid"
18
+ ]);
19
+
20
+ var propertyKeywords = keySet([
21
+ "align-content", "align-items", "align-self", "alignment-adjust",
22
+ "alignment-baseline", "anchor-point", "animation", "animation-delay",
23
+ "animation-direction", "animation-duration", "animation-iteration-count",
24
+ "animation-name", "animation-play-state", "animation-timing-function",
25
+ "appearance", "azimuth", "backface-visibility", "background",
26
+ "background-attachment", "background-clip", "background-color",
27
+ "background-image", "background-origin", "background-position",
28
+ "background-repeat", "background-size", "baseline-shift", "binding",
29
+ "bleed", "bookmark-label", "bookmark-level", "bookmark-state",
30
+ "bookmark-target", "border", "border-bottom", "border-bottom-color",
31
+ "border-bottom-left-radius", "border-bottom-right-radius",
32
+ "border-bottom-style", "border-bottom-width", "border-collapse",
33
+ "border-color", "border-image", "border-image-outset",
34
+ "border-image-repeat", "border-image-slice", "border-image-source",
35
+ "border-image-width", "border-left", "border-left-color",
36
+ "border-left-style", "border-left-width", "border-radius", "border-right",
37
+ "border-right-color", "border-right-style", "border-right-width",
38
+ "border-spacing", "border-style", "border-top", "border-top-color",
39
+ "border-top-left-radius", "border-top-right-radius", "border-top-style",
40
+ "border-top-width", "border-width", "bottom", "box-decoration-break",
41
+ "box-shadow", "box-sizing", "break-after", "break-before", "break-inside",
42
+ "caption-side", "clear", "clip", "color", "color-profile", "column-count",
43
+ "column-fill", "column-gap", "column-rule", "column-rule-color",
44
+ "column-rule-style", "column-rule-width", "column-span", "column-width",
45
+ "columns", "content", "counter-increment", "counter-reset", "crop", "cue",
46
+ "cue-after", "cue-before", "cursor", "direction", "display",
47
+ "dominant-baseline", "drop-initial-after-adjust",
48
+ "drop-initial-after-align", "drop-initial-before-adjust",
49
+ "drop-initial-before-align", "drop-initial-size", "drop-initial-value",
50
+ "elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis",
51
+ "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap",
52
+ "float", "float-offset", "font", "font-feature-settings", "font-family",
53
+ "font-kerning", "font-language-override", "font-size", "font-size-adjust",
54
+ "font-stretch", "font-style", "font-synthesis", "font-variant",
55
+ "font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
56
+ "font-variant-ligatures", "font-variant-numeric", "font-variant-position",
57
+ "font-weight", "grid-cell", "grid-column", "grid-column-align",
58
+ "grid-column-sizing", "grid-column-span", "grid-columns", "grid-flow",
59
+ "grid-row", "grid-row-align", "grid-row-sizing", "grid-row-span",
60
+ "grid-rows", "grid-template", "hanging-punctuation", "height", "hyphens",
61
+ "icon", "image-orientation", "image-rendering", "image-resolution",
62
+ "inline-box-align", "justify-content", "left", "letter-spacing",
63
+ "line-break", "line-height", "line-stacking", "line-stacking-ruby",
64
+ "line-stacking-shift", "line-stacking-strategy", "list-style",
65
+ "list-style-image", "list-style-position", "list-style-type", "margin",
66
+ "margin-bottom", "margin-left", "margin-right", "margin-top",
67
+ "marker-offset", "marks", "marquee-direction", "marquee-loop",
68
+ "marquee-play-count", "marquee-speed", "marquee-style", "max-height",
69
+ "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index",
70
+ "nav-left", "nav-right", "nav-up", "opacity", "order", "orphans", "outline",
71
+ "outline-color", "outline-offset", "outline-style", "outline-width",
72
+ "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y",
73
+ "padding", "padding-bottom", "padding-left", "padding-right", "padding-top",
74
+ "page", "page-break-after", "page-break-before", "page-break-inside",
75
+ "page-policy", "pause", "pause-after", "pause-before", "perspective",
76
+ "perspective-origin", "pitch", "pitch-range", "play-during", "position",
77
+ "presentation-level", "punctuation-trim", "quotes", "rendering-intent",
78
+ "resize", "rest", "rest-after", "rest-before", "richness", "right",
79
+ "rotation", "rotation-point", "ruby-align", "ruby-overhang",
80
+ "ruby-position", "ruby-span", "size", "speak", "speak-as", "speak-header",
81
+ "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set",
82
+ "tab-size", "table-layout", "target", "target-name", "target-new",
83
+ "target-position", "text-align", "text-align-last", "text-decoration",
84
+ "text-decoration-color", "text-decoration-line", "text-decoration-skip",
85
+ "text-decoration-style", "text-emphasis", "text-emphasis-color",
86
+ "text-emphasis-position", "text-emphasis-style", "text-height",
87
+ "text-indent", "text-justify", "text-outline", "text-shadow",
88
+ "text-space-collapse", "text-transform", "text-underline-position",
89
+ "text-wrap", "top", "transform", "transform-origin", "transform-style",
90
+ "transition", "transition-delay", "transition-duration",
91
+ "transition-property", "transition-timing-function", "unicode-bidi",
92
+ "vertical-align", "visibility", "voice-balance", "voice-duration",
93
+ "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
94
+ "voice-volume", "volume", "white-space", "widows", "width", "word-break",
95
+ "word-spacing", "word-wrap", "z-index"
96
+ ]);
97
+
98
+ var colorKeywords = keySet([
99
+ "black", "silver", "gray", "white", "maroon", "red", "purple", "fuchsia",
100
+ "green", "lime", "olive", "yellow", "navy", "blue", "teal", "aqua"
101
+ ]);
102
+
103
+ var valueKeywords = keySet([
104
+ "above", "absolute", "activeborder", "activecaption", "afar",
105
+ "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate",
106
+ "always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
107
+ "arabic-indic", "armenian", "asterisks", "auto", "avoid", "background",
108
+ "backwards", "baseline", "below", "bidi-override", "binary", "bengali",
109
+ "blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
110
+ "both", "bottom", "break-all", "break-word", "button", "button-bevel",
111
+ "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian",
112
+ "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
113
+ "cell", "center", "checkbox", "circle", "cjk-earthly-branch",
114
+ "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
115
+ "col-resize", "collapse", "compact", "condensed", "contain", "content",
116
+ "content-box", "context-menu", "continuous", "copy", "cover", "crop",
117
+ "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal",
118
+ "decimal-leading-zero", "default", "default-button", "destination-atop",
119
+ "destination-in", "destination-out", "destination-over", "devanagari",
120
+ "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted",
121
+ "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
122
+ "element", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
123
+ "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
124
+ "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
125
+ "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
126
+ "ethiopic-halehame-gez", "ethiopic-halehame-om-et",
127
+ "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
128
+ "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et",
129
+ "ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed",
130
+ "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes",
131
+ "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove",
132
+ "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew",
133
+ "help", "hidden", "hide", "higher", "highlight", "highlighttext",
134
+ "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore",
135
+ "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
136
+ "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
137
+ "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert",
138
+ "italic", "justify", "kannada", "katakana", "katakana-iroha", "khmer",
139
+ "landscape", "lao", "large", "larger", "left", "level", "lighter",
140
+ "line-through", "linear", "lines", "list-item", "listbox", "listitem",
141
+ "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
142
+ "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
143
+ "lower-roman", "lowercase", "ltr", "malayalam", "match",
144
+ "media-controls-background", "media-current-time-display",
145
+ "media-fullscreen-button", "media-mute-button", "media-play-button",
146
+ "media-return-to-realtime-button", "media-rewind-button",
147
+ "media-seek-back-button", "media-seek-forward-button", "media-slider",
148
+ "media-sliderthumb", "media-time-remaining-display", "media-volume-slider",
149
+ "media-volume-slider-container", "media-volume-sliderthumb", "medium",
150
+ "menu", "menulist", "menulist-button", "menulist-text",
151
+ "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
152
+ "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize",
153
+ "narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
154
+ "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
155
+ "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote",
156
+ "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
157
+ "outside", "overlay", "overline", "padding", "padding-box", "painted",
158
+ "paused", "persian", "plus-darker", "plus-lighter", "pointer", "portrait",
159
+ "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button",
160
+ "radio", "read-only", "read-write", "read-write-plaintext-only", "relative",
161
+ "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba",
162
+ "ridge", "right", "round", "row-resize", "rtl", "run-in", "running",
163
+ "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield",
164
+ "searchfield-cancel-button", "searchfield-decoration",
165
+ "searchfield-results-button", "searchfield-results-decoration",
166
+ "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
167
+ "single", "skip-white-space", "slide", "slider-horizontal",
168
+ "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
169
+ "small", "small-caps", "small-caption", "smaller", "solid", "somali",
170
+ "source-atop", "source-in", "source-out", "source-over", "space", "square",
171
+ "square-button", "start", "static", "status-bar", "stretch", "stroke",
172
+ "sub", "subpixel-antialiased", "super", "sw-resize", "table",
173
+ "table-caption", "table-cell", "table-column", "table-column-group",
174
+ "table-footer-group", "table-header-group", "table-row", "table-row-group",
175
+ "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
176
+ "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
177
+ "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
178
+ "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
179
+ "transparent", "ultra-condensed", "ultra-expanded", "underline", "up",
180
+ "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
181
+ "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
182
+ "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
183
+ "visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider",
184
+ "window", "windowframe", "windowtext", "x-large", "x-small", "xor",
185
+ "xx-large", "xx-small", "yellow"
186
+ ]);
187
+
188
+ function keySet(array) { var keys = {}; for (var i = 0; i < array.length; ++i) keys[array[i]] = true; return keys; }
189
+ function ret(style, tp) {type = tp; return style;}
190
+
191
+ function tokenBase(stream, state) {
192
+ var ch = stream.next();
193
+ if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("def", stream.current());}
194
+ else if (ch == "/" && stream.eat("*")) {
195
+ state.tokenize = tokenCComment;
196
+ return tokenCComment(stream, state);
197
+ }
198
+ else if (ch == "<" && stream.eat("!")) {
199
+ state.tokenize = tokenSGMLComment;
200
+ return tokenSGMLComment(stream, state);
201
+ }
202
+ else if (ch == "=") ret(null, "compare");
203
+ else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare");
204
+ else if (ch == "\"" || ch == "'") {
205
+ state.tokenize = tokenString(ch);
206
+ return state.tokenize(stream, state);
207
+ }
208
+ else if (ch == "#") {
209
+ stream.eatWhile(/[\w\\\-]/);
210
+ return ret("atom", "hash");
211
+ }
212
+ else if (ch == "!") {
213
+ stream.match(/^\s*\w*/);
214
+ return ret("keyword", "important");
215
+ }
216
+ else if (/\d/.test(ch)) {
217
+ stream.eatWhile(/[\w.%]/);
218
+ return ret("number", "unit");
219
+ }
220
+ else if (ch === "-") {
221
+ if (/\d/.test(stream.peek())) {
222
+ stream.eatWhile(/[\w.%]/);
223
+ return ret("number", "unit");
224
+ } else if (stream.match(/^[^-]+-/)) {
225
+ return ret("meta", type);
226
+ }
227
+ }
228
+ else if (/[,+>*\/]/.test(ch)) {
229
+ return ret(null, "select-op");
230
+ }
231
+ else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
232
+ return ret("qualifier", type);
233
+ }
234
+ else if (ch == ":") {
235
+ return ret("operator", ch);
236
+ }
237
+ else if (/[;{}\[\]\(\)]/.test(ch)) {
238
+ return ret(null, ch);
239
+ }
240
+ else if (ch == "u" && stream.match("rl(")) {
241
+ stream.backUp(1);
242
+ state.tokenize = tokenParenthesized;
243
+ return ret("property", "variable");
244
+ }
245
+ else {
246
+ stream.eatWhile(/[\w\\\-]/);
247
+ return ret("property", "variable");
248
+ }
249
+ }
250
+
251
+ function tokenCComment(stream, state) {
252
+ var maybeEnd = false, ch;
253
+ while ((ch = stream.next()) != null) {
254
+ if (maybeEnd && ch == "/") {
255
+ state.tokenize = tokenBase;
256
+ break;
257
+ }
258
+ maybeEnd = (ch == "*");
259
+ }
260
+ return ret("comment", "comment");
261
+ }
262
+
263
+ function tokenSGMLComment(stream, state) {
264
+ var dashes = 0, ch;
265
+ while ((ch = stream.next()) != null) {
266
+ if (dashes >= 2 && ch == ">") {
267
+ state.tokenize = tokenBase;
268
+ break;
269
+ }
270
+ dashes = (ch == "-") ? dashes + 1 : 0;
271
+ }
272
+ return ret("comment", "comment");
273
+ }
274
+
275
+ function tokenString(quote, nonInclusive) {
276
+ return function(stream, state) {
277
+ var escaped = false, ch;
278
+ while ((ch = stream.next()) != null) {
279
+ if (ch == quote && !escaped)
280
+ break;
281
+ escaped = !escaped && ch == "\\";
282
+ }
283
+ if (!escaped) {
284
+ if (nonInclusive) stream.backUp(1);
285
+ state.tokenize = tokenBase;
286
+ }
287
+ return ret("string", "string");
288
+ };
289
+ }
290
+
291
+ function tokenParenthesized(stream, state) {
292
+ stream.next(); // Must be '('
293
+ if (!stream.match(/\s*[\"\']/, false))
294
+ state.tokenize = tokenString(")", true);
295
+ else
296
+ state.tokenize = tokenBase;
297
+ return ret(null, "(");
298
+ }
299
+
300
+ return {
301
+ startState: function(base) {
302
+ return {tokenize: tokenBase,
303
+ baseIndent: base || 0,
304
+ stack: []};
305
+ },
306
+
307
+ token: function(stream, state) {
308
+
309
+ // Use these terms when applicable (see http://www.xanthir.com/blog/b4E50)
310
+ //
311
+ // rule** or **ruleset:
312
+ // A selector + braces combo, or an at-rule.
313
+ //
314
+ // declaration block:
315
+ // A sequence of declarations.
316
+ //
317
+ // declaration:
318
+ // A property + colon + value combo.
319
+ //
320
+ // property value:
321
+ // The entire value of a property.
322
+ //
323
+ // component value:
324
+ // A single piece of a property value. Like the 5px in
325
+ // text-shadow: 0 0 5px blue;. Can also refer to things that are
326
+ // multiple terms, like the 1-4 terms that make up the background-size
327
+ // portion of the background shorthand.
328
+ //
329
+ // term:
330
+ // The basic unit of author-facing CSS, like a single number (5),
331
+ // dimension (5px), string ("foo"), or function. Officially defined
332
+ // by the CSS 2.1 grammar (look for the 'term' production)
333
+ //
334
+ //
335
+ // simple selector:
336
+ // A single atomic selector, like a type selector, an attr selector, a
337
+ // class selector, etc.
338
+ //
339
+ // compound selector:
340
+ // One or more simple selectors without a combinator. div.example is
341
+ // compound, div > .example is not.
342
+ //
343
+ // complex selector:
344
+ // One or more compound selectors chained with combinators.
345
+ //
346
+ // combinator:
347
+ // The parts of selectors that express relationships. There are four
348
+ // currently - the space (descendant combinator), the greater-than
349
+ // bracket (child combinator), the plus sign (next sibling combinator),
350
+ // and the tilda (following sibling combinator).
351
+ //
352
+ // sequence of selectors:
353
+ // One or more of the named type of selector chained with commas.
354
+
355
+ if (state.tokenize == tokenBase && stream.eatSpace()) return null;
356
+ var style = state.tokenize(stream, state);
357
+
358
+ // Changing style returned based on context
359
+ var context = state.stack[state.stack.length-1];
360
+ if (style == "property") {
361
+ if (context == "propertyValue"){
362
+ if (valueKeywords[stream.current()]) {
363
+ style = "string-2";
364
+ } else if (colorKeywords[stream.current()]) {
365
+ style = "keyword";
366
+ } else {
367
+ style = "variable-2";
368
+ }
369
+ } else if (context == "rule") {
370
+ if (!propertyKeywords[stream.current()]) {
371
+ style += " error";
372
+ }
373
+ } else if (!context || context == "@media{") {
374
+ style = "tag";
375
+ } else if (context == "@media") {
376
+ if (atMediaTypes[stream.current()]) {
377
+ style = "attribute"; // Known attribute
378
+ } else if (/^(only|not)$/i.test(stream.current())) {
379
+ style = "keyword";
380
+ } else if (stream.current().toLowerCase() == "and") {
381
+ style = "error"; // "and" is only allowed in @mediaType
382
+ } else if (atMediaFeatures[stream.current()]) {
383
+ style = "error"; // Known property, should be in @mediaType(
384
+ } else {
385
+ // Unknown, expecting keyword or attribute, assuming attribute
386
+ style = "attribute error";
387
+ }
388
+ } else if (context == "@mediaType") {
389
+ if (atMediaTypes[stream.current()]) {
390
+ style = "attribute";
391
+ } else if (stream.current().toLowerCase() == "and") {
392
+ style = "operator";
393
+ } else if (/^(only|not)$/i.test(stream.current())) {
394
+ style = "error"; // Only allowed in @media
395
+ } else if (atMediaFeatures[stream.current()]) {
396
+ style = "error"; // Known property, should be in parentheses
397
+ } else {
398
+ // Unknown attribute or property, but expecting property (preceded
399
+ // by "and"). Should be in parentheses
400
+ style = "error";
401
+ }
402
+ } else if (context == "@mediaType(") {
403
+ if (propertyKeywords[stream.current()]) {
404
+ // do nothing, remains "property"
405
+ } else if (atMediaTypes[stream.current()]) {
406
+ style = "error"; // Known property, should be in parentheses
407
+ } else if (stream.current().toLowerCase() == "and") {
408
+ style = "operator";
409
+ } else if (/^(only|not)$/i.test(stream.current())) {
410
+ style = "error"; // Only allowed in @media
411
+ } else {
412
+ style += " error";
413
+ }
414
+ } else {
415
+ style = "error";
416
+ }
417
+ } else if (style == "atom") {
418
+ if(!context || context == "@media{") {
419
+ style = "builtin";
420
+ } else if (context == "propertyValue") {
421
+ if (!/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) {
422
+ style += " error";
423
+ }
424
+ } else {
425
+ style = "error";
426
+ }
427
+ } else if (context == "@media" && type == "{") {
428
+ style = "error";
429
+ }
430
+
431
+ // Push/pop context stack
432
+ if (type == "{") {
433
+ if (context == "@media" || context == "@mediaType") {
434
+ state.stack.pop();
435
+ state.stack[state.stack.length-1] = "@media{";
436
+ }
437
+ else state.stack.push("rule");
438
+ }
439
+ else if (type == "}") {
440
+ state.stack.pop();
441
+ if (context == "propertyValue") state.stack.pop();
442
+ }
443
+ else if (type == "@media") state.stack.push("@media");
444
+ else if (context == "@media" && /\b(keyword|attribute)\b/.test(style))
445
+ state.stack.push("@mediaType");
446
+ else if (context == "@mediaType" && stream.current() == ",") state.stack.pop();
447
+ else if (context == "@mediaType" && type == "(") state.stack.push("@mediaType(");
448
+ else if (context == "@mediaType(" && type == ")") state.stack.pop();
449
+ else if (context == "rule" && type == ":") state.stack.push("propertyValue");
450
+ else if (context == "propertyValue" && type == ";") state.stack.pop();
451
+ return style;
452
+ },
453
+
454
+ indent: function(state, textAfter) {
455
+ var n = state.stack.length;
456
+ if (/^\}/.test(textAfter))
457
+ n -= state.stack[state.stack.length-1] == "propertyValue" ? 2 : 1;
458
+ return state.baseIndent + n * indentUnit;
459
+ },
460
+
461
+ electricChars: "}"
462
+ };
463
+ });
464
+
465
+ CodeMirror.defineMIME("text/css", "css");
assets/mode/htmlmixed.js ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.defineMode("htmlmixed", function(config) {
2
+ var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true});
3
+ var jsMode = CodeMirror.getMode(config, "javascript");
4
+ var cssMode = CodeMirror.getMode(config, "css");
5
+
6
+ function html(stream, state) {
7
+ var style = htmlMode.token(stream, state.htmlState);
8
+ if (/(?:^|\s)tag(?:\s|$)/.test(style) && stream.current() == ">" && state.htmlState.context) {
9
+ if (/^script$/i.test(state.htmlState.context.tagName)) {
10
+ state.token = javascript;
11
+ state.localState = jsMode.startState(htmlMode.indent(state.htmlState, ""));
12
+ }
13
+ else if (/^style$/i.test(state.htmlState.context.tagName)) {
14
+ state.token = css;
15
+ state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
16
+ }
17
+ }
18
+ return style;
19
+ }
20
+ function maybeBackup(stream, pat, style) {
21
+ var cur = stream.current();
22
+ var close = cur.search(pat), m;
23
+ if (close > -1) stream.backUp(cur.length - close);
24
+ else if (m = cur.match(/<\/?$/)) {
25
+ stream.backUp(cur.length);
26
+ if (!stream.match(pat, false)) stream.match(cur[0]);
27
+ }
28
+ return style;
29
+ }
30
+ function javascript(stream, state) {
31
+ if (stream.match(/^<\/\s*script\s*>/i, false)) {
32
+ state.token = html;
33
+ state.localState = null;
34
+ return html(stream, state);
35
+ }
36
+ return maybeBackup(stream, /<\/\s*script\s*>/,
37
+ jsMode.token(stream, state.localState));
38
+ }
39
+ function css(stream, state) {
40
+ if (stream.match(/^<\/\s*style\s*>/i, false)) {
41
+ state.token = html;
42
+ state.localState = null;
43
+ return html(stream, state);
44
+ }
45
+ return maybeBackup(stream, /<\/\s*style\s*>/,
46
+ cssMode.token(stream, state.localState));
47
+ }
48
+
49
+ return {
50
+ startState: function() {
51
+ var state = htmlMode.startState();
52
+ return {token: html, localState: null, mode: "html", htmlState: state};
53
+ },
54
+
55
+ copyState: function(state) {
56
+ if (state.localState)
57
+ var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState);
58
+ return {token: state.token, localState: local, mode: state.mode,
59
+ htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
60
+ },
61
+
62
+ token: function(stream, state) {
63
+ return state.token(stream, state);
64
+ },
65
+
66
+ indent: function(state, textAfter) {
67
+ if (state.token == html || /^\s*<\//.test(textAfter))
68
+ return htmlMode.indent(state.htmlState, textAfter);
69
+ else if (state.token == javascript)
70
+ return jsMode.indent(state.localState, textAfter);
71
+ else
72
+ return cssMode.indent(state.localState, textAfter);
73
+ },
74
+
75
+ electricChars: "/{}:",
76
+
77
+ innerMode: function(state) {
78
+ var mode = state.token == html ? htmlMode : state.token == javascript ? jsMode : cssMode;
79
+ return {state: state.localState || state.htmlState, mode: mode};
80
+ }
81
+ };
82
+ }, "xml", "javascript", "css");
83
+
84
+ CodeMirror.defineMIME("text/html", "htmlmixed");
{js → assets/mode}/javascript.js RENAMED
@@ -1,6 +1,9 @@
 
 
1
  CodeMirror.defineMode("javascript", function(config, parserConfig) {
2
  var indentUnit = config.indentUnit;
3
  var jsonMode = parserConfig.json;
 
4
 
5
  // Tokenizer
6
 
@@ -8,7 +11,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
8
  function kw(type) {return {type: type, style: "keyword"};}
9
  var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
10
  var operator = kw("operator"), atom = {type: "atom", style: "atom"};
11
- return {
 
12
  "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
13
  "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
14
  "var": kw("var"), "const": kw("var"), "let": kw("var"),
@@ -17,6 +21,35 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
17
  "in": operator, "typeof": operator, "instanceof": operator,
18
  "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
19
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }();
21
 
22
  var isOperatorChar = /[+\-*&%=<>!?|]/;
@@ -66,7 +99,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
66
  stream.skipToEnd();
67
  return ret("comment", "comment");
68
  }
69
- else if (state.reAllowed) {
 
70
  nextUntilUnescaped(stream, "/");
71
  stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
72
  return ret("regexp", "string-2");
@@ -87,7 +121,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
87
  else {
88
  stream.eatWhile(/[\w\$_]/);
89
  var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
90
- return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
91
  ret("variable", "variable", word);
92
  }
93
  }
@@ -175,8 +209,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
175
 
176
  var defaultVars = {name: "this", next: {name: "arguments"}};
177
  function pushcontext() {
178
- if (!cx.state.context) cx.state.localVars = defaultVars;
179
  cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
 
180
  }
181
  function popcontext() {
182
  cx.state.localVars = cx.state.context.vars;
@@ -275,21 +309,32 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
275
  if (type == "}") return cont();
276
  return pass(statement, block);
277
  }
 
 
 
 
 
 
 
 
278
  function vardef1(type, value) {
279
- if (type == "variable"){register(value); return cont(vardef2);}
280
- return cont();
 
 
 
281
  }
282
  function vardef2(type, value) {
283
  if (value == "=") return cont(expression, vardef2);
284
  if (type == ",") return cont(vardef1);
285
  }
286
  function forspec1(type) {
287
- if (type == "var") return cont(vardef1, forspec2);
288
- if (type == ";") return pass(forspec2);
289
  if (type == "variable") return cont(formaybein);
290
- return pass(forspec2);
291
  }
292
- function formaybein(type, value) {
293
  if (value == "in") return cont(expression);
294
  return cont(maybeoperator, forspec2);
295
  }
@@ -306,7 +351,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
306
  if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
307
  }
308
  function funarg(type, value) {
309
- if (type == "variable") {register(value); return cont();}
310
  }
311
 
312
  // Interface
@@ -315,8 +360,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
315
  startState: function(basecolumn) {
316
  return {
317
  tokenize: jsTokenBase,
318
- reAllowed: true,
319
- kwAllowed: true,
320
  cc: [],
321
  lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
322
  localVars: parserConfig.localVars,
@@ -334,28 +378,34 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
334
  if (stream.eatSpace()) return null;
335
  var style = state.tokenize(stream, state);
336
  if (type == "comment") return style;
337
- state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/));
338
- state.kwAllowed = type != '.';
339
  return parseJS(state, style, type, content, stream);
340
  },
341
 
342
  indent: function(state, textAfter) {
 
343
  if (state.tokenize != jsTokenBase) return 0;
344
  var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
345
  if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
346
  var type = lexical.type, closing = firstChar == type;
347
- if (type == "vardef") return lexical.indented + 4;
348
  else if (type == "form" && firstChar == "{") return lexical.indented;
349
- else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
 
 
350
  else if (lexical.info == "switch" && !closing)
351
  return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
352
  else if (lexical.align) return lexical.column + (closing ? 0 : 1);
353
  else return lexical.indented + (closing ? 0 : indentUnit);
354
  },
355
 
356
- electricChars: ":{}"
 
 
357
  };
358
  });
359
 
360
  CodeMirror.defineMIME("text/javascript", "javascript");
361
  CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
 
 
1
+ // TODO actually recognize syntax of TypeScript constructs
2
+
3
  CodeMirror.defineMode("javascript", function(config, parserConfig) {
4
  var indentUnit = config.indentUnit;
5
  var jsonMode = parserConfig.json;
6
+ var isTS = parserConfig.typescript;
7
 
8
  // Tokenizer
9
 
11
  function kw(type) {return {type: type, style: "keyword"};}
12
  var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
13
  var operator = kw("operator"), atom = {type: "atom", style: "atom"};
14
+
15
+ var jsKeywords = {
16
  "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
17
  "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
18
  "var": kw("var"), "const": kw("var"), "let": kw("var"),
21
  "in": operator, "typeof": operator, "instanceof": operator,
22
  "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
23
  };
24
+
25
+ // Extend the 'normal' keywords with the TypeScript language extensions
26
+ if (isTS) {
27
+ var type = {type: "variable", style: "variable-3"};
28
+ var tsKeywords = {
29
+ // object-like things
30
+ "interface": kw("interface"),
31
+ "class": kw("class"),
32
+ "extends": kw("extends"),
33
+ "constructor": kw("constructor"),
34
+
35
+ // scope modifiers
36
+ "public": kw("public"),
37
+ "private": kw("private"),
38
+ "protected": kw("protected"),
39
+ "static": kw("static"),
40
+
41
+ "super": kw("super"),
42
+
43
+ // types
44
+ "string": type, "number": type, "bool": type, "any": type
45
+ };
46
+
47
+ for (var attr in tsKeywords) {
48
+ jsKeywords[attr] = tsKeywords[attr];
49
+ }
50
+ }
51
+
52
+ return jsKeywords;
53
  }();
54
 
55
  var isOperatorChar = /[+\-*&%=<>!?|]/;
99
  stream.skipToEnd();
100
  return ret("comment", "comment");
101
  }
102
+ else if (state.lastType == "operator" || state.lastType == "keyword c" ||
103
+ /^[\[{}\(,;:]$/.test(state.lastType)) {
104
  nextUntilUnescaped(stream, "/");
105
  stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
106
  return ret("regexp", "string-2");
121
  else {
122
  stream.eatWhile(/[\w\$_]/);
123
  var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
124
+ return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
125
  ret("variable", "variable", word);
126
  }
127
  }
209
 
210
  var defaultVars = {name: "this", next: {name: "arguments"}};
211
  function pushcontext() {
 
212
  cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
213
+ cx.state.localVars = defaultVars;
214
  }
215
  function popcontext() {
216
  cx.state.localVars = cx.state.context.vars;
309
  if (type == "}") return cont();
310
  return pass(statement, block);
311
  }
312
+ function maybetype(type) {
313
+ if (type == ":") return cont(typedef);
314
+ return pass();
315
+ }
316
+ function typedef(type) {
317
+ if (type == "variable"){cx.marked = "variable-3"; return cont();}
318
+ return pass();
319
+ }
320
  function vardef1(type, value) {
321
+ if (type == "variable") {
322
+ register(value);
323
+ return isTS ? cont(maybetype, vardef2) : cont(vardef2);
324
+ }
325
+ return pass();
326
  }
327
  function vardef2(type, value) {
328
  if (value == "=") return cont(expression, vardef2);
329
  if (type == ",") return cont(vardef1);
330
  }
331
  function forspec1(type) {
332
+ if (type == "var") return cont(vardef1, expect(";"), forspec2);
333
+ if (type == ";") return cont(forspec2);
334
  if (type == "variable") return cont(formaybein);
335
+ return cont(forspec2);
336
  }
337
+ function formaybein(_type, value) {
338
  if (value == "in") return cont(expression);
339
  return cont(maybeoperator, forspec2);
340
  }
351
  if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
352
  }
353
  function funarg(type, value) {
354
+ if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();}
355
  }
356
 
357
  // Interface
360
  startState: function(basecolumn) {
361
  return {
362
  tokenize: jsTokenBase,
363
+ lastType: null,
 
364
  cc: [],
365
  lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
366
  localVars: parserConfig.localVars,
378
  if (stream.eatSpace()) return null;
379
  var style = state.tokenize(stream, state);
380
  if (type == "comment") return style;
381
+ state.lastType = type;
 
382
  return parseJS(state, style, type, content, stream);
383
  },
384
 
385
  indent: function(state, textAfter) {
386
+ if (state.tokenize == jsTokenComment) return CodeMirror.Pass;
387
  if (state.tokenize != jsTokenBase) return 0;
388
  var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
389
  if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
390
  var type = lexical.type, closing = firstChar == type;
391
+ if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0);
392
  else if (type == "form" && firstChar == "{") return lexical.indented;
393
+ else if (type == "form") return lexical.indented + indentUnit;
394
+ else if (type == "stat")
395
+ return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? indentUnit : 0);
396
  else if (lexical.info == "switch" && !closing)
397
  return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
398
  else if (lexical.align) return lexical.column + (closing ? 0 : 1);
399
  else return lexical.indented + (closing ? 0 : indentUnit);
400
  },
401
 
402
+ electricChars: ":{}",
403
+
404
+ jsonMode: jsonMode
405
  };
406
  });
407
 
408
  CodeMirror.defineMIME("text/javascript", "javascript");
409
  CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
410
+ CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
411
+ CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
assets/mode/php.js ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ function keywords(str) {
3
+ var obj = {}, words = str.split(" ");
4
+ for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
5
+ return obj;
6
+ }
7
+ function heredoc(delim) {
8
+ return function(stream, state) {
9
+ if (stream.match(delim)) state.tokenize = null;
10
+ else stream.skipToEnd();
11
+ return "string";
12
+ };
13
+ }
14
+ var phpConfig = {
15
+ name: "clike",
16
+ keywords: keywords("abstract and array as break case catch class clone const continue declare default " +
17
+ "do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final " +
18
+ "for foreach function global goto if implements interface instanceof namespace " +
19
+ "new or private protected public static switch throw trait try use var while xor " +
20
+ "die echo empty exit eval include include_once isset list require require_once return " +
21
+ "print unset __halt_compiler self static parent"),
22
+ blockKeywords: keywords("catch do else elseif for foreach if switch try while"),
23
+ atoms: keywords("true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__"),
24
+ builtin: keywords("func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport echo print global static exit array empty eval isset unset die include require include_once require_once"),
25
+ multiLineStrings: true,
26
+ hooks: {
27
+ "$": function(stream) {
28
+ stream.eatWhile(/[\w\$_]/);
29
+ return "variable-2";
30
+ },
31
+ "<": function(stream, state) {
32
+ if (stream.match(/<</)) {
33
+ stream.eatWhile(/[\w\.]/);
34
+ state.tokenize = heredoc(stream.current().slice(3));
35
+ return state.tokenize(stream, state);
36
+ }
37
+ return false;
38
+ },
39
+ "#": function(stream) {
40
+ while (!stream.eol() && !stream.match("?>", false)) stream.next();
41
+ return "comment";
42
+ },
43
+ "/": function(stream) {
44
+ if (stream.eat("/")) {
45
+ while (!stream.eol() && !stream.match("?>", false)) stream.next();
46
+ return "comment";
47
+ }
48
+ return false;
49
+ }
50
+ }
51
+ };
52
+
53
+ CodeMirror.defineMode("php", function(config, parserConfig) {
54
+ var htmlMode = CodeMirror.getMode(config, "text/html");
55
+ var phpMode = CodeMirror.getMode(config, phpConfig);
56
+
57
+ function dispatch(stream, state) {
58
+ var isPHP = state.curMode == phpMode;
59
+ if (stream.sol() && state.pending != '"') state.pending = null;
60
+ if (!isPHP) {
61
+ if (stream.match(/^<\?\w*/)) {
62
+ state.curMode = phpMode;
63
+ state.curState = state.php;
64
+ return "meta";
65
+ }
66
+ if (state.pending == '"') {
67
+ while (!stream.eol() && stream.next() != '"') {}
68
+ var style = "string";
69
+ } else if (state.pending && stream.pos < state.pending.end) {
70
+ stream.pos = state.pending.end;
71
+ var style = state.pending.style;
72
+ } else {
73
+ var style = htmlMode.token(stream, state.curState);
74
+ }
75
+ state.pending = null;
76
+ var cur = stream.current(), openPHP = cur.search(/<\?/);
77
+ if (openPHP != -1) {
78
+ if (style == "string" && /\"$/.test(cur) && !/\?>/.test(cur)) state.pending = '"';
79
+ else state.pending = {end: stream.pos, style: style};
80
+ stream.backUp(cur.length - openPHP);
81
+ }
82
+ return style;
83
+ } else if (isPHP && state.php.tokenize == null && stream.match("?>")) {
84
+ state.curMode = htmlMode;
85
+ state.curState = state.html;
86
+ return "meta";
87
+ } else {
88
+ return phpMode.token(stream, state.curState);
89
+ }
90
+ }
91
+
92
+ return {
93
+ startState: function() {
94
+ var html = CodeMirror.startState(htmlMode), php = CodeMirror.startState(phpMode);
95
+ return {html: html,
96
+ php: php,
97
+ curMode: parserConfig.startOpen ? phpMode : htmlMode,
98
+ curState: parserConfig.startOpen ? php : html,
99
+ pending: null};
100
+ },
101
+
102
+ copyState: function(state) {
103
+ var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html),
104
+ php = state.php, phpNew = CodeMirror.copyState(phpMode, php), cur;
105
+ if (state.curMode == htmlMode) cur = htmlNew;
106
+ else cur = phpNew;
107
+ return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur,
108
+ pending: state.pending};
109
+ },
110
+
111
+ token: dispatch,
112
+
113
+ indent: function(state, textAfter) {
114
+ if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) ||
115
+ (state.curMode == phpMode && /^\?>/.test(textAfter)))
116
+ return htmlMode.indent(state.html, textAfter);
117
+ return state.curMode.indent(state.curState, textAfter);
118
+ },
119
+
120
+ electricChars: "/{}:",
121
+
122
+ innerMode: function(state) { return {state: state.curState, mode: state.curMode}; }
123
+ };
124
+ }, "htmlmixed");
125
+
126
+ CodeMirror.defineMIME("application/x-httpd-php", "php");
127
+ CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true});
128
+ CodeMirror.defineMIME("text/x-php", phpConfig);
129
+ })();
{js → assets/mode}/xml.js RENAMED
@@ -70,11 +70,12 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
70
  return "meta";
71
  }
72
  else {
73
- type = stream.eat("/") ? "closeTag" : "openTag";
74
- stream.eatSpace();
75
  tagName = "";
76
  var c;
77
  while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
 
 
78
  state.tokenize = inTag;
79
  return "tag";
80
  }
@@ -114,7 +115,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
114
  return state.tokenize(stream, state);
115
  }
116
  else {
117
- stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/);
118
  return "word";
119
  }
120
  }
@@ -210,14 +211,16 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
210
  }
211
  function endtag(startOfLine) {
212
  return function(type) {
 
 
213
  if (type == "selfcloseTag" ||
214
- (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) {
215
- maybePopContext(curState.tagName.toLowerCase());
216
  return cont();
217
  }
218
  if (type == "endTag") {
219
- maybePopContext(curState.tagName.toLowerCase());
220
- pushContext(curState.tagName, startOfLine);
221
  return cont();
222
  }
223
  return cont();
@@ -255,6 +258,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
255
  function attribute(type) {
256
  if (type == "equals") return cont(attvalue, attributes);
257
  if (!Kludges.allowMissing) setStyle = "error";
 
258
  return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
259
  }
260
  function attvalue(type) {
@@ -308,15 +312,9 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
308
  else return 0;
309
  },
310
 
311
- compareStates: function(a, b) {
312
- if (a.indented != b.indented || a.tokenize != b.tokenize) return false;
313
- for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {
314
- if (!ca || !cb) return ca == cb;
315
- if (ca.tagName != cb.tagName || ca.indent != cb.indent) return false;
316
- }
317
- },
318
 
319
- electricChars: "/"
320
  };
321
  });
322
 
70
  return "meta";
71
  }
72
  else {
73
+ var isClose = stream.eat("/");
 
74
  tagName = "";
75
  var c;
76
  while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
77
+ if (!tagName) return "error";
78
+ type = isClose ? "closeTag" : "openTag";
79
  state.tokenize = inTag;
80
  return "tag";
81
  }
115
  return state.tokenize(stream, state);
116
  }
117
  else {
118
+ stream.eatWhile(/[^\s\u00a0=<>\"\']/);
119
  return "word";
120
  }
121
  }
211
  }
212
  function endtag(startOfLine) {
213
  return function(type) {
214
+ var tagName = curState.tagName;
215
+ curState.tagName = null;
216
  if (type == "selfcloseTag" ||
217
+ (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase()))) {
218
+ maybePopContext(tagName.toLowerCase());
219
  return cont();
220
  }
221
  if (type == "endTag") {
222
+ maybePopContext(tagName.toLowerCase());
223
+ pushContext(tagName, startOfLine);
224
  return cont();
225
  }
226
  return cont();
258
  function attribute(type) {
259
  if (type == "equals") return cont(attvalue, attributes);
260
  if (!Kludges.allowMissing) setStyle = "error";
261
+ else if (type == "word") setStyle = "attribute";
262
  return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
263
  }
264
  function attvalue(type) {
312
  else return 0;
313
  },
314
 
315
+ electricChars: "/",
 
 
 
 
 
 
316
 
317
+ configuration: parserConfig.htmlMode ? "html" : "xml"
318
  };
319
  });
320
 
css/style.css → assets/table.css RENAMED
@@ -1,9 +1,3 @@
1
- #icon-snippets.icon32 {
2
- background: url('../images/icon32.png') no-repeat scroll transparent;
3
- }
4
-
5
- /* Snippets > Manage Snippets */
6
-
7
  .snippets .inactive a {
8
  color: #557799;
9
  }
@@ -14,7 +8,7 @@
14
  color: #d54e21;
15
  }
16
 
17
- .snippets .delete a{
18
  color: #21759b;
19
  }
20
 
@@ -38,23 +32,6 @@
38
  white-space: nowrap; /* prevents wrapping of snippet title */
39
  }
40
 
41
- #wpbody-content .snippets .manage-column.column-id.sortable{
42
  min-width: 44px;
43
- }
44
-
45
- /* Snippets > Add New */
46
-
47
- .CodeMirror {
48
- border: 1px solid #ccc;
49
- -o-border-radius: 3px;
50
- -moz-border-radius: 3px;
51
- -webkit-border-radius: 3px;
52
- border-radius: 3px;
53
- }
54
-
55
- .CodeMirror-scroll {
56
- height: auto;
57
- overflow-y: hidden;
58
- overflow-x: auto;
59
- min-height: 242px;
60
  }
 
 
 
 
 
 
1
  .snippets .inactive a {
2
  color: #557799;
3
  }
8
  color: #d54e21;
9
  }
10
 
11
+ .snippets .delete a {
12
  color: #21759b;
13
  }
14
 
32
  white-space: nowrap; /* prevents wrapping of snippet title */
33
  }
34
 
35
+ #wpbody-content .snippets .manage-column.column-id.sortable {
36
  min-width: 44px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
{css → assets/util}/dialog.css RENAMED
@@ -1,18 +1,23 @@
1
  .CodeMirror-dialog {
2
- position: relative;
3
- }
4
-
5
- .CodeMirror-dialog > div {
6
  position: absolute;
7
- top: 0; left: 0; right: 0;
8
  background: white;
9
- border-bottom: 1px solid #eee;
10
  z-index: 15;
11
  padding: .1em .8em;
12
  overflow: hidden;
13
  color: #333;
14
  }
15
 
 
 
 
 
 
 
 
 
 
 
16
  .CodeMirror-dialog input {
17
  border: none;
18
  outline: none;
@@ -24,4 +29,4 @@
24
 
25
  .CodeMirror-dialog button {
26
  font-size: 70%;
27
- }
1
  .CodeMirror-dialog {
 
 
 
 
2
  position: absolute;
3
+ left: 0; right: 0;
4
  background: white;
 
5
  z-index: 15;
6
  padding: .1em .8em;
7
  overflow: hidden;
8
  color: #333;
9
  }
10
 
11
+ .CodeMirror-dialog-top {
12
+ border-bottom: 1px solid #eee;
13
+ top: 0;
14
+ }
15
+
16
+ .CodeMirror-dialog-bottom {
17
+ border-top: 1px solid #eee;
18
+ bottom: 0;
19
+ }
20
+
21
  .CodeMirror-dialog input {
22
  border: none;
23
  outline: none;
29
 
30
  .CodeMirror-dialog button {
31
  font-size: 70%;
32
+ }
{js → assets/util}/dialog.js RENAMED
@@ -1,16 +1,21 @@
1
  // Open simple dialogs on top of an editor. Relies on dialog.css.
2
 
3
  (function() {
4
- function dialogDiv(cm, template) {
5
  var wrap = cm.getWrapperElement();
6
- var dialog = wrap.insertBefore(document.createElement("div"), wrap.firstChild);
7
- dialog.className = "CodeMirror-dialog";
8
- dialog.innerHTML = '<div>' + template + '</div>';
 
 
 
 
 
9
  return dialog;
10
  }
11
 
12
- CodeMirror.defineExtension("openDialog", function(template, callback) {
13
- var dialog = dialogDiv(this, template);
14
  var closed = false, me = this;
15
  function close() {
16
  if (closed) return;
@@ -19,7 +24,7 @@
19
  }
20
  var inp = dialog.getElementsByTagName("input")[0], button;
21
  if (inp) {
22
- CodeMirror.connect(inp, "keydown", function(e) {
23
  if (e.keyCode == 13 || e.keyCode == 27) {
24
  CodeMirror.e_stop(e);
25
  close();
@@ -28,20 +33,20 @@
28
  }
29
  });
30
  inp.focus();
31
- CodeMirror.connect(inp, "blur", close);
32
  } else if (button = dialog.getElementsByTagName("button")[0]) {
33
- CodeMirror.connect(button, "click", function() {
34
  close();
35
  me.focus();
36
  });
37
  button.focus();
38
- CodeMirror.connect(button, "blur", close);
39
  }
40
  return close;
41
  });
42
 
43
- CodeMirror.defineExtension("openConfirm", function(template, callbacks) {
44
- var dialog = dialogDiv(this, template);
45
  var buttons = dialog.getElementsByTagName("button");
46
  var closed = false, me = this, blurring = 1;
47
  function close() {
@@ -54,17 +59,17 @@
54
  for (var i = 0; i < buttons.length; ++i) {
55
  var b = buttons[i];
56
  (function(callback) {
57
- CodeMirror.connect(b, "click", function(e) {
58
  CodeMirror.e_preventDefault(e);
59
  close();
60
  if (callback) callback(me);
61
  });
62
  })(callbacks[i]);
63
- CodeMirror.connect(b, "blur", function() {
64
  --blurring;
65
  setTimeout(function() { if (blurring <= 0) close(); }, 200);
66
  });
67
- CodeMirror.connect(b, "focus", function() { ++blurring; });
68
  }
69
  });
70
- })();
1
  // Open simple dialogs on top of an editor. Relies on dialog.css.
2
 
3
  (function() {
4
+ function dialogDiv(cm, template, bottom) {
5
  var wrap = cm.getWrapperElement();
6
+ var dialog;
7
+ dialog = wrap.appendChild(document.createElement("div"));
8
+ if (bottom) {
9
+ dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
10
+ } else {
11
+ dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
12
+ }
13
+ dialog.innerHTML = template;
14
  return dialog;
15
  }
16
 
17
+ CodeMirror.defineExtension("openDialog", function(template, callback, options) {
18
+ var dialog = dialogDiv(this, template, options && options.bottom);
19
  var closed = false, me = this;
20
  function close() {
21
  if (closed) return;
24
  }
25
  var inp = dialog.getElementsByTagName("input")[0], button;
26
  if (inp) {
27
+ CodeMirror.on(inp, "keydown", function(e) {
28
  if (e.keyCode == 13 || e.keyCode == 27) {
29
  CodeMirror.e_stop(e);
30
  close();
33
  }
34
  });
35
  inp.focus();
36
+ CodeMirror.on(inp, "blur", close);
37
  } else if (button = dialog.getElementsByTagName("button")[0]) {
38
+ CodeMirror.on(button, "click", function() {
39
  close();
40
  me.focus();
41
  });
42
  button.focus();
43
+ CodeMirror.on(button, "blur", close);
44
  }
45
  return close;
46
  });
47
 
48
+ CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
49
+ var dialog = dialogDiv(this, template, options && options.bottom);
50
  var buttons = dialog.getElementsByTagName("button");
51
  var closed = false, me = this, blurring = 1;
52
  function close() {
59
  for (var i = 0; i < buttons.length; ++i) {
60
  var b = buttons[i];
61
  (function(callback) {
62
+ CodeMirror.on(b, "click", function(e) {
63
  CodeMirror.e_preventDefault(e);
64
  close();
65
  if (callback) callback(me);
66
  });
67
  })(callbacks[i]);
68
+ CodeMirror.on(b, "blur", function() {
69
  --blurring;
70
  setTimeout(function() { if (blurring <= 0) close(); }, 200);
71
  });
72
+ CodeMirror.on(b, "focus", function() { ++blurring; });
73
  }
74
  });
75
+ })();
assets/util/matchbrackets.js ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
3
+ function findMatchingBracket(cm) {
4
+ var cur = cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1;
5
+ var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
6
+ if (!match) return null;
7
+ var forward = match.charAt(1) == ">", d = forward ? 1 : -1;
8
+ var style = cm.getTokenAt({line: cur.line, ch: pos + 1}).type;
9
+
10
+ var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
11
+ function scan(line, lineNo, start) {
12
+ if (!line.text) return;
13
+ var pos = forward ? 0 : line.text.length - 1, end = forward ? line.text.length : -1;
14
+ if (start != null) pos = start + d;
15
+ for (; pos != end; pos += d) {
16
+ var ch = line.text.charAt(pos);
17
+ if (re.test(ch) && cm.getTokenAt({line: lineNo, ch: pos + 1}).type == style) {
18
+ var match = matching[ch];
19
+ if (match.charAt(1) == ">" == forward) stack.push(ch);
20
+ else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
21
+ else if (!stack.length) return {pos: pos, match: true};
22
+ }
23
+ }
24
+ }
25
+ for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) {
26
+ if (i == cur.line) found = scan(line, i, pos);
27
+ else found = scan(cm.getLineHandle(i), i);
28
+ if (found) break;
29
+ }
30
+ return {from: {line: cur.line, ch: pos}, to: found && {line: i, ch: found.pos}, match: found && found.match};
31
+ }
32
+
33
+ function matchBrackets(cm, autoclear) {
34
+ var found = findMatchingBracket(cm);
35
+ if (!found) return;
36
+ var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
37
+ var one = cm.markText(found.from, {line: found.from.line, ch: found.from.ch + 1},
38
+ {className: style});
39
+ var two = found.to && cm.markText(found.to, {line: found.to.line, ch: found.to.ch + 1},
40
+ {className: style});
41
+ var clear = function() {
42
+ cm.operation(function() { one.clear(); two && two.clear(); });
43
+ };
44
+ if (autoclear) setTimeout(clear, 800);
45
+ else return clear;
46
+ }
47
+
48
+ var currentlyHighlighted = null;
49
+ function doMatchBrackets(cm) {
50
+ cm.operation(function() {
51
+ if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
52
+ if (!cm.somethingSelected()) currentlyHighlighted = matchBrackets(cm, false);
53
+ });
54
+ }
55
+
56
+ CodeMirror.defineOption("matchBrackets", false, function(cm, val) {
57
+ if (val) cm.on("cursorActivity", doMatchBrackets);
58
+ else cm.off("cursorActivity", doMatchBrackets);
59
+ });
60
+
61
+ CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
62
+ CodeMirror.defineExtension("findMatchingBracket", function(){return findMatchingBracket(this);});
63
+ })();
{js → assets/util}/search.js RENAMED
@@ -41,7 +41,8 @@
41
  state.query = parseQuery(query);
42
  if (cm.lineCount() < 2000) { // This is too expensive on big documents.
43
  for (var cursor = getSearchCursor(cm, state.query); cursor.findNext();)
44
- state.marked.push(cm.markText(cursor.from(), cursor.to(), "CodeMirror-searching"));
 
45
  }
46
  state.posFrom = state.posTo = cm.getCursor();
47
  findNext(cm, rev);
@@ -76,14 +77,14 @@
76
  query = parseQuery(query);
77
  dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
78
  if (all) {
79
- cm.compoundChange(function() { cm.operation(function() {
80
  for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
81
  if (typeof query != "string") {
82
  var match = cm.getRange(cursor.from(), cursor.to()).match(query);
83
- cursor.replace(text.replace(/\$(\d)/, function(w, i) {return match[i];}));
84
  } else cursor.replace(text);
85
  }
86
- });});
87
  } else {
88
  clearSearch(cm);
89
  var cursor = getSearchCursor(cm, query, cm.getCursor());
@@ -100,7 +101,7 @@
100
  }
101
  function doReplace(match) {
102
  cursor.replace(typeof query == "string" ? text :
103
- text.replace(/\$(\d)/, function(w, i) {return match[i];}));
104
  advance();
105
  }
106
  advance();
41
  state.query = parseQuery(query);
42
  if (cm.lineCount() < 2000) { // This is too expensive on big documents.
43
  for (var cursor = getSearchCursor(cm, state.query); cursor.findNext();)
44
+ state.marked.push(cm.markText(cursor.from(), cursor.to(),
45
+ {className: "CodeMirror-searching"}));
46
  }
47
  state.posFrom = state.posTo = cm.getCursor();
48
  findNext(cm, rev);
77
  query = parseQuery(query);
78
  dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
79
  if (all) {
80
+ cm.operation(function() {
81
  for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
82
  if (typeof query != "string") {
83
  var match = cm.getRange(cursor.from(), cursor.to()).match(query);
84
+ cursor.replace(text.replace(/\$(\d)/, function(_, i) {return match[i];}));
85
  } else cursor.replace(text);
86
  }
87
+ });
88
  } else {
89
  clearSearch(cm);
90
  var cursor = getSearchCursor(cm, query, cm.getCursor());
101
  }
102
  function doReplace(match) {
103
  cursor.replace(typeof query == "string" ? text :
104
+ text.replace(/\$(\d)/, function(_, i) {return match[i];}));
105
  advance();
106
  }
107
  advance();
{js → assets/util}/searchcursor.js RENAMED
@@ -17,14 +17,14 @@
17
  query.lastIndex = 0;
18
  var line = cm.getLine(pos.line).slice(0, pos.ch), match = query.exec(line), start = 0;
19
  while (match) {
20
- start += match.index;
21
- line = line.slice(match.index);
22
  query.lastIndex = 0;
23
  var newmatch = query.exec(line);
24
  if (newmatch) match = newmatch;
25
  else break;
26
- start++;
27
  }
 
28
  } else {
29
  query.lastIndex = pos.ch;
30
  var line = cm.getLine(pos.line), match = query.exec(line),
17
  query.lastIndex = 0;
18
  var line = cm.getLine(pos.line).slice(0, pos.ch), match = query.exec(line), start = 0;
19
  while (match) {
20
+ start += match.index + 1;
21
+ line = line.slice(start);
22
  query.lastIndex = 0;
23
  var newmatch = query.exec(line);
24
  if (newmatch) match = newmatch;
25
  else break;
 
26
  }
27
+ start--;
28
  } else {
29
  query.lastIndex = pos.ch;
30
  var line = cm.getLine(pos.line), match = query.exec(line),
code-snippets.php CHANGED
@@ -1,1114 +1,1340 @@
1
- <?php
2
-
3
- /**
4
- * Code Snippets - An easy, clean and simple way to add code snippets to your site.
5
- *
6
- * If you're interested in helping to develop Code Snippets, or perhaps contribute
7
- * to the localization, please see http://cs.bungeshea.com/dev.
8
- *
9
- * @package Code Snippets
10
- * @subpackage Main
11
- *
12
- *
13
- * Plugin Name: Code Snippets
14
- * Plugin URI: http://cs.bungeshea.com
15
- * Description: An easy, clean and simple way to add code snippets to your site. No need to edit to your theme's functions.php file again!
16
- * Author: Shea Bunge
17
- * Author URI: http://bungeshea.com
18
- * Version: 1.5
19
- * License: GPLv3 or later
20
- * Network: true
21
- * Text Domain: code-snippets
22
- * Domain Path: /languages/
23
- *
24
- *
25
- * Code Snippets - WordPress Plugin
26
- * Copyright (C) 2012 Shea Bunge
27
-
28
- * This program is free software: you can redistribute it and/or modify
29
- * it under the terms of the GNU General Public License as published by
30
- * the Free Software Foundation, either version 3 of the License, or
31
- * (at your option) any later version.
32
-
33
- * This program is distributed in the hope that it will be useful,
34
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
35
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36
- * GNU General Public License for more details.
37
-
38
- * You should have received a copy of the GNU General Public License
39
- * along with this program. If not, see <http://www.gnu.org/licenses>.
40
- */
41
-
42
- // Exit if accessed directly
43
- if ( ! defined( 'ABSPATH' ) ) exit;
44
-
45
- if( ! class_exists( 'Code_Snippets' ) ) :
46
-
47
- /**
48
- * The main class for our plugin
49
- * It all happens here, folks
50
- *
51
- * Please use the global variable $cs to access
52
- * the methods in this class. Anything you need
53
- * to access should be publicly available there
54
- *
55
- * @since Code Snippets 1.0
56
- * @access private
57
- */
58
- class Code_Snippets {
59
-
60
- /**
61
- * The base name for the snippets table in the database
62
- * This will later be prepended with the WordPress table prefix
63
- *
64
- * DO NOT EDIT THIS VARIABLE!
65
- * Instead, use the 'cs_table' filter
66
- *
67
- * @since Code Snippets 1.0
68
- * @access public
69
- */
70
- public $table = 'snippets';
71
-
72
- /**
73
- * The base name for the network snippets table in the database
74
- * This will later be prepended with the WordPress base table prefix
75
- *
76
- * DO NOT EDIT THIS VARIABLE!
77
- * Instead, use the 'cs_ms_table' filter
78
- *
79
- * @since Code Snippets 1.4
80
- * @access public
81
- */
82
- public $ms_table = 'ms_snippets';
83
-
84
- /**
85
- * The version number for this release of the plugin.
86
- * This will later be used for upgrades and enqueueing files
87
- *
88
- * This should be set to the 'Plugin Version' value,
89
- * as defined above
90
- *
91
- * @since Code Snippets 1.0
92
- * @access public
93
- */
94
- public $version = 1.5;
95
-
96
- /**
97
- * The base URLs for the admin pages
98
- *
99
- * DO NOT EDIT THESE VARIABLES!
100
- * Instead, use the 'cs_admin_manage', 'cs_admin_single'
101
- * and 'cs_admin_import' filters
102
- *
103
- * @since Code Snippets 1.0
104
- * @access public
105
- */
106
- public $admin_manage_url, $admin_single_url, $admin_import_url;
107
-
108
- /**
109
- * The hooks for the admin pages
110
- *
111
- * @since Code Snippets 1.0
112
- * @access public
113
- */
114
- public $admin_manage, $admin_single, $admin_import;
115
-
116
- /**
117
- * The main function for our class
118
- *
119
- * @since Code Snippets 1.0
120
- * @access public
121
- */
122
- public function Code_Snippets() {
123
- $this->__construct();
124
- }
125
-
126
- /**
127
- * The constructor function for our class
128
- *
129
- * @since Code Snippets 1.0
130
- * @access private
131
- */
132
- function __construct() {
133
- $this->setup(); // initialise the variables and run the hooks
134
- $this->create_table(); // create the snippet tables if they do not exist
135
- $this->upgrade(); // check if we need to change some stuff
136
- if( is_multisite() ) { // perform multisite-specific actions
137
- $this->create_table( true );
138
- $this->run_snippets( true );
139
- }
140
- $this->run_snippets(); // execute the snippets
141
- }
142
-
143
- /**
144
- * Initialise variables and add actions and filters
145
- *
146
- * @since Code Snippets 1.2
147
- * @access private
148
- */
149
- function setup() {
150
- global $wpdb;
151
- $this->file = __FILE__;
152
- $this->table = apply_filters( 'cs_table', $wpdb->prefix . $this->table );
153
- $this->ms_table = apply_filters( 'cs_ms_table', $wpdb->base_prefix . $this->ms_table );
154
- $this->current_version = get_option( 'cs_db_version', $this->version );
155
-
156
- $wpdb->snippets = $this->table;
157
- $wpdb->ms_snippets = $this->ms_table;
158
-
159
- $this->basename = plugin_basename( $this->file );
160
- $this->plugin_dir = plugin_dir_path( $this->file );
161
- $this->plugin_url = plugin_dir_url ( $this->file );
162
-
163
- $this->admin_manage_url = apply_filters( 'cs_manage_url', 'snippets' );
164
- $this->admin_single_url = apply_filters( 'cs_single_url', 'snippet' );
165
- $this->admin_import_url = apply_filters( 'cs_import_url', 'import-snippets' );
166
-
167
- if( ! get_option( 'cs_db_version' ) ) {
168
- // This is the first time the plugin has run
169
-
170
- $this->add_caps(); // register the capabilities ONCE ONLY
171
-
172
- if( is_multisite() ) {
173
- $this->add_caps( true ); // register the multisite capabilities ONCE ONLY
174
- }
175
- }
176
-
177
- add_action( 'admin_menu', array( $this, 'add_admin_menus' ) );
178
- add_action( 'network_admin_menu', array( $this, 'add_network_admin_menus' ) );
179
- add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );
180
- add_filter( 'plugin_action_links_' . $this->basename, array( $this, 'settings_link' ) );
181
- add_filter( 'plugin_row_meta', array( $this, 'plugin_meta' ), 10, 2 );
182
- add_filter( 'set-screen-option', array( $this, 'set_screen_option' ), 10, 3 );
183
- }
184
-
185
- /**
186
- * Create the snippet table if it does not already exist
187
- *
188
- * @since Code Snippets 1.2
189
- * @access private
190
- */
191
- function create_table( $network = false ) {
192
-
193
- $table = ( $network ? $this->ms_table : $this->table );
194
-
195
- global $wpdb;
196
-
197
- if( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) != $table ) {
198
- $sql = "CREATE TABLE $table (
199
- id MEDIUMINT NOT NULL AUTO_INCREMENT,
200
- name VARCHAR(64) NOT NULL,
201
- description TEXT,
202
- code TEXT NOT NULL,
203
- active TINYINT(1) NOT NULL DEFAULT 0,
204
- UNIQUE KEY id (id)
205
- );";
206
- require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
207
- dbDelta( $sql );
208
- add_option( 'cs_db_version', $this->version );
209
- }
210
- }
211
-
212
- /**
213
- * Preform upgrade tasks such as deleting and updating options
214
- *
215
- * @since Code Snippets 1.2
216
- * @access private
217
- */
218
- function upgrade() {
219
- if( $this->current_version < 1.5 ) {
220
- global $wpdb;
221
-
222
- // Let's alter the name column to accept up to 64 characters
223
- $wpdb->query( "ALTER TABLE $this->table CHANGE COLUMN name name VARCHAR(64) NOT NULL" );
224
-
225
- if( is_multisite() ) {
226
- // We must not forget the multisite table!
227
- $wpdb->query( "ALTER TABLE $this->ms_table CHANGE COLUMN name name VARCHAR(64) NOT NULL" );
228
- }
229
-
230
- // Add the custom capabilities that were introduced in version 1.5.
231
- $this->add_caps();
232
- }
233
-
234
- if( $this->current_version < 1.2 ) {
235
- // The 'Complete Uninstall' option was removed in version 1.2
236
- delete_option( 'cs_complete_uninstall' );
237
- }
238
-
239
- if( $this->current_version < $this->version ) {
240
- // Update the current version
241
- update_option( 'cs_db_version', $this->version );
242
- }
243
- }
244
-
245
- /**
246
- * Load up the localization file if we're using WordPress in a different language
247
- * Place it in this plugin's "languages" folder and name it "code-snippets-[value in wp-config].mo"
248
- *
249
- * If you wish to contribute a language file to be included in the Code Snippets package,
250
- * please see the project's development website at http://cs.bungeshea.com/dev
251
- *
252
- * @since Code Snippets 1.5
253
- * @access private
254
- */
255
- function load_textdomain() {
256
- load_plugin_textdomain( 'code-snippets', false, dirname( $this->basename ) . '/languages/' );
257
- }
258
-
259
- /**
260
- * Add the user capabilities
261
- *
262
- * @since Code Snippets 1.5
263
- * @access public
264
- *
265
- * @uses $this->setup_roles() To register the capabilities
266
- *
267
- * @param bool $multisite Add site-specific or multisite-specific capabilities?
268
- */
269
- public function add_caps( $multisite = false ) {
270
- if( $multisite && is_multisite() )
271
- $this->setup_ms_roles( true );
272
- else
273
- $this->setup_roles( true );
274
- }
275
-
276
- /**
277
- * Remove the user capabilities
278
- *
279
- * @since Code Snippets 1.5
280
- * @access public
281
- *
282
- * @uses $this->setup_roles() To register the capabilities
283
- *
284
- * @param bool $multisite Add site-specific or multisite-specific capabilities?
285
- */
286
- public function remove_caps( $multisite = false ) {
287
- if( $multisite && is_multisite() )
288
- $this->setup_ms_roles( false );
289
- else
290
- $this->setup_roles( false );
291
- }
292
-
293
- /**
294
- * Register the user roles and capabilities
295
- *
296
- * @since Code Snippets 1.5
297
- * @access private
298
- *
299
- * @param bool $install True to add the capabilities, false to remove
300
- */
301
- function setup_roles( $install = true ) {
302
-
303
- $this->caps = apply_filters( 'cs_caps', array(
304
- 'manage_snippets',
305
- 'install_snippets',
306
- 'edit_snippets'
307
- ) );
308
-
309
- $this->role = get_role( apply_filters( 'cs_role', 'administrator' ) );
310
-
311
- foreach( $this->caps as $cap ) {
312
- if( $install )
313
- $this->role->add_cap( $cap );
314
- else
315
- $this->role->remove_cap( $cap );
316
- }
317
- }
318
-
319
- /**
320
- * Register the multisite user roles and capabilities
321
- *
322
- * @since Code Snippets 1.5
323
- * @access private
324
- *
325
- * @param bool $install True to add the capabilities, false to remove
326
- */
327
- function setup_ms_roles( $install = true ) {
328
-
329
- if( ! is_multisite() ) return;
330
-
331
- $this->network_caps = apply_filters( 'cs_network_caps', array(
332
- 'manage_network_snippets',
333
- 'install_network_snippets',
334
- 'edit_network_snippets'
335
- ) );
336
-
337
- $supers = get_super_admins();
338
- foreach( $supers as $admin ) {
339
- $user = new WP_User( 0, $admin );
340
- foreach( $this->network_caps as $cap ) {
341
- if( $install )
342
- $user->add_cap( $cap );
343
- else
344
- $user->remove_cap( $cap );
345
- }
346
- }
347
- }
348
-
349
- /**
350
- * Add the dashboard admin menu and subpages
351
- *
352
- * @since Code Snippets 1.0
353
- * @access private
354
- */
355
- function add_admin_menus() {
356
- $this->admin_manage = add_menu_page(
357
- __('Snippets', 'code-snippets'),
358
- __('Snippets', 'code-snippets'),
359
- 'manage_snippets',
360
- $this->admin_manage_url,
361
- array( $this, 'display_admin_manage' ),
362
- $this->plugin_url . 'images/icon16.png',
363
- 67
364
- );
365
- add_submenu_page(
366
- $this->admin_manage_url,
367
- __('Snippets', 'code-snippets'),
368
- __('Manage Snippets', 'code-snippets'),
369
- 'manage_snippets',
370
- $this->admin_manage_url,
371
- array( $this, 'display_admin_manage')
372
- );
373
- $this->admin_single = add_submenu_page(
374
- $this->admin_manage_url,
375
- __('Add New Snippet', 'code-snippets'),
376
- __('Add New', 'code-snippets'),
377
- 'install_snippets',
378
- $this->admin_single_url,
379
- array( $this, 'display_admin_single' )
380
- );
381
- $this->admin_import = add_submenu_page(
382
- $this->admin_manage_url,
383
- __('Import Snippets', 'code-snippets'),
384
- __('Import', 'code-snippets'),
385
- 'install_snippets',
386
- $this->admin_import_url,
387
- array( $this, 'display_admin_import' )
388
- );
389
-
390
- $this->after_admin_menu();
391
- }
392
-
393
- /**
394
- * Add the network dashboard admin menu and subpages
395
- *
396
- * @since Code Snippets 1.4
397
- * @access private
398
- */
399
- function add_network_admin_menus() {
400
- $this->table = $this->ms_table;
401
- $this->is_network = true;
402
-
403
- $this->admin_manage = add_menu_page(
404
- __('Snippets', 'code-snippets'),
405
- __('Snippets', 'code-snippets'),
406
- 'manage_network_snippets',
407
- $this->admin_manage_url,
408
- array( $this, 'display_admin_manage' ),
409
- $this->plugin_url . 'images/icon16.png',
410
- 21
411
- );
412
- add_submenu_page(
413
- $this->admin_manage_url,
414
- __('Snippets', 'code-snippets'),
415
- __('Manage Snippets', 'code-snippets'),
416
- 'manage_network_snippets',
417
- $this->admin_manage_url,
418
- array( $this, 'display_admin_manage' )
419
- );
420
- $this->admin_single = add_submenu_page(
421
- $this->admin_manage_url,
422
- __('Add New Snippet', 'code-snippets'),
423
- __('Add New', 'code-snippets'),
424
- 'install_network_snippets',
425
- $this->admin_single_url,
426
- array( $this, 'display_admin_single' )
427
- );
428
- $this->admin_import = add_submenu_page(
429
- $this->admin_manage_url,
430
- __('Import Snippets', 'code-snippets'),
431
- __('Import', 'code-snippets'),
432
- 'install_network_snippets',
433
- $this->admin_import_url,
434
- array( $this, 'display_admin_import' )
435
- );
436
-
437
- $this->after_admin_menu();
438
- }
439
-
440
- /**
441
- * Preform necessary tasks on the subpages such as setting the URLs
442
- * and enqueueing the styles and scripts
443
- *
444
- * @since Code Snippets 1.5
445
- * @access private
446
- */
447
- function after_admin_menu() {
448
- $this->admin_manage_url = self_admin_url( 'admin.php?page=' . $this->admin_manage_url );
449
- $this->admin_single_url = self_admin_url( 'admin.php?page=' . $this->admin_single_url );
450
- $this->admin_import_url = self_admin_url( 'admin.php?page=' . $this->admin_import_url );
451
-
452
- add_action( "admin_print_styles-$this->admin_single", array( $this, 'load_editor_styles' ) );
453
- add_action( "admin_print_scripts-$this->admin_single", array( $this, 'load_editor_scripts' ) );
454
-
455
- add_action( "admin_print_styles-$this->admin_manage", array( $this, 'load_stylesheet' ) );
456
- add_action( "admin_print_styles-$this->admin_single", array( $this, 'load_stylesheet' ) );
457
- add_action( "admin_print_styles-$this->admin_import", array( $this, 'load_stylesheet' ) );
458
-
459
- add_action( "load-$this->admin_manage", array( $this, 'load_admin_manage' ) );
460
- add_action( "load-$this->admin_single", array( $this, 'load_admin_single' ) );
461
- add_action( "load-$this->admin_import", array( $this, 'load_admin_import' ) );
462
- }
463
-
464
- /**
465
- * Enqueue the admin stylesheet
466
- *
467
- * @since Code Snippets 1.0
468
- * @access private
469
- *
470
- * @uses wp_enqueue_style() To add the stylesheet to the queue
471
- */
472
- function load_stylesheet() {
473
- wp_enqueue_style(
474
- 'code-snippets',
475
- plugins_url( 'css/style.css', $this->file ),
476
- false,
477
- $this->version
478
- );
479
- }
480
-
481
- /**
482
- * Registers and loads the code editor's scripts
483
- *
484
- * @since Code Snippets 1.4
485
- * @access private
486
- *
487
- * @uses wp_register_script()
488
- * @uses wp_enqueue_style() To add the scripts to the queue
489
- */
490
- function load_editor_scripts() {
491
- $version = 2.33;
492
- wp_register_script(
493
- 'codemirror',
494
- plugins_url( 'js/codemirror.js', $this->file ),
495
- false,
496
- $version
497
- );
498
- wp_register_script(
499
- 'codemirror-php',
500
- plugins_url( 'js/php.js', $this->file ),
501
- array( 'codemirror' ),
502
- $version
503
- );
504
- wp_register_script(
505
- 'codemirror-xml',
506
- plugins_url( 'js/xml.js', $this->file ),
507
- array( 'codemirror' ),
508
- $version
509
- );
510
- wp_register_script(
511
- 'codemirror-js',
512
- plugins_url( 'js/javascript.js', $this->file ),
513
- array( 'codemirror' ),
514
- $version
515
- );
516
- wp_register_script(
517
- 'codemirror-css',
518
- plugins_url( 'js/css.js', $this->file ),
519
- array( 'codemirror' ),
520
- $version
521
- );
522
- wp_register_script(
523
- 'codemirror-clike',
524
- plugins_url( 'js/clike.js', $this->file ),
525
- array( 'codemirror' ),
526
- $version
527
- );
528
-
529
- /* CodeMirror utilities */
530
-
531
- wp_register_script(
532
- 'codemirror-dialog.js',
533
- plugins_url( 'js/dialog.js', $this->file ),
534
- array( 'codemirror' ),
535
- $version
536
- );
537
- wp_register_script(
538
- 'codemirror-searchcursor.js',
539
- plugins_url( 'js/searchcursor.js', $this->file ),
540
- array( 'codemirror-dialog.js' ),
541
- $version
542
- );
543
- wp_register_script(
544
- 'codemirror-search.js',
545
- plugins_url( 'js/search.js', $this->file ),
546
- array( 'codemirror-searchcursor.js' ),
547
- $version
548
- );
549
-
550
- wp_enqueue_script( array(
551
- 'codemirror-xml',
552
- 'codemirror-js',
553
- 'codemirror-css',
554
- 'codemirror-clike',
555
- 'codemirror-php',
556
- 'codemirror-search.js',
557
- ) );
558
- }
559
-
560
- /**
561
- * Registers and loads the code editor's styles
562
- *
563
- * @since Code Snippets 1.4
564
- * @access private
565
- *
566
- * @uses wp_register_style()
567
- * @uses wp_enqueue_style() To add the stylesheets to the queue
568
- */
569
- function load_editor_styles() {
570
- $version = 2.33;
571
- wp_register_style(
572
- 'codemirror',
573
- plugins_url( 'css/codemirror.css', $this->file ),
574
- false,
575
- $version
576
- );
577
- wp_register_style(
578
- 'codemirror-dialog',
579
- plugins_url( 'css/dialog.css', $this->file ),
580
- array( 'codemirror' ),
581
- $version
582
- );
583
- wp_enqueue_style( array(
584
- 'codemirror',
585
- 'codemirror-dialog'
586
- ) );
587
- }
588
-
589
- /**
590
- * Activates a snippet
591
- *
592
- * @since Code Snippets 1.5
593
- * @access public
594
- *
595
- * @uses $wpdb To set the snippets' active status
596
- *
597
- * @param array $ids The IDs of the snippets to activate
598
- * @param bool $network Are the snippets network-wide (true) or site-wide (false)?
599
- */
600
- public function activate( $ids, $network = null ) {
601
- global $wpdb;
602
-
603
- $ids = (array) $ids;
604
-
605
- if( ! isset( $network ) ) {
606
- $screen = get_current_screen();
607
- $network = $screen->is_network;
608
- }
609
-
610
- foreach( $ids as $id ) {
611
- $wpdb->update(
612
- ( $network ? $this->ms_table : $this->table ),
613
- array( 'active' => '1' ),
614
- array( 'id' => $id ),
615
- array( '%d' ),
616
- array( '%d' )
617
- );
618
- }
619
- }
620
-
621
- /**
622
- * Deactivates selected snippets
623
- *
624
- * @since Code Snippets 1.5
625
- * @access public
626
- *
627
- * @uses $wpdb To set the snippets' active status
628
- *
629
- * @param array $ids The IDs of the snippets to deactivate
630
- * @param bool $network Are the snippets network-wide (true) or site-wide (false)?
631
- */
632
- public function deactivate( $ids, $network = null ) {
633
- global $wpdb;
634
-
635
- $ids = (array) $ids;
636
-
637
- if( ! isset( $network ) ) {
638
- $screen = get_current_screen();
639
- $network = $screen->is_network;
640
- }
641
-
642
- // Where in the database are the recently activated snippets stored?
643
- $option = ( $network ? 'recently_network_activated_snippets' : 'recently_activated_snippets' );
644
-
645
- foreach( $ids as $id ) {
646
- $wpdb->update(
647
- ( $network ? $this->ms_table : $this->table ),
648
- array( 'active' => '0' ),
649
- array( 'id' => $id ),
650
- array( '%d' ),
651
- array( '%d' )
652
- );
653
- update_option( $option, array( $id => time() ) + (array) get_option( $option ) );
654
- }
655
- }
656
-
657
- public function delete_snippet( $id, $network = null ) {
658
- global $wpdb;
659
-
660
- if( ! isset( $network ) ) {
661
- $screen = get_current_screen();
662
- $network = $screen->is_network;
663
- }
664
-
665
- $table = ( $network ? $this->ms_table : $this->table );
666
- $id = intval( $id );
667
-
668
- $wpdb->query( "DELETE FROM $table WHERE id='$id' LIMIT 1" );
669
- }
670
-
671
- /**
672
- * Saves a snippet to the database.
673
- *
674
- * @since Code Snippets 1.5
675
- * @access public
676
- *
677
- * @uses $wpdb To update/add the snippet to the database
678
- *
679
- * @param array $snippet The snippet to add/update to the database
680
- * @return int|bool The ID of the snippet on success, false on failure
681
- */
682
- public function save_snippet( $snippet, $network = null ) {
683
- global $wpdb;
684
-
685
- $name = mysql_real_escape_string( htmlspecialchars( $snippet['name'] ) );
686
- $description = mysql_real_escape_string( htmlspecialchars( $snippet['description'] ) );
687
- $code = mysql_real_escape_string( htmlspecialchars( $snippet['code'] ) );
688
-
689
- if( empty( $name ) or empty( $code ) )
690
- return false;
691
-
692
- if( ! isset( $network ) ) {
693
- $screen = get_current_screen();
694
- $network = $screen->is_network;
695
- }
696
-
697
- $table = ( $network ? $this->ms_table : $this->table );
698
-
699
- if( isset( $snippet['id'] ) && ( intval( $snippet['id'] ) != 0 ) ) {
700
- $wpdb->query( "UPDATE $table SET
701
- name='$name',
702
- description='$description',
703
- code='$code' WHERE id=" . intval( $snippet['id'] ) . "
704
- LIMIT 1"
705
- );
706
- return intval( $snippet['id'] );
707
- } else {
708
- $wpdb->query(
709
- "INSERT INTO $table(name,description,code)
710
- VALUES ('$name','$description','$code')"
711
- );
712
- return $wpdb->insert_id;
713
- }
714
- }
715
-
716
- /**
717
- * Imports snippets from an XML file
718
- *
719
- * @since Code Snippets 1.5
720
- * @access public
721
- *
722
- * @uses $this->save_snippet() To add the snippets to the database
723
- *
724
- * @param file $file The XML file to import
725
- * @return mixed The number of snippets imported on success, false on failure
726
- */
727
- public function import( $file, $network = null ) {
728
-
729
- if( ! file_exists( $file ) || ! is_file( $file ) )
730
- return false;
731
-
732
- $xml = simplexml_load_file( $file );
733
-
734
- foreach( $xml->children() as $child ) {
735
- $this->save_snippet( array(
736
- 'name' => $child->name,
737
- 'description' => $child->description,
738
- 'code' => $child->code,
739
- ), $network );
740
- }
741
- return $xml->count();
742
- }
743
-
744
- /**
745
- * Exports snippets as an XML file
746
- *
747
- * @since Code Snippets 1.5
748
- * @access public
749
- *
750
- * @uses cs_export() To export selected snippets
751
- *
752
- * @param array $id An array if the IDs of the snippets to export
753
- * @param bool $network Is the snippet a network-wide (true) or site-wide (false) snippet?
754
- */
755
- public function export( $ids, $network = null ) {
756
-
757
- if( ! isset( $network ) ) {
758
- $screen = get_current_screen();
759
- $network = $screen->is_network;
760
- }
761
-
762
- $table = ( $network ? $this->ms_table : $this->table );
763
-
764
- if( ! function_exists( 'cs_export' ) )
765
- require_once $this->plugin_dir . 'includes/export.php';
766
-
767
- cs_export( $ids, 'xml', $table );
768
- }
769
-
770
- /**
771
- * Exports snippets as a PHP file
772
- *
773
- * @since Code Snippets 1.5
774
- * @access public
775
- *
776
- * @uses cs_export() To export selected snippets
777
- *
778
- * @param array $id An array if the IDs of the snippets to export
779
- * @param bool $network Is the snippet a network-wide (true) or site-wide (false) snippet?
780
- */
781
- public function exportphp( $ids, $network = null ) {
782
-
783
- if( ! isset( $network ) ) {
784
- $screen = get_current_screen();
785
- $network = $screen->is_network;
786
- }
787
-
788
- $table = ( $network ? $this->ms_table : $this->table );
789
-
790
- if( ! function_exists( 'cs_export' ) )
791
- require_once $this->plugin_dir . 'includes/export.php';
792
-
793
- cs_export( $ids, 'php', $table );
794
- }
795
-
796
- /**
797
- * Execute a snippet
798
- *
799
- * Code must NOT be escaped, as
800
- * it will be executed directly
801
- *
802
- * @since Code Snippets 1.5
803
- * @access public
804
- *
805
- * @param string $code The snippet code to execute
806
- * @return $result The result of the code execution
807
- */
808
- public function execute_snippet( $code ) {
809
- ob_start();
810
- $result = eval( $code );
811
- $output = ob_get_contents();
812
- ob_end_clean();
813
- return $result;
814
- }
815
-
816
- /**
817
- * Replaces the text 'Add New Snippet' with 'Edit Snippet'
818
- *
819
- * @since Code Snippets 1.1
820
- * @access private
821
- */
822
- function admin_single_title( $title ) {
823
- return str_ireplace(
824
- __('Add New Snippet', 'code-snippets'),
825
- __('Edit Snippet', 'code-snippets'),
826
- $title
827
- );
828
- }
829
-
830
- /**
831
- * Handles saving the user's screen option preference
832
- *
833
- * @since Code Snippets 1.5
834
- * @access private
835
- */
836
- function set_screen_option( $status, $option, $value ) {
837
- if( 'snippets_per_page' == $option ) return $value;
838
- }
839
-
840
- /**
841
- * Processes any action command and loads the help tabs
842
- *
843
- * @since Code Snippets 1.0
844
- * @access private
845
- *
846
- * @uses $wpdb To activate, deactivate and delete snippets
847
- */
848
- function load_admin_manage() {
849
- global $wpdb;
850
-
851
- if( isset( $_GET['action'], $_GET['id'] ) ) :
852
-
853
- $id = intval( $_GET['id'] );
854
-
855
- $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'action', 'id' ) );
856
-
857
- if( 'activate' == $_GET['action'] ) {
858
- $this->activate( $id, $this->is_network );
859
- wp_redirect( add_query_arg( 'activate', true ) );
860
- }
861
- elseif( 'deactivate' == $_GET['action'] ) {
862
- $this->deactivate( $id );
863
- wp_redirect( add_query_arg( 'deactivate', true ) );
864
- }
865
- elseif( 'delete' == $_GET['action'] ) {
866
- $this->delete_snippet( $id );
867
- wp_redirect( add_query_arg( 'delete', true ) );
868
- }
869
- elseif( 'export' == $_GET['action'] ) {
870
- $this->export( $id );
871
- }
872
- elseif( 'exportphp' == $_GET['action'] ) {
873
- $this->exportphp( $id );
874
- }
875
-
876
- endif;
877
-
878
- include $this->plugin_dir . 'includes/help/manage.php'; // Load the help tabs
879
-
880
- /**
881
- * Initialize the snippet table class
882
- */
883
- require_once $this->plugin_dir . 'includes/class-list-table.php';
884
-
885
- global $cs_list_table;
886
- $cs_list_table = new Code_Snippets_List_Table();
887
- $cs_list_table->prepare_items();
888
- }
889
-
890
- /**
891
- * Loads the help tabs for the Edit Snippets page
892
- *
893
- * @since Code Snippets 1.0
894
- * @access private
895
- *
896
- * @uses $wpdb To save the posted snippet to the database
897
- * @uses wp_redirect To pass the results to the page
898
- */
899
- function load_admin_single() {
900
-
901
- if( isset( $_REQUEST['save_snippet'] ) ) {
902
-
903
- if( isset( $_REQUEST['snippet_id'] ) ) {
904
- $result = $this->save_snippet( array(
905
- 'name' => $_REQUEST['snippet_name'],
906
- 'description' => $_REQUEST['snippet_description'],
907
- 'code' => $_REQUEST['snippet_code'],
908
- 'id' => $_REQUEST['snippet_id'],
909
- ) );
910
- } else {
911
- $result = $this->save_snippet( array(
912
- 'name' => $_REQUEST['snippet_name'],
913
- 'description' => $_REQUEST['snippet_description'],
914
- 'code' => $_REQUEST['snippet_code'],
915
- ) );
916
- }
917
-
918
- $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'added', 'updated', 'invalid' ) );
919
-
920
- if( ! $result || $result < 1 ) {
921
- wp_redirect( add_query_arg( 'invalid', true ) );
922
- }
923
- elseif( isset( $_REQUEST['snippet_id'] ) ) {
924
- wp_redirect( add_query_arg( array(
925
- 'edit' => $result,
926
- 'updated' => true
927
- ) ) );
928
- }
929
- else {
930
- wp_redirect( add_query_arg( array(
931
- 'edit' => $result,
932
- 'added' => true
933
- ) ) );
934
- }
935
- }
936
-
937
- if( isset( $_GET['edit'] ) )
938
- add_filter( 'admin_title', array( $this, 'admin_single_title' ) );
939
-
940
- include $this->plugin_dir . 'includes/help/single.php'; // Load the help tabs
941
- }
942
-
943
- /**
944
- * Processes import files and loads the help tabs for the Import Snippets page
945
- *
946
- * @since Code Snippets 1.3
947
- *
948
- * @uses $this->import() To process the import file
949
- * @uses wp_redirect() To pass the import results to the page
950
- * @uses add_query_arg() To append the results to the current URI
951
- */
952
- function load_admin_import() {
953
- if( isset( $_FILES['cs_import_file']['tmp_name'] ) ) {
954
- $count = $this->import( $_FILES['cs_import_file']['tmp_name'] );
955
- if( $count ) {
956
- wp_redirect( add_query_arg( 'imported', $count ) );
957
- }
958
- }
959
- include $this->plugin_dir . 'includes/help/import.php'; // Load the help tabs
960
- }
961
-
962
- /**
963
- * Displays the Manage Snippets page
964
- *
965
- * @since Code Snippets 1.0
966
- * @access private
967
- */
968
- function display_admin_manage() {
969
- require_once $this->plugin_dir . 'includes/admin/manage.php';
970
- }
971
-
972
- /**
973
- * Displays the Add New/Edit Snippet page
974
- *
975
- * @since Code Snippets 1.0
976
- * @access private
977
- */
978
- function display_admin_single() {
979
- require_once $this->plugin_dir . 'includes/admin/single.php';
980
- }
981
-
982
- /**
983
- * Displays the Import Snippets page
984
- *
985
- * @since Code Snippets 1.3
986
- */
987
- function display_admin_import() {
988
- require_once $this->plugin_dir . 'includes/admin/import.php';
989
- }
990
-
991
- /**
992
- * Adds a link pointing to the Manage Snippets page
993
- *
994
- * @since Code Snippets 1.0
995
- * @access private
996
- */
997
- function settings_link( $links ) {
998
- array_unshift( $links, sprintf(
999
- '<a href="%1$s" title="%2$s">%3$s</a>',
1000
- $this->admin_manage_url,
1001
- __('Manage your existing snippets', 'code-snippets'),
1002
- __('Manage', 'code-snippets')
1003
- ) );
1004
- return $links;
1005
- }
1006
-
1007
- /**
1008
- * Adds extra links related to the plugin
1009
- *
1010
- * @since Code Snippets 1.2
1011
- */
1012
- function plugin_meta( $links, $file ) {
1013
-
1014
- if( $file != $this->basename ) return $links;
1015
-
1016
- $format = '<a href="%1$s" title="%2$s">%3$s</a>';
1017
-
1018
- return array_merge( $links, array(
1019
- sprintf( $format,
1020
- 'http://wordpress.org/extend/plugins/code-snippets/',
1021
- __('Visit the WordPress.org plugin page', 'code-snippets'),
1022
- __('About', 'code-snippets')
1023
- ),
1024
- sprintf( $format,
1025
- 'http://wordpress.org/support/plugin/code-snippets/',
1026
- __('Visit the support forums', 'code-snippets'),
1027
- __('Support', 'code-snippets')
1028
- ),
1029
- sprintf( $format,
1030
- 'http://cs.bungeshea.com/donate/',
1031
- __('Support this plugin\'s development', 'code-snippets'),
1032
- __('Donate', 'code-snippets')
1033
- )
1034
- ) );
1035
- }
1036
-
1037
- /**
1038
- * Run the active snippets
1039
- *
1040
- * @since Code Snippets 1.0
1041
- * @access private
1042
- *
1043
- * @uses $wpdb To grab the active snippets from the database
1044
- * @uses $this->execute_snippet() To execute a snippet
1045
- */
1046
- function run_snippets( $network = false ) {
1047
- if( defined( 'CS_SAFE_MODE' ) && CS_SAFE_MODE ) return;
1048
-
1049
- $table = ( $network ? $this->ms_table : $this->table );
1050
-
1051
- global $wpdb;
1052
-
1053
- // grab the active snippets from the database
1054
- $active_snippets = $wpdb->get_results( "SELECT code FROM $table WHERE active=1;");
1055
- if( count( $active_snippets ) ) {
1056
- foreach( $active_snippets as $snippet ) {
1057
- // execute the php code
1058
- $this->execute_snippet( htmlspecialchars_decode( stripslashes( $snippet->code ) ) );
1059
- }
1060
- }
1061
- }
1062
- }
1063
-
1064
- endif; // class exists check
1065
-
1066
- /**
1067
- * The global variable in which the Code Snippets class is stored
1068
- *
1069
- * @since Code Snippets 1.0
1070
- * @access public
1071
- */
1072
- global $cs;
1073
- $cs = new Code_Snippets;
1074
-
1075
- register_uninstall_hook( $cs->file, 'cs_uninstall' );
1076
-
1077
- /**
1078
- * Cleans up data created by the Code_Snippets class
1079
- *
1080
- * @since Code Snippets 1.2
1081
- * @access private
1082
- *
1083
- * @uses $wpdb To remove tables from the database
1084
- * @uses $cs To find out which table to drop
1085
- * @uses is_multisite() To check the type of installation
1086
- * @uses switch_to_blog() To switch between blogs
1087
- * @uses restore_current_blog() To switch between blogs
1088
- * @uses delete_option() To remove site options
1089
- */
1090
- function cs_uninstall() {
1091
- global $wpdb, $cs;
1092
- if( is_multisite() ) {
1093
- $blogs = $wpdb->get_results( "SELECT blog_id FROM $wpdb->blogs", ARRAY_A );
1094
- if( $blogs ) {
1095
- foreach( $blogs as $blog ) {
1096
- switch_to_blog( $blog['blog_id'] );
1097
- $table = apply_filters( 'cs_table', $wpdb->prefix . 'snippets' );
1098
- $wpdb->query( "DROP TABLE IF EXISTS $table" );
1099
- delete_option( 'cs_db_version' );
1100
- delete_option( 'recently_activated_snippets' );
1101
- $cs->remove_caps();
1102
- }
1103
- restore_current_blog();
1104
- }
1105
- $wpdb->query( "DROP TABLE IF EXISTS $cs->ms_table" );
1106
- delete_option( 'recently_network_activated_snippets' );
1107
- $cs->remove_caps( true );
1108
- } else {
1109
- $wpdb->query( "DROP TABLE IF EXISTS $cs->table" );
1110
- delete_option( 'cs_db_version' );
1111
- delete_option( 'recently_activated_snippets' );
1112
- $cs->remove_caps();
1113
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1114
  }
1
+ <?php
2
+
3
+ /**
4
+ * Code Snippets - An easy, clean and simple way to add code snippets to your site.
5
+ *
6
+ * If you're interested in helping to develop Code Snippets, or perhaps
7
+ * contribute to the localization, please see http://code-snippets.bungeshea.com
8
+ *
9
+ * @package Code Snippets
10
+ * @subpackage Main
11
+ *
12
+ *
13
+ * Plugin Name: Code Snippets
14
+ * Plugin URI: http://code-snippets.bungeshea.com
15
+ * Description: An easy, clean and simple way to add code snippets to your site. No need to edit to your theme's functions.php file again!
16
+ * Author: Shea Bunge
17
+ * Author URI: http://bungeshea.com
18
+ * Version: 1.6
19
+ * License: GPLv3 or later
20
+ * Network: true
21
+ * Text Domain: code-snippets
22
+ * Domain Path: /languages/
23
+ *
24
+ *
25
+ * Code Snippets - WordPress Plugin
26
+ * Copyright (C) 2012 Shea Bunge
27
+ *
28
+ * This program is free software: you can redistribute it and/or modify
29
+ * it under the terms of the GNU General Public License as published by
30
+ * the Free Software Foundation, either version 3 of the License, or
31
+ * (at your option) any later version.
32
+ *
33
+ * This program is distributed in the hope that it will be useful,
34
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
35
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36
+ * GNU General Public License for more details.
37
+ *
38
+ * You should have received a copy of the GNU General Public License
39
+ * along with this program. If not, see <http://www.gnu.org/licenses>.
40
+ */
41
+
42
+ // Exit if accessed directly
43
+ if ( ! defined( 'ABSPATH' ) ) exit;
44
+
45
+ if ( ! class_exists( 'Code_Snippets' ) ) :
46
+
47
+ /**
48
+ * The main class for our plugin
49
+ * It all happens here, folks
50
+ *
51
+ * Please use the global variable $code_snippets to access
52
+ * the methods in this class. Anything you need
53
+ * to access should be publicly available there
54
+ *
55
+ * @since Code Snippets 1.0
56
+ * @access private
57
+ */
58
+ final class Code_Snippets {
59
+
60
+ /**
61
+ * The base name for the snippets table in the database
62
+ * This will later be prepended with the WordPress table prefix
63
+ *
64
+ * DO NOT EDIT THIS VARIABLE!
65
+ * Instead, use the 'code_snippets_table' filter
66
+ *
67
+ * If you need to use the table name in your own code,
68
+ * use the $code_snippets->get_table_name() function
69
+ *
70
+ * @since Code Snippets 1.0
71
+ * @access public
72
+ */
73
+ private $table = 'snippets';
74
+
75
+ /**
76
+ * The base name for the network snippets table in the database
77
+ * This will later be prepended with the WordPress base table prefix
78
+ *
79
+ * DO NOT EDIT THIS VARIABLE!
80
+ * Instead, use the 'code_snippets_multisite_table' filter
81
+ *
82
+ * If you need to use the table name in your own code,
83
+ * use the $code_snippets->get_table_name() function
84
+ *
85
+ * @since Code Snippets 1.4
86
+ * @access private
87
+ */
88
+ private $ms_table = 'ms_snippets';
89
+
90
+ /**
91
+ * The version number for this release of the plugin.
92
+ * This will later be used for upgrades and enqueueing files
93
+ *
94
+ * This should be set to the 'Plugin Version' value,
95
+ * as defined above in the plugin header
96
+ *
97
+ * @since Code Snippets 1.0
98
+ * @access public
99
+ */
100
+ public $version = 1.6;
101
+
102
+ /**
103
+ * The base URLs for the admin pages
104
+ *
105
+ * @since Code Snippets 1.0
106
+ * @access public
107
+ */
108
+ public $admin_manage_url, $admin_single_url, $admin_import_url;
109
+
110
+ /**
111
+ * The hooks for the admin pages
112
+ * Used primarily for enqueueing scripts and styles
113
+ *
114
+ * @since Code Snippets 1.0
115
+ * @access public
116
+ */
117
+ public $admin_manage, $admin_single, $admin_import;
118
+
119
+ /**
120
+ * The constructor function for our class
121
+ *
122
+ * @since Code Snippets 1.0
123
+ * @access private
124
+ *
125
+ * @return void
126
+ */
127
+ function __construct() {
128
+ $this->setup_vars(); // initialise the variables
129
+ $this->setup_hooks(); // register the action and filter hooks
130
+ $this->upgrade(); // check if we need to change some stuff
131
+ }
132
+
133
+ /**
134
+ * Initialise variables
135
+ *
136
+ * @since Code Snippets 1.2
137
+ * @access private
138
+ *
139
+ * @return void
140
+ */
141
+ function setup_vars() {
142
+ global $wpdb;
143
+ $this->file = __FILE__;
144
+
145
+ $this->table = apply_filters( 'code_snippets_table', $wpdb->prefix . $this->table );
146
+ $this->ms_table = apply_filters( 'code_snippets_multisite_table', $wpdb->base_prefix . $this->ms_table );
147
+
148
+ $this->basename = plugin_basename( $this->file );
149
+ $this->plugin_dir = plugin_dir_path( $this->file );
150
+ $this->plugin_url = plugin_dir_url ( $this->file );
151
+
152
+ $this->admin_manage_slug = apply_filters( 'code_snippets_admin_manage', 'snippets' );
153
+ $this->admin_single_slug = apply_filters( 'code_snippets_admin_single', 'snippet' );
154
+
155
+ $this->admin_manage_url = self_admin_url( 'admin.php?page=' . $this->admin_manage_slug );
156
+ $this->admin_single_url = self_admin_url( 'admin.php?page=' . $this->admin_single_slug );
157
+ }
158
+
159
+ /**
160
+ * Register action and filter hooks
161
+ *
162
+ * @since Code Snippets 1.6
163
+ * @access private
164
+ *
165
+ * @return void
166
+ */
167
+ function setup_hooks() {
168
+
169
+ /* execute the snippets once the plugins are loaded */
170
+ add_action( 'plugins_loaded', array( $this, 'run_snippets' ) );
171
+
172
+ /* add the administration menus */
173
+ add_action( 'admin_menu', array( $this, 'add_admin_menus' ) );
174
+ add_action( 'network_admin_menu', array( $this, 'add_network_admin_menus' ) );
175
+
176
+ /* register the importer */
177
+ add_action( 'admin_init', array( $this, 'load_importer' ), 999 );
178
+ add_action( 'network_admin_menu', array( $this, 'add_import_admin_menu' ) );
179
+
180
+ /* load the translations */
181
+ add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );
182
+
183
+ /* add helpful links to the Plugins menu */
184
+ add_filter( 'plugin_action_links_' . $this->basename, array( $this, 'settings_link' ) );
185
+ add_filter( 'plugin_row_meta', array( $this, 'plugin_meta' ), 10, 2 );
186
+
187
+ /* Add a custom icon to Snippets menu pages */
188
+ add_action( 'admin_head', array( $this, 'load_admin_icon_style' ) );
189
+ }
190
+
191
+ /**
192
+ * Load the Code Snippets importer
193
+ *
194
+ * Add both an importer to the Tools menu
195
+ * and an Import Snippets page to the network admin menu
196
+ *
197
+ * @since Code Snippets 1.6
198
+ * @access private
199
+ *
200
+ * @return void
201
+ */
202
+ function load_importer() {
203
+
204
+ if ( defined( 'WP_LOAD_IMPORTERS' ) ) {
205
+
206
+ // Load Importer API
207
+ require_once ABSPATH . 'wp-admin/includes/import.php';
208
+
209
+ if ( ! class_exists( 'WP_Importer' ) ) {
210
+ $class_wp_importer = ABSPATH . 'wp-admin/includes/class-wp-importer.php';
211
+ if ( file_exists( $class_wp_importer ) )
212
+ require_once $class_wp_importer;
213
+ }
214
+
215
+ register_importer(
216
+ 'code-snippets',
217
+ __('Code Snippets', 'code-snippets'),
218
+ __('Import snippets from a <strong>Code Snippets</strong> export file', 'code-snippets'),
219
+ array( $this, 'display_admin_import' )
220
+ );
221
+ }
222
+
223
+ $this->admin_import_url = self_admin_url( 'admin.php?import=code-snippets' );
224
+ add_action( 'load-importer-code-snippets', array( $this, 'load_admin_import' ) );
225
+ }
226
+
227
+ /**
228
+ * Return the appropriate snippet table name
229
+ *
230
+ * @since Code Snippets 1.6
231
+ * @access private
232
+ *
233
+ * @param string $scope Retrieve the multisite table name or the site table name?
234
+ * @param bool $check_screen Query the current screen if no scope passed?
235
+ * @return string $table The snippet table name
236
+ */
237
+ function get_table_name( $scope = '', $check_screen = true ) {
238
+
239
+ $this->create_tables(); // create the snippet tables if they do not exist
240
+
241
+ if ( ! is_multisite() ) {
242
+ $network = false;
243
+ }
244
+ elseif ( empty( $network ) && $check_screen && function_exists( 'get_current_screen' ) ) {
245
+ /* if no scope is set, query the current screen to see if in network admin */
246
+ $screen = get_current_screen();
247
+ $network = $screen->is_network;
248
+ }
249
+ elseif ( $scope == 'multisite' || $scope == 'network' ) {
250
+ $network = true;
251
+ }
252
+ elseif ( $scope == 'site' || $scope = 'single' ) {
253
+ $network = false;
254
+ }
255
+ else {
256
+ $network = false;
257
+ }
258
+
259
+ $table = ( $network ? $this->ms_table : $this->table );
260
+
261
+ return $table;
262
+ }
263
+
264
+ /**
265
+ * Create the snippet tables if they do not already exist
266
+ *
267
+ * @since Code Snippets 1.2
268
+ * @access public
269
+ *
270
+ * @uses $this->create_table() To create a single snippet table
271
+ *
272
+ * @return void
273
+ */
274
+ public function create_tables() {
275
+
276
+ $this->create_table( $this->table );
277
+
278
+ if ( is_multisite() )
279
+ $this->create_table( $this->ms_table );
280
+
281
+ add_site_option( 'code_snippets_version', $this->version );
282
+ }
283
+
284
+ /**
285
+ * Create a single snippet table
286
+ * if one of the same name does not already exist
287
+ *
288
+ * @since Code Snippets 1.6
289
+ * @access public
290
+ *
291
+ * @param string $table_name The name of the table to create
292
+ * @return void
293
+ */
294
+ function create_table( $table_name ) {
295
+
296
+ global $wpdb;
297
+
298
+ if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) != $table_name ) {
299
+ $sql = "CREATE TABLE $table_name (
300
+ id MEDIUMINT NOT NULL AUTO_INCREMENT,
301
+ name VARCHAR(64) NOT NULL,
302
+ description TEXT,
303
+ code TEXT NOT NULL,
304
+ active TINYINT(1) NOT NULL DEFAULT 0,
305
+ UNIQUE KEY id (id)
306
+ );";
307
+ require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
308
+ dbDelta( $sql );
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Preform upgrade tasks such as deleting and updating options
314
+ *
315
+ * @since Code Snippets 1.2
316
+ * @access private
317
+ *
318
+ * @return bool True on successful upgrade, false on failure
319
+ */
320
+ function upgrade() {
321
+
322
+ /* add backwards-compatibly for the CS_SAFE_MODE constant */
323
+ if ( defined( 'CS_SAFE_MODE' ) && ! defined( 'CODE_SNIPPETS_SAFE_MODE' ) ) {
324
+ define( 'CODE_SNIPPETS_SAFE_MODE', CS_SAFE_MODE );
325
+ }
326
+
327
+ /* get the current plugin version from the database */
328
+ if ( get_option( 'cs_db_version' ) ) {
329
+ $this->current_version = get_option( 'cs_db_version', $this->version );
330
+ delete_option( 'cs_db_version' );
331
+ add_site_option( 'code_snippets_version', $this->current_version );
332
+ }
333
+ else {
334
+ $this->current_version = get_site_option( 'code_snippets_version', $this->version );
335
+ }
336
+
337
+ /* bail early if we're on the latest version */
338
+ if ( ! ( $this->current_version < $this->version ) ) return false;
339
+
340
+ if ( ! get_site_option( 'code_snippets_version' ) ) {
341
+
342
+ /* This is the first time the plugin has run */
343
+
344
+ $this->add_caps(); // register the capabilities ONCE ONLY
345
+
346
+ if ( is_multisite() ) {
347
+ $this->add_caps( 'multisite' ); // register the multisite capabilities ONCE ONLY
348
+ }
349
+ }
350
+
351
+ /* migrate the recently_network_activated_snippets to the site options */
352
+ if ( is_multisite() && get_option( 'recently_network_activated_snippets' ) ) {
353
+ add_site_option( 'recently_activated_snippets', get_option( 'recently_network_activated_snippets', array() ) );
354
+ delete_option( 'recently_network_activated_snippets' );
355
+ }
356
+
357
+ /* preform version specific upgrades */
358
+
359
+ if ( $this->current_version < 1.5 ) {
360
+ global $wpdb;
361
+
362
+ /* Let's alter the name column to accept up to 64 characters */
363
+ $wpdb->query( "ALTER TABLE $this->table CHANGE COLUMN name name VARCHAR(64) NOT NULL" );
364
+
365
+ if ( is_multisite() ) {
366
+ /* We must not forget the multisite table! */
367
+ $wpdb->query( "ALTER TABLE $this->ms_table CHANGE COLUMN name name VARCHAR(64) NOT NULL" );
368
+ }
369
+
370
+ /* Add the custom capabilities that were introduced in version 1.5 */
371
+ $this->add_roles();
372
+ }
373
+
374
+ if ( $this->current_version < 1.2 ) {
375
+ /* The 'Complete Uninstall' option was removed in version 1.2 */
376
+ delete_option( 'cs_complete_uninstall' );
377
+ }
378
+
379
+ if ( $this->current_version < $this->version ) {
380
+ /* Update the current version */
381
+ update_site_option( 'code_snippets_version', $this->version );
382
+ }
383
+
384
+ return true;
385
+ }
386
+
387
+ /**
388
+ * Load up the localization file if we're using WordPress in a different language
389
+ * Place it in this plugin's "languages" folder and name it "code-snippets-[value in wp-config].mo"
390
+ *
391
+ * If you wish to contribute a language file to be included in the Code Snippets package,
392
+ * please see the plugin's website at http://code-snippets.bungeshea.com
393
+ *
394
+ * @since Code Snippets 1.5
395
+ * @access private
396
+ *
397
+ * @return void
398
+ */
399
+ function load_textdomain() {
400
+ load_plugin_textdomain( 'code-snippets', false, dirname( $this->basename ) . '/languages/' );
401
+ }
402
+
403
+ /**
404
+ * Add the user capabilities
405
+ *
406
+ * @since Code Snippets 1.5
407
+ * @access public
408
+ *
409
+ * @uses $this->setup_roles() To register the capabilities
410
+ *
411
+ * @param string $scope Add site-specific or multisite-specific capabilities?
412
+ * @return void
413
+ */
414
+ public function add_caps( $scope = '' ) {
415
+
416
+ $network = ( $scope == 'multisite' || $scope == 'network' ? true : false );
417
+
418
+ if ( $network && is_multisite() )
419
+ $this->setup_ms_roles( 'add' );
420
+ else
421
+ $this->setup_roles( 'add' );
422
+ }
423
+
424
+ /**
425
+ * Remove the user capabilities
426
+ *
427
+ * @since Code Snippets 1.5
428
+ * @access public
429
+ *
430
+ * @uses $this->setup_roles() To register the capabilities
431
+ *
432
+ * @param string $scope Add site-specific or multisite-specific capabilities?
433
+ * @return void
434
+ */
435
+ public function remove_caps( $scope = '' ) {
436
+
437
+ $network = ( $scope == 'multisite' || $scope == 'network' ? true : false );
438
+
439
+ if ( $network && is_multisite() )
440
+ $this->setup_ms_roles( 'remove' );
441
+ else
442
+ $this->setup_roles( 'remove' );
443
+ }
444
+
445
+ /**
446
+ * Register the user roles and capabilities
447
+ *
448
+ * @since Code Snippets 1.5
449
+ * @access private
450
+ *
451
+ * @param string $install True to add the capabilities, false to remove
452
+ * @return void
453
+ */
454
+ function setup_roles( $action = 'install' ) {
455
+
456
+ if ( $action == 'install' || $action = 'add' )
457
+ $install = true;
458
+
459
+ elseif ( $action == 'uninstall' || $action = 'remove' )
460
+ $install = false;
461
+
462
+ else
463
+ $install = true;
464
+
465
+
466
+ $this->caps = apply_filters( 'code_snippets_caps', array(
467
+ 'manage_snippets',
468
+ 'install_snippets',
469
+ 'edit_snippets'
470
+ ) );
471
+
472
+ $this->role = get_role( apply_filters( 'code_snippets_role', 'administrator' ) );
473
+
474
+ foreach( $this->caps as $cap ) {
475
+ if ( $install )
476
+ $this->role->add_cap( $cap );
477
+ else
478
+ $this->role->remove_cap( $cap );
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Register the multisite user roles and capabilities
484
+ *
485
+ * @since Code Snippets 1.5
486
+ * @access private
487
+ *
488
+ * @param string $action Add or remove the capabilities
489
+ * @return void
490
+ */
491
+ function setup_ms_roles( $action = 'install' ) {
492
+
493
+ if ( ! is_multisite() ) return;
494
+
495
+ if ( $action == 'install' || $action = 'add' )
496
+ $install = true;
497
+
498
+ elseif ( $action == 'uninstall' || $action = 'remove' )
499
+ $install = false;
500
+
501
+ else
502
+ $install = true;
503
+
504
+ $this->network_caps = apply_filters( 'code_snippets_network_caps', array(
505
+ 'manage_network_snippets',
506
+ 'install_network_snippets',
507
+ 'edit_network_snippets'
508
+ ) );
509
+
510
+ $supers = get_super_admins();
511
+ foreach( $supers as $admin ) {
512
+ $user = new WP_User( 0, $admin );
513
+ foreach( $this->network_caps as $cap ) {
514
+ if ( $install )
515
+ $user->add_cap( $cap );
516
+ else
517
+ $user->remove_cap( $cap );
518
+ }
519
+ }
520
+ }
521
+
522
+ /**
523
+ * Add the dashboard admin menu and subpages
524
+ *
525
+ * @since Code Snippets 1.0
526
+ * @access private
527
+ *
528
+ * @uses add_menu_page() To register a top-level menu
529
+ * @uses add_submenu_page() To register a submenu page
530
+ * @uses apply_filters() To retrieve the corrent menu slug
531
+ * @uses plugins_url() To retrieve the URL to a resource
532
+ * @return void
533
+ */
534
+ function add_admin_menus() {
535
+
536
+ $this->admin_manage = add_menu_page(
537
+ __('Snippets', 'code-snippets'),
538
+ __('Snippets', 'code-snippets'),
539
+ 'manage_snippets',
540
+ apply_filters( 'code_snippets_manage_url', 'snippets' ),
541
+ array( $this, 'display_admin_manage' ),
542
+ plugins_url( 'images/icon16.png', $this->file ),
543
+ 67
544
+ );
545
+
546
+ add_submenu_page(
547
+ apply_filters( 'code_snippets_manage_url', 'snippets' ),
548
+ __('Snippets', 'code-snippets'),
549
+ __('Manage Snippets', 'code-snippets'),
550
+ 'manage_snippets',
551
+ apply_filters( 'code_snippets_manage_url', 'snippets' ),
552
+ array( $this, 'display_admin_manage')
553
+ );
554
+
555
+ $this->admin_single = add_submenu_page(
556
+ apply_filters( 'code_snippets_manage_url', 'snippets' ),
557
+ __('Add New Snippet', 'code-snippets'),
558
+ __('Add New', 'code-snippets'),
559
+ 'install_snippets',
560
+ apply_filters( 'code_snippets_single_url', 'snippet' ),
561
+ array( $this, 'display_admin_single' )
562
+ );
563
+
564
+ add_action( "load-$this->admin_manage", array( $this, 'load_admin_manage' ) );
565
+ add_action( "load-$this->admin_single", array( $this, 'load_admin_single' ) );
566
+
567
+ add_action( "admin_print_styles-$this->admin_single", array( $this, 'load_editor_styles' ) );
568
+ add_action( "admin_print_scripts-$this->admin_single", array( $this, 'load_editor_scripts' ) );
569
+ }
570
+
571
+ /**
572
+ * Add the network dashboard admin menu and subpages
573
+ *
574
+ * @since Code Snippets 1.4
575
+ * @access private
576
+ *
577
+ * @uses add_menu_page() To register a top-level menu
578
+ * @uses add_submenu_page() To register a submenu page
579
+ * @uses apply_filters() To retrieve the corrent menu slug
580
+ * @uses plugins_url() To retrieve the URL to a resource
581
+ * @return void
582
+ */
583
+ function add_network_admin_menus() {
584
+
585
+ $this->admin_manage = add_menu_page(
586
+ __('Snippets', 'code-snippets'),
587
+ __('Snippets', 'code-snippets'),
588
+ 'manage_network_snippets',
589
+ apply_filters( 'code_snippets_manage_url', 'snippets' ),
590
+ array( $this, 'display_admin_manage' ),
591
+ plugins_url( 'images/icon16.png', $this->file ),
592
+ 21
593
+ );
594
+
595
+ add_submenu_page(
596
+ apply_filters( 'code_snippets_manage_url', 'snippets' ),
597
+ __('Snippets', 'code-snippets'),
598
+ __('Manage Snippets', 'code-snippets'),
599
+ 'manage_network_snippets',
600
+ apply_filters( 'code_snippets_manage_url', 'snippets' ),
601
+ array( $this, 'display_admin_manage' )
602
+ );
603
+
604
+ $this->admin_single = add_submenu_page(
605
+ apply_filters( 'code_snippets_manage_url', 'snippets' ),
606
+ __('Add New Snippet', 'code-snippets'),
607
+ __('Add New', 'code-snippets'),
608
+ 'install_network_snippets',
609
+ apply_filters( 'code_snippets_single_url', 'snippet' ),
610
+ array( $this, 'display_admin_single' )
611
+ );
612
+
613
+ add_action( "load-$this->admin_manage", array( $this, 'load_admin_manage' ) );
614
+ add_action( "load-$this->admin_single", array( $this, 'load_admin_single' ) );
615
+
616
+ add_action( "admin_print_styles-$this->admin_single", array( $this, 'load_editor_styles' ) );
617
+ add_action( "admin_print_scripts-$this->admin_single", array( $this, 'load_editor_scripts' ) );
618
+ }
619
+
620
+ /**
621
+ * Add an Import Snippets page to the network admin menu
622
+ * We need to do this as there is no Tools menu in the network
623
+ * admin, and so we cannot register an importer
624
+ *
625
+ * @since Code Snippets 1.6
626
+ * @access private
627
+ *
628
+ * @uses add_submenu_page() To register the menu page
629
+ * @uses apply_filters() To retrieve the corrent menu slug
630
+ * @uses add_action() To enqueue scripts and styles
631
+ * @return void
632
+ */
633
+ function add_import_admin_menu() {
634
+
635
+ $this->admin_import = add_submenu_page(
636
+ apply_filters( 'code_snippets_manage_url', 'snippets' ),
637
+ __('Import Snippets', 'code-snippets'),
638
+ __('Import', 'code-snippets'),
639
+ 'import_snippets',
640
+ 'import-code-snippets',
641
+ array( $this, 'display_admin_import' )
642
+ );
643
+
644
+ $this->admin_import_url = self_admin_url( 'admin.php?page=import-code-snippets' );
645
+ add_action( "admin_print_styles-$this->admin_import", array( $this, 'load_stylesheet' ) );
646
+ add_action( "load-$this->admin_import", array( $this, 'load_admin_import' ) );
647
+ }
648
+
649
+ /**
650
+ * Enqueue the icon stylesheet
651
+ *
652
+ * @since Code Snippets 1.0
653
+ * @access private
654
+ *
655
+ * @uses wp_enqueue_style() To add the stylesheet to the queue
656
+ *
657
+ * @return void
658
+ */
659
+ function load_admin_icon_style() {
660
+
661
+ wp_enqueue_style(
662
+ 'icon-snippets',
663
+ plugins_url( 'assets/icon.css', $this->file ),
664
+ false,
665
+ $this->version
666
+ );
667
+ }
668
+
669
+ /**
670
+ * Registers and loads the code editor's scripts
671
+ *
672
+ * @since Code Snippets 1.4
673
+ * @access private
674
+ *
675
+ * @uses wp_register_script()
676
+ * @uses wp_enqueue_style() To add the scripts to the queue
677
+ *
678
+ * @return void
679
+ */
680
+ function load_editor_scripts() {
681
+
682
+ /* CodeMirror package version */
683
+ $version = '3.0';
684
+
685
+ /* CodeMirror base framework */
686
+
687
+ wp_register_script(
688
+ 'codemirror',
689
+ plugins_url( 'assets/codemirror.js', $this->file ),
690
+ false,
691
+ $version
692
+ );
693
+
694
+ /* CodeMirror modes */
695
+
696
+ $modes = array( 'php', 'xml', 'javascript', 'css', 'clike', 'htmlmixed' );
697
+
698
+ foreach ( $modes as $mode ) {
699
+
700
+ wp_register_script(
701
+ "codemirror-mode-$mode",
702
+ plugins_url( "assets/mode/$mode.js", $this->file ),
703
+ array( 'codemirror' ),
704
+ $version
705
+ );
706
+ }
707
+
708
+ /* CodeMirror utilities */
709
+
710
+ $utils = array( 'dialog', 'searchcursor', 'search', 'matchbrackets' );
711
+
712
+ foreach ( $utils as $util ) {
713
+
714
+ wp_register_script(
715
+ "codemirror-util-$util",
716
+ plugins_url( "assets/util/$util.js", $this->file ),
717
+ array( 'codemirror' ),
718
+ $version
719
+ );
720
+ }
721
+
722
+ /* Enqueue the registered scripts */
723
+
724
+ wp_enqueue_script( array(
725
+ 'codemirror-util-matchbrackets',
726
+ 'codemirror-mode-htmlmixed',
727
+ 'codemirror-mode-xml',
728
+ 'codemirror-mode-js',
729
+ 'codemirror-mode-css',
730
+ 'codemirror-mode-clike',
731
+ 'codemirror-mode-php',
732
+ 'codemirror-util-search',
733
+ ) );
734
+ }
735
+
736
+ /**
737
+ * Registers and loads the code editor's styles
738
+ *
739
+ * @since Code Snippets 1.4
740
+ * @access private
741
+ *
742
+ * @uses wp_register_style()
743
+ * @uses wp_enqueue_style() To add the stylesheets to the queue
744
+ *
745
+ * @return void
746
+ */
747
+ function load_editor_styles() {
748
+
749
+ /* CodeMirror package version */
750
+ $version = '3.0';
751
+
752
+ /* CodeMirror base framework */
753
+
754
+ wp_register_style(
755
+ 'codemirror',
756
+ plugins_url( 'assets/codemirror.css', $this->file ),
757
+ false,
758
+ $version
759
+ );
760
+
761
+ /* CodeMirror utilities */
762
+
763
+ wp_register_style(
764
+ 'codemirror-util-dialog',
765
+ plugins_url( 'assets/util/dialog.css', $this->file ),
766
+ array( 'codemirror' ),
767
+ $version
768
+ );
769
+
770
+ /* Enqueue the registered stylesheets */
771
+
772
+ wp_enqueue_style( array(
773
+ 'codemirror',
774
+ 'codemirror-util-dialog',
775
+ ) );
776
+
777
+ }
778
+
779
+ /**
780
+ * Activates a snippet
781
+ *
782
+ * @since Code Snippets 1.5
783
+ * @access public
784
+ *
785
+ * @uses $wpdb To set the snippets' active status
786
+ *
787
+ * @param array $ids The ids of the snippets to activate
788
+ * @param string $scope Are the snippets multisite-wide or site-wide?
789
+ * @return void
790
+ */
791
+ public function activate( $ids, $scope = '' ) {
792
+ global $wpdb;
793
+
794
+ $ids = (array) $ids;
795
+
796
+ $table = $this->get_table_name( $scope );
797
+
798
+ foreach( $ids as $id ) {
799
+ $wpdb->update(
800
+ $table,
801
+ array( 'active' => '1' ),
802
+ array( 'id' => $id ),
803
+ array( '%d' ),
804
+ array( '%d' )
805
+ );
806
+ }
807
+ }
808
+
809
+ /**
810
+ * Deactivates selected snippets
811
+ *
812
+ * @since Code Snippets 1.5
813
+ * @access public
814
+ *
815
+ * @uses $wpdb To set the snippets' active status
816
+ *
817
+ * @param array $ids The IDs of the snippets to deactivate
818
+ * @param string $scope Are the snippets multisite-wide or site-wide?
819
+ * @return void
820
+ */
821
+ public function deactivate( $ids, $scope = '' ) {
822
+ global $wpdb;
823
+
824
+ $ids = (array) $ids;
825
+ $recently_active = array();
826
+
827
+ $table = $this->get_table_name( $scope );
828
+
829
+ foreach( $ids as $id ) {
830
+ $wpdb->update(
831
+ $table,
832
+ array( 'active' => '0' ),
833
+ array( 'id' => $id ),
834
+ array( '%d' ),
835
+ array( '%d' )
836
+ );
837
+ $recently_active = array( $id => time() ) + (array) $recently_active;
838
+ }
839
+
840
+ if ( $table == $this->ms_table )
841
+ update_site_option(
842
+ 'recently_activated_snippets',
843
+ $recently_active + (array) get_site_option( 'recently_activated_snippets' )
844
+ );
845
+ else
846
+ update_option(
847
+ 'recently_activated_snippets',
848
+ $recently_active + (array) get_option( 'recently_activated_snippets' )
849
+ );
850
+ }
851
+
852
+ /**
853
+ * Deletes a snippet from the database
854
+ *
855
+ * @since Code Snippets 1.5
856
+ * @access public
857
+ *
858
+ * @uses $wpdb To access the database
859
+ * @uses $this->get_table_name() To dynamically retrieve the name of the snippet table
860
+ */
861
+ public function delete_snippet( $id, $scope = '' ) {
862
+ global $wpdb;
863
+
864
+ $table = $this->get_table_name( $scope );
865
+ $id = intval( $id );
866
+
867
+ $wpdb->query( "DELETE FROM $table WHERE id='$id' LIMIT 1" );
868
+ }
869
+
870
+ /**
871
+ * Saves a snippet to the database.
872
+ *
873
+ * @since Code Snippets 1.5
874
+ * @access public
875
+ *
876
+ * @uses $wpdb To update/add the snippet to the database
877
+ * @uses $this->get_table_name() To dynamically retrieve the name of the snippet table
878
+ *
879
+ * @param array $snippet The snippet to add/update to the database
880
+ * @return int|bool The ID of the snippet on success, false on failure
881
+ */
882
+ public function save_snippet( $snippet, $scope = '' ) {
883
+ global $wpdb;
884
+
885
+ $name = mysql_real_escape_string( htmlspecialchars( $snippet['name'] ) );
886
+ $description = mysql_real_escape_string( htmlspecialchars( $snippet['description'] ) );
887
+ $code = mysql_real_escape_string( htmlspecialchars( $snippet['code'] ) );
888
+
889
+ if ( empty( $name ) or empty( $code ) )
890
+ return false;
891
+
892
+ $table = $this->get_table_name( $scope );
893
+
894
+ if ( isset( $snippet['id'] ) && ( intval( $snippet['id'] ) != 0 ) ) {
895
+ $wpdb->query( "UPDATE $table SET
896
+ name='$name',
897
+ description='$description',
898
+ code='$code' WHERE id=" . intval( $snippet['id'] ) . "
899
+ LIMIT 1"
900
+ );
901
+ return intval( $snippet['id'] );
902
+ } else {
903
+ $wpdb->query(
904
+ "INSERT INTO $table(name,description,code)
905
+ VALUES ('$name','$description','$code')"
906
+ );
907
+ return $wpdb->insert_id;
908
+ }
909
+ }
910
+
911
+ /**
912
+ * Imports snippets from an XML file
913
+ *
914
+ * @since Code Snippets 1.5
915
+ * @access public
916
+ *
917
+ * @uses $this->save_snippet() To add the snippets to the database
918
+ *
919
+ * @param file $file The path to the XML file to import
920
+ * @param string $scope Import into network-wide table or site-wide table?
921
+ * @return mixed The number of snippets imported on success, false on failure
922
+ */
923
+ public function import( $file, $scope = '' ) {
924
+
925
+ if ( ! file_exists( $file ) || ! is_file( $file ) )
926
+ return false;
927
+
928
+ $xml = simplexml_load_file( $file );
929
+
930
+ foreach ( $xml->children() as $child ) {
931
+ $this->save_snippet( array(
932
+ 'name' => $child->name,
933
+ 'description' => $child->description,
934
+ 'code' => $child->code,
935
+ ), $scope );
936
+ }
937
+ return $xml->count();
938
+ }
939
+
940
+ /**
941
+ * Exports snippets as an XML file
942
+ *
943
+ * @since Code Snippets 1.5
944
+ * @access public
945
+ *
946
+ * @uses code_snippets_export() To export selected snippets
947
+ * @uses $this->get_table_name() To dynamically retrieve the name of the snippet table
948
+ *
949
+ * @param array $id An array if the IDs of the snippets to export
950
+ * @param string $scope Is the snippet a network-wide or site-wide snippet?
951
+ * @return void
952
+ */
953
+ public function export( $ids, $scope = '' ) {
954
+
955
+ $table = $this->get_table_name( $scope );
956
+
957
+ if ( ! function_exists( 'code_snippets_export' ) )
958
+ require_once $this->plugin_dir . 'includes/export.php';
959
+
960
+ code_snippets_export( $ids, 'xml', $table );
961
+ }
962
+
963
+ /**
964
+ * Exports snippets as a PHP file
965
+ *
966
+ * @since Code Snippets 1.5
967
+ * @access public
968
+ *
969
+ * @uses code_snippets_export() To export selected snippets
970
+ * @uses $this->get_table_name() To dynamically retrieve the name of the snippet table
971
+ *
972
+ * @param array $id An array if the IDs of the snippets to export
973
+ * @param string $scope Is the snippet a network-wide or site-wide snippet?
974
+ * @return void
975
+ */
976
+ public function export_php( $ids, $scope = '' ) {
977
+
978
+ $table = $this->get_table_name( $scope );
979
+
980
+ if ( ! function_exists( 'code_snippets_export' ) )
981
+ require_once $this->plugin_dir . 'includes/export.php';
982
+
983
+ code_snippets_export( $ids, 'php', $table );
984
+ }
985
+
986
+ /**
987
+ * Execute a snippet
988
+ *
989
+ * Code must NOT be escaped, as
990
+ * it will be executed directly
991
+ *
992
+ * @since Code Snippets 1.5
993
+ * @access public
994
+ *
995
+ * @param string $code The snippet code to execute
996
+ * @return $result The result of the code execution
997
+ */
998
+ public function execute_snippet( $code ) {
999
+ ob_start();
1000
+ $result = eval( $code );
1001
+ $output = ob_get_contents();
1002
+ ob_end_clean();
1003
+ return $result;
1004
+ }
1005
+
1006
+ /**
1007
+ * Replaces the text 'Add New Snippet' with 'Edit Snippet'
1008
+ *
1009
+ * @since Code Snippets 1.1
1010
+ * @access private
1011
+ *
1012
+ * @param $title The current page title
1013
+ * @return $title The modified page title
1014
+ */
1015
+ function admin_single_title( $title ) {
1016
+ return str_ireplace(
1017
+ __('Add New Snippet', 'code-snippets'),
1018
+ __('Edit Snippet', 'code-snippets'),
1019
+ $title
1020
+ );
1021
+ }
1022
+
1023
+ /**
1024
+ * Processes any action command and loads the help tabs
1025
+ *
1026
+ * @since Code Snippets 1.0
1027
+ * @access private
1028
+ *
1029
+ * @uses $wpdb To activate, deactivate and delete snippets
1030
+ *
1031
+ * @return void
1032
+ */
1033
+ function load_admin_manage() {
1034
+ global $wpdb;
1035
+
1036
+ $this->create_tables(); // create the snippet tables if they do not exist
1037
+
1038
+ if ( isset( $_GET['action'], $_GET['id'] ) ) :
1039
+
1040
+ $id = intval( $_GET['id'] );
1041
+
1042
+ $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'action', 'id' ) );
1043
+
1044
+ if ( 'activate' == $_GET['action'] ) {
1045
+ $this->activate( $id );
1046
+ wp_redirect( add_query_arg( 'activate', true ) );
1047
+ }
1048
+ elseif ( 'deactivate' == $_GET['action'] ) {
1049
+ $this->deactivate( $id );
1050
+ wp_redirect( add_query_arg( 'deactivate', true ) );
1051
+ }
1052
+ elseif ( 'delete' == $_GET['action'] ) {
1053
+ $this->delete_snippet( $id );
1054
+ wp_redirect( add_query_arg( 'delete', true ) );
1055
+ }
1056
+ elseif ( 'export' == $_GET['action'] ) {
1057
+ $this->export( $id );
1058
+ }
1059
+ elseif ( 'export-php' == $_GET['action'] ) {
1060
+ $this->export_php( $id );
1061
+ }
1062
+
1063
+ endif;
1064
+
1065
+ include $this->plugin_dir . 'includes/help/manage.php'; // Load the help tabs
1066
+
1067
+ /**
1068
+ * Initialize the snippet table class
1069
+ */
1070
+ require_once $this->plugin_dir . 'includes/class-list-table.php';
1071
+
1072
+ global $code_snippets_list_table;
1073
+ $code_snippets_list_table = new Code_Snippets_List_Table();
1074
+ $code_snippets_list_table->prepare_items();
1075
+ }
1076
+
1077
+ /**
1078
+ * Loads the help tabs for the Edit Snippets page
1079
+ *
1080
+ * @since Code Snippets 1.0
1081
+ * @access private
1082
+ *
1083
+ * @uses $wpdb To save the posted snippet to the database
1084
+ * @uses wp_redirect To pass the results to the page
1085
+ *
1086
+ * @return void
1087
+ */
1088
+ function load_admin_single() {
1089
+
1090
+ $this->create_tables(); // create the snippet tables if they do not exist
1091
+
1092
+ if ( isset( $_REQUEST['save_snippet'] ) ) {
1093
+
1094
+ if ( isset( $_REQUEST['snippet_id'] ) ) {
1095
+ $result = $this->save_snippet( array(
1096
+ 'name' => $_REQUEST['snippet_name'],
1097
+ 'description' => $_REQUEST['snippet_description'],
1098
+ 'code' => $_REQUEST['snippet_code'],
1099
+ 'id' => $_REQUEST['snippet_id'],
1100
+ ) );
1101
+ } else {
1102
+ $result = $this->save_snippet( array(
1103
+ 'name' => $_REQUEST['snippet_name'],
1104
+ 'description' => $_REQUEST['snippet_description'],
1105
+ 'code' => $_REQUEST['snippet_code'],
1106
+ ) );
1107
+ }
1108
+
1109
+ $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'added', 'updated', 'invalid' ) );
1110
+
1111
+ if ( ! $result || $result < 1 ) {
1112
+ wp_redirect( add_query_arg( 'invalid', true ) );
1113
+ }
1114
+ elseif ( isset( $_REQUEST['snippet_id'] ) ) {
1115
+ wp_redirect( add_query_arg( array(
1116
+ 'edit' => $result,
1117
+ 'updated' => true
1118
+ ) ) );
1119
+ }
1120
+ else {
1121
+ wp_redirect( add_query_arg( array(
1122
+ 'edit' => $result,
1123
+ 'added' => true
1124
+ ) ) );
1125
+ }
1126
+ }
1127
+
1128
+ if ( isset( $_GET['edit'] ) )
1129
+ add_filter( 'admin_title', array( $this, 'admin_single_title' ) );
1130
+
1131
+ include $this->plugin_dir . 'includes/help/single.php'; // Load the help tabs
1132
+ }
1133
+
1134
+ /**
1135
+ * Processes import files and loads the help tabs for the Import Snippets page
1136
+ *
1137
+ * @since Code Snippets 1.3
1138
+ *
1139
+ * @uses $this->import() To process the import file
1140
+ * @uses wp_redirect() To pass the import results to the page
1141
+ * @uses add_query_arg() To append the results to the current URI
1142
+ *
1143
+ * @return void
1144
+ */
1145
+ function load_admin_import() {
1146
+
1147
+ $this->create_tables(); // create the snippet tables if they do not exist
1148
+
1149
+ if ( isset( $_FILES['code_snippets_import_file']['tmp_name'] ) ) {
1150
+ $count = $this->import( $_FILES['code_snippets_import_file']['tmp_name'] );
1151
+ if ( $count ) {
1152
+ wp_redirect( add_query_arg( 'imported', $count ) );
1153
+ }
1154
+ }
1155
+ require_once $this->plugin_dir . 'includes/help/import.php';
1156
+ }
1157
+
1158
+ /**
1159
+ * Displays the Manage Snippets page
1160
+ *
1161
+ * @since Code Snippets 1.0
1162
+ * @access private
1163
+ *
1164
+ * @return void
1165
+ */
1166
+ function display_admin_manage() {
1167
+ require_once $this->plugin_dir . 'includes/admin/manage.php';
1168
+ }
1169
+
1170
+ /**
1171
+ * Displays the Add New/Edit Snippet page
1172
+ *
1173
+ * @since Code Snippets 1.0
1174
+ * @access private
1175
+ *
1176
+ * @return void
1177
+ */
1178
+ function display_admin_single() {
1179
+ require_once $this->plugin_dir . 'includes/admin/single.php';
1180
+ }
1181
+
1182
+ /**
1183
+ * Displays the Import Snippets page
1184
+ *
1185
+ * @since Code Snippets 1.3
1186
+ * @access private
1187
+ *
1188
+ * @return void
1189
+ */
1190
+ function display_admin_import() {
1191
+ require_once $this->plugin_dir . 'includes/admin/import.php';
1192
+ }
1193
+
1194
+ /**
1195
+ * Adds a link pointing to the Manage Snippets page
1196
+ *
1197
+ * @since Code Snippets 1.0
1198
+ * @access private
1199
+ *
1200
+ * @return void
1201
+ */
1202
+ function settings_link( $links ) {
1203
+ array_unshift( $links, sprintf(
1204
+ '<a href="%1$s" title="%2$s">%3$s</a>',
1205
+ $this->admin_manage_url,
1206
+ __('Manage your existing snippets', 'code-snippets'),
1207
+ __('Manage', 'code-snippets')
1208
+ ) );
1209
+ return $links;
1210
+ }
1211
+
1212
+ /**
1213
+ * Adds extra links related to the plugin
1214
+ *
1215
+ * @since Code Snippets 1.2
1216
+ * @access private
1217
+ */
1218
+ function plugin_meta( $links, $file ) {
1219
+
1220
+ if ( $file != $this->basename ) return $links;
1221
+
1222
+ $format = '<a href="%1$s" title="%2$s">%3$s</a>';
1223
+
1224
+ return array_merge( $links, array(
1225
+ sprintf( $format,
1226
+ 'http://wordpress.org/extend/plugins/code-snippets/',
1227
+ __('Visit the WordPress.org plugin page', 'code-snippets'),
1228
+ __('About', 'code-snippets')
1229
+ ),
1230
+ sprintf( $format,
1231
+ 'http://wordpress.org/support/plugin/code-snippets/',
1232
+ __('Visit the support forums', 'code-snippets'),
1233
+ __('Support', 'code-snippets')
1234
+ ),
1235
+ sprintf( $format,
1236
+ 'http://code-snippets.bungeshea.com/donate/',
1237
+ __('Support this plugin\'s development', 'code-snippets'),
1238
+ __('Donate', 'code-snippets')
1239
+ )
1240
+ ) );
1241
+ }
1242
+
1243
+ /**
1244
+ * Run the active snippets
1245
+ *
1246
+ * @since Code Snippets 1.0
1247
+ * @access private
1248
+ *
1249
+ * @uses $wpdb To grab the active snippets from the database
1250
+ * @uses $this->execute_snippet() To execute a snippet
1251
+ * @uses $this->get_table_name() To retrieve the name of the snippet table
1252
+ *
1253
+ * @param string $scope Execute network-wide or site-wide snippets?
1254
+ * @return void
1255
+ */
1256
+ function run_snippets() {
1257
+
1258
+ if ( defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE )
1259
+ return;
1260
+
1261
+ global $wpdb;
1262
+
1263
+ // grab the active snippets from the database
1264
+ $active_snippets = $wpdb->get_results( "SELECT code FROM $this->table WHERE active=1;" );
1265
+
1266
+ if ( is_multisite() ) {
1267
+ $active_snippets = array_merge(
1268
+ $wpdb->get_results( "SELECT code FROM $this->ms_table WHERE active=1;" ),
1269
+ $active_snippets
1270
+ );
1271
+ }
1272
+
1273
+ if ( count( $active_snippets ) ) {
1274
+ foreach( $active_snippets as $snippet ) {
1275
+ // execute the php code
1276
+ $this->execute_snippet( htmlspecialchars_decode( stripslashes( $snippet->code ) ) );
1277
+ }
1278
+ }
1279
+ }
1280
+ }
1281
+
1282
+ /**
1283
+ * The global variable in which the Code Snippets class is stored
1284
+ *
1285
+ * @since Code Snippets 1.0
1286
+ * @access public
1287
+ */
1288
+ global $code_snippets;
1289
+ $code_snippets = new Code_Snippets;
1290
+
1291
+ /* set up a pointer in the old variable (for backwards-compatibility) */
1292
+ global $cs;
1293
+ $cs = &$code_snippets;
1294
+
1295
+ endif; // class exists check
1296
+
1297
+ register_uninstall_hook( $code_snippets->file, 'code_snippets_uninstall' );
1298
+
1299
+ /**
1300
+ * Cleans up data created by the Code_Snippets class
1301
+ *
1302
+ * @since Code Snippets 1.2
1303
+ * @access private
1304
+ *
1305
+ * @uses $wpdb To remove tables from the database
1306
+ * @uses $code_snippets To find out which table to drop
1307
+ * @uses is_multisite() To check the type of installation
1308
+ * @uses switch_to_blog() To switch between blogs
1309
+ * @uses restore_current_blog() To switch between blogs
1310
+ * @uses delete_option() To remove site options
1311
+ *
1312
+ * @return void
1313
+ */
1314
+ function code_snippets_uninstall() {
1315
+ global $wpdb, $code_snippets;
1316
+ if ( is_multisite() ) {
1317
+ $blogs = $wpdb->get_results( "SELECT blog_id FROM $wpdb->blogs", ARRAY_A );
1318
+ if ( $blogs ) {
1319
+ foreach( $blogs as $blog ) {
1320
+ switch_to_blog( $blog['blog_id'] );
1321
+ $table = apply_filters( 'code_snippets_table', $wpdb->prefix . 'snippets' );
1322
+ $wpdb->query( "DROP TABLE IF EXISTS $table" );
1323
+ delete_option( 'cs_db_version' );
1324
+ delete_option( 'recently_activated_snippets' );
1325
+ $code_snippets->remove_caps();
1326
+ }
1327
+ restore_current_blog();
1328
+ }
1329
+ $wpdb->query( "DROP TABLE IF EXISTS $code_snippets->ms_table" );
1330
+ delete_site_option( 'recently_activated_snippets' );
1331
+ $code_snippets->remove_caps( 'multisite' );
1332
+ } else {
1333
+ $wpdb->query( "DROP TABLE IF EXISTS $code_snippets->table" );
1334
+ delete_option( 'recently_activated_snippets' );
1335
+ delete_option( 'cs_db_version' );
1336
+ $code_snippets->remove_caps();
1337
+ }
1338
+
1339
+ delete_site_option( 'code_snippets_version' );
1340
  }
css/codemirror.css DELETED
@@ -1,173 +0,0 @@
1
- .CodeMirror {
2
- line-height: 1em;
3
- font-family: monospace;
4
-
5
- /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */
6
- position: relative;
7
- /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */
8
- overflow: hidden;
9
- }
10
-
11
- .CodeMirror-scroll {
12
- overflow: auto;
13
- height: 300px;
14
- /* This is needed to prevent an IE[67] bug where the scrolled content
15
- is visible outside of the scrolling box. */
16
- position: relative;
17
- outline: none;
18
- }
19
-
20
- /* Vertical scrollbar */
21
- .CodeMirror-scrollbar {
22
- position: absolute;
23
- right: 0; top: 0;
24
- overflow-x: hidden;
25
- overflow-y: scroll;
26
- z-index: 5;
27
- }
28
- .CodeMirror-scrollbar-inner {
29
- /* This needs to have a nonzero width in order for the scrollbar to appear
30
- in Firefox and IE9. */
31
- width: 1px;
32
- }
33
- .CodeMirror-scrollbar.cm-sb-overlap {
34
- /* Ensure that the scrollbar appears in Lion, and that it overlaps the content
35
- rather than sitting to the right of it. */
36
- position: absolute;
37
- z-index: 1;
38
- float: none;
39
- right: 0;
40
- min-width: 12px;
41
- }
42
- .CodeMirror-scrollbar.cm-sb-nonoverlap {
43
- min-width: 12px;
44
- }
45
- .CodeMirror-scrollbar.cm-sb-ie7 {
46
- min-width: 18px;
47
- }
48
-
49
- .CodeMirror-gutter {
50
- position: absolute; left: 0; top: 0;
51
- z-index: 10;
52
- background-color: #f7f7f7;
53
- border-right: 1px solid #eee;
54
- min-width: 2em;
55
- height: 100%;
56
- }
57
- .CodeMirror-gutter-text {
58
- color: #aaa;
59
- text-align: right;
60
- padding: .4em .2em .4em .4em;
61
- white-space: pre !important;
62
- cursor: default;
63
- }
64
- .CodeMirror-lines {
65
- padding: .4em;
66
- white-space: pre;
67
- cursor: text;
68
- }
69
-
70
- .CodeMirror pre {
71
- -moz-border-radius: 0;
72
- -webkit-border-radius: 0;
73
- -o-border-radius: 0;
74
- border-radius: 0;
75
- border-width: 0; margin: 0; padding: 0; background: transparent;
76
- font-family: inherit;
77
- font-size: inherit;
78
- padding: 0; margin: 0;
79
- white-space: pre;
80
- word-wrap: normal;
81
- line-height: inherit;
82
- color: inherit;
83
- }
84
-
85
- .CodeMirror-wrap pre {
86
- word-wrap: break-word;
87
- white-space: pre-wrap;
88
- word-break: normal;
89
- }
90
- .CodeMirror-wrap .CodeMirror-scroll {
91
- overflow-x: hidden;
92
- }
93
-
94
- .CodeMirror textarea {
95
- outline: none !important;
96
- }
97
-
98
- .CodeMirror pre.CodeMirror-cursor {
99
- z-index: 10;
100
- position: absolute;
101
- visibility: hidden;
102
- border-left: 1px solid black;
103
- border-right: none;
104
- width: 0;
105
- }
106
- .cm-keymap-fat-cursor pre.CodeMirror-cursor {
107
- width: auto;
108
- border: 0;
109
- background: transparent;
110
- background: rgba(0, 200, 0, .4);
111
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800);
112
- }
113
- /* Kludge to turn off filter in ie9+, which also accepts rgba */
114
- .cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) {
115
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
116
- }
117
- .CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
118
- .CodeMirror-focused pre.CodeMirror-cursor {
119
- visibility: visible;
120
- }
121
-
122
- div.CodeMirror-selected { background: #d9d9d9; }
123
- .CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
124
-
125
- .CodeMirror-searching {
126
- background: #ffa;
127
- background: rgba(255, 255, 0, .4);
128
- }
129
-
130
- /* Default theme */
131
-
132
- .cm-s-default span.cm-keyword {color: #708;}
133
- .cm-s-default span.cm-atom {color: #219;}
134
- .cm-s-default span.cm-number {color: #164;}
135
- .cm-s-default span.cm-def {color: #00f;}
136
- .cm-s-default span.cm-variable {color: black;}
137
- .cm-s-default span.cm-variable-2 {color: #05a;}
138
- .cm-s-default span.cm-variable-3 {color: #085;}
139
- .cm-s-default span.cm-property {color: black;}
140
- .cm-s-default span.cm-operator {color: black;}
141
- .cm-s-default span.cm-comment {color: #a50;}
142
- .cm-s-default span.cm-string {color: #a11;}
143
- .cm-s-default span.cm-string-2 {color: #f50;}
144
- .cm-s-default span.cm-meta {color: #555;}
145
- .cm-s-default span.cm-error {color: #f00;}
146
- .cm-s-default span.cm-qualifier {color: #555;}
147
- .cm-s-default span.cm-builtin {color: #30a;}
148
- .cm-s-default span.cm-bracket {color: #cc7;}
149
- .cm-s-default span.cm-tag {color: #170;}
150
- .cm-s-default span.cm-attribute {color: #00c;}
151
- .cm-s-default span.cm-header {color: blue;}
152
- .cm-s-default span.cm-quote {color: #090;}
153
- .cm-s-default span.cm-hr {color: #999;}
154
- .cm-s-default span.cm-link {color: #00c;}
155
-
156
- span.cm-header, span.cm-strong {font-weight: bold;}
157
- span.cm-em {font-style: italic;}
158
- span.cm-emstrong {font-style: italic; font-weight: bold;}
159
- span.cm-link {text-decoration: underline;}
160
-
161
- span.cm-invalidchar {color: #f00;}
162
-
163
- div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
164
- div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
165
-
166
- @media print {
167
-
168
- /* Hide the cursor when printing */
169
- .CodeMirror pre.CodeMirror-cursor {
170
- visibility: hidden;
171
- }
172
-
173
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/admin/import.php CHANGED
@@ -1,27 +1,38 @@
1
- <?php if( isset( $_REQUEST['imported'] ) && intval( $_REQUEST['imported'] ) != 0 ) : ?>
2
- <div id="message" class="updated fade"><p><?php
3
- printf(
4
- _n(
5
- 'Imported <strong>%s</strong> snippet.',
6
- 'Imported <strong>%s</strong> snippets.',
7
- $_REQUEST['imported'],
8
- 'code-snippets'
9
- ),
10
- $_REQUEST['imported']
11
- );
12
- ?></p></div>
13
- <?php endif; ?>
 
 
 
 
 
 
 
14
  <div class="wrap">
15
  <?php screen_icon(); ?>
16
  <h2><?php _e('Import Snippets', 'code-snippets'); ?></h2>
 
17
  <div class="narrow">
 
18
  <p><?php _e('Howdy! Upload your Code Snippets export file and we&#8217;ll import the snippets to this site.', 'code-snippets' ); ?></p>
 
19
  <p><?php printf( __('You will need to go to the <a href="%s">Manage Snippets</a> page to activate the imported snippets.', 'code-snippets'), $this->admin_manage_url ); ?></p>
 
20
  <p><?php _e('Choose a Code Snippets (.xml) file to upload, then click Upload file and import.', 'code-snippets'); ?></p>
21
- <form enctype="multipart/form-data" id="import-upload-form" method="post" action="" name="cs_import">
22
  <p>
23
  <label for="upload"><?php _e('Choose a file from your computer:', 'code-snippets' ); ?></label> <?php _e('(Maximum size: 8MB)', 'code-snippets'); ?>
24
- <input type="file" id="upload" name="cs_import_file" size="25" accept="text/xml" />
25
  <input type="hidden" name="action" value="save" />
26
  <input type="hidden" name="max_file_size" value="8388608" />
27
  </p>
1
+ <?php
2
+
3
+ if ( isset( $_REQUEST['imported'] ) && intval( $_REQUEST['imported'] ) != 0 ) {
4
+
5
+ echo '<div id="message" class="updated fade"><p>';
6
+
7
+ printf(
8
+ _n(
9
+ 'Imported <strong>%d</strong> snippet.',
10
+ 'Imported <strong>%d</strong> snippets.',
11
+ $_REQUEST['imported'],
12
+ 'code-snippets'
13
+ ),
14
+ $_REQUEST['imported']
15
+ );
16
+
17
+ echo '</p></div>';
18
+ }
19
+
20
+ ?>
21
  <div class="wrap">
22
  <?php screen_icon(); ?>
23
  <h2><?php _e('Import Snippets', 'code-snippets'); ?></h2>
24
+
25
  <div class="narrow">
26
+
27
  <p><?php _e('Howdy! Upload your Code Snippets export file and we&#8217;ll import the snippets to this site.', 'code-snippets' ); ?></p>
28
+
29
  <p><?php printf( __('You will need to go to the <a href="%s">Manage Snippets</a> page to activate the imported snippets.', 'code-snippets'), $this->admin_manage_url ); ?></p>
30
+
31
  <p><?php _e('Choose a Code Snippets (.xml) file to upload, then click Upload file and import.', 'code-snippets'); ?></p>
32
+ <form enctype="multipart/form-data" id="import-upload-form" method="post" action="" name="code_snippets_import">
33
  <p>
34
  <label for="upload"><?php _e('Choose a file from your computer:', 'code-snippets' ); ?></label> <?php _e('(Maximum size: 8MB)', 'code-snippets'); ?>
35
+ <input type="file" id="upload" name="code_snippets_import_file" size="25" accept="text/xml" />
36
  <input type="hidden" name="action" value="save" />
37
  <input type="hidden" name="max_file_size" value="8388608" />
38
  </p>
includes/admin/manage.php CHANGED
@@ -1,15 +1,15 @@
1
  <?php
2
- if( ! class_exists( 'Code_Snippets' ) ) exit;
3
 
4
  require_once $this->plugin_dir . 'includes/class-list-table.php';
5
 
6
- global $cs_list_table;
7
  $screen = get_current_screen();
8
  ?>
9
- <?php if( defined( 'CS_SAFE_MODE' ) ) if( CS_SAFE_MODE ) : ?>
10
- <div class="error"><p><strong>Warning:</strong> Safe mode is active and snippets will not execute! Remove the <code>CS_SAFE_MODE</code> constant from <code>wp-config.php</code> to turn off safe mode. <a href="http://cs.bungeshea.com/docs/safe-mode/" target="_blank">Help</a></p></div>
11
  <?php endif; ?>
12
-
13
  <?php if ( isset($_GET['activate']) ) : ?>
14
  <div id="message" class="updated"><p><?php _e('Snippet <strong>activated</strong>.', 'code-snippets') ?></p></div>
15
  <?php elseif (isset($_GET['activate-multi'])) : ?>
@@ -23,24 +23,24 @@ $screen = get_current_screen();
23
  <?php elseif (isset($_GET['delete-multi'])) : ?>
24
  <div id="message" class="updated"><p><?php _e('Selected snippets <strong>deleted</strong>.', 'code-snippets'); ?></p></div>
25
  <?php endif; ?>
26
-
27
  <div class="wrap">
28
  <?php screen_icon(); ?>
29
  <h2><?php _e('Snippets', 'code-snippets'); ?>
30
- <?php if( current_user_can( $screen->is_network ? 'install_network_snippets' : 'install_snippets' ) ) { ?>
31
  <a href="<?php echo $this->admin_single_url; ?>" class="add-new-h2"><?php echo esc_html_x('Add New', 'snippet', 'code-snippets'); ?></a>
32
  <?php }
33
- if( isset( $s ) && $s )
34
  printf( '<span class="subtitle">' . __('Search results for &#8220;%s&#8221;', 'code-snippets') . '</span>', esc_html( $s ) ); ?></h2>
35
-
36
- <?php $cs_list_table->views(); ?>
37
-
38
  <form method="get" action="">
39
  <input type="hidden" name="page" value="<?php echo $_REQUEST['page'] ?>" />
40
- <?php $cs_list_table->search_box( __( 'Search Installed Snippets', 'code-snippets' ), 'search_id' ); ?>
41
  </form>
42
  <form method="post" action="">
43
  <input type="hidden" name="page" value="<?php echo $_REQUEST['page'] ?>" />
44
- <?php $cs_list_table->display(); ?>
45
  </form>
46
  </div>
1
  <?php
2
+ if ( ! class_exists( 'Code_Snippets' ) ) exit;
3
 
4
  require_once $this->plugin_dir . 'includes/class-list-table.php';
5
 
6
+ global $code_snippets_list_table;
7
  $screen = get_current_screen();
8
  ?>
9
+ <?php if ( defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) : ?>
10
+ <div class="error"><p><strong>Warning:</strong> Safe mode is active and snippets will not execute! Remove the <code>CODE_SNIPPETS_SAFE_MODE</code> constant from <code>wp-config.php</code> to turn off safe mode. <a href="http://code-snippets.bungeshea.com/docs/safe-mode/" target="_blank">Help</a></p></div>
11
  <?php endif; ?>
12
+
13
  <?php if ( isset($_GET['activate']) ) : ?>
14
  <div id="message" class="updated"><p><?php _e('Snippet <strong>activated</strong>.', 'code-snippets') ?></p></div>
15
  <?php elseif (isset($_GET['activate-multi'])) : ?>
23
  <?php elseif (isset($_GET['delete-multi'])) : ?>
24
  <div id="message" class="updated"><p><?php _e('Selected snippets <strong>deleted</strong>.', 'code-snippets'); ?></p></div>
25
  <?php endif; ?>
26
+
27
  <div class="wrap">
28
  <?php screen_icon(); ?>
29
  <h2><?php _e('Snippets', 'code-snippets'); ?>
30
+ <?php if ( current_user_can( $screen->is_network ? 'install_network_snippets' : 'install_snippets' ) ) { ?>
31
  <a href="<?php echo $this->admin_single_url; ?>" class="add-new-h2"><?php echo esc_html_x('Add New', 'snippet', 'code-snippets'); ?></a>
32
  <?php }
33
+ if ( isset( $s ) && $s )
34
  printf( '<span class="subtitle">' . __('Search results for &#8220;%s&#8221;', 'code-snippets') . '</span>', esc_html( $s ) ); ?></h2>
35
+
36
+ <?php $code_snippets_list_table->views(); ?>
37
+
38
  <form method="get" action="">
39
  <input type="hidden" name="page" value="<?php echo $_REQUEST['page'] ?>" />
40
+ <?php $code_snippets_list_table->search_box( __( 'Search Installed Snippets', 'code-snippets' ), 'search_id' ); ?>
41
  </form>
42
  <form method="post" action="">
43
  <input type="hidden" name="page" value="<?php echo $_REQUEST['page'] ?>" />
44
+ <?php $code_snippets_list_table->display(); ?>
45
  </form>
46
  </div>
includes/admin/single.php CHANGED
@@ -1,33 +1,34 @@
1
  <?php
2
- if( ! class_exists( 'Code_Snippets' ) ) exit;
3
  global $wpdb;
4
 
 
5
  $screen = get_current_screen();
6
  $can_edit = current_user_can( $screen->is_network ? 'edit_network_snippets' : 'edit_snippets' );
7
  $can_install = current_user_can( $screen->is_network ? 'install_network_snippets' : 'install_snippets' );
8
 
9
- if( isset( $_REQUEST['edit'] ) && ! $can_edit )
10
- wp_die( __("Sorry, you're not allowed to edit snippets", 'code-snippets') );
11
-
12
- if( isset( $_REQUEST['edit'] ) )
13
  $edit_id = intval( $_REQUEST['edit'] );
14
  ?>
15
 
16
- <?php if( isset( $_REQUEST['invalid'] ) && $_REQUEST['invalid'] ) : ?>
17
  <div id="message" class="error fade"><p><?php _e('Please provide a name for the snippet and its code.', 'code-snippets'); ?></p></div>
18
- <?php elseif( isset( $_REQUEST['updated'] ) && $_REQUEST['updated'] ) : ?>
19
  <div id="message" class="updated fade"><p><?php _e('Snippet <strong>updated</strong>.', 'code-snippets'); ?></p></div>
20
- <?php elseif( isset( $_REQUEST['added'] ) && $_REQUEST['added'] ) : ?>
21
  <div id="message" class="updated fade"><p><?php _e('Snippet <strong>added</strong>.', 'code-snippets'); ?></p></div>
22
  <?php endif; ?>
23
 
24
  <div class="wrap">
25
  <?php screen_icon(); ?>
26
  <h2><?php
27
- if( isset( $edit_id ) ) {
28
  esc_html_e('Edit Snippet', 'code-snippets');
29
-
30
- if( $can_install )
31
  printf( ' <a href="%1$s" class="add-new-h2">%2$s</a>',
32
  $this->admin_single_url,
33
  esc_html('Add New', 'code-snippets')
@@ -36,21 +37,20 @@ if( isset( $_REQUEST['edit'] ) )
36
  _e('Add New Snippet', 'code-snippets');
37
  }
38
  ?></h2>
39
-
40
  <form method="post" action="" style="margin-top: 10px;">
41
- <?php if( isset( $edit_id ) ) : ?>
42
- <?php $snippet = $wpdb->get_row( "SELECT * FROM $this->table WHERE id = $edit_id" ); ?>
43
- <input type="hidden" name="snippet_id" value="<?php echo $snippet->id; ?>" />
44
- <?php else : ?>
45
- <?php
46
  // define a empty object (or one with default values)
47
  $snippet = new stdClass();
48
  $snippet->name = '';
49
  $snippet->description = '';
50
  $snippet->code = '';
51
- ?>
52
- <?php endif; ?>
53
-
54
  <div id="titlediv">
55
  <div id="titlewrap">
56
  <label for="title" style="display: none;"><?php esc_html_e('Name (short title)', 'code-snippets'); ?></label>
@@ -62,16 +62,16 @@ if( isset( $_REQUEST['edit'] ) )
62
  <h3 style="display: inline;"><?php esc_html_e('Code', 'code-snippets'); ?></h3>
63
  <span style="float: right;"><?php _e('Enter or paste the snippet code without the <code>&lt;?php</code> and <code>?&gt;</code> tags.', 'code-snippets'); ?></span>
64
  </label>
65
- <br />
66
- <textarea id="snippet_code" name="snippet_code" spellcheck="false" style="font-family: monospace; width:100%;"><?php echo stripslashes( $snippet->code ); ?></textarea>
67
- <br style="margin: 20px;" />
68
 
69
- <label for="description" style="text-align: center; margin: 10px auto;">
70
- <h3 style="display: inline;"><?php esc_html_e('Description', 'code-snippets'); ?></h3> <?php _e('(Optional)', 'code-snippets'); ?>
 
 
 
 
 
71
  </label>
72
-
73
- <br />
74
-
75
  <?php
76
  wp_editor(
77
  htmlspecialchars_decode( stripslashes( $snippet->description ) ),
@@ -79,6 +79,7 @@ if( isset( $_REQUEST['edit'] ) )
79
  array(
80
  'textarea_name' => 'snippet_description',
81
  'textarea_rows' => 10,
 
82
  )
83
  );
84
  ?>
@@ -90,10 +91,10 @@ if( isset( $_REQUEST['edit'] ) )
90
  </div>
91
  <script type="text/javascript">
92
  var editor = CodeMirror.fromTextArea(document.getElementById("snippet_code"), {
93
- mode: "application/x-httpd-php-open",
94
  lineNumbers: true,
95
- lineWrapping: true,
96
  matchBrackets: true,
 
 
97
  indentUnit: 4,
98
  indentWithTabs: true,
99
  enterMode: "keep",
1
  <?php
2
+ if ( ! class_exists( 'Code_Snippets' ) ) exit;
3
  global $wpdb;
4
 
5
+ $table = $this->get_table_name();
6
  $screen = get_current_screen();
7
  $can_edit = current_user_can( $screen->is_network ? 'edit_network_snippets' : 'edit_snippets' );
8
  $can_install = current_user_can( $screen->is_network ? 'install_network_snippets' : 'install_snippets' );
9
 
10
+ if ( isset( $_REQUEST['edit'] ) && ! $can_edit )
11
+ wp_die( __('Sorry, you&#8217;re not allowed to edit snippets', 'code-snippets') );
12
+
13
+ if ( isset( $_REQUEST['edit'] ) )
14
  $edit_id = intval( $_REQUEST['edit'] );
15
  ?>
16
 
17
+ <?php if ( isset( $_REQUEST['invalid'] ) && $_REQUEST['invalid'] ) : ?>
18
  <div id="message" class="error fade"><p><?php _e('Please provide a name for the snippet and its code.', 'code-snippets'); ?></p></div>
19
+ <?php elseif ( isset( $_REQUEST['updated'] ) && $_REQUEST['updated'] ) : ?>
20
  <div id="message" class="updated fade"><p><?php _e('Snippet <strong>updated</strong>.', 'code-snippets'); ?></p></div>
21
+ <?php elseif ( isset( $_REQUEST['added'] ) && $_REQUEST['added'] ) : ?>
22
  <div id="message" class="updated fade"><p><?php _e('Snippet <strong>added</strong>.', 'code-snippets'); ?></p></div>
23
  <?php endif; ?>
24
 
25
  <div class="wrap">
26
  <?php screen_icon(); ?>
27
  <h2><?php
28
+ if ( isset( $edit_id ) ) {
29
  esc_html_e('Edit Snippet', 'code-snippets');
30
+
31
+ if ( $can_install )
32
  printf( ' <a href="%1$s" class="add-new-h2">%2$s</a>',
33
  $this->admin_single_url,
34
  esc_html('Add New', 'code-snippets')
37
  _e('Add New Snippet', 'code-snippets');
38
  }
39
  ?></h2>
40
+
41
  <form method="post" action="" style="margin-top: 10px;">
42
+ <?php
43
+ if ( isset( $edit_id ) ) {
44
+ $snippet = $wpdb->get_row( "SELECT * FROM $table WHERE id = $edit_id" );
45
+ printf ( '<input type="hidden" name="snippet_id" value="%d" />', $snippet->id );
46
+ } else {
47
  // define a empty object (or one with default values)
48
  $snippet = new stdClass();
49
  $snippet->name = '';
50
  $snippet->description = '';
51
  $snippet->code = '';
52
+ }
53
+ ?>
 
54
  <div id="titlediv">
55
  <div id="titlewrap">
56
  <label for="title" style="display: none;"><?php esc_html_e('Name (short title)', 'code-snippets'); ?></label>
62
  <h3 style="display: inline;"><?php esc_html_e('Code', 'code-snippets'); ?></h3>
63
  <span style="float: right;"><?php _e('Enter or paste the snippet code without the <code>&lt;?php</code> and <code>?&gt;</code> tags.', 'code-snippets'); ?></span>
64
  </label>
 
 
 
65
 
66
+ <textarea id="snippet_code" name="snippet_code" rows="20" spellcheck="false" style="font-family: monospace; width:100%;"><?php echo stripslashes( $snippet->code ); ?></textarea>
67
+
68
+ <label for="description">
69
+ <h3>
70
+ <?php esc_html_e('Description', 'code-snippets'); ?>
71
+ <span style="font-weight: normal; font-size: normal;"><?php _e('(Optional)', 'code-snippets'); ?></span>
72
+ </h3>
73
  </label>
74
+
 
 
75
  <?php
76
  wp_editor(
77
  htmlspecialchars_decode( stripslashes( $snippet->description ) ),
79
  array(
80
  'textarea_name' => 'snippet_description',
81
  'textarea_rows' => 10,
82
+ 'media_buttons' => false,
83
  )
84
  );
85
  ?>
91
  </div>
92
  <script type="text/javascript">
93
  var editor = CodeMirror.fromTextArea(document.getElementById("snippet_code"), {
 
94
  lineNumbers: true,
 
95
  matchBrackets: true,
96
+ lineWrapping: true,
97
+ mode: "application/x-httpd-php-open",
98
  indentUnit: 4,
99
  indentWithTabs: true,
100
  enterMode: "keep",
includes/class-list-table.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- if( ! class_exists( 'WP_List_Table' ) ) {
4
  require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
5
  }
6
 
@@ -12,12 +12,12 @@ if( ! class_exists( 'WP_List_Table' ) ) {
12
  * @access private
13
  */
14
  class Code_Snippets_List_Table extends WP_List_Table {
15
-
16
  function __construct() {
17
- global $status, $page;
18
-
19
  $screen = get_current_screen();
20
-
21
  $status = 'all';
22
  if ( isset( $_REQUEST['status'] ) && in_array( $_REQUEST['status'], array( 'active', 'inactive', 'recently_activated', 'search' ) ) )
23
  $status = $_REQUEST['status'];
@@ -26,39 +26,71 @@ class Code_Snippets_List_Table extends WP_List_Table {
26
  $_SERVER['REQUEST_URI'] = add_query_arg( 's', stripslashes($_REQUEST['s'] ) );
27
 
28
  $page = $this->get_pagenum();
29
-
30
  add_screen_option( 'per_page', array(
31
  'label' => __('Snippets per page', 'code-snippets'),
32
  'default' => 10,
33
  'option' => 'snippets_per_page'
34
  ) );
35
-
36
  add_filter( "get_user_option_manage{$screen->id}columnshidden", array( $this, 'get_default_hidden_columns' ) );
37
-
 
 
38
  parent::__construct( array(
39
  'singular' => 'snippet',
40
  'plural' => 'snippets',
41
  'ajax' => true,
42
  ) );
43
  }
44
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  function column_default( $item, $column_name ) {
46
  switch( $column_name ) {
47
  case 'id':
48
- return intval( $item[$column_name] );
49
  case 'description':
50
- return stripslashes( html_entity_decode( $item[$column_name] ) );
51
  default:
52
  return print_r( $item, true ); // Show the whole array for troubleshooting purposes
53
  }
54
  }
55
-
56
  function column_name( $item ) {
57
- global $cs;
58
  $screen = get_current_screen();
59
  $actions = array(); // Build row actions
60
-
61
- if( $item['active'] ) {
62
  $actions['deactivate'] = sprintf(
63
  '<a href="%1$s">%2$s</a>',
64
  add_query_arg( array(
@@ -79,10 +111,10 @@ class Code_Snippets_List_Table extends WP_List_Table {
79
  $screen->is_network ? __('Network Activate', 'code-snippets') : __('Activate', 'code-snippets')
80
  );
81
  }
82
-
83
  $actions['edit'] = sprintf(
84
  '<a href="%s&edit=%s">Edit</a>',
85
- $cs->admin_single_url,
86
  $item['id']
87
  );
88
  $actions['export'] = sprintf(
@@ -103,11 +135,11 @@ class Code_Snippets_List_Table extends WP_List_Table {
103
  esc_js( 'return confirm( "You are about to permanently delete the selected item.
104
  \'Cancel\' to stop, \'OK\' to delete.");' )
105
  );
106
-
107
  // Return the name contents
108
  return '<strong>' . stripslashes( $item['name'] ) . '</strong>' . $this->row_actions( $actions, true );
109
  }
110
-
111
  function column_cb( $item ) {
112
  return sprintf(
113
  '<input type="checkbox" name="%1$s[]" value="%2$s" />',
@@ -115,7 +147,7 @@ class Code_Snippets_List_Table extends WP_List_Table {
115
  /*$2%s*/ $item['id'] // The value of the checkbox should be the snippet's id
116
  );
117
  }
118
-
119
  function get_columns() {
120
  return array(
121
  'cb' => '<input type="checkbox" />',
@@ -124,21 +156,21 @@ class Code_Snippets_List_Table extends WP_List_Table {
124
  'description' => __('Description', 'code-snippets'),
125
  );
126
  }
127
-
128
  function get_sortable_columns() {
129
  return array(
130
  'id' => array( 'id', true ),
131
  'name' => array( 'name', false ),
132
  );
133
  }
134
-
135
  function get_default_hidden_columns( $result ) {
136
- if( ! $result )
137
  return array( 'id' );
138
  else
139
  return $result;
140
  }
141
-
142
  function get_bulk_actions() {
143
  $screen = get_current_screen();
144
  $actions = array(
@@ -146,15 +178,15 @@ class Code_Snippets_List_Table extends WP_List_Table {
146
  'deactivate-selected' => $screen->is_network ? __('Network Deactivate', 'code-snippets') : __('Deactivate', 'code-snippets'),
147
  'export-selected' => __('Export', 'code-snippets'),
148
  'delete-selected' => __('Delete', 'code-snippets'),
149
- 'exportphp-selected' => __('Export to PHP', 'code-snippets'),
150
  );
151
  return $actions;
152
  }
153
-
154
  function get_table_classes() {
155
  return array( 'widefat', $this->_args['plural'] );
156
  }
157
-
158
  function get_views() {
159
  global $totals, $status;
160
 
@@ -179,17 +211,17 @@ class Code_Snippets_List_Table extends WP_List_Table {
179
  }
180
 
181
  if ( 'search' != $type ) {
182
- $status_links[$type] = sprintf( "<a href='%s' %s>%s</a>",
183
  add_query_arg('status', $type, '?page=' . $_REQUEST['page'] ),
184
  ( $type == $status ) ? ' class="current"' : '',
185
  sprintf( $text, number_format_i18n( $count ) )
186
- );
187
  }
188
  }
189
 
190
  return $status_links;
191
  }
192
-
193
  function extra_tablenav( $which ) {
194
  global $status;
195
 
@@ -212,93 +244,102 @@ class Code_Snippets_List_Table extends WP_List_Table {
212
 
213
  return parent::current_action();
214
  }
215
-
216
  /**
217
  * Processes a bulk action
218
  *
219
- * @uses $cs->activate() To activate snippets
220
- * @uses $cs->deactivate() To deactivate snippets
221
- * @uses $cs->delete_snippet() To delete snippets
222
- * @uses cs_export() To export selected snippets
223
  * @uses wp_redirect To pass the results to the current page
224
  * @uses add_query_arg() To append the results to the current URI
225
  */
226
  function process_bulk_actions() {
227
- global $cs;
228
- if( ! isset( $_POST[ $this->_args['singular'] ] ) ) return;
229
  $ids = $_POST[ $this->_args['singular'] ];
230
-
231
  $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'activate', 'deactivate', 'delete', 'activate-multi', 'deactivate-multi', 'delete-multi' ) );
232
-
233
  switch( $this->current_action() ) {
234
-
235
  case 'activate-selected':
236
- $cs->activate( $ids );
237
  wp_redirect( add_query_arg( 'activate-multi', true ) );
238
  break;
239
-
240
  case 'deactivate-selected':
241
- $cs->deactivate( $ids );
242
  wp_redirect( add_query_arg( 'deactivate-multi', true ) );
243
  break;
244
-
245
  case 'export-selected':
246
- $cs->export( $ids );
247
  break;
248
-
249
- case 'exportphp-selected':
250
- $cs->exportphp( $ids );
251
  break;
252
-
253
  case 'delete-selected':
254
  foreach( $ids as $id ) {
255
- $cs->delete_snippet( $id );
256
  }
257
  wp_redirect( add_query_arg( 'delete-multi', true ) );
258
  break;
259
-
260
  case 'clear-recent-list':
261
  $screen = get_current_screen();
262
- $option = ( $screen->is_network ? 'recently_network_activated_snippets' : 'recently_activated_snippets' );
263
- update_option( $option, array() );
 
 
264
  break;
265
  }
266
  }
267
-
268
  function no_items() {
269
- global $cs;
270
- printf( __('You do not appear to have any snippets available at this time. <a href="%s">Add New&rarr;</a>', 'code-snippets'), $cs->admin_single_url );
271
  }
272
-
273
  function prepare_items() {
274
-
275
- global $wpdb, $cs, $status, $snippets, $totals, $page, $orderby, $order, $s;
276
 
277
  wp_reset_vars( array( 'orderby', 'order', 's' ) );
278
-
279
  $screen = get_current_screen();
280
  $user = get_current_user_id();
281
-
 
282
  // first, lets process the bulk actions
283
  $this->process_bulk_actions();
284
-
285
  $snippets = array(
286
- 'all' => $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $cs->table" ), ARRAY_A ),
287
  'search' => array(),
288
  'active' => array(),
289
  'inactive' => array(),
290
  'recently_activated' => array(),
291
  );
292
-
293
- $option = $screen->is_network ? 'recently_network_activated_snippets' : 'recently_activated_snippets';
294
- $recently_activated = get_option( $option, array() );
 
 
295
 
296
  $one_week = 7*24*60*60;
297
  foreach ( $recently_activated as $key => $time )
298
  if ( $time + $one_week < time() )
299
  unset( $recently_activated[$key] );
300
- update_option( $option, $recently_activated );
301
-
 
 
 
 
302
  foreach ( (array) $snippets['all'] as $snippet ) {
303
  // Filter into individual sections
304
  if ( $snippet['active'] ) {
@@ -310,20 +351,20 @@ class Code_Snippets_List_Table extends WP_List_Table {
310
  }
311
  }
312
 
313
- if( $s ) {
314
  $status = 'search';
315
  $snippets['search'] = array_filter( $snippets['all'], array( &$this, '_search_callback' ) );
316
  }
317
-
318
  $totals = array();
319
  foreach ( $snippets as $type => $list )
320
  $totals[ $type ] = count( $list );
321
-
322
  if ( empty( $snippets[ $status ] ) && !in_array( $status, array( 'all', 'search' ) ) )
323
  $status = 'all';
324
-
325
  $data = $snippets[ $status ];
326
-
327
  /**
328
  * First, lets decide how many records per page to show
329
  * by getting the user's setting in the Screen Opions
@@ -331,67 +372,67 @@ class Code_Snippets_List_Table extends WP_List_Table {
331
  */
332
  $sort_by = $screen->get_option( 'per_page', 'option' );
333
  $screen_option = $screen->get_option( 'per_page', 'option' );
334
-
335
  $per_page = get_user_meta( $user, $screen_option, true );
336
-
337
- if( empty ( $per_page ) || $per_page < 1 ) {
338
  $per_page = $screen->get_option( 'per_page', 'default' );
339
  }
340
-
341
  $per_page = (int) $per_page;
342
-
343
  $this->_column_headers = $this->get_column_info();
344
-
345
  /**
346
  * This checks for sorting input and sorts the data in our array accordingly.
347
  */
348
  function usort_reorder( $a, $b ) {
349
-
350
  // If no sort, default to id
351
- $orderby = ( ! empty($_REQUEST['orderby'] ) ) ? $_REQUEST['orderby'] : 'id';
352
-
353
  // If no order, default to asc
354
  $order = ( ! empty( $_REQUEST['order'] ) ) ? $_REQUEST['order'] : 'asc';
355
-
356
  // Determine sort order
357
- if( $orderby === 'id' )
358
  $result = $a[$orderby] - $b[$orderby]; // get the result for numerical data
359
  else
360
  $result = strcmp( $a[$orderby], $b[$orderby] ); // get the result for string data
361
-
362
  // Send final sort direction to usort
363
  return ( $order === 'asc' ) ? $result : -$result;
364
  }
365
-
366
  usort($data, 'usort_reorder');
367
-
368
-
369
  /**
370
- * Let's figure out what page the user is currently
371
  * looking at.
372
  */
373
  $current_page = $this->get_pagenum();
374
-
375
  /**
376
  * Let's check how many items are in our data array.
377
  */
378
  $total_items = count($data);
379
-
380
-
381
  /**
382
  * The WP_List_Table class does not handle pagination for us, so we need
383
  * to ensure that the data is trimmed to only the current page.
384
  */
385
  $data = array_slice( $data, ( ( $current_page - 1 ) * $per_page ), $per_page );
386
-
387
-
388
  /**
389
- * Now we can add our *sorted* data to the items property, where
390
  * it can be used by the rest of the class.
391
  */
392
  $this->items = $data;
393
-
394
-
395
  /**
396
  * We also have to register our pagination options & calculations.
397
  */
@@ -401,7 +442,7 @@ class Code_Snippets_List_Table extends WP_List_Table {
401
  'total_pages' => ceil($total_items/$per_page) // WE have to calculate the total number of pages
402
  ) );
403
  }
404
-
405
  function _search_callback( $item ) {
406
  static $term;
407
  if ( is_null( $term ) )
@@ -413,7 +454,7 @@ class Code_Snippets_List_Table extends WP_List_Table {
413
 
414
  return false;
415
  }
416
-
417
  /**
418
  * Generates content for a single row of the table
419
  */
1
  <?php
2
 
3
+ if ( ! class_exists( 'WP_List_Table' ) ) {
4
  require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
5
  }
6
 
12
  * @access private
13
  */
14
  class Code_Snippets_List_Table extends WP_List_Table {
15
+
16
  function __construct() {
17
+ global $status, $page, $code_snippets;
18
+
19
  $screen = get_current_screen();
20
+
21
  $status = 'all';
22
  if ( isset( $_REQUEST['status'] ) && in_array( $_REQUEST['status'], array( 'active', 'inactive', 'recently_activated', 'search' ) ) )
23
  $status = $_REQUEST['status'];
26
  $_SERVER['REQUEST_URI'] = add_query_arg( 's', stripslashes($_REQUEST['s'] ) );
27
 
28
  $page = $this->get_pagenum();
29
+
30
  add_screen_option( 'per_page', array(
31
  'label' => __('Snippets per page', 'code-snippets'),
32
  'default' => 10,
33
  'option' => 'snippets_per_page'
34
  ) );
35
+
36
  add_filter( "get_user_option_manage{$screen->id}columnshidden", array( $this, 'get_default_hidden_columns' ) );
37
+ add_filter( 'set-screen-option', array( $this, 'set_screen_option' ), 10, 3 );
38
+ add_action( "admin_print_scripts-$code_snippets->admin_manage", array( $this, 'load_table_style' ) );
39
+
40
  parent::__construct( array(
41
  'singular' => 'snippet',
42
  'plural' => 'snippets',
43
  'ajax' => true,
44
  ) );
45
  }
46
+
47
+ /**
48
+ * Handles saving the user's screen option preference
49
+ *
50
+ * @since Code Snippets 1.5
51
+ * @access private
52
+ */
53
+ function set_screen_option( $status, $option, $value ) {
54
+ if ( 'snippets_per_page' === $option ) return $value;
55
+ }
56
+
57
+ /**
58
+ * Enqueue the table stylesheet
59
+ *
60
+ * @since Code Snippets 1.6
61
+ * @access private
62
+ *
63
+ * @uses wp_enqueue_style() To add the stylesheet to the queue
64
+ *
65
+ * @return void
66
+ */
67
+ function load_table_style() {
68
+ global $code_snippets;
69
+ wp_enqueue_style(
70
+ 'snippets-table',
71
+ plugins_url( 'assets/table.css', $code_snippets->file ),
72
+ false,
73
+ $code_snippets->version
74
+ );
75
+ }
76
+
77
  function column_default( $item, $column_name ) {
78
  switch( $column_name ) {
79
  case 'id':
80
+ return intval( $item[ $column_name ] );
81
  case 'description':
82
+ return stripslashes( html_entity_decode( $item[ $column_name ] ) );
83
  default:
84
  return print_r( $item, true ); // Show the whole array for troubleshooting purposes
85
  }
86
  }
87
+
88
  function column_name( $item ) {
89
+ global $code_snippets;
90
  $screen = get_current_screen();
91
  $actions = array(); // Build row actions
92
+
93
+ if ( $item['active'] ) {
94
  $actions['deactivate'] = sprintf(
95
  '<a href="%1$s">%2$s</a>',
96
  add_query_arg( array(
111
  $screen->is_network ? __('Network Activate', 'code-snippets') : __('Activate', 'code-snippets')
112
  );
113
  }
114
+
115
  $actions['edit'] = sprintf(
116
  '<a href="%s&edit=%s">Edit</a>',
117
+ $code_snippets->admin_single_url,
118
  $item['id']
119
  );
120
  $actions['export'] = sprintf(
135
  esc_js( 'return confirm( "You are about to permanently delete the selected item.
136
  \'Cancel\' to stop, \'OK\' to delete.");' )
137
  );
138
+
139
  // Return the name contents
140
  return '<strong>' . stripslashes( $item['name'] ) . '</strong>' . $this->row_actions( $actions, true );
141
  }
142
+
143
  function column_cb( $item ) {
144
  return sprintf(
145
  '<input type="checkbox" name="%1$s[]" value="%2$s" />',
147
  /*$2%s*/ $item['id'] // The value of the checkbox should be the snippet's id
148
  );
149
  }
150
+
151
  function get_columns() {
152
  return array(
153
  'cb' => '<input type="checkbox" />',
156
  'description' => __('Description', 'code-snippets'),
157
  );
158
  }
159
+
160
  function get_sortable_columns() {
161
  return array(
162
  'id' => array( 'id', true ),
163
  'name' => array( 'name', false ),
164
  );
165
  }
166
+
167
  function get_default_hidden_columns( $result ) {
168
+ if ( ! $result )
169
  return array( 'id' );
170
  else
171
  return $result;
172
  }
173
+
174
  function get_bulk_actions() {
175
  $screen = get_current_screen();
176
  $actions = array(
178
  'deactivate-selected' => $screen->is_network ? __('Network Deactivate', 'code-snippets') : __('Deactivate', 'code-snippets'),
179
  'export-selected' => __('Export', 'code-snippets'),
180
  'delete-selected' => __('Delete', 'code-snippets'),
181
+ 'export-php-selected' => __('Export to PHP', 'code-snippets'),
182
  );
183
  return $actions;
184
  }
185
+
186
  function get_table_classes() {
187
  return array( 'widefat', $this->_args['plural'] );
188
  }
189
+
190
  function get_views() {
191
  global $totals, $status;
192
 
211
  }
212
 
213
  if ( 'search' != $type ) {
214
+ $status_links[$type] = sprintf( '<a href="%s"%s>%s</a>',
215
  add_query_arg('status', $type, '?page=' . $_REQUEST['page'] ),
216
  ( $type == $status ) ? ' class="current"' : '',
217
  sprintf( $text, number_format_i18n( $count ) )
218
+ );
219
  }
220
  }
221
 
222
  return $status_links;
223
  }
224
+
225
  function extra_tablenav( $which ) {
226
  global $status;
227
 
244
 
245
  return parent::current_action();
246
  }
247
+
248
  /**
249
  * Processes a bulk action
250
  *
251
+ * @uses $code_snippets->activate() To activate snippets
252
+ * @uses $code_snippets->deactivate() To deactivate snippets
253
+ * @uses $code_snippets->delete_snippet() To delete snippets
254
+ * @uses $code_snippets->export() To export selected snippets
255
  * @uses wp_redirect To pass the results to the current page
256
  * @uses add_query_arg() To append the results to the current URI
257
  */
258
  function process_bulk_actions() {
259
+ global $code_snippets;
260
+ if ( ! isset( $_POST[ $this->_args['singular'] ] ) ) return;
261
  $ids = $_POST[ $this->_args['singular'] ];
262
+
263
  $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'activate', 'deactivate', 'delete', 'activate-multi', 'deactivate-multi', 'delete-multi' ) );
264
+
265
  switch( $this->current_action() ) {
266
+
267
  case 'activate-selected':
268
+ $code_snippets->activate( $ids );
269
  wp_redirect( add_query_arg( 'activate-multi', true ) );
270
  break;
271
+
272
  case 'deactivate-selected':
273
+ $code_snippets->deactivate( $ids );
274
  wp_redirect( add_query_arg( 'deactivate-multi', true ) );
275
  break;
276
+
277
  case 'export-selected':
278
+ $code_snippets->export( $ids );
279
  break;
280
+
281
+ case 'export-php-selected':
282
+ $code_snippets->export_php( $ids );
283
  break;
284
+
285
  case 'delete-selected':
286
  foreach( $ids as $id ) {
287
+ $code_snippets->delete_snippet( $id );
288
  }
289
  wp_redirect( add_query_arg( 'delete-multi', true ) );
290
  break;
291
+
292
  case 'clear-recent-list':
293
  $screen = get_current_screen();
294
+ if ( $screen->is_network )
295
+ update_site_option( 'recently_activated_snippets', array() );
296
+ else
297
+ update_option( 'recently_activated_snippets', array() );
298
  break;
299
  }
300
  }
301
+
302
  function no_items() {
303
+ global $code_snippets;
304
+ printf( __('You do not appear to have any snippets available at this time. <a href="%s">Add New&rarr;</a>', 'code-snippets'), $code_snippets->admin_single_url );
305
  }
306
+
307
  function prepare_items() {
308
+
309
+ global $wpdb, $code_snippets, $status, $snippets, $totals, $page, $orderby, $order, $s;
310
 
311
  wp_reset_vars( array( 'orderby', 'order', 's' ) );
312
+
313
  $screen = get_current_screen();
314
  $user = get_current_user_id();
315
+ $table = $code_snippets->get_table_name();
316
+
317
  // first, lets process the bulk actions
318
  $this->process_bulk_actions();
319
+
320
  $snippets = array(
321
+ 'all' => $wpdb->get_results( "SELECT * FROM $table", ARRAY_A ),
322
  'search' => array(),
323
  'active' => array(),
324
  'inactive' => array(),
325
  'recently_activated' => array(),
326
  );
327
+
328
+ if ( $screen->is_network )
329
+ $recently_activated = get_site_option( 'recently_activated_snippets', array() );
330
+ else
331
+ $recently_activated = get_option( 'recently_activated_snippets', array() );
332
 
333
  $one_week = 7*24*60*60;
334
  foreach ( $recently_activated as $key => $time )
335
  if ( $time + $one_week < time() )
336
  unset( $recently_activated[$key] );
337
+
338
+ if ( $screen->is_network )
339
+ update_site_option( 'recently_activated_snippets', $recently_activated );
340
+ else
341
+ update_option( 'recently_activated_snippets', $recently_activated );
342
+
343
  foreach ( (array) $snippets['all'] as $snippet ) {
344
  // Filter into individual sections
345
  if ( $snippet['active'] ) {
351
  }
352
  }
353
 
354
+ if ( $s ) {
355
  $status = 'search';
356
  $snippets['search'] = array_filter( $snippets['all'], array( &$this, '_search_callback' ) );
357
  }
358
+
359
  $totals = array();
360
  foreach ( $snippets as $type => $list )
361
  $totals[ $type ] = count( $list );
362
+
363
  if ( empty( $snippets[ $status ] ) && !in_array( $status, array( 'all', 'search' ) ) )
364
  $status = 'all';
365
+
366
  $data = $snippets[ $status ];
367
+
368
  /**
369
  * First, lets decide how many records per page to show
370
  * by getting the user's setting in the Screen Opions
372
  */
373
  $sort_by = $screen->get_option( 'per_page', 'option' );
374
  $screen_option = $screen->get_option( 'per_page', 'option' );
375
+
376
  $per_page = get_user_meta( $user, $screen_option, true );
377
+
378
+ if ( empty ( $per_page ) || $per_page < 1 ) {
379
  $per_page = $screen->get_option( 'per_page', 'default' );
380
  }
381
+
382
  $per_page = (int) $per_page;
383
+
384
  $this->_column_headers = $this->get_column_info();
385
+
386
  /**
387
  * This checks for sorting input and sorts the data in our array accordingly.
388
  */
389
  function usort_reorder( $a, $b ) {
390
+
391
  // If no sort, default to id
392
+ $orderby = ( ! empty($_REQUEST['orderby'] ) ) ? $_REQUEST['orderby'] : apply_filters( 'code_snippets_default_orderby', 'id' );
393
+
394
  // If no order, default to asc
395
  $order = ( ! empty( $_REQUEST['order'] ) ) ? $_REQUEST['order'] : 'asc';
396
+
397
  // Determine sort order
398
+ if ( $orderby === 'id' )
399
  $result = $a[$orderby] - $b[$orderby]; // get the result for numerical data
400
  else
401
  $result = strcmp( $a[$orderby], $b[$orderby] ); // get the result for string data
402
+
403
  // Send final sort direction to usort
404
  return ( $order === 'asc' ) ? $result : -$result;
405
  }
406
+
407
  usort($data, 'usort_reorder');
408
+
409
+
410
  /**
411
+ * Let's figure out what page the user is currently
412
  * looking at.
413
  */
414
  $current_page = $this->get_pagenum();
415
+
416
  /**
417
  * Let's check how many items are in our data array.
418
  */
419
  $total_items = count($data);
420
+
421
+
422
  /**
423
  * The WP_List_Table class does not handle pagination for us, so we need
424
  * to ensure that the data is trimmed to only the current page.
425
  */
426
  $data = array_slice( $data, ( ( $current_page - 1 ) * $per_page ), $per_page );
427
+
428
+
429
  /**
430
+ * Now we can add our *sorted* data to the items property, where
431
  * it can be used by the rest of the class.
432
  */
433
  $this->items = $data;
434
+
435
+
436
  /**
437
  * We also have to register our pagination options & calculations.
438
  */
442
  'total_pages' => ceil($total_items/$per_page) // WE have to calculate the total number of pages
443
  ) );
444
  }
445
+
446
  function _search_callback( $item ) {
447
  static $term;
448
  if ( is_null( $term ) )
454
 
455
  return false;
456
  }
457
+
458
  /**
459
  * Generates content for a single row of the table
460
  */
includes/export.php CHANGED
@@ -3,15 +3,15 @@
3
  /**
4
  * This file handles the export functions
5
  *
6
- * It's better to call the $cs->export()
7
- * and $cs->exportphp() methods then
8
  * directly use those in this file
9
  *
10
  * @package Code Snippets
11
  * @subpackage Export
12
  */
13
 
14
- if( ! function_exists( 'cs_export') ) :
15
 
16
  /**
17
  * Exports seleted snippets to a XML or PHP file.
@@ -20,62 +20,65 @@ if( ! function_exists( 'cs_export') ) :
20
  * @since Code Snippets 1.3
21
  *
22
  * @param array $ids The IDs of the snippets to export
23
- * $param string $format The format of the export file
 
24
  */
25
- function cs_export( $ids, $format = 'xml' ) {
26
-
27
- global $wpdb, $cs;
28
-
29
  $ids = (array) $ids;
30
-
31
- if( count( $ids ) < 2 ) {
 
 
32
  // If there is only snippet to export, use its name instead of the site name
33
- $entry = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $cs->table WHERE id=%d", $ids ) );
34
  $sitename = sanitize_key( $entry->name );
35
  } else {
36
  // Otherwise, use the site name as set in Settings > General
37
  $sitename = sanitize_key( get_bloginfo( 'name' ) );
38
  }
39
-
40
- $filename = apply_filters( 'cs_export_filename', "{$sitename}.code-snippets.{$format}" );
41
 
42
  header( 'Content-Disposition: attachment; filename=' . $filename );
43
-
44
- if( $format === 'xml' ) {
45
  header( 'Content-Type: text/xml; charset=utf-8' );
46
-
47
  echo '<?xml version="1.0"?>' . "\n";
48
  echo '<snippets sitename="' . $sitename . '">';
49
-
50
  foreach( $ids as $id ) {
51
-
52
- if( ! intval( $id ) > 0 ) continue; // skip this one if we don't have a valid ID
53
-
54
- $snippet = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $cs->table WHERE id=%d", $id ) );
55
-
56
  echo "\n\t" . '<snippet>';
57
  echo "\n\t\t" . "<name>$snippet->name</name>";
58
  echo "\n\t\t" . "<description>$snippet->description</description>";
59
  echo "\n\t\t" . "<code>$snippet->code</code>";
60
  echo "\n\t" . '</snippet>';
61
  }
62
-
63
  echo "\n</snippets>";
64
-
65
- } elseif( $format === 'php' ) {
66
-
67
  echo "<?php\n";
68
-
69
  foreach( $ids as $id ) {
70
-
71
- if( ! intval( $id ) > 0 ) continue; // skip this one if we don't have a valid ID
72
-
73
- $snippet = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $cs->table WHERE id=%d", $id ) );
74
  ?>
75
 
76
  /**
77
  * <?php echo htmlspecialchars_decode( stripslashes( $snippet->name ) ) . "\n"; ?>
78
- <?php if( ! empty( $snippet->description ) ) : ?>
79
  *
80
  * <?php echo htmlspecialchars_decode( stripslashes( $snippet->description ) ) . "\n"; ?>
81
  <?php endif; ?>
@@ -84,10 +87,10 @@ function cs_export( $ids, $format = 'xml' ) {
84
 
85
  <?php
86
  }
87
-
88
  echo '?>';
89
  }
90
-
91
  exit;
92
  }
93
 
3
  /**
4
  * This file handles the export functions
5
  *
6
+ * It's better to call the $code_snippets->export()
7
+ * and $code_snippets->export_php() methods then
8
  * directly use those in this file
9
  *
10
  * @package Code Snippets
11
  * @subpackage Export
12
  */
13
 
14
+ if ( ! function_exists( 'code_snippets_export' ) ) :
15
 
16
  /**
17
  * Exports seleted snippets to a XML or PHP file.
20
  * @since Code Snippets 1.3
21
  *
22
  * @param array $ids The IDs of the snippets to export
23
+ * @param string $format The format of the export file
24
+ * @return void
25
  */
26
+ function code_snippets_export( $ids, $format = 'xml' ) {
27
+
28
+ global $wpdb, $code_snippets;
29
+
30
  $ids = (array) $ids;
31
+
32
+ $table = $code_snippets->get_table_name();
33
+
34
+ if ( count( $ids ) < 2 ) {
35
  // If there is only snippet to export, use its name instead of the site name
36
+ $entry = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE id=%d", $ids ) );
37
  $sitename = sanitize_key( $entry->name );
38
  } else {
39
  // Otherwise, use the site name as set in Settings > General
40
  $sitename = sanitize_key( get_bloginfo( 'name' ) );
41
  }
42
+
43
+ $filename = apply_filters( 'code_snippets_export_filename', "{$sitename}.code-snippets.{$format}", $format, $sitename );
44
 
45
  header( 'Content-Disposition: attachment; filename=' . $filename );
46
+
47
+ if ( $format === 'xml' ) {
48
  header( 'Content-Type: text/xml; charset=utf-8' );
49
+
50
  echo '<?xml version="1.0"?>' . "\n";
51
  echo '<snippets sitename="' . $sitename . '">';
52
+
53
  foreach( $ids as $id ) {
54
+
55
+ if ( ! intval( $id ) > 0 ) continue; // skip this one if we don't have a valid ID
56
+
57
+ $snippet = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE id=%d", $id ) );
58
+
59
  echo "\n\t" . '<snippet>';
60
  echo "\n\t\t" . "<name>$snippet->name</name>";
61
  echo "\n\t\t" . "<description>$snippet->description</description>";
62
  echo "\n\t\t" . "<code>$snippet->code</code>";
63
  echo "\n\t" . '</snippet>';
64
  }
65
+
66
  echo "\n</snippets>";
67
+
68
+ } elseif ( $format === 'php' ) {
69
+
70
  echo "<?php\n";
71
+
72
  foreach( $ids as $id ) {
73
+
74
+ if ( ! intval( $id ) > 0 ) continue; // skip this one if we don't have a valid ID
75
+
76
+ $snippet = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE id=%d", $id ) );
77
  ?>
78
 
79
  /**
80
  * <?php echo htmlspecialchars_decode( stripslashes( $snippet->name ) ) . "\n"; ?>
81
+ <?php if ( ! empty( $snippet->description ) ) : ?>
82
  *
83
  * <?php echo htmlspecialchars_decode( stripslashes( $snippet->description ) ) . "\n"; ?>
84
  <?php endif; ?>
87
 
88
  <?php
89
  }
90
+
91
  echo '?>';
92
  }
93
+
94
  exit;
95
  }
96
 
includes/help/import.php CHANGED
@@ -6,15 +6,15 @@ $screen->add_help_tab( array(
6
  'content' =>
7
  '<p>' . __('Snippets are similar to plugins - they both extend and expand the functionality of WordPress. Snippets are more light-weight, just a few lines of code, and do not put as much load on your server. Here you can load snippets from a Code Snippets (.xml) import file into the database with your existing snippets.', 'code-snippets') . '</p>'
8
  ) );
9
-
10
  $screen->add_help_tab( array(
11
  'id' => 'import',
12
  'title' => __('Importing', 'code-snippets'),
13
  'content' =>
14
- '<p>' . __('You can load your snippets from a Code Snippets (.xml) import file using this page.', 'code-snippets') .
15
  sprintf( __('Snippets will be added to the database along with your existing snippets. Regardless of whether the snippets were active on the previous site, imported snippets are always inactive until activated using the <a href="%s">Manage Snippets</a> page.</p>', 'code-snippets'), $this->admin_manage_url ) . '</p>'
16
  ) );
17
-
18
  $screen->add_help_tab( array(
19
  'id' => 'export',
20
  'title' => __('Exporting'),
@@ -24,7 +24,7 @@ $screen->add_help_tab( array(
24
 
25
  $screen->set_help_sidebar(
26
  '<p><strong>' . __('For more information:', 'code-snippets') . '</strong></p>' .
27
- '<p>' . __('<a href="http://wordpress.org/extend/plugins/code-snippets" target="_blank">WordPress Extend</a>', 'code-snippets') . '</p>' .
28
  '<p>' . __('<a href="http://wordpress.org/support/plugin/code-snippets" target="_blank">Support Forums</a>', 'code-snippets') . '</p>' .
29
- '<p>' . __('<a href="http://cs.bungeshea.com" target="_blank">Project Website</a>', 'code-snippets') . '</p>'
30
  );
6
  'content' =>
7
  '<p>' . __('Snippets are similar to plugins - they both extend and expand the functionality of WordPress. Snippets are more light-weight, just a few lines of code, and do not put as much load on your server. Here you can load snippets from a Code Snippets (.xml) import file into the database with your existing snippets.', 'code-snippets') . '</p>'
8
  ) );
9
+
10
  $screen->add_help_tab( array(
11
  'id' => 'import',
12
  'title' => __('Importing', 'code-snippets'),
13
  'content' =>
14
+ '<p>' . __('You can load your snippets from a Code Snippets (.xml) import file using this page.', 'code-snippets') .
15
  sprintf( __('Snippets will be added to the database along with your existing snippets. Regardless of whether the snippets were active on the previous site, imported snippets are always inactive until activated using the <a href="%s">Manage Snippets</a> page.</p>', 'code-snippets'), $this->admin_manage_url ) . '</p>'
16
  ) );
17
+
18
  $screen->add_help_tab( array(
19
  'id' => 'export',
20
  'title' => __('Exporting'),
24
 
25
  $screen->set_help_sidebar(
26
  '<p><strong>' . __('For more information:', 'code-snippets') . '</strong></p>' .
27
+ '<p>' . __('<a href="http://wordpress.org/extend/plugins/code-snippets" target="_blank">WordPress Extend</a>', 'code-snippets') . '</p>' .
28
  '<p>' . __('<a href="http://wordpress.org/support/plugin/code-snippets" target="_blank">Support Forums</a>', 'code-snippets') . '</p>' .
29
+ '<p>' . __('<a href="http://code-snippets.bungeshea.com/" target="_blank">Project Website</a>', 'code-snippets') . '</p>'
30
  );
includes/help/manage.php CHANGED
@@ -12,7 +12,7 @@ $screen->add_help_tab( array(
12
  'title' => __('Safe Mode', 'code-snippets'),
13
  'content' =>
14
  '<p>' . __('Be sure to check your snippets for errors before you activate them, as a faulty snippet could bring your whole blog down. If your site starts doing strange things, deactivate all your snippets and activate them one at a time.', 'code-snippets') . '</p>' .
15
- '<p>' . __("If something goes wrong with a snippet and you can't use WordPress, you can cause all snippets to stop executing by adding <code>define('CS_SAFE_MODE', true);</code> to your <code>wp-config.php</code> file. After you have deactivated the offending snippet, you can turn off safe mode by removing this line or replacing <strong>true</strong> with <strong>false</strong>.", 'code-snippets') . '</p>'
16
  ) );
17
 
18
  $screen->add_help_tab( array(
@@ -27,5 +27,5 @@ $screen->set_help_sidebar(
27
  '<p><strong>' . __('For more information:', 'code-snippets') . '</strong></p>' .
28
  '<p>' . __('<a href="http://wordpress.org/extend/plugins/code-snippets" target="_blank">WordPress Extend</a></p>', 'code-snippets') . '</p>' .
29
  '<p>' . __('<a href="http://wordpress.org/support/plugin/code-snippets" target="_blank">Support Forums</a>', 'code-snippets') . '</p>' .
30
- '<p>' . __('<a href="http://cs.bungeshea.com" target="_blank">Project Website</a>', 'code-snippets') . '</p>'
31
  );
12
  'title' => __('Safe Mode', 'code-snippets'),
13
  'content' =>
14
  '<p>' . __('Be sure to check your snippets for errors before you activate them, as a faulty snippet could bring your whole blog down. If your site starts doing strange things, deactivate all your snippets and activate them one at a time.', 'code-snippets') . '</p>' .
15
+ '<p>' . __("If something goes wrong with a snippet and you can't use WordPress, you can cause all snippets to stop executing by adding <code>define('CODE_SNIPPETS_SAFE_MODE', true);</code> to your <code>wp-config.php</code> file. After you have deactivated the offending snippet, you can turn off safe mode by removing this line or replacing <strong>true</strong> with <strong>false</strong>.", 'code-snippets') . '</p>'
16
  ) );
17
 
18
  $screen->add_help_tab( array(
27
  '<p><strong>' . __('For more information:', 'code-snippets') . '</strong></p>' .
28
  '<p>' . __('<a href="http://wordpress.org/extend/plugins/code-snippets" target="_blank">WordPress Extend</a></p>', 'code-snippets') . '</p>' .
29
  '<p>' . __('<a href="http://wordpress.org/support/plugin/code-snippets" target="_blank">Support Forums</a>', 'code-snippets') . '</p>' .
30
+ '<p>' . __('<a href="http://code-snippets.bungeshea.com/" target="_blank">Project Website</a>', 'code-snippets') . '</p>'
31
  );
includes/help/single.php CHANGED
@@ -17,7 +17,7 @@ $screen->add_help_tab( array(
17
  <li><a href="http://www.catswhocode.com/blog/snippets" title="Cats Who Code Snippet Library">Cats Who Code</a></li>
18
  <li><a href="http://www.wpfunction.me">WP Function Me</a></li>
19
  </ul>', 'code-snippets') .
20
- __('More places to find snippets, as well as a selection of example snippets, can be found in the <a href="http://cs.bungeshea.com/docs/finding-snippets/">plugin documentation</a>', 'code-snippets') . '</p>'
21
  ) );
22
  $screen->add_help_tab( array(
23
  'id' => 'adding',
@@ -30,7 +30,7 @@ $screen->add_help_tab( array(
30
 
31
  $screen->set_help_sidebar(
32
  '<p><strong>' . __('For more information:', 'code-snippets') . '</strong></p>' .
33
- '<p>' . __('<a href="http://wordpress.org/extend/plugins/code-snippets" target="_blank">WordPress Extend</a>', 'code-snippets') . '</p>' .
34
  '<p>' . __('<a href="http://wordpress.org/support/plugin/code-snippets" target="_blank">Support Forums</a>', 'code-snippets') . '</p>' .
35
- '<p>' . __('<a href="http://cs.bungeshea.com" target="_blank">Project Website</a>', 'code-snippets') . '</p>'
36
  );
17
  <li><a href="http://www.catswhocode.com/blog/snippets" title="Cats Who Code Snippet Library">Cats Who Code</a></li>
18
  <li><a href="http://www.wpfunction.me">WP Function Me</a></li>
19
  </ul>', 'code-snippets') .
20
+ __('More places to find snippets, as well as a selection of example snippets, can be found in the <a href="http://code-snippets.bungeshea.com/docs/finding-snippets/">plugin documentation</a>', 'code-snippets') . '</p>'
21
  ) );
22
  $screen->add_help_tab( array(
23
  'id' => 'adding',
30
 
31
  $screen->set_help_sidebar(
32
  '<p><strong>' . __('For more information:', 'code-snippets') . '</strong></p>' .
33
+ '<p>' . __('<a href="http://wordpress.org/extend/plugins/code-snippets" target="_blank">WordPress Extend</a>', 'code-snippets') . '</p>' .
34
  '<p>' . __('<a href="http://wordpress.org/support/plugin/code-snippets" target="_blank">Support Forums</a>', 'code-snippets') . '</p>' .
35
+ '<p>' . __('<a href="http://code-snippets.bungeshea.com/" target="_blank">Project Website</a>', 'code-snippets') . '</p>'
36
  );
js/codemirror.js DELETED
@@ -1,3237 +0,0 @@
1
- // CodeMirror version 2.33
2
- //
3
- // All functions that need access to the editor's state live inside
4
- // the CodeMirror function. Below that, at the bottom of the file,
5
- // some utilities are defined.
6
-
7
- // CodeMirror is the only global var we claim
8
- window.CodeMirror = (function() {
9
- "use strict";
10
- // This is the function that produces an editor instance. Its
11
- // closure is used to store the editor state.
12
- function CodeMirror(place, givenOptions) {
13
- // Determine effective options based on given values and defaults.
14
- var options = {}, defaults = CodeMirror.defaults;
15
- for (var opt in defaults)
16
- if (defaults.hasOwnProperty(opt))
17
- options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
18
-
19
- var input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em");
20
- input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off");
21
- // Wraps and hides input textarea
22
- var inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
23
- // The empty scrollbar content, used solely for managing the scrollbar thumb.
24
- var scrollbarInner = elt("div", null, "CodeMirror-scrollbar-inner");
25
- // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself.
26
- var scrollbar = elt("div", [scrollbarInner], "CodeMirror-scrollbar");
27
- // DIVs containing the selection and the actual code
28
- var lineDiv = elt("div"), selectionDiv = elt("div", null, null, "position: relative; z-index: -1");
29
- // Blinky cursor, and element used to ensure cursor fits at the end of a line
30
- var cursor = elt("pre", "\u00a0", "CodeMirror-cursor"), widthForcer = elt("pre", "\u00a0", "CodeMirror-cursor", "visibility: hidden");
31
- // Used to measure text size
32
- var measure = elt("div", null, null, "position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden;");
33
- var lineSpace = elt("div", [measure, cursor, widthForcer, selectionDiv, lineDiv], null, "position: relative; z-index: 0");
34
- var gutterText = elt("div", null, "CodeMirror-gutter-text"), gutter = elt("div", [gutterText], "CodeMirror-gutter");
35
- // Moved around its parent to cover visible view
36
- var mover = elt("div", [gutter, elt("div", [lineSpace], "CodeMirror-lines")], null, "position: relative");
37
- // Set to the height of the text, causes scrolling
38
- var sizer = elt("div", [mover], null, "position: relative");
39
- // Provides scrolling
40
- var scroller = elt("div", [sizer], "CodeMirror-scroll");
41
- scroller.setAttribute("tabIndex", "-1");
42
- // The element in which the editor lives.
43
- var wrapper = elt("div", [inputDiv, scrollbar, scroller], "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""));
44
- if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
45
-
46
- themeChanged(); keyMapChanged();
47
- // Needed to hide big blue blinking cursor on Mobile Safari
48
- if (ios) input.style.width = "0px";
49
- if (!webkit) scroller.draggable = true;
50
- lineSpace.style.outline = "none";
51
- if (options.tabindex != null) input.tabIndex = options.tabindex;
52
- if (options.autofocus) focusInput();
53
- if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
54
- // Needed to handle Tab key in KHTML
55
- if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute";
56
-
57
- // Check for OS X >= 10.7. This has transparent scrollbars, so the
58
- // overlaying of one scrollbar with another won't work. This is a
59
- // temporary hack to simply turn off the overlay scrollbar. See
60
- // issue #727.
61
- if (mac_geLion) { scrollbar.style.zIndex = -2; scrollbar.style.visibility = "hidden"; }
62
- // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
63
- else if (ie_lt8) scrollbar.style.minWidth = "18px";
64
-
65
- // Check for problem with IE innerHTML not working when we have a
66
- // P (or similar) parent node.
67
- try { charWidth(); }
68
- catch (e) {
69
- if (e.message.match(/runtime/i))
70
- e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
71
- throw e;
72
- }
73
-
74
- // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
75
- var poll = new Delayed(), highlight = new Delayed(), blinker;
76
-
77
- // mode holds a mode API object. doc is the tree of Line objects,
78
- // work an array of lines that should be parsed, and history the
79
- // undo history (instance of History constructor).
80
- var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused;
81
- loadMode();
82
- // The selection. These are always maintained to point at valid
83
- // positions. Inverted is used to remember that the user is
84
- // selecting bottom-to-top.
85
- var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
86
- // Selection-related flags. shiftSelecting obviously tracks
87
- // whether the user is holding shift.
88
- var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, draggingText,
89
- overwrite = false, suppressEdits = false;
90
- // Variables used by startOperation/endOperation to track what
91
- // happened during the operation.
92
- var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,
93
- gutterDirty, callbacks;
94
- // Current visible range (may be bigger than the view window).
95
- var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;
96
- // bracketHighlighted is used to remember that a bracket has been
97
- // marked.
98
- var bracketHighlighted;
99
- // Tracks the maximum line length so that the horizontal scrollbar
100
- // can be kept static when scrolling.
101
- var maxLine = getLine(0), updateMaxLine = false, maxLineChanged = true;
102
- var tabCache = {};
103
- var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
104
- var goalColumn = null;
105
-
106
- // Initialize the content.
107
- operation(function(){setValue(options.value || ""); updateInput = false;})();
108
- var history = new History();
109
-
110
- // Register our event handlers.
111
- connect(scroller, "mousedown", operation(onMouseDown));
112
- connect(scroller, "dblclick", operation(onDoubleClick));
113
- connect(lineSpace, "selectstart", e_preventDefault);
114
- // Gecko browsers fire contextmenu *after* opening the menu, at
115
- // which point we can't mess with it anymore. Context menu is
116
- // handled in onMouseDown for Gecko.
117
- if (!gecko) connect(scroller, "contextmenu", onContextMenu);
118
- connect(scroller, "scroll", onScrollMain);
119
- connect(scrollbar, "scroll", onScrollBar);
120
- connect(scrollbar, "mousedown", function() {if (focused) setTimeout(focusInput, 0);});
121
- var resizeHandler = connect(window, "resize", function() {
122
- if (wrapper.parentNode) updateDisplay(true);
123
- else resizeHandler();
124
- }, true);
125
- connect(input, "keyup", operation(onKeyUp));
126
- connect(input, "input", fastPoll);
127
- connect(input, "keydown", operation(onKeyDown));
128
- connect(input, "keypress", operation(onKeyPress));
129
- connect(input, "focus", onFocus);
130
- connect(input, "blur", onBlur);
131
-
132
- function drag_(e) {
133
- if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
134
- e_stop(e);
135
- }
136
- if (options.dragDrop) {
137
- connect(scroller, "dragstart", onDragStart);
138
- connect(scroller, "dragenter", drag_);
139
- connect(scroller, "dragover", drag_);
140
- connect(scroller, "drop", operation(onDrop));
141
- }
142
- connect(scroller, "paste", function(){focusInput(); fastPoll();});
143
- connect(input, "paste", fastPoll);
144
- connect(input, "cut", operation(function(){
145
- if (!options.readOnly) replaceSelection("");
146
- }));
147
-
148
- // Needed to handle Tab key in KHTML
149
- if (khtml) connect(sizer, "mouseup", function() {
150
- if (document.activeElement == input) input.blur();
151
- focusInput();
152
- });
153
-
154
- // IE throws unspecified error in certain cases, when
155
- // trying to access activeElement before onload
156
- var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { }
157
- if (hasFocus || options.autofocus) setTimeout(onFocus, 20);
158
- else onBlur();
159
-
160
- function isLine(l) {return l >= 0 && l < doc.size;}
161
- // The instance object that we'll return. Mostly calls out to
162
- // local functions in the CodeMirror function. Some do some extra
163
- // range checking and/or clipping. operation is used to wrap the
164
- // call so that changes it makes are tracked, and the display is
165
- // updated afterwards.
166
- var instance = wrapper.CodeMirror = {
167
- getValue: getValue,
168
- setValue: operation(setValue),
169
- getSelection: getSelection,
170
- replaceSelection: operation(replaceSelection),
171
- focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();},
172
- setOption: function(option, value) {
173
- var oldVal = options[option];
174
- options[option] = value;
175
- if (option == "mode" || option == "indentUnit") loadMode();
176
- else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();}
177
- else if (option == "readOnly" && !value) {resetInput(true);}
178
- else if (option == "theme") themeChanged();
179
- else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
180
- else if (option == "tabSize") updateDisplay(true);
181
- else if (option == "keyMap") keyMapChanged();
182
- if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" ||
183
- option == "theme" || option == "lineNumberFormatter") {
184
- gutterChanged();
185
- updateDisplay(true);
186
- }
187
- },
188
- getOption: function(option) {return options[option];},
189
- undo: operation(undo),
190
- redo: operation(redo),
191
- indentLine: operation(function(n, dir) {
192
- if (typeof dir != "string") {
193
- if (dir == null) dir = options.smartIndent ? "smart" : "prev";
194
- else dir = dir ? "add" : "subtract";
195
- }
196
- if (isLine(n)) indentLine(n, dir);
197
- }),
198
- indentSelection: operation(indentSelected),
199
- historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
200
- clearHistory: function() {history = new History();},
201
- setHistory: function(histData) {
202
- history = new History();
203
- history.done = histData.done;
204
- history.undone = histData.undone;
205
- },
206
- getHistory: function() {
207
- history.time = 0;
208
- return {done: history.done.concat([]), undone: history.undone.concat([])};
209
- },
210
- matchBrackets: operation(function(){matchBrackets(true);}),
211
- getTokenAt: operation(function(pos) {
212
- pos = clipPos(pos);
213
- return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), options.tabSize, pos.ch);
214
- }),
215
- getStateAfter: function(line) {
216
- line = clipLine(line == null ? doc.size - 1: line);
217
- return getStateBefore(line + 1);
218
- },
219
- cursorCoords: function(start, mode) {
220
- if (start == null) start = sel.inverted;
221
- return this.charCoords(start ? sel.from : sel.to, mode);
222
- },
223
- charCoords: function(pos, mode) {
224
- pos = clipPos(pos);
225
- if (mode == "local") return localCoords(pos, false);
226
- if (mode == "div") return localCoords(pos, true);
227
- return pageCoords(pos);
228
- },
229
- coordsChar: function(coords) {
230
- var off = eltOffset(lineSpace);
231
- return coordsChar(coords.x - off.left, coords.y - off.top);
232
- },
233
- markText: operation(markText),
234
- setBookmark: setBookmark,
235
- findMarksAt: findMarksAt,
236
- setMarker: operation(addGutterMarker),
237
- clearMarker: operation(removeGutterMarker),
238
- setLineClass: operation(setLineClass),
239
- hideLine: operation(function(h) {return setLineHidden(h, true);}),
240
- showLine: operation(function(h) {return setLineHidden(h, false);}),
241
- onDeleteLine: function(line, f) {
242
- if (typeof line == "number") {
243
- if (!isLine(line)) return null;
244
- line = getLine(line);
245
- }
246
- (line.handlers || (line.handlers = [])).push(f);
247
- return line;
248
- },
249
- lineInfo: lineInfo,
250
- getViewport: function() { return {from: showingFrom, to: showingTo};},
251
- addWidget: function(pos, node, scroll, vert, horiz) {
252
- pos = localCoords(clipPos(pos));
253
- var top = pos.yBot, left = pos.x;
254
- node.style.position = "absolute";
255
- sizer.appendChild(node);
256
- if (vert == "over") top = pos.y;
257
- else if (vert == "near") {
258
- var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
259
- hspace = Math.max(sizer.clientWidth, lineSpace.clientWidth) - paddingLeft();
260
- if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
261
- top = pos.y - node.offsetHeight;
262
- if (left + node.offsetWidth > hspace)
263
- left = hspace - node.offsetWidth;
264
- }
265
- node.style.top = (top + paddingTop()) + "px";
266
- node.style.left = node.style.right = "";
267
- if (horiz == "right") {
268
- left = sizer.clientWidth - node.offsetWidth;
269
- node.style.right = "0px";
270
- } else {
271
- if (horiz == "left") left = 0;
272
- else if (horiz == "middle") left = (sizer.clientWidth - node.offsetWidth) / 2;
273
- node.style.left = (left + paddingLeft()) + "px";
274
- }
275
- if (scroll)
276
- scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
277
- },
278
-
279
- lineCount: function() {return doc.size;},
280
- clipPos: clipPos,
281
- getCursor: function(start) {
282
- if (start == null) start = sel.inverted;
283
- return copyPos(start ? sel.from : sel.to);
284
- },
285
- somethingSelected: function() {return !posEq(sel.from, sel.to);},
286
- setCursor: operation(function(line, ch, user) {
287
- if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user);
288
- else setCursor(line, ch, user);
289
- }),
290
- setSelection: operation(function(from, to, user) {
291
- (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));
292
- }),
293
- getLine: function(line) {if (isLine(line)) return getLine(line).text;},
294
- getLineHandle: function(line) {if (isLine(line)) return getLine(line);},
295
- setLine: operation(function(line, text) {
296
- if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
297
- }),
298
- removeLine: operation(function(line) {
299
- if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
300
- }),
301
- replaceRange: operation(replaceRange),
302
- getRange: function(from, to, lineSep) {return getRange(clipPos(from), clipPos(to), lineSep);},
303
-
304
- triggerOnKeyDown: operation(onKeyDown),
305
- execCommand: function(cmd) {return commands[cmd](instance);},
306
- // Stuff used by commands, probably not much use to outside code.
307
- moveH: operation(moveH),
308
- deleteH: operation(deleteH),
309
- moveV: operation(moveV),
310
- toggleOverwrite: function() {
311
- if(overwrite){
312
- overwrite = false;
313
- cursor.className = cursor.className.replace(" CodeMirror-overwrite", "");
314
- } else {
315
- overwrite = true;
316
- cursor.className += " CodeMirror-overwrite";
317
- }
318
- },
319
-
320
- posFromIndex: function(off) {
321
- var lineNo = 0, ch;
322
- doc.iter(0, doc.size, function(line) {
323
- var sz = line.text.length + 1;
324
- if (sz > off) { ch = off; return true; }
325
- off -= sz;
326
- ++lineNo;
327
- });
328
- return clipPos({line: lineNo, ch: ch});
329
- },
330
- indexFromPos: function (coords) {
331
- if (coords.line < 0 || coords.ch < 0) return 0;
332
- var index = coords.ch;
333
- doc.iter(0, coords.line, function (line) {
334
- index += line.text.length + 1;
335
- });
336
- return index;
337
- },
338
- scrollTo: function(x, y) {
339
- if (x != null) scroller.scrollLeft = x;
340
- if (y != null) scrollbar.scrollTop = scroller.scrollTop = y;
341
- updateDisplay([]);
342
- },
343
- getScrollInfo: function() {
344
- return {x: scroller.scrollLeft, y: scrollbar.scrollTop,
345
- height: scrollbar.scrollHeight, width: scroller.scrollWidth};
346
- },
347
- setSize: function(width, height) {
348
- function interpret(val) {
349
- val = String(val);
350
- return /^\d+$/.test(val) ? val + "px" : val;
351
- }
352
- if (width != null) wrapper.style.width = interpret(width);
353
- if (height != null) scroller.style.height = interpret(height);
354
- instance.refresh();
355
- },
356
-
357
- operation: function(f){return operation(f)();},
358
- compoundChange: function(f){return compoundChange(f);},
359
- refresh: function(){
360
- updateDisplay(true, null, lastScrollTop);
361
- if (scrollbar.scrollHeight > lastScrollTop)
362
- scrollbar.scrollTop = lastScrollTop;
363
- },
364
- getInputField: function(){return input;},
365
- getWrapperElement: function(){return wrapper;},
366
- getScrollerElement: function(){return scroller;},
367
- getGutterElement: function(){return gutter;}
368
- };
369
-
370
- function getLine(n) { return getLineAt(doc, n); }
371
- function updateLineHeight(line, height) {
372
- gutterDirty = true;
373
- var diff = height - line.height;
374
- for (var n = line; n; n = n.parent) n.height += diff;
375
- }
376
-
377
- function setValue(code) {
378
- var top = {line: 0, ch: 0};
379
- updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
380
- splitLines(code), top, top);
381
- updateInput = true;
382
- }
383
- function getValue(lineSep) {
384
- var text = [];
385
- doc.iter(0, doc.size, function(line) { text.push(line.text); });
386
- return text.join(lineSep || "\n");
387
- }
388
-
389
- function onScrollBar(e) {
390
- if (scrollbar.scrollTop != lastScrollTop) {
391
- lastScrollTop = scroller.scrollTop = scrollbar.scrollTop;
392
- updateDisplay([]);
393
- }
394
- }
395
-
396
- function onScrollMain(e) {
397
- if (options.fixedGutter && gutter.style.left != scroller.scrollLeft + "px")
398
- gutter.style.left = scroller.scrollLeft + "px";
399
- if (scroller.scrollTop != lastScrollTop) {
400
- lastScrollTop = scroller.scrollTop;
401
- if (scrollbar.scrollTop != lastScrollTop)
402
- scrollbar.scrollTop = lastScrollTop;
403
- updateDisplay([]);
404
- }
405
- if (options.onScroll) options.onScroll(instance);
406
- }
407
-
408
- function onMouseDown(e) {
409
- setShift(e_prop(e, "shiftKey"));
410
- // Check whether this is a click in a widget
411
- for (var n = e_target(e); n != wrapper; n = n.parentNode)
412
- if (n.parentNode == sizer && n != mover) return;
413
-
414
- // See if this is a click in the gutter
415
- for (var n = e_target(e); n != wrapper; n = n.parentNode)
416
- if (n.parentNode == gutterText) {
417
- if (options.onGutterClick)
418
- options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
419
- return e_preventDefault(e);
420
- }
421
-
422
- var start = posFromMouse(e);
423
-
424
- switch (e_button(e)) {
425
- case 3:
426
- if (gecko) onContextMenu(e);
427
- return;
428
- case 2:
429
- if (start) setCursor(start.line, start.ch, true);
430
- setTimeout(focusInput, 20);
431
- e_preventDefault(e);
432
- return;
433
- }
434
- // For button 1, if it was clicked inside the editor
435
- // (posFromMouse returning non-null), we have to adjust the
436
- // selection.
437
- if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
438
-
439
- if (!focused) onFocus();
440
-
441
- var now = +new Date, type = "single";
442
- if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
443
- type = "triple";
444
- e_preventDefault(e);
445
- setTimeout(focusInput, 20);
446
- selectLine(start.line);
447
- } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
448
- type = "double";
449
- lastDoubleClick = {time: now, pos: start};
450
- e_preventDefault(e);
451
- var word = findWordAt(start);
452
- setSelectionUser(word.from, word.to);
453
- } else { lastClick = {time: now, pos: start}; }
454
-
455
- function dragEnd(e2) {
456
- if (webkit) scroller.draggable = false;
457
- draggingText = false;
458
- up(); drop();
459
- if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
460
- e_preventDefault(e2);
461
- setCursor(start.line, start.ch, true);
462
- focusInput();
463
- }
464
- }
465
- var last = start, going;
466
- if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
467
- !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
468
- // Let the drag handler handle this.
469
- if (webkit) scroller.draggable = true;
470
- var up = connect(document, "mouseup", operation(dragEnd), true);
471
- var drop = connect(scroller, "drop", operation(dragEnd), true);
472
- draggingText = true;
473
- // IE's approach to draggable
474
- if (scroller.dragDrop) scroller.dragDrop();
475
- return;
476
- }
477
- e_preventDefault(e);
478
- if (type == "single") setCursor(start.line, start.ch, true);
479
-
480
- var startstart = sel.from, startend = sel.to;
481
-
482
- function doSelect(cur) {
483
- if (type == "single") {
484
- setSelectionUser(start, cur);
485
- } else if (type == "double") {
486
- var word = findWordAt(cur);
487
- if (posLess(cur, startstart)) setSelectionUser(word.from, startend);
488
- else setSelectionUser(startstart, word.to);
489
- } else if (type == "triple") {
490
- if (posLess(cur, startstart)) setSelectionUser(startend, clipPos({line: cur.line, ch: 0}));
491
- else setSelectionUser(startstart, clipPos({line: cur.line + 1, ch: 0}));
492
- }
493
- }
494
-
495
- function extend(e) {
496
- var cur = posFromMouse(e, true);
497
- if (cur && !posEq(cur, last)) {
498
- if (!focused) onFocus();
499
- last = cur;
500
- doSelect(cur);
501
- updateInput = false;
502
- var visible = visibleLines();
503
- if (cur.line >= visible.to || cur.line < visible.from)
504
- going = setTimeout(operation(function(){extend(e);}), 150);
505
- }
506
- }
507
-
508
- function done(e) {
509
- clearTimeout(going);
510
- var cur = posFromMouse(e);
511
- if (cur) doSelect(cur);
512
- e_preventDefault(e);
513
- focusInput();
514
- updateInput = true;
515
- move(); up();
516
- }
517
- var move = connect(document, "mousemove", operation(function(e) {
518
- clearTimeout(going);
519
- e_preventDefault(e);
520
- if (!ie && !e_button(e)) done(e);
521
- else extend(e);
522
- }), true);
523
- var up = connect(document, "mouseup", operation(done), true);
524
- }
525
- function onDoubleClick(e) {
526
- for (var n = e_target(e); n != wrapper; n = n.parentNode)
527
- if (n.parentNode == gutterText) return e_preventDefault(e);
528
- e_preventDefault(e);
529
- }
530
- function onDrop(e) {
531
- if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
532
- e_preventDefault(e);
533
- var pos = posFromMouse(e, true), files = e.dataTransfer.files;
534
- if (!pos || options.readOnly) return;
535
- if (files && files.length && window.FileReader && window.File) {
536
- var n = files.length, text = Array(n), read = 0;
537
- var loadFile = function(file, i) {
538
- var reader = new FileReader;
539
- reader.onload = function() {
540
- text[i] = reader.result;
541
- if (++read == n) {
542
- pos = clipPos(pos);
543
- operation(function() {
544
- var end = replaceRange(text.join(""), pos, pos);
545
- setSelectionUser(pos, end);
546
- })();
547
- }
548
- };
549
- reader.readAsText(file);
550
- };
551
- for (var i = 0; i < n; ++i) loadFile(files[i], i);
552
- } else {
553
- // Don't do a replace if the drop happened inside of the selected text.
554
- if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return;
555
- try {
556
- var text = e.dataTransfer.getData("Text");
557
- if (text) {
558
- compoundChange(function() {
559
- var curFrom = sel.from, curTo = sel.to;
560
- setSelectionUser(pos, pos);
561
- if (draggingText) replaceRange("", curFrom, curTo);
562
- replaceSelection(text);
563
- focusInput();
564
- });
565
- }
566
- }
567
- catch(e){}
568
- }
569
- }
570
- function onDragStart(e) {
571
- var txt = getSelection();
572
- e.dataTransfer.setData("Text", txt);
573
-
574
- // Use dummy image instead of default browsers image.
575
- if (gecko || chrome || opera) {
576
- var img = elt('img');
577
- img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image
578
- e.dataTransfer.setDragImage(img, 0, 0);
579
- }
580
- }
581
-
582
- function doHandleBinding(bound, dropShift) {
583
- if (typeof bound == "string") {
584
- bound = commands[bound];
585
- if (!bound) return false;
586
- }
587
- var prevShift = shiftSelecting;
588
- try {
589
- if (options.readOnly) suppressEdits = true;
590
- if (dropShift) shiftSelecting = null;
591
- bound(instance);
592
- } catch(e) {
593
- if (e != Pass) throw e;
594
- return false;
595
- } finally {
596
- shiftSelecting = prevShift;
597
- suppressEdits = false;
598
- }
599
- return true;
600
- }
601
- var maybeTransition;
602
- function handleKeyBinding(e) {
603
- // Handle auto keymap transitions
604
- var startMap = getKeyMap(options.keyMap), next = startMap.auto;
605
- clearTimeout(maybeTransition);
606
- if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
607
- if (getKeyMap(options.keyMap) == startMap) {
608
- options.keyMap = (next.call ? next.call(null, instance) : next);
609
- }
610
- }, 50);
611
-
612
- var name = keyNames[e_prop(e, "keyCode")], handled = false;
613
- if (name == null || e.altGraphKey) return false;
614
- if (e_prop(e, "altKey")) name = "Alt-" + name;
615
- if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name;
616
- if (e_prop(e, "metaKey")) name = "Cmd-" + name;
617
-
618
- var stopped = false;
619
- function stop() { stopped = true; }
620
-
621
- if (e_prop(e, "shiftKey")) {
622
- handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap,
623
- function(b) {return doHandleBinding(b, true);}, stop)
624
- || lookupKey(name, options.extraKeys, options.keyMap, function(b) {
625
- if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b);
626
- }, stop);
627
- } else {
628
- handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop);
629
- }
630
- if (stopped) handled = false;
631
- if (handled) {
632
- e_preventDefault(e);
633
- restartBlink();
634
- if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
635
- }
636
- return handled;
637
- }
638
- function handleCharBinding(e, ch) {
639
- var handled = lookupKey("'" + ch + "'", options.extraKeys,
640
- options.keyMap, function(b) { return doHandleBinding(b, true); });
641
- if (handled) {
642
- e_preventDefault(e);
643
- restartBlink();
644
- }
645
- return handled;
646
- }
647
-
648
- var lastStoppedKey = null;
649
- function onKeyDown(e) {
650
- if (!focused) onFocus();
651
- if (ie && e.keyCode == 27) { e.returnValue = false; }
652
- if (pollingFast) { if (readInput()) pollingFast = false; }
653
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
654
- var code = e_prop(e, "keyCode");
655
- // IE does strange things with escape.
656
- setShift(code == 16 || e_prop(e, "shiftKey"));
657
- // First give onKeyEvent option a chance to handle this.
658
- var handled = handleKeyBinding(e);
659
- if (opera) {
660
- lastStoppedKey = handled ? code : null;
661
- // Opera has no cut event... we try to at least catch the key combo
662
- if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey"))
663
- replaceSelection("");
664
- }
665
- }
666
- function onKeyPress(e) {
667
- if (pollingFast) readInput();
668
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
669
- var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
670
- if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
671
- if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return;
672
- var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
673
- if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) {
674
- if (mode.electricChars.indexOf(ch) > -1)
675
- setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
676
- }
677
- if (handleCharBinding(e, ch)) return;
678
- fastPoll();
679
- }
680
- function onKeyUp(e) {
681
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
682
- if (e_prop(e, "keyCode") == 16) shiftSelecting = null;
683
- }
684
-
685
- function onFocus() {
686
- if (options.readOnly == "nocursor") return;
687
- if (!focused) {
688
- if (options.onFocus) options.onFocus(instance);
689
- focused = true;
690
- if (scroller.className.search(/\bCodeMirror-focused\b/) == -1)
691
- scroller.className += " CodeMirror-focused";
692
- if (!leaveInputAlone) resetInput(true);
693
- }
694
- slowPoll();
695
- restartBlink();
696
- }
697
- function onBlur() {
698
- if (focused) {
699
- if (options.onBlur) options.onBlur(instance);
700
- focused = false;
701
- if (bracketHighlighted)
702
- operation(function(){
703
- if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; }
704
- })();
705
- scroller.className = scroller.className.replace(" CodeMirror-focused", "");
706
- }
707
- clearInterval(blinker);
708
- setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
709
- }
710
-
711
- // Replace the range from from to to by the strings in newText.
712
- // Afterwards, set the selection to selFrom, selTo.
713
- function updateLines(from, to, newText, selFrom, selTo) {
714
- if (suppressEdits) return;
715
- if (history) {
716
- var old = [];
717
- doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
718
- history.addChange(from.line, newText.length, old);
719
- while (history.done.length > options.undoDepth) history.done.shift();
720
- }
721
- updateLinesNoUndo(from, to, newText, selFrom, selTo);
722
- }
723
- function unredoHelper(from, to) {
724
- if (!from.length) return;
725
- var set = from.pop(), out = [];
726
- for (var i = set.length - 1; i >= 0; i -= 1) {
727
- var change = set[i];
728
- var replaced = [], end = change.start + change.added;
729
- doc.iter(change.start, end, function(line) { replaced.push(line.text); });
730
- out.push({start: change.start, added: change.old.length, old: replaced});
731
- var pos = {line: change.start + change.old.length - 1,
732
- ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])};
733
- updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
734
- }
735
- updateInput = true;
736
- to.push(out);
737
- }
738
- function undo() {unredoHelper(history.done, history.undone);}
739
- function redo() {unredoHelper(history.undone, history.done);}
740
-
741
- function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
742
- if (suppressEdits) return;
743
- var recomputeMaxLength = false, maxLineLength = maxLine.text.length;
744
- if (!options.lineWrapping)
745
- doc.iter(from.line, to.line + 1, function(line) {
746
- if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
747
- });
748
- if (from.line != to.line || newText.length > 1) gutterDirty = true;
749
-
750
- var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
751
- // First adjust the line structure, taking some care to leave highlighting intact.
752
- if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") {
753
- // This is a whole-line replace. Treated specially to make
754
- // sure line objects move the way they are supposed to.
755
- var added = [], prevLine = null;
756
- if (from.line) {
757
- prevLine = getLine(from.line - 1);
758
- prevLine.fixMarkEnds(lastLine);
759
- } else lastLine.fixMarkStarts();
760
- for (var i = 0, e = newText.length - 1; i < e; ++i)
761
- added.push(Line.inheritMarks(newText[i], prevLine));
762
- if (nlines) doc.remove(from.line, nlines, callbacks);
763
- if (added.length) doc.insert(from.line, added);
764
- } else if (firstLine == lastLine) {
765
- if (newText.length == 1)
766
- firstLine.replace(from.ch, to.ch, newText[0]);
767
- else {
768
- lastLine = firstLine.split(to.ch, newText[newText.length-1]);
769
- firstLine.replace(from.ch, null, newText[0]);
770
- firstLine.fixMarkEnds(lastLine);
771
- var added = [];
772
- for (var i = 1, e = newText.length - 1; i < e; ++i)
773
- added.push(Line.inheritMarks(newText[i], firstLine));
774
- added.push(lastLine);
775
- doc.insert(from.line + 1, added);
776
- }
777
- } else if (newText.length == 1) {
778
- firstLine.replace(from.ch, null, newText[0]);
779
- lastLine.replace(null, to.ch, "");
780
- firstLine.append(lastLine);
781
- doc.remove(from.line + 1, nlines, callbacks);
782
- } else {
783
- var added = [];
784
- firstLine.replace(from.ch, null, newText[0]);
785
- lastLine.replace(null, to.ch, newText[newText.length-1]);
786
- firstLine.fixMarkEnds(lastLine);
787
- for (var i = 1, e = newText.length - 1; i < e; ++i)
788
- added.push(Line.inheritMarks(newText[i], firstLine));
789
- if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);
790
- doc.insert(from.line + 1, added);
791
- }
792
- if (options.lineWrapping) {
793
- var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3);
794
- doc.iter(from.line, from.line + newText.length, function(line) {
795
- if (line.hidden) return;
796
- var guess = Math.ceil(line.text.length / perLine) || 1;
797
- if (guess != line.height) updateLineHeight(line, guess);
798
- });
799
- } else {
800
- doc.iter(from.line, from.line + newText.length, function(line) {
801
- var l = line.text;
802
- if (!line.hidden && l.length > maxLineLength) {
803
- maxLine = line; maxLineLength = l.length; maxLineChanged = true;
804
- recomputeMaxLength = false;
805
- }
806
- });
807
- if (recomputeMaxLength) updateMaxLine = true;
808
- }
809
-
810
- // Add these lines to the work array, so that they will be
811
- // highlighted. Adjust work lines if lines were added/removed.
812
- var newWork = [], lendiff = newText.length - nlines - 1;
813
- for (var i = 0, l = work.length; i < l; ++i) {
814
- var task = work[i];
815
- if (task < from.line) newWork.push(task);
816
- else if (task > to.line) newWork.push(task + lendiff);
817
- }
818
- var hlEnd = from.line + Math.min(newText.length, 500);
819
- highlightLines(from.line, hlEnd);
820
- newWork.push(hlEnd);
821
- work = newWork;
822
- startWorker(100);
823
- // Remember that these lines changed, for updating the display
824
- changes.push({from: from.line, to: to.line + 1, diff: lendiff});
825
- var changeObj = {from: from, to: to, text: newText};
826
- if (textChanged) {
827
- for (var cur = textChanged; cur.next; cur = cur.next) {}
828
- cur.next = changeObj;
829
- } else textChanged = changeObj;
830
-
831
- // Update the selection
832
- function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
833
- setSelection(clipPos(selFrom), clipPos(selTo),
834
- updateLine(sel.from.line), updateLine(sel.to.line));
835
- }
836
-
837
- function needsScrollbar() {
838
- var realHeight = doc.height * textHeight() + 2 * paddingTop();
839
- return realHeight * .99 > scroller.offsetHeight ? realHeight : false;
840
- }
841
-
842
- function updateVerticalScroll(scrollTop) {
843
- var scrollHeight = needsScrollbar();
844
- scrollbar.style.display = scrollHeight ? "block" : "none";
845
- if (scrollHeight) {
846
- scrollbarInner.style.height = sizer.style.minHeight = scrollHeight + "px";
847
- scrollbar.style.height = scroller.clientHeight + "px";
848
- if (scrollTop != null) {
849
- scrollbar.scrollTop = scroller.scrollTop = scrollTop;
850
- // 'Nudge' the scrollbar to work around a Webkit bug where,
851
- // in some situations, we'd end up with a scrollbar that
852
- // reported its scrollTop (and looked) as expected, but
853
- // *behaved* as if it was still in a previous state (i.e.
854
- // couldn't scroll up, even though it appeared to be at the
855
- // bottom).
856
- if (webkit) setTimeout(function() {
857
- if (scrollbar.scrollTop != scrollTop) return;
858
- scrollbar.scrollTop = scrollTop + (scrollTop ? -1 : 1);
859
- scrollbar.scrollTop = scrollTop;
860
- }, 0);
861
- }
862
- } else {
863
- sizer.style.minHeight = "";
864
- }
865
- // Position the mover div to align with the current virtual scroll position
866
- mover.style.top = displayOffset * textHeight() + "px";
867
- }
868
-
869
- function computeMaxLength() {
870
- maxLine = getLine(0); maxLineChanged = true;
871
- var maxLineLength = maxLine.text.length;
872
- doc.iter(1, doc.size, function(line) {
873
- var l = line.text;
874
- if (!line.hidden && l.length > maxLineLength) {
875
- maxLineLength = l.length; maxLine = line;
876
- }
877
- });
878
- updateMaxLine = false;
879
- }
880
-
881
- function replaceRange(code, from, to) {
882
- from = clipPos(from);
883
- if (!to) to = from; else to = clipPos(to);
884
- code = splitLines(code);
885
- function adjustPos(pos) {
886
- if (posLess(pos, from)) return pos;
887
- if (!posLess(to, pos)) return end;
888
- var line = pos.line + code.length - (to.line - from.line) - 1;
889
- var ch = pos.ch;
890
- if (pos.line == to.line)
891
- ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0));
892
- return {line: line, ch: ch};
893
- }
894
- var end;
895
- replaceRange1(code, from, to, function(end1) {
896
- end = end1;
897
- return {from: adjustPos(sel.from), to: adjustPos(sel.to)};
898
- });
899
- return end;
900
- }
901
- function replaceSelection(code, collapse) {
902
- replaceRange1(splitLines(code), sel.from, sel.to, function(end) {
903
- if (collapse == "end") return {from: end, to: end};
904
- else if (collapse == "start") return {from: sel.from, to: sel.from};
905
- else return {from: sel.from, to: end};
906
- });
907
- }
908
- function replaceRange1(code, from, to, computeSel) {
909
- var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length;
910
- var newSel = computeSel({line: from.line + code.length - 1, ch: endch});
911
- updateLines(from, to, code, newSel.from, newSel.to);
912
- }
913
-
914
- function getRange(from, to, lineSep) {
915
- var l1 = from.line, l2 = to.line;
916
- if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
917
- var code = [getLine(l1).text.slice(from.ch)];
918
- doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
919
- code.push(getLine(l2).text.slice(0, to.ch));
920
- return code.join(lineSep || "\n");
921
- }
922
- function getSelection(lineSep) {
923
- return getRange(sel.from, sel.to, lineSep);
924
- }
925
-
926
- function slowPoll() {
927
- if (pollingFast) return;
928
- poll.set(options.pollInterval, function() {
929
- startOperation();
930
- readInput();
931
- if (focused) slowPoll();
932
- endOperation();
933
- });
934
- }
935
- function fastPoll() {
936
- var missed = false;
937
- pollingFast = true;
938
- function p() {
939
- startOperation();
940
- var changed = readInput();
941
- if (!changed && !missed) {missed = true; poll.set(60, p);}
942
- else {pollingFast = false; slowPoll();}
943
- endOperation();
944
- }
945
- poll.set(20, p);
946
- }
947
-
948
- // Previnput is a hack to work with IME. If we reset the textarea
949
- // on every change, that breaks IME. So we look for changes
950
- // compared to the previous content instead. (Modern browsers have
951
- // events that indicate IME taking place, but these are not widely
952
- // supported or compatible enough yet to rely on.)
953
- var prevInput = "";
954
- function readInput() {
955
- if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false;
956
- var text = input.value;
957
- if (text == prevInput) return false;
958
- shiftSelecting = null;
959
- var same = 0, l = Math.min(prevInput.length, text.length);
960
- while (same < l && prevInput[same] == text[same]) ++same;
961
- if (same < prevInput.length)
962
- sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};
963
- else if (overwrite && posEq(sel.from, sel.to))
964
- sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};
965
- replaceSelection(text.slice(same), "end");
966
- if (text.length > 1000) { input.value = prevInput = ""; }
967
- else prevInput = text;
968
- return true;
969
- }
970
- function resetInput(user) {
971
- if (!posEq(sel.from, sel.to)) {
972
- prevInput = "";
973
- input.value = getSelection();
974
- if (focused) selectInput(input);
975
- } else if (user) prevInput = input.value = "";
976
- }
977
-
978
- function focusInput() {
979
- if (options.readOnly != "nocursor") input.focus();
980
- }
981
-
982
- function scrollCursorIntoView() {
983
- var coords = calculateCursorCoords();
984
- scrollIntoView(coords.x, coords.y, coords.x, coords.yBot);
985
- if (!focused) return;
986
- var box = sizer.getBoundingClientRect(), doScroll = null;
987
- if (coords.y + box.top < 0) doScroll = true;
988
- else if (coords.y + box.top + textHeight() > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
989
- if (doScroll != null) {
990
- var hidden = cursor.style.display == "none";
991
- if (hidden) {
992
- cursor.style.display = "";
993
- cursor.style.left = coords.x + "px";
994
- cursor.style.top = (coords.y - displayOffset) + "px";
995
- }
996
- cursor.scrollIntoView(doScroll);
997
- if (hidden) cursor.style.display = "none";
998
- }
999
- }
1000
- function calculateCursorCoords() {
1001
- var cursor = localCoords(sel.inverted ? sel.from : sel.to);
1002
- var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
1003
- return {x: x, y: cursor.y, yBot: cursor.yBot};
1004
- }
1005
- function scrollIntoView(x1, y1, x2, y2) {
1006
- var scrollPos = calculateScrollPos(x1, y1, x2, y2);
1007
- if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft;}
1008
- if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scroller.scrollTop = scrollPos.scrollTop;}
1009
- }
1010
- function calculateScrollPos(x1, y1, x2, y2) {
1011
- var pl = paddingLeft(), pt = paddingTop();
1012
- y1 += pt; y2 += pt; x1 += pl; x2 += pl;
1013
- var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {};
1014
- var docBottom = needsScrollbar() || Infinity;
1015
- var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;
1016
- if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
1017
- else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
1018
-
1019
- var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
1020
- var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
1021
- var atLeft = x1 < gutterw + pl + 10;
1022
- if (x1 < screenleft + gutterw || atLeft) {
1023
- if (atLeft) x1 = 0;
1024
- result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
1025
- } else if (x2 > screenw + screenleft - 3) {
1026
- result.scrollLeft = x2 + 10 - screenw;
1027
- }
1028
- return result;
1029
- }
1030
-
1031
- function visibleLines(scrollTop) {
1032
- var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop();
1033
- var fromHeight = Math.max(0, Math.floor(top / lh));
1034
- var toHeight = Math.ceil((top + scroller.clientHeight) / lh);
1035
- return {from: lineAtHeight(doc, fromHeight),
1036
- to: lineAtHeight(doc, toHeight)};
1037
- }
1038
- // Uses a set of changes plus the current scroll position to
1039
- // determine which DOM updates have to be made, and makes the
1040
- // updates.
1041
- function updateDisplay(changes, suppressCallback, scrollTop) {
1042
- if (!scroller.clientWidth) {
1043
- showingFrom = showingTo = displayOffset = 0;
1044
- return;
1045
- }
1046
- // Compute the new visible window
1047
- // If scrollTop is specified, use that to determine which lines
1048
- // to render instead of the current scrollbar position.
1049
- var visible = visibleLines(scrollTop);
1050
- // Bail out if the visible area is already rendered and nothing changed.
1051
- if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) {
1052
- updateVerticalScroll(scrollTop);
1053
- return;
1054
- }
1055
- var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
1056
- if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
1057
- if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
1058
-
1059
- // Create a range of theoretically intact lines, and punch holes
1060
- // in that using the change info.
1061
- var intact = changes === true ? [] :
1062
- computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
1063
- // Clip off the parts that won't be visible
1064
- var intactLines = 0;
1065
- for (var i = 0; i < intact.length; ++i) {
1066
- var range = intact[i];
1067
- if (range.from < from) {range.domStart += (from - range.from); range.from = from;}
1068
- if (range.to > to) range.to = to;
1069
- if (range.from >= range.to) intact.splice(i--, 1);
1070
- else intactLines += range.to - range.from;
1071
- }
1072
- if (intactLines == to - from && from == showingFrom && to == showingTo) {
1073
- updateVerticalScroll(scrollTop);
1074
- return;
1075
- }
1076
- intact.sort(function(a, b) {return a.domStart - b.domStart;});
1077
-
1078
- var th = textHeight(), gutterDisplay = gutter.style.display;
1079
- lineDiv.style.display = "none";
1080
- patchDisplay(from, to, intact);
1081
- lineDiv.style.display = gutter.style.display = "";
1082
-
1083
- var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;
1084
- // This is just a bogus formula that detects when the editor is
1085
- // resized or the font size changes.
1086
- if (different) lastSizeC = scroller.clientHeight + th;
1087
- if (from != showingFrom || to != showingTo && options.onViewportChange)
1088
- setTimeout(function(){
1089
- if (options.onViewportChange) options.onViewportChange(instance, from, to);
1090
- });
1091
- showingFrom = from; showingTo = to;
1092
- displayOffset = heightAtLine(doc, from);
1093
-
1094
- // Since this is all rather error prone, it is honoured with the
1095
- // only assertion in the whole file.
1096
- if (lineDiv.childNodes.length != showingTo - showingFrom)
1097
- throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
1098
- " nodes=" + lineDiv.childNodes.length);
1099
-
1100
- function checkHeights() {
1101
- var curNode = lineDiv.firstChild, heightChanged = false;
1102
- doc.iter(showingFrom, showingTo, function(line) {
1103
- // Work around bizarro IE7 bug where, sometimes, our curNode
1104
- // is magically replaced with a new node in the DOM, leaving
1105
- // us with a reference to an orphan (nextSibling-less) node.
1106
- if (!curNode) return;
1107
- if (!line.hidden) {
1108
- var height = Math.round(curNode.offsetHeight / th) || 1;
1109
- if (line.height != height) {
1110
- updateLineHeight(line, height);
1111
- gutterDirty = heightChanged = true;
1112
- }
1113
- }
1114
- curNode = curNode.nextSibling;
1115
- });
1116
- return heightChanged;
1117
- }
1118
-
1119
- if (options.lineWrapping) checkHeights();
1120
-
1121
- gutter.style.display = gutterDisplay;
1122
- if (different || gutterDirty) {
1123
- // If the gutter grew in size, re-check heights. If those changed, re-draw gutter.
1124
- updateGutter() && options.lineWrapping && checkHeights() && updateGutter();
1125
- }
1126
- updateVerticalScroll(scrollTop);
1127
- updateSelection();
1128
- if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
1129
- return true;
1130
- }
1131
-
1132
- function computeIntact(intact, changes) {
1133
- for (var i = 0, l = changes.length || 0; i < l; ++i) {
1134
- var change = changes[i], intact2 = [], diff = change.diff || 0;
1135
- for (var j = 0, l2 = intact.length; j < l2; ++j) {
1136
- var range = intact[j];
1137
- if (change.to <= range.from && change.diff)
1138
- intact2.push({from: range.from + diff, to: range.to + diff,
1139
- domStart: range.domStart});
1140
- else if (change.to <= range.from || change.from >= range.to)
1141
- intact2.push(range);
1142
- else {
1143
- if (change.from > range.from)
1144
- intact2.push({from: range.from, to: change.from, domStart: range.domStart});
1145
- if (change.to < range.to)
1146
- intact2.push({from: change.to + diff, to: range.to + diff,
1147
- domStart: range.domStart + (change.to - range.from)});
1148
- }
1149
- }
1150
- intact = intact2;
1151
- }
1152
- return intact;
1153
- }
1154
-
1155
- function patchDisplay(from, to, intact) {
1156
- function killNode(node) {
1157
- var tmp = node.nextSibling;
1158
- node.parentNode.removeChild(node);
1159
- return tmp;
1160
- }
1161
- // The first pass removes the DOM nodes that aren't intact.
1162
- if (!intact.length) removeChildren(lineDiv);
1163
- else {
1164
- var domPos = 0, curNode = lineDiv.firstChild, n;
1165
- for (var i = 0; i < intact.length; ++i) {
1166
- var cur = intact[i];
1167
- while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
1168
- for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
1169
- }
1170
- while (curNode) curNode = killNode(curNode);
1171
- }
1172
- // This pass fills in the lines that actually changed.
1173
- var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
1174
- doc.iter(from, to, function(line) {
1175
- if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
1176
- if (!nextIntact || nextIntact.from > j) {
1177
- if (line.hidden) var lineElement = elt("pre");
1178
- else {
1179
- var lineElement = line.getElement(makeTab);
1180
- if (line.className) lineElement.className = line.className;
1181
- // Kludge to make sure the styled element lies behind the selection (by z-index)
1182
- if (line.bgClassName) {
1183
- var pre = elt("pre", "\u00a0", line.bgClassName, "position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2");
1184
- lineElement = elt("div", [pre, lineElement], null, "position: relative");
1185
- }
1186
- }
1187
- lineDiv.insertBefore(lineElement, curNode);
1188
- } else {
1189
- curNode = curNode.nextSibling;
1190
- }
1191
- ++j;
1192
- });
1193
- }
1194
-
1195
- function updateGutter() {
1196
- if (!options.gutter && !options.lineNumbers) return;
1197
- var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
1198
- gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
1199
- var fragment = document.createDocumentFragment(), i = showingFrom, normalNode;
1200
- doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
1201
- if (line.hidden) {
1202
- fragment.appendChild(elt("pre"));
1203
- } else {
1204
- var marker = line.gutterMarker;
1205
- var text = options.lineNumbers ? options.lineNumberFormatter(i + options.firstLineNumber) : null;
1206
- if (marker && marker.text)
1207
- text = marker.text.replace("%N%", text != null ? text : "");
1208
- else if (text == null)
1209
- text = "\u00a0";
1210
- var markerElement = fragment.appendChild(elt("pre", null, marker && marker.style));
1211
- markerElement.innerHTML = text;
1212
- for (var j = 1; j < line.height; ++j) {
1213
- markerElement.appendChild(elt("br"));
1214
- markerElement.appendChild(document.createTextNode("\u00a0"));
1215
- }
1216
- if (!marker) normalNode = i;
1217
- }
1218
- ++i;
1219
- });
1220
- gutter.style.display = "none";
1221
- removeChildrenAndAdd(gutterText, fragment);
1222
- // Make sure scrolling doesn't cause number gutter size to pop
1223
- if (normalNode != null && options.lineNumbers) {
1224
- var node = gutterText.childNodes[normalNode - showingFrom];
1225
- var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = "";
1226
- while (val.length + pad.length < minwidth) pad += "\u00a0";
1227
- if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild);
1228
- }
1229
- gutter.style.display = "";
1230
- var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2;
1231
- lineSpace.style.marginLeft = gutter.offsetWidth + "px";
1232
- gutterDirty = false;
1233
- return resized;
1234
- }
1235
- function updateSelection() {
1236
- var collapsed = posEq(sel.from, sel.to);
1237
- var fromPos = localCoords(sel.from, true);
1238
- var toPos = collapsed ? fromPos : localCoords(sel.to, true);
1239
- var headPos = sel.inverted ? fromPos : toPos, th = textHeight();
1240
- var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
1241
- inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px";
1242
- inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px";
1243
- if (collapsed) {
1244
- cursor.style.top = headPos.y + "px";
1245
- cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px";
1246
- cursor.style.display = "";
1247
- selectionDiv.style.display = "none";
1248
- } else {
1249
- var sameLine = fromPos.y == toPos.y, fragment = document.createDocumentFragment();
1250
- var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth;
1251
- var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight;
1252
- var add = function(left, top, right, height) {
1253
- var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px"
1254
- : "right: " + right + "px";
1255
- fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
1256
- "px; top: " + top + "px; " + rstyle + "; height: " + height + "px"));
1257
- };
1258
- if (sel.from.ch && fromPos.y >= 0) {
1259
- var right = sameLine ? clientWidth - toPos.x : 0;
1260
- add(fromPos.x, fromPos.y, right, th);
1261
- }
1262
- var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0));
1263
- var middleHeight = Math.min(toPos.y, clientHeight) - middleStart;
1264
- if (middleHeight > 0.2 * th)
1265
- add(0, middleStart, 0, middleHeight);
1266
- if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th)
1267
- add(0, toPos.y, clientWidth - toPos.x, th);
1268
- removeChildrenAndAdd(selectionDiv, fragment);
1269
- cursor.style.display = "none";
1270
- selectionDiv.style.display = "";
1271
- }
1272
- }
1273
-
1274
- function setShift(val) {
1275
- if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
1276
- else shiftSelecting = null;
1277
- }
1278
- function setSelectionUser(from, to) {
1279
- var sh = shiftSelecting && clipPos(shiftSelecting);
1280
- if (sh) {
1281
- if (posLess(sh, from)) from = sh;
1282
- else if (posLess(to, sh)) to = sh;
1283
- }
1284
- setSelection(from, to);
1285
- userSelChange = true;
1286
- }
1287
- // Update the selection. Last two args are only used by
1288
- // updateLines, since they have to be expressed in the line
1289
- // numbers before the update.
1290
- function setSelection(from, to, oldFrom, oldTo) {
1291
- goalColumn = null;
1292
- if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
1293
- if (posEq(sel.from, from) && posEq(sel.to, to)) return;
1294
- if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
1295
-
1296
- // Skip over hidden lines.
1297
- if (from.line != oldFrom) {
1298
- var from1 = skipHidden(from, oldFrom, sel.from.ch);
1299
- // If there is no non-hidden line left, force visibility on current line
1300
- if (!from1) setLineHidden(from.line, false);
1301
- else from = from1;
1302
- }
1303
- if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
1304
-
1305
- if (posEq(from, to)) sel.inverted = false;
1306
- else if (posEq(from, sel.to)) sel.inverted = false;
1307
- else if (posEq(to, sel.from)) sel.inverted = true;
1308
-
1309
- if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) {
1310
- var head = sel.inverted ? from : to;
1311
- if (head.line != sel.from.line && sel.from.line < doc.size) {
1312
- var oldLine = getLine(sel.from.line);
1313
- if (/^\s+$/.test(oldLine.text))
1314
- setTimeout(operation(function() {
1315
- if (oldLine.parent && /^\s+$/.test(oldLine.text)) {
1316
- var no = lineNo(oldLine);
1317
- replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length});
1318
- }
1319
- }, 10));
1320
- }
1321
- }
1322
-
1323
- sel.from = from; sel.to = to;
1324
- selectionChanged = true;
1325
- }
1326
- function skipHidden(pos, oldLine, oldCh) {
1327
- function getNonHidden(dir) {
1328
- var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
1329
- while (lNo != end) {
1330
- var line = getLine(lNo);
1331
- if (!line.hidden) {
1332
- var ch = pos.ch;
1333
- if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length;
1334
- return {line: lNo, ch: ch};
1335
- }
1336
- lNo += dir;
1337
- }
1338
- }
1339
- var line = getLine(pos.line);
1340
- var toEnd = pos.ch == line.text.length && pos.ch != oldCh;
1341
- if (!line.hidden) return pos;
1342
- if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
1343
- else return getNonHidden(-1) || getNonHidden(1);
1344
- }
1345
- function setCursor(line, ch, user) {
1346
- var pos = clipPos({line: line, ch: ch || 0});
1347
- (user ? setSelectionUser : setSelection)(pos, pos);
1348
- }
1349
-
1350
- function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
1351
- function clipPos(pos) {
1352
- if (pos.line < 0) return {line: 0, ch: 0};
1353
- if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
1354
- var ch = pos.ch, linelen = getLine(pos.line).text.length;
1355
- if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
1356
- else if (ch < 0) return {line: pos.line, ch: 0};
1357
- else return pos;
1358
- }
1359
-
1360
- function findPosH(dir, unit) {
1361
- var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;
1362
- var lineObj = getLine(line);
1363
- function findNextLine() {
1364
- for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {
1365
- var lo = getLine(l);
1366
- if (!lo.hidden) { line = l; lineObj = lo; return true; }
1367
- }
1368
- }
1369
- function moveOnce(boundToLine) {
1370
- if (ch == (dir < 0 ? 0 : lineObj.text.length)) {
1371
- if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;
1372
- else return false;
1373
- } else ch += dir;
1374
- return true;
1375
- }
1376
- if (unit == "char") moveOnce();
1377
- else if (unit == "column") moveOnce(true);
1378
- else if (unit == "word") {
1379
- var sawWord = false;
1380
- for (;;) {
1381
- if (dir < 0) if (!moveOnce()) break;
1382
- if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
1383
- else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
1384
- if (dir > 0) if (!moveOnce()) break;
1385
- }
1386
- }
1387
- return {line: line, ch: ch};
1388
- }
1389
- function moveH(dir, unit) {
1390
- var pos = dir < 0 ? sel.from : sel.to;
1391
- if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);
1392
- setCursor(pos.line, pos.ch, true);
1393
- }
1394
- function deleteH(dir, unit) {
1395
- if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to);
1396
- else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to);
1397
- else replaceRange("", sel.from, findPosH(dir, unit));
1398
- userSelChange = true;
1399
- }
1400
- function moveV(dir, unit) {
1401
- var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);
1402
- if (goalColumn != null) pos.x = goalColumn;
1403
- if (unit == "page") {
1404
- var screen = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight);
1405
- var target = coordsChar(pos.x, pos.y + screen * dir);
1406
- } else if (unit == "line") {
1407
- var th = textHeight();
1408
- var target = coordsChar(pos.x, pos.y + .5 * th + dir * th);
1409
- }
1410
- if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y;
1411
- setCursor(target.line, target.ch, true);
1412
- goalColumn = pos.x;
1413
- }
1414
-
1415
- function findWordAt(pos) {
1416
- var line = getLine(pos.line).text;
1417
- var start = pos.ch, end = pos.ch;
1418
- if (line) {
1419
- if (pos.after === false || end == line.length) --start; else ++end;
1420
- var startChar = line.charAt(start);
1421
- var check = isWordChar(startChar) ? isWordChar :
1422
- /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} :
1423
- function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
1424
- while (start > 0 && check(line.charAt(start - 1))) --start;
1425
- while (end < line.length && check(line.charAt(end))) ++end;
1426
- }
1427
- return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}};
1428
- }
1429
- function selectLine(line) {
1430
- setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0}));
1431
- }
1432
- function indentSelected(mode) {
1433
- if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
1434
- var e = sel.to.line - (sel.to.ch ? 0 : 1);
1435
- for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
1436
- }
1437
-
1438
- function indentLine(n, how) {
1439
- if (!how) how = "add";
1440
- if (how == "smart") {
1441
- if (!mode.indent) how = "prev";
1442
- else var state = getStateBefore(n);
1443
- }
1444
-
1445
- var line = getLine(n), curSpace = line.indentation(options.tabSize),
1446
- curSpaceString = line.text.match(/^\s*/)[0], indentation;
1447
- if (how == "smart") {
1448
- indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);
1449
- if (indentation == Pass) how = "prev";
1450
- }
1451
- if (how == "prev") {
1452
- if (n) indentation = getLine(n-1).indentation(options.tabSize);
1453
- else indentation = 0;
1454
- }
1455
- else if (how == "add") indentation = curSpace + options.indentUnit;
1456
- else if (how == "subtract") indentation = curSpace - options.indentUnit;
1457
- indentation = Math.max(0, indentation);
1458
- var diff = indentation - curSpace;
1459
-
1460
- var indentString = "", pos = 0;
1461
- if (options.indentWithTabs)
1462
- for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";}
1463
- while (pos < indentation) {++pos; indentString += " ";}
1464
-
1465
- if (indentString != curSpaceString)
1466
- replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});
1467
- }
1468
-
1469
- function loadMode() {
1470
- mode = CodeMirror.getMode(options, options.mode);
1471
- doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
1472
- work = [0];
1473
- startWorker();
1474
- }
1475
- function gutterChanged() {
1476
- var visible = options.gutter || options.lineNumbers;
1477
- gutter.style.display = visible ? "" : "none";
1478
- if (visible) gutterDirty = true;
1479
- else lineDiv.parentNode.style.marginLeft = 0;
1480
- }
1481
- function wrappingChanged(from, to) {
1482
- if (options.lineWrapping) {
1483
- wrapper.className += " CodeMirror-wrap";
1484
- var perLine = scroller.clientWidth / charWidth() - 3;
1485
- doc.iter(0, doc.size, function(line) {
1486
- if (line.hidden) return;
1487
- var guess = Math.ceil(line.text.length / perLine) || 1;
1488
- if (guess != 1) updateLineHeight(line, guess);
1489
- });
1490
- lineSpace.style.minWidth = widthForcer.style.left = "";
1491
- } else {
1492
- wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
1493
- computeMaxLength();
1494
- doc.iter(0, doc.size, function(line) {
1495
- if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
1496
- });
1497
- }
1498
- changes.push({from: 0, to: doc.size});
1499
- }
1500
- function makeTab(col) {
1501
- var w = options.tabSize - col % options.tabSize, cached = tabCache[w];
1502
- if (cached) return cached;
1503
- for (var str = "", i = 0; i < w; ++i) str += " ";
1504
- var span = elt("span", str, "cm-tab");
1505
- return (tabCache[w] = {element: span, width: w});
1506
- }
1507
- function themeChanged() {
1508
- scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") +
1509
- options.theme.replace(/(^|\s)\s*/g, " cm-s-");
1510
- }
1511
- function keyMapChanged() {
1512
- var style = keyMap[options.keyMap].style;
1513
- wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
1514
- (style ? " cm-keymap-" + style : "");
1515
- }
1516
-
1517
- function TextMarker() { this.set = []; }
1518
- TextMarker.prototype.clear = operation(function() {
1519
- var min = Infinity, max = -Infinity;
1520
- for (var i = 0, e = this.set.length; i < e; ++i) {
1521
- var line = this.set[i], mk = line.marked;
1522
- if (!mk || !line.parent) continue;
1523
- var lineN = lineNo(line);
1524
- min = Math.min(min, lineN); max = Math.max(max, lineN);
1525
- for (var j = 0; j < mk.length; ++j)
1526
- if (mk[j].marker == this) mk.splice(j--, 1);
1527
- }
1528
- if (min != Infinity)
1529
- changes.push({from: min, to: max + 1});
1530
- });
1531
- TextMarker.prototype.find = function() {
1532
- var from, to;
1533
- for (var i = 0, e = this.set.length; i < e; ++i) {
1534
- var line = this.set[i], mk = line.marked;
1535
- for (var j = 0; j < mk.length; ++j) {
1536
- var mark = mk[j];
1537
- if (mark.marker == this) {
1538
- if (mark.from != null || mark.to != null) {
1539
- var found = lineNo(line);
1540
- if (found != null) {
1541
- if (mark.from != null) from = {line: found, ch: mark.from};
1542
- if (mark.to != null) to = {line: found, ch: mark.to};
1543
- }
1544
- }
1545
- }
1546
- }
1547
- }
1548
- return {from: from, to: to};
1549
- };
1550
-
1551
- function markText(from, to, className) {
1552
- from = clipPos(from); to = clipPos(to);
1553
- var tm = new TextMarker();
1554
- if (!posLess(from, to)) return tm;
1555
- function add(line, from, to, className) {
1556
- getLine(line).addMark(new MarkedText(from, to, className, tm));
1557
- }
1558
- if (from.line == to.line) add(from.line, from.ch, to.ch, className);
1559
- else {
1560
- add(from.line, from.ch, null, className);
1561
- for (var i = from.line + 1, e = to.line; i < e; ++i)
1562
- add(i, null, null, className);
1563
- add(to.line, null, to.ch, className);
1564
- }
1565
- changes.push({from: from.line, to: to.line + 1});
1566
- return tm;
1567
- }
1568
-
1569
- function setBookmark(pos) {
1570
- pos = clipPos(pos);
1571
- var bm = new Bookmark(pos.ch);
1572
- getLine(pos.line).addMark(bm);
1573
- return bm;
1574
- }
1575
-
1576
- function findMarksAt(pos) {
1577
- pos = clipPos(pos);
1578
- var markers = [], marked = getLine(pos.line).marked;
1579
- if (!marked) return markers;
1580
- for (var i = 0, e = marked.length; i < e; ++i) {
1581
- var m = marked[i];
1582
- if ((m.from == null || m.from <= pos.ch) &&
1583
- (m.to == null || m.to >= pos.ch))
1584
- markers.push(m.marker || m);
1585
- }
1586
- return markers;
1587
- }
1588
-
1589
- function addGutterMarker(line, text, className) {
1590
- if (typeof line == "number") line = getLine(clipLine(line));
1591
- line.gutterMarker = {text: text, style: className};
1592
- gutterDirty = true;
1593
- return line;
1594
- }
1595
- function removeGutterMarker(line) {
1596
- if (typeof line == "number") line = getLine(clipLine(line));
1597
- line.gutterMarker = null;
1598
- gutterDirty = true;
1599
- }
1600
-
1601
- function changeLine(handle, op) {
1602
- var no = handle, line = handle;
1603
- if (typeof handle == "number") line = getLine(clipLine(handle));
1604
- else no = lineNo(handle);
1605
- if (no == null) return null;
1606
- if (op(line, no)) changes.push({from: no, to: no + 1});
1607
- else return null;
1608
- return line;
1609
- }
1610
- function setLineClass(handle, className, bgClassName) {
1611
- return changeLine(handle, function(line) {
1612
- if (line.className != className || line.bgClassName != bgClassName) {
1613
- line.className = className;
1614
- line.bgClassName = bgClassName;
1615
- return true;
1616
- }
1617
- });
1618
- }
1619
- function setLineHidden(handle, hidden) {
1620
- return changeLine(handle, function(line, no) {
1621
- if (line.hidden != hidden) {
1622
- line.hidden = hidden;
1623
- if (!options.lineWrapping) {
1624
- if (hidden && line.text.length == maxLine.text.length) {
1625
- updateMaxLine = true;
1626
- } else if (!hidden && line.text.length > maxLine.text.length) {
1627
- maxLine = line; updateMaxLine = false;
1628
- }
1629
- }
1630
- updateLineHeight(line, hidden ? 0 : 1);
1631
- var fline = sel.from.line, tline = sel.to.line;
1632
- if (hidden && (fline == no || tline == no)) {
1633
- var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from;
1634
- var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to;
1635
- // Can't hide the last visible line, we'd have no place to put the cursor
1636
- if (!to) return;
1637
- setSelection(from, to);
1638
- }
1639
- return (gutterDirty = true);
1640
- }
1641
- });
1642
- }
1643
-
1644
- function lineInfo(line) {
1645
- if (typeof line == "number") {
1646
- if (!isLine(line)) return null;
1647
- var n = line;
1648
- line = getLine(line);
1649
- if (!line) return null;
1650
- } else {
1651
- var n = lineNo(line);
1652
- if (n == null) return null;
1653
- }
1654
- var marker = line.gutterMarker;
1655
- return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
1656
- markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName};
1657
- }
1658
-
1659
- // These are used to go from pixel positions to character
1660
- // positions, taking varying character widths into account.
1661
- function charFromX(line, x) {
1662
- if (x <= 0) return 0;
1663
- var lineObj = getLine(line), text = lineObj.text;
1664
- function getX(len) {
1665
- return measureLine(lineObj, len).left;
1666
- }
1667
- var from = 0, fromX = 0, to = text.length, toX;
1668
- // Guess a suitable upper bound for our search.
1669
- var estimated = Math.min(to, Math.ceil(x / charWidth()));
1670
- for (;;) {
1671
- var estX = getX(estimated);
1672
- if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1673
- else {toX = estX; to = estimated; break;}
1674
- }
1675
- if (x > toX) return to;
1676
- // Try to guess a suitable lower bound as well.
1677
- estimated = Math.floor(to * 0.8); estX = getX(estimated);
1678
- if (estX < x) {from = estimated; fromX = estX;}
1679
- // Do a binary search between these bounds.
1680
- for (;;) {
1681
- if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
1682
- var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
1683
- if (middleX > x) {to = middle; toX = middleX;}
1684
- else {from = middle; fromX = middleX;}
1685
- }
1686
- }
1687
-
1688
- function measureLine(line, ch) {
1689
- if (ch == 0) return {top: 0, left: 0};
1690
- var wbr = options.lineWrapping && ch < line.text.length &&
1691
- spanAffectsWrapping.test(line.text.slice(ch - 1, ch + 1));
1692
- var pre = line.getElement(makeTab, ch, wbr);
1693
- removeChildrenAndAdd(measure, pre);
1694
- var anchor = pre.anchor;
1695
- var top = anchor.offsetTop, left = anchor.offsetLeft;
1696
- // Older IEs report zero offsets for spans directly after a wrap
1697
- if (ie && top == 0 && left == 0) {
1698
- var backup = elt("span", "x");
1699
- anchor.parentNode.insertBefore(backup, anchor.nextSibling);
1700
- top = backup.offsetTop;
1701
- }
1702
- return {top: top, left: left};
1703
- }
1704
- function localCoords(pos, inLineWrap) {
1705
- var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
1706
- if (pos.ch == 0) x = 0;
1707
- else {
1708
- var sp = measureLine(getLine(pos.line), pos.ch);
1709
- x = sp.left;
1710
- if (options.lineWrapping) y += Math.max(0, sp.top);
1711
- }
1712
- return {x: x, y: y, yBot: y + lh};
1713
- }
1714
- // Coords must be lineSpace-local
1715
- function coordsChar(x, y) {
1716
- var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
1717
- if (heightPos < 0) return {line: 0, ch: 0};
1718
- var lineNo = lineAtHeight(doc, heightPos);
1719
- if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};
1720
- var lineObj = getLine(lineNo), text = lineObj.text;
1721
- var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
1722
- if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
1723
- var wrongLine = false;
1724
- function getX(len) {
1725
- var sp = measureLine(lineObj, len);
1726
- if (tw) {
1727
- var off = Math.round(sp.top / th);
1728
- wrongLine = off != innerOff;
1729
- return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
1730
- }
1731
- return sp.left;
1732
- }
1733
- var from = 0, fromX = 0, to = text.length, toX;
1734
- // Guess a suitable upper bound for our search.
1735
- var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));
1736
- for (;;) {
1737
- var estX = getX(estimated);
1738
- if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1739
- else {toX = estX; to = estimated; break;}
1740
- }
1741
- if (x > toX) return {line: lineNo, ch: to};
1742
- // Try to guess a suitable lower bound as well.
1743
- estimated = Math.floor(to * 0.8); estX = getX(estimated);
1744
- if (estX < x) {from = estimated; fromX = estX;}
1745
- // Do a binary search between these bounds.
1746
- for (;;) {
1747
- if (to - from <= 1) {
1748
- var after = x - fromX < toX - x;
1749
- return {line: lineNo, ch: after ? from : to, after: after};
1750
- }
1751
- var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
1752
- if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; }
1753
- else {from = middle; fromX = middleX;}
1754
- }
1755
- }
1756
- function pageCoords(pos) {
1757
- var local = localCoords(pos, true), off = eltOffset(lineSpace);
1758
- return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
1759
- }
1760
-
1761
- var cachedHeight, cachedHeightFor, measurePre;
1762
- function textHeight() {
1763
- if (measurePre == null) {
1764
- measurePre = elt("pre");
1765
- for (var i = 0; i < 49; ++i) {
1766
- measurePre.appendChild(document.createTextNode("x"));
1767
- measurePre.appendChild(elt("br"));
1768
- }
1769
- measurePre.appendChild(document.createTextNode("x"));
1770
- }
1771
- var offsetHeight = lineDiv.clientHeight;
1772
- if (offsetHeight == cachedHeightFor) return cachedHeight;
1773
- cachedHeightFor = offsetHeight;
1774
- removeChildrenAndAdd(measure, measurePre.cloneNode(true));
1775
- cachedHeight = measure.firstChild.offsetHeight / 50 || 1;
1776
- removeChildren(measure);
1777
- return cachedHeight;
1778
- }
1779
- var cachedWidth, cachedWidthFor = 0;
1780
- function charWidth() {
1781
- if (scroller.clientWidth == cachedWidthFor) return cachedWidth;
1782
- cachedWidthFor = scroller.clientWidth;
1783
- var anchor = elt("span", "x");
1784
- var pre = elt("pre", [anchor]);
1785
- removeChildrenAndAdd(measure, pre);
1786
- return (cachedWidth = anchor.offsetWidth || 10);
1787
- }
1788
- function paddingTop() {return lineSpace.offsetTop;}
1789
- function paddingLeft() {return lineSpace.offsetLeft;}
1790
-
1791
- function posFromMouse(e, liberal) {
1792
- var offW = eltOffset(scroller, true), x, y;
1793
- // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1794
- try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1795
- // This is a mess of a heuristic to try and determine whether a
1796
- // scroll-bar was clicked or not, and to return null if one was
1797
- // (and !liberal).
1798
- if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
1799
- return null;
1800
- var offL = eltOffset(lineSpace, true);
1801
- return coordsChar(x - offL.left, y - offL.top);
1802
- }
1803
- function onContextMenu(e) {
1804
- var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop;
1805
- if (!pos || opera) return; // Opera is difficult.
1806
- if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
1807
- operation(setCursor)(pos.line, pos.ch);
1808
-
1809
- var oldCSS = input.style.cssText;
1810
- inputDiv.style.position = "absolute";
1811
- input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
1812
- "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
1813
- "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1814
- leaveInputAlone = true;
1815
- var val = input.value = getSelection();
1816
- focusInput();
1817
- selectInput(input);
1818
- function rehide() {
1819
- var newVal = splitLines(input.value).join("\n");
1820
- if (newVal != val && !options.readOnly) operation(replaceSelection)(newVal, "end");
1821
- inputDiv.style.position = "relative";
1822
- input.style.cssText = oldCSS;
1823
- if (ie_lt9) scrollbar.scrollTop = scrollPos;
1824
- leaveInputAlone = false;
1825
- resetInput(true);
1826
- slowPoll();
1827
- }
1828
-
1829
- if (gecko) {
1830
- e_stop(e);
1831
- var mouseup = connect(window, "mouseup", function() {
1832
- mouseup();
1833
- setTimeout(rehide, 20);
1834
- }, true);
1835
- } else {
1836
- setTimeout(rehide, 50);
1837
- }
1838
- }
1839
-
1840
- // Cursor-blinking
1841
- function restartBlink() {
1842
- clearInterval(blinker);
1843
- var on = true;
1844
- cursor.style.visibility = "";
1845
- blinker = setInterval(function() {
1846
- cursor.style.visibility = (on = !on) ? "" : "hidden";
1847
- }, options.cursorBlinkRate);
1848
- }
1849
-
1850
- var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
1851
- function matchBrackets(autoclear) {
1852
- var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
1853
- var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
1854
- if (!match) return;
1855
- var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
1856
- for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2)
1857
- if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;}
1858
-
1859
- var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
1860
- function scan(line, from, to) {
1861
- if (!line.text) return;
1862
- var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur;
1863
- for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) {
1864
- var text = st[i];
1865
- if (st[i+1] != style) {pos += d * text.length; continue;}
1866
- for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) {
1867
- if (pos >= from && pos < to && re.test(cur = text.charAt(j))) {
1868
- var match = matching[cur];
1869
- if (match.charAt(1) == ">" == forward) stack.push(cur);
1870
- else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
1871
- else if (!stack.length) return {pos: pos, match: true};
1872
- }
1873
- }
1874
- }
1875
- }
1876
- for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
1877
- var line = getLine(i), first = i == head.line;
1878
- var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
1879
- if (found) break;
1880
- }
1881
- if (!found) found = {pos: null, match: false};
1882
- var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
1883
- var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
1884
- two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
1885
- var clear = operation(function(){one.clear(); two && two.clear();});
1886
- if (autoclear) setTimeout(clear, 800);
1887
- else bracketHighlighted = clear;
1888
- }
1889
-
1890
- // Finds the line to start with when starting a parse. Tries to
1891
- // find a line with a stateAfter, so that it can start with a
1892
- // valid state. If that fails, it returns the line with the
1893
- // smallest indentation, which tends to need the least context to
1894
- // parse correctly.
1895
- function findStartLine(n) {
1896
- var minindent, minline;
1897
- for (var search = n, lim = n - 40; search > lim; --search) {
1898
- if (search == 0) return 0;
1899
- var line = getLine(search-1);
1900
- if (line.stateAfter) return search;
1901
- var indented = line.indentation(options.tabSize);
1902
- if (minline == null || minindent > indented) {
1903
- minline = search - 1;
1904
- minindent = indented;
1905
- }
1906
- }
1907
- return minline;
1908
- }
1909
- function getStateBefore(n) {
1910
- var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
1911
- if (!state) state = startState(mode);
1912
- else state = copyState(mode, state);
1913
- doc.iter(start, n, function(line) {
1914
- line.highlight(mode, state, options.tabSize);
1915
- line.stateAfter = copyState(mode, state);
1916
- });
1917
- if (start < n) changes.push({from: start, to: n});
1918
- if (n < doc.size && !getLine(n).stateAfter) work.push(n);
1919
- return state;
1920
- }
1921
- function highlightLines(start, end) {
1922
- var state = getStateBefore(start);
1923
- doc.iter(start, end, function(line) {
1924
- line.highlight(mode, state, options.tabSize);
1925
- line.stateAfter = copyState(mode, state);
1926
- });
1927
- }
1928
- function highlightWorker() {
1929
- var end = +new Date + options.workTime;
1930
- var foundWork = work.length;
1931
- while (work.length) {
1932
- if (!getLine(showingFrom).stateAfter) var task = showingFrom;
1933
- else var task = work.pop();
1934
- if (task >= doc.size) continue;
1935
- var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
1936
- if (state) state = copyState(mode, state);
1937
- else state = startState(mode);
1938
-
1939
- var unchanged = 0, compare = mode.compareStates, realChange = false,
1940
- i = start, bail = false;
1941
- doc.iter(i, doc.size, function(line) {
1942
- var hadState = line.stateAfter;
1943
- if (+new Date > end) {
1944
- work.push(i);
1945
- startWorker(options.workDelay);
1946
- if (realChange) changes.push({from: task, to: i + 1});
1947
- return (bail = true);
1948
- }
1949
- var changed = line.highlight(mode, state, options.tabSize);
1950
- if (changed) realChange = true;
1951
- line.stateAfter = copyState(mode, state);
1952
- var done = null;
1953
- if (compare) {
1954
- var same = hadState && compare(hadState, state);
1955
- if (same != Pass) done = !!same;
1956
- }
1957
- if (done == null) {
1958
- if (changed !== false || !hadState) unchanged = 0;
1959
- else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, "")))
1960
- done = true;
1961
- }
1962
- if (done) return true;
1963
- ++i;
1964
- });
1965
- if (bail) return;
1966
- if (realChange) changes.push({from: task, to: i + 1});
1967
- }
1968
- if (foundWork && options.onHighlightComplete)
1969
- options.onHighlightComplete(instance);
1970
- }
1971
- function startWorker(time) {
1972
- if (!work.length) return;
1973
- highlight.set(time, operation(highlightWorker));
1974
- }
1975
-
1976
- // Operations are used to wrap changes in such a way that each
1977
- // change won't have to update the cursor and display (which would
1978
- // be awkward, slow, and error-prone), but instead updates are
1979
- // batched and then all combined and executed at once.
1980
- function startOperation() {
1981
- updateInput = userSelChange = textChanged = null;
1982
- changes = []; selectionChanged = false; callbacks = [];
1983
- }
1984
- function endOperation() {
1985
- if (updateMaxLine) computeMaxLength();
1986
- if (maxLineChanged && !options.lineWrapping) {
1987
- var cursorWidth = widthForcer.offsetWidth, left = measureLine(maxLine, maxLine.text.length).left;
1988
- if (!ie_lt8) {
1989
- widthForcer.style.left = left + "px";
1990
- lineSpace.style.minWidth = (left + cursorWidth) + "px";
1991
- }
1992
- maxLineChanged = false;
1993
- }
1994
- var newScrollPos, updated;
1995
- if (selectionChanged) {
1996
- var coords = calculateCursorCoords();
1997
- newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot);
1998
- }
1999
- if (changes.length || newScrollPos && newScrollPos.scrollTop != null)
2000
- updated = updateDisplay(changes, true, newScrollPos && newScrollPos.scrollTop);
2001
- if (!updated) {
2002
- if (selectionChanged) updateSelection();
2003
- if (gutterDirty) updateGutter();
2004
- }
2005
- if (newScrollPos) scrollCursorIntoView();
2006
- if (selectionChanged) restartBlink();
2007
-
2008
- if (focused && !leaveInputAlone &&
2009
- (updateInput === true || (updateInput !== false && selectionChanged)))
2010
- resetInput(userSelChange);
2011
-
2012
- if (selectionChanged && options.matchBrackets)
2013
- setTimeout(operation(function() {
2014
- if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
2015
- if (posEq(sel.from, sel.to)) matchBrackets(false);
2016
- }), 20);
2017
- var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks
2018
- if (textChanged && options.onChange && instance)
2019
- options.onChange(instance, textChanged);
2020
- if (sc && options.onCursorActivity)
2021
- options.onCursorActivity(instance);
2022
- for (var i = 0; i < cbs.length; ++i) cbs[i](instance);
2023
- if (updated && options.onUpdate) options.onUpdate(instance);
2024
- }
2025
- var nestedOperation = 0;
2026
- function operation(f) {
2027
- return function() {
2028
- if (!nestedOperation++) startOperation();
2029
- try {var result = f.apply(this, arguments);}
2030
- finally {if (!--nestedOperation) endOperation();}
2031
- return result;
2032
- };
2033
- }
2034
-
2035
- function compoundChange(f) {
2036
- history.startCompound();
2037
- try { return f(); } finally { history.endCompound(); }
2038
- }
2039
-
2040
- for (var ext in extensions)
2041
- if (extensions.propertyIsEnumerable(ext) &&
2042
- !instance.propertyIsEnumerable(ext))
2043
- instance[ext] = extensions[ext];
2044
- return instance;
2045
- } // (end of function CodeMirror)
2046
-
2047
- // The default configuration options.
2048
- CodeMirror.defaults = {
2049
- value: "",
2050
- mode: null,
2051
- theme: "default",
2052
- indentUnit: 2,
2053
- indentWithTabs: false,
2054
- smartIndent: true,
2055
- tabSize: 4,
2056
- keyMap: "default",
2057
- extraKeys: null,
2058
- electricChars: true,
2059
- autoClearEmptyLines: false,
2060
- onKeyEvent: null,
2061
- onDragEvent: null,
2062
- lineWrapping: false,
2063
- lineNumbers: false,
2064
- gutter: false,
2065
- fixedGutter: false,
2066
- firstLineNumber: 1,
2067
- readOnly: false,
2068
- dragDrop: true,
2069
- onChange: null,
2070
- onCursorActivity: null,
2071
- onViewportChange: null,
2072
- onGutterClick: null,
2073
- onHighlightComplete: null,
2074
- onUpdate: null,
2075
- onFocus: null, onBlur: null, onScroll: null,
2076
- matchBrackets: false,
2077
- cursorBlinkRate: 530,
2078
- workTime: 100,
2079
- workDelay: 200,
2080
- pollInterval: 100,
2081
- undoDepth: 40,
2082
- tabindex: null,
2083
- autofocus: null,
2084
- lineNumberFormatter: function(integer) { return integer; }
2085
- };
2086
-
2087
- var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
2088
- var mac = ios || /Mac/.test(navigator.platform);
2089
- var win = /Win/.test(navigator.platform);
2090
-
2091
- // Known modes, by name and by MIME
2092
- var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
2093
- CodeMirror.defineMode = function(name, mode) {
2094
- if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
2095
- if (arguments.length > 2) {
2096
- mode.dependencies = [];
2097
- for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
2098
- }
2099
- modes[name] = mode;
2100
- };
2101
- CodeMirror.defineMIME = function(mime, spec) {
2102
- mimeModes[mime] = spec;
2103
- };
2104
- CodeMirror.resolveMode = function(spec) {
2105
- if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
2106
- spec = mimeModes[spec];
2107
- else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
2108
- return CodeMirror.resolveMode("application/xml");
2109
- if (typeof spec == "string") return {name: spec};
2110
- else return spec || {name: "null"};
2111
- };
2112
- CodeMirror.getMode = function(options, spec) {
2113
- var spec = CodeMirror.resolveMode(spec);
2114
- var mfactory = modes[spec.name];
2115
- if (!mfactory) return CodeMirror.getMode(options, "text/plain");
2116
- return mfactory(options, spec);
2117
- };
2118
- CodeMirror.listModes = function() {
2119
- var list = [];
2120
- for (var m in modes)
2121
- if (modes.propertyIsEnumerable(m)) list.push(m);
2122
- return list;
2123
- };
2124
- CodeMirror.listMIMEs = function() {
2125
- var list = [];
2126
- for (var m in mimeModes)
2127
- if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
2128
- return list;
2129
- };
2130
-
2131
- var extensions = CodeMirror.extensions = {};
2132
- CodeMirror.defineExtension = function(name, func) {
2133
- extensions[name] = func;
2134
- };
2135
-
2136
- var commands = CodeMirror.commands = {
2137
- selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
2138
- killLine: function(cm) {
2139
- var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
2140
- if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0});
2141
- else cm.replaceRange("", from, sel ? to : {line: from.line});
2142
- },
2143
- deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});},
2144
- undo: function(cm) {cm.undo();},
2145
- redo: function(cm) {cm.redo();},
2146
- goDocStart: function(cm) {cm.setCursor(0, 0, true);},
2147
- goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},
2148
- goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},
2149
- goLineStartSmart: function(cm) {
2150
- var cur = cm.getCursor();
2151
- var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/));
2152
- cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
2153
- },
2154
- goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},
2155
- goLineUp: function(cm) {cm.moveV(-1, "line");},
2156
- goLineDown: function(cm) {cm.moveV(1, "line");},
2157
- goPageUp: function(cm) {cm.moveV(-1, "page");},
2158
- goPageDown: function(cm) {cm.moveV(1, "page");},
2159
- goCharLeft: function(cm) {cm.moveH(-1, "char");},
2160
- goCharRight: function(cm) {cm.moveH(1, "char");},
2161
- goColumnLeft: function(cm) {cm.moveH(-1, "column");},
2162
- goColumnRight: function(cm) {cm.moveH(1, "column");},
2163
- goWordLeft: function(cm) {cm.moveH(-1, "word");},
2164
- goWordRight: function(cm) {cm.moveH(1, "word");},
2165
- delCharLeft: function(cm) {cm.deleteH(-1, "char");},
2166
- delCharRight: function(cm) {cm.deleteH(1, "char");},
2167
- delWordLeft: function(cm) {cm.deleteH(-1, "word");},
2168
- delWordRight: function(cm) {cm.deleteH(1, "word");},
2169
- indentAuto: function(cm) {cm.indentSelection("smart");},
2170
- indentMore: function(cm) {cm.indentSelection("add");},
2171
- indentLess: function(cm) {cm.indentSelection("subtract");},
2172
- insertTab: function(cm) {cm.replaceSelection("\t", "end");},
2173
- defaultTab: function(cm) {
2174
- if (cm.somethingSelected()) cm.indentSelection("add");
2175
- else cm.replaceSelection("\t", "end");
2176
- },
2177
- transposeChars: function(cm) {
2178
- var cur = cm.getCursor(), line = cm.getLine(cur.line);
2179
- if (cur.ch > 0 && cur.ch < line.length - 1)
2180
- cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
2181
- {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
2182
- },
2183
- newlineAndIndent: function(cm) {
2184
- cm.replaceSelection("\n", "end");
2185
- cm.indentLine(cm.getCursor().line);
2186
- },
2187
- toggleOverwrite: function(cm) {cm.toggleOverwrite();}
2188
- };
2189
-
2190
- var keyMap = CodeMirror.keyMap = {};
2191
- keyMap.basic = {
2192
- "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
2193
- "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
2194
- "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
2195
- "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
2196
- };
2197
- // Note that the save and find-related commands aren't defined by
2198
- // default. Unknown commands are simply ignored.
2199
- keyMap.pcDefault = {
2200
- "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
2201
- "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
2202
- "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
2203
- "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find",
2204
- "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
2205
- "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
2206
- fallthrough: "basic"
2207
- };
2208
- keyMap.macDefault = {
2209
- "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
2210
- "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
2211
- "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft",
2212
- "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find",
2213
- "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
2214
- "Cmd-[": "indentLess", "Cmd-]": "indentMore",
2215
- fallthrough: ["basic", "emacsy"]
2216
- };
2217
- keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
2218
- keyMap.emacsy = {
2219
- "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
2220
- "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
2221
- "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
2222
- "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
2223
- };
2224
-
2225
- function getKeyMap(val) {
2226
- if (typeof val == "string") return keyMap[val];
2227
- else return val;
2228
- }
2229
- function lookupKey(name, extraMap, map, handle, stop) {
2230
- function lookup(map) {
2231
- map = getKeyMap(map);
2232
- var found = map[name];
2233
- if (found === false) {
2234
- if (stop) stop();
2235
- return true;
2236
- }
2237
- if (found != null && handle(found)) return true;
2238
- if (map.nofallthrough) {
2239
- if (stop) stop();
2240
- return true;
2241
- }
2242
- var fallthrough = map.fallthrough;
2243
- if (fallthrough == null) return false;
2244
- if (Object.prototype.toString.call(fallthrough) != "[object Array]")
2245
- return lookup(fallthrough);
2246
- for (var i = 0, e = fallthrough.length; i < e; ++i) {
2247
- if (lookup(fallthrough[i])) return true;
2248
- }
2249
- return false;
2250
- }
2251
- if (extraMap && lookup(extraMap)) return true;
2252
- return lookup(map);
2253
- }
2254
- function isModifierKey(event) {
2255
- var name = keyNames[e_prop(event, "keyCode")];
2256
- return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
2257
- }
2258
-
2259
- CodeMirror.fromTextArea = function(textarea, options) {
2260
- if (!options) options = {};
2261
- options.value = textarea.value;
2262
- if (!options.tabindex && textarea.tabindex)
2263
- options.tabindex = textarea.tabindex;
2264
- // Set autofocus to true if this textarea is focused, or if it has
2265
- // autofocus and no other element is focused.
2266
- if (options.autofocus == null) {
2267
- var hasFocus = document.body;
2268
- // doc.activeElement occasionally throws on IE
2269
- try { hasFocus = document.activeElement; } catch(e) {}
2270
- options.autofocus = hasFocus == textarea ||
2271
- textarea.getAttribute("autofocus") != null && hasFocus == document.body;
2272
- }
2273
-
2274
- function save() {textarea.value = instance.getValue();}
2275
- if (textarea.form) {
2276
- // Deplorable hack to make the submit method do the right thing.
2277
- var rmSubmit = connect(textarea.form, "submit", save, true);
2278
- if (typeof textarea.form.submit == "function") {
2279
- var realSubmit = textarea.form.submit;
2280
- textarea.form.submit = function wrappedSubmit() {
2281
- save();
2282
- textarea.form.submit = realSubmit;
2283
- textarea.form.submit();
2284
- textarea.form.submit = wrappedSubmit;
2285
- };
2286
- }
2287
- }
2288
-
2289
- textarea.style.display = "none";
2290
- var instance = CodeMirror(function(node) {
2291
- textarea.parentNode.insertBefore(node, textarea.nextSibling);
2292
- }, options);
2293
- instance.save = save;
2294
- instance.getTextArea = function() { return textarea; };
2295
- instance.toTextArea = function() {
2296
- save();
2297
- textarea.parentNode.removeChild(instance.getWrapperElement());
2298
- textarea.style.display = "";
2299
- if (textarea.form) {
2300
- rmSubmit();
2301
- if (typeof textarea.form.submit == "function")
2302
- textarea.form.submit = realSubmit;
2303
- }
2304
- };
2305
- return instance;
2306
- };
2307
-
2308
- var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
2309
- var ie = /MSIE \d/.test(navigator.userAgent);
2310
- var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
2311
- var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
2312
- var quirksMode = ie && document.documentMode == 5;
2313
- var webkit = /WebKit\//.test(navigator.userAgent);
2314
- var chrome = /Chrome\//.test(navigator.userAgent);
2315
- var opera = /Opera\//.test(navigator.userAgent);
2316
- var safari = /Apple Computer/.test(navigator.vendor);
2317
- var khtml = /KHTML\//.test(navigator.userAgent);
2318
- var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent);
2319
-
2320
- // Utility functions for working with state. Exported because modes
2321
- // sometimes need to do this.
2322
- function copyState(mode, state) {
2323
- if (state === true) return state;
2324
- if (mode.copyState) return mode.copyState(state);
2325
- var nstate = {};
2326
- for (var n in state) {
2327
- var val = state[n];
2328
- if (val instanceof Array) val = val.concat([]);
2329
- nstate[n] = val;
2330
- }
2331
- return nstate;
2332
- }
2333
- CodeMirror.copyState = copyState;
2334
- function startState(mode, a1, a2) {
2335
- return mode.startState ? mode.startState(a1, a2) : true;
2336
- }
2337
- CodeMirror.startState = startState;
2338
-
2339
- // The character stream used by a mode's parser.
2340
- function StringStream(string, tabSize) {
2341
- this.pos = this.start = 0;
2342
- this.string = string;
2343
- this.tabSize = tabSize || 8;
2344
- }
2345
- StringStream.prototype = {
2346
- eol: function() {return this.pos >= this.string.length;},
2347
- sol: function() {return this.pos == 0;},
2348
- peek: function() {return this.string.charAt(this.pos) || undefined;},
2349
- next: function() {
2350
- if (this.pos < this.string.length)
2351
- return this.string.charAt(this.pos++);
2352
- },
2353
- eat: function(match) {
2354
- var ch = this.string.charAt(this.pos);
2355
- if (typeof match == "string") var ok = ch == match;
2356
- else var ok = ch && (match.test ? match.test(ch) : match(ch));
2357
- if (ok) {++this.pos; return ch;}
2358
- },
2359
- eatWhile: function(match) {
2360
- var start = this.pos;
2361
- while (this.eat(match)){}
2362
- return this.pos > start;
2363
- },
2364
- eatSpace: function() {
2365
- var start = this.pos;
2366
- while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
2367
- return this.pos > start;
2368
- },
2369
- skipToEnd: function() {this.pos = this.string.length;},
2370
- skipTo: function(ch) {
2371
- var found = this.string.indexOf(ch, this.pos);
2372
- if (found > -1) {this.pos = found; return true;}
2373
- },
2374
- backUp: function(n) {this.pos -= n;},
2375
- column: function() {return countColumn(this.string, this.start, this.tabSize);},
2376
- indentation: function() {return countColumn(this.string, null, this.tabSize);},
2377
- match: function(pattern, consume, caseInsensitive) {
2378
- if (typeof pattern == "string") {
2379
- var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
2380
- if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
2381
- if (consume !== false) this.pos += pattern.length;
2382
- return true;
2383
- }
2384
- } else {
2385
- var match = this.string.slice(this.pos).match(pattern);
2386
- if (match && consume !== false) this.pos += match[0].length;
2387
- return match;
2388
- }
2389
- },
2390
- current: function(){return this.string.slice(this.start, this.pos);}
2391
- };
2392
- CodeMirror.StringStream = StringStream;
2393
-
2394
- function MarkedText(from, to, className, marker) {
2395
- this.from = from; this.to = to; this.style = className; this.marker = marker;
2396
- }
2397
- MarkedText.prototype = {
2398
- attach: function(line) { this.marker.set.push(line); },
2399
- detach: function(line) {
2400
- var ix = indexOf(this.marker.set, line);
2401
- if (ix > -1) this.marker.set.splice(ix, 1);
2402
- },
2403
- split: function(pos, lenBefore) {
2404
- if (this.to <= pos && this.to != null) return null;
2405
- var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
2406
- var to = this.to == null ? null : this.to - pos + lenBefore;
2407
- return new MarkedText(from, to, this.style, this.marker);
2408
- },
2409
- dup: function() { return new MarkedText(null, null, this.style, this.marker); },
2410
- clipTo: function(fromOpen, from, toOpen, to, diff) {
2411
- if (fromOpen && to > this.from && (to < this.to || this.to == null))
2412
- this.from = null;
2413
- else if (this.from != null && this.from >= from)
2414
- this.from = Math.max(to, this.from) + diff;
2415
- if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
2416
- this.to = null;
2417
- else if (this.to != null && this.to > from)
2418
- this.to = to < this.to ? this.to + diff : from;
2419
- },
2420
- isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
2421
- sameSet: function(x) { return this.marker == x.marker; }
2422
- };
2423
-
2424
- function Bookmark(pos) {
2425
- this.from = pos; this.to = pos; this.line = null;
2426
- }
2427
- Bookmark.prototype = {
2428
- attach: function(line) { this.line = line; },
2429
- detach: function(line) { if (this.line == line) this.line = null; },
2430
- split: function(pos, lenBefore) {
2431
- if (pos < this.from) {
2432
- this.from = this.to = (this.from - pos) + lenBefore;
2433
- return this;
2434
- }
2435
- },
2436
- isDead: function() { return this.from > this.to; },
2437
- clipTo: function(fromOpen, from, toOpen, to, diff) {
2438
- if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
2439
- this.from = 0; this.to = -1;
2440
- } else if (this.from > from) {
2441
- this.from = this.to = Math.max(to, this.from) + diff;
2442
- }
2443
- },
2444
- sameSet: function(x) { return false; },
2445
- find: function() {
2446
- if (!this.line || !this.line.parent) return null;
2447
- return {line: lineNo(this.line), ch: this.from};
2448
- },
2449
- clear: function() {
2450
- if (this.line) {
2451
- var found = indexOf(this.line.marked, this);
2452
- if (found != -1) this.line.marked.splice(found, 1);
2453
- this.line = null;
2454
- }
2455
- }
2456
- };
2457
-
2458
- // When measuring the position of the end of a line, different
2459
- // browsers require different approaches. If an empty span is added,
2460
- // many browsers report bogus offsets. Of those, some (Webkit,
2461
- // recent IE) will accept a space without moving the whole span to
2462
- // the next line when wrapping it, others work with a zero-width
2463
- // space.
2464
- var eolSpanContent = " ";
2465
- if (gecko || (ie && !ie_lt8)) eolSpanContent = "\u200b";
2466
- else if (opera) eolSpanContent = "";
2467
-
2468
- // Line objects. These hold state related to a line, including
2469
- // highlighting info (the styles array).
2470
- function Line(text, styles) {
2471
- this.styles = styles || [text, null];
2472
- this.text = text;
2473
- this.height = 1;
2474
- }
2475
- Line.inheritMarks = function(text, orig) {
2476
- var ln = new Line(text), mk = orig && orig.marked;
2477
- if (mk) {
2478
- for (var i = 0; i < mk.length; ++i) {
2479
- if (mk[i].to == null && mk[i].style) {
2480
- var newmk = ln.marked || (ln.marked = []), mark = mk[i];
2481
- var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
2482
- }
2483
- }
2484
- }
2485
- return ln;
2486
- };
2487
- Line.prototype = {
2488
- // Replace a piece of a line, keeping the styles around it intact.
2489
- replace: function(from, to_, text) {
2490
- var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
2491
- copyStyles(0, from, this.styles, st);
2492
- if (text) st.push(text, null);
2493
- copyStyles(to, this.text.length, this.styles, st);
2494
- this.styles = st;
2495
- this.text = this.text.slice(0, from) + text + this.text.slice(to);
2496
- this.stateAfter = null;
2497
- if (mk) {
2498
- var diff = text.length - (to - from);
2499
- for (var i = 0; i < mk.length; ++i) {
2500
- var mark = mk[i];
2501
- mark.clipTo(from == null, from || 0, to_ == null, to, diff);
2502
- if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
2503
- }
2504
- }
2505
- },
2506
- // Split a part off a line, keeping styles and markers intact.
2507
- split: function(pos, textBefore) {
2508
- var st = [textBefore, null], mk = this.marked;
2509
- copyStyles(pos, this.text.length, this.styles, st);
2510
- var taken = new Line(textBefore + this.text.slice(pos), st);
2511
- if (mk) {
2512
- for (var i = 0; i < mk.length; ++i) {
2513
- var mark = mk[i];
2514
- var newmark = mark.split(pos, textBefore.length);
2515
- if (newmark) {
2516
- if (!taken.marked) taken.marked = [];
2517
- taken.marked.push(newmark); newmark.attach(taken);
2518
- if (newmark == mark) mk.splice(i--, 1);
2519
- }
2520
- }
2521
- }
2522
- return taken;
2523
- },
2524
- append: function(line) {
2525
- var mylen = this.text.length, mk = line.marked, mymk = this.marked;
2526
- this.text += line.text;
2527
- copyStyles(0, line.text.length, line.styles, this.styles);
2528
- if (mymk) {
2529
- for (var i = 0; i < mymk.length; ++i)
2530
- if (mymk[i].to == null) mymk[i].to = mylen;
2531
- }
2532
- if (mk && mk.length) {
2533
- if (!mymk) this.marked = mymk = [];
2534
- outer: for (var i = 0; i < mk.length; ++i) {
2535
- var mark = mk[i];
2536
- if (!mark.from) {
2537
- for (var j = 0; j < mymk.length; ++j) {
2538
- var mymark = mymk[j];
2539
- if (mymark.to == mylen && mymark.sameSet(mark)) {
2540
- mymark.to = mark.to == null ? null : mark.to + mylen;
2541
- if (mymark.isDead()) {
2542
- mymark.detach(this);
2543
- mk.splice(i--, 1);
2544
- }
2545
- continue outer;
2546
- }
2547
- }
2548
- }
2549
- mymk.push(mark);
2550
- mark.attach(this);
2551
- mark.from += mylen;
2552
- if (mark.to != null) mark.to += mylen;
2553
- }
2554
- }
2555
- },
2556
- fixMarkEnds: function(other) {
2557
- var mk = this.marked, omk = other.marked;
2558
- if (!mk) return;
2559
- outer: for (var i = 0; i < mk.length; ++i) {
2560
- var mark = mk[i], close = mark.to == null;
2561
- if (close && omk) {
2562
- for (var j = 0; j < omk.length; ++j) {
2563
- var om = omk[j];
2564
- if (!om.sameSet(mark) || om.from != null) continue;
2565
- if (mark.from == this.text.length && om.to == 0) {
2566
- omk.splice(j, 1);
2567
- mk.splice(i--, 1);
2568
- continue outer;
2569
- } else {
2570
- close = false; break;
2571
- }
2572
- }
2573
- }
2574
- if (close) mark.to = this.text.length;
2575
- }
2576
- },
2577
- fixMarkStarts: function() {
2578
- var mk = this.marked;
2579
- if (!mk) return;
2580
- for (var i = 0; i < mk.length; ++i)
2581
- if (mk[i].from == null) mk[i].from = 0;
2582
- },
2583
- addMark: function(mark) {
2584
- mark.attach(this);
2585
- if (this.marked == null) this.marked = [];
2586
- this.marked.push(mark);
2587
- this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
2588
- },
2589
- // Run the given mode's parser over a line, update the styles
2590
- // array, which contains alternating fragments of text and CSS
2591
- // classes.
2592
- highlight: function(mode, state, tabSize) {
2593
- var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0;
2594
- var changed = false, curWord = st[0], prevWord;
2595
- if (this.text == "" && mode.blankLine) mode.blankLine(state);
2596
- while (!stream.eol()) {
2597
- var style = mode.token(stream, state);
2598
- var substr = this.text.slice(stream.start, stream.pos);
2599
- stream.start = stream.pos;
2600
- if (pos && st[pos-1] == style)
2601
- st[pos-2] += substr;
2602
- else if (substr) {
2603
- if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;
2604
- st[pos++] = substr; st[pos++] = style;
2605
- prevWord = curWord; curWord = st[pos];
2606
- }
2607
- // Give up when line is ridiculously long
2608
- if (stream.pos > 5000) {
2609
- st[pos++] = this.text.slice(stream.pos); st[pos++] = null;
2610
- break;
2611
- }
2612
- }
2613
- if (st.length != pos) {st.length = pos; changed = true;}
2614
- if (pos && st[pos-2] != prevWord) changed = true;
2615
- // Short lines with simple highlights return null, and are
2616
- // counted as changed by the driver because they are likely to
2617
- // highlight the same way in various contexts.
2618
- return changed || (st.length < 5 && this.text.length < 10 ? null : false);
2619
- },
2620
- // Fetch the parser token for a given character. Useful for hacks
2621
- // that want to inspect the mode state (say, for completion).
2622
- getTokenAt: function(mode, state, tabSize, ch) {
2623
- var txt = this.text, stream = new StringStream(txt, tabSize);
2624
- while (stream.pos < ch && !stream.eol()) {
2625
- stream.start = stream.pos;
2626
- var style = mode.token(stream, state);
2627
- }
2628
- return {start: stream.start,
2629
- end: stream.pos,
2630
- string: stream.current(),
2631
- className: style || null,
2632
- state: state};
2633
- },
2634
- indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},
2635
- // Produces an HTML fragment for the line, taking selection,
2636
- // marking, and highlighting into account.
2637
- getElement: function(makeTab, wrapAt, wrapWBR) {
2638
- var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g;
2639
- var pre = elt("pre");
2640
- function span_(html, text, style) {
2641
- if (!text) return;
2642
- // Work around a bug where, in some compat modes, IE ignores leading spaces
2643
- if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
2644
- first = false;
2645
- if (!specials.test(text)) {
2646
- col += text.length;
2647
- var content = document.createTextNode(text);
2648
- } else {
2649
- var content = document.createDocumentFragment(), pos = 0;
2650
- while (true) {
2651
- specials.lastIndex = pos;
2652
- var m = specials.exec(text);
2653
- var skipped = m ? m.index - pos : text.length - pos;
2654
- if (skipped) {
2655
- content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
2656
- col += skipped;
2657
- }
2658
- if (!m) break;
2659
- pos += skipped + 1;
2660
- if (m[0] == "\t") {
2661
- var tab = makeTab(col);
2662
- content.appendChild(tab.element.cloneNode(true));
2663
- col += tab.width;
2664
- } else {
2665
- var token = elt("span", "\u2022", "cm-invalidchar");
2666
- token.title = "\\u" + m[0].charCodeAt(0).toString(16);
2667
- content.appendChild(token);
2668
- col += 1;
2669
- }
2670
- }
2671
- }
2672
- if (style) html.appendChild(elt("span", [content], style));
2673
- else html.appendChild(content);
2674
- }
2675
- var span = span_;
2676
- if (wrapAt != null) {
2677
- var outPos = 0, anchor = pre.anchor = elt("span");
2678
- span = function(html, text, style) {
2679
- var l = text.length;
2680
- if (wrapAt >= outPos && wrapAt < outPos + l) {
2681
- if (wrapAt > outPos) {
2682
- span_(html, text.slice(0, wrapAt - outPos), style);
2683
- // See comment at the definition of spanAffectsWrapping
2684
- if (wrapWBR) html.appendChild(elt("wbr"));
2685
- }
2686
- html.appendChild(anchor);
2687
- var cut = wrapAt - outPos;
2688
- span_(anchor, opera ? text.slice(cut, cut + 1) : text.slice(cut), style);
2689
- if (opera) span_(html, text.slice(cut + 1), style);
2690
- wrapAt--;
2691
- outPos += l;
2692
- } else {
2693
- outPos += l;
2694
- span_(html, text, style);
2695
- if (outPos == wrapAt && outPos == len) {
2696
- setTextContent(anchor, eolSpanContent);
2697
- html.appendChild(anchor);
2698
- }
2699
- // Stop outputting HTML when gone sufficiently far beyond measure
2700
- else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){};
2701
- }
2702
- };
2703
- }
2704
-
2705
- var st = this.styles, allText = this.text, marked = this.marked;
2706
- var len = allText.length;
2707
- function styleToClass(style) {
2708
- if (!style) return null;
2709
- return "cm-" + style.replace(/ +/g, " cm-");
2710
- }
2711
- if (!allText && wrapAt == null) {
2712
- span(pre, " ");
2713
- } else if (!marked || !marked.length) {
2714
- for (var i = 0, ch = 0; ch < len; i+=2) {
2715
- var str = st[i], style = st[i+1], l = str.length;
2716
- if (ch + l > len) str = str.slice(0, len - ch);
2717
- ch += l;
2718
- span(pre, str, styleToClass(style));
2719
- }
2720
- } else {
2721
- var pos = 0, i = 0, text = "", style, sg = 0;
2722
- var nextChange = marked[0].from || 0, marks = [], markpos = 0;
2723
- var advanceMarks = function() {
2724
- var m;
2725
- while (markpos < marked.length &&
2726
- ((m = marked[markpos]).from == pos || m.from == null)) {
2727
- if (m.style != null) marks.push(m);
2728
- ++markpos;
2729
- }
2730
- nextChange = markpos < marked.length ? marked[markpos].from : Infinity;
2731
- for (var i = 0; i < marks.length; ++i) {
2732
- var to = marks[i].to;
2733
- if (to == null) to = Infinity;
2734
- if (to == pos) marks.splice(i--, 1);
2735
- else nextChange = Math.min(to, nextChange);
2736
- }
2737
- };
2738
- var m = 0;
2739
- while (pos < len) {
2740
- if (nextChange == pos) advanceMarks();
2741
- var upto = Math.min(len, nextChange);
2742
- while (true) {
2743
- if (text) {
2744
- var end = pos + text.length;
2745
- var appliedStyle = style;
2746
- for (var j = 0; j < marks.length; ++j)
2747
- appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style;
2748
- span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
2749
- if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
2750
- pos = end;
2751
- }
2752
- text = st[i++]; style = styleToClass(st[i++]);
2753
- }
2754
- }
2755
- }
2756
- return pre;
2757
- },
2758
- cleanUp: function() {
2759
- this.parent = null;
2760
- if (this.marked)
2761
- for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
2762
- }
2763
- };
2764
- // Utility used by replace and split above
2765
- function copyStyles(from, to, source, dest) {
2766
- for (var i = 0, pos = 0, state = 0; pos < to; i+=2) {
2767
- var part = source[i], end = pos + part.length;
2768
- if (state == 0) {
2769
- if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]);
2770
- if (end >= from) state = 1;
2771
- } else if (state == 1) {
2772
- if (end > to) dest.push(part.slice(0, to - pos), source[i+1]);
2773
- else dest.push(part, source[i+1]);
2774
- }
2775
- pos = end;
2776
- }
2777
- }
2778
-
2779
- // Data structure that holds the sequence of lines.
2780
- function LeafChunk(lines) {
2781
- this.lines = lines;
2782
- this.parent = null;
2783
- for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
2784
- lines[i].parent = this;
2785
- height += lines[i].height;
2786
- }
2787
- this.height = height;
2788
- }
2789
- LeafChunk.prototype = {
2790
- chunkSize: function() { return this.lines.length; },
2791
- remove: function(at, n, callbacks) {
2792
- for (var i = at, e = at + n; i < e; ++i) {
2793
- var line = this.lines[i];
2794
- this.height -= line.height;
2795
- line.cleanUp();
2796
- if (line.handlers)
2797
- for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);
2798
- }
2799
- this.lines.splice(at, n);
2800
- },
2801
- collapse: function(lines) {
2802
- lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
2803
- },
2804
- insertHeight: function(at, lines, height) {
2805
- this.height += height;
2806
- this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
2807
- for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
2808
- },
2809
- iterN: function(at, n, op) {
2810
- for (var e = at + n; at < e; ++at)
2811
- if (op(this.lines[at])) return true;
2812
- }
2813
- };
2814
- function BranchChunk(children) {
2815
- this.children = children;
2816
- var size = 0, height = 0;
2817
- for (var i = 0, e = children.length; i < e; ++i) {
2818
- var ch = children[i];
2819
- size += ch.chunkSize(); height += ch.height;
2820
- ch.parent = this;
2821
- }
2822
- this.size = size;
2823
- this.height = height;
2824
- this.parent = null;
2825
- }
2826
- BranchChunk.prototype = {
2827
- chunkSize: function() { return this.size; },
2828
- remove: function(at, n, callbacks) {
2829
- this.size -= n;
2830
- for (var i = 0; i < this.children.length; ++i) {
2831
- var child = this.children[i], sz = child.chunkSize();
2832
- if (at < sz) {
2833
- var rm = Math.min(n, sz - at), oldHeight = child.height;
2834
- child.remove(at, rm, callbacks);
2835
- this.height -= oldHeight - child.height;
2836
- if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
2837
- if ((n -= rm) == 0) break;
2838
- at = 0;
2839
- } else at -= sz;
2840
- }
2841
- if (this.size - n < 25) {
2842
- var lines = [];
2843
- this.collapse(lines);
2844
- this.children = [new LeafChunk(lines)];
2845
- this.children[0].parent = this;
2846
- }
2847
- },
2848
- collapse: function(lines) {
2849
- for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
2850
- },
2851
- insert: function(at, lines) {
2852
- var height = 0;
2853
- for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
2854
- this.insertHeight(at, lines, height);
2855
- },
2856
- insertHeight: function(at, lines, height) {
2857
- this.size += lines.length;
2858
- this.height += height;
2859
- for (var i = 0, e = this.children.length; i < e; ++i) {
2860
- var child = this.children[i], sz = child.chunkSize();
2861
- if (at <= sz) {
2862
- child.insertHeight(at, lines, height);
2863
- if (child.lines && child.lines.length > 50) {
2864
- while (child.lines.length > 50) {
2865
- var spilled = child.lines.splice(child.lines.length - 25, 25);
2866
- var newleaf = new LeafChunk(spilled);
2867
- child.height -= newleaf.height;
2868
- this.children.splice(i + 1, 0, newleaf);
2869
- newleaf.parent = this;
2870
- }
2871
- this.maybeSpill();
2872
- }
2873
- break;
2874
- }
2875
- at -= sz;
2876
- }
2877
- },
2878
- maybeSpill: function() {
2879
- if (this.children.length <= 10) return;
2880
- var me = this;
2881
- do {
2882
- var spilled = me.children.splice(me.children.length - 5, 5);
2883
- var sibling = new BranchChunk(spilled);
2884
- if (!me.parent) { // Become the parent node
2885
- var copy = new BranchChunk(me.children);
2886
- copy.parent = me;
2887
- me.children = [copy, sibling];
2888
- me = copy;
2889
- } else {
2890
- me.size -= sibling.size;
2891
- me.height -= sibling.height;
2892
- var myIndex = indexOf(me.parent.children, me);
2893
- me.parent.children.splice(myIndex + 1, 0, sibling);
2894
- }
2895
- sibling.parent = me.parent;
2896
- } while (me.children.length > 10);
2897
- me.parent.maybeSpill();
2898
- },
2899
- iter: function(from, to, op) { this.iterN(from, to - from, op); },
2900
- iterN: function(at, n, op) {
2901
- for (var i = 0, e = this.children.length; i < e; ++i) {
2902
- var child = this.children[i], sz = child.chunkSize();
2903
- if (at < sz) {
2904
- var used = Math.min(n, sz - at);
2905
- if (child.iterN(at, used, op)) return true;
2906
- if ((n -= used) == 0) break;
2907
- at = 0;
2908
- } else at -= sz;
2909
- }
2910
- }
2911
- };
2912
-
2913
- function getLineAt(chunk, n) {
2914
- while (!chunk.lines) {
2915
- for (var i = 0;; ++i) {
2916
- var child = chunk.children[i], sz = child.chunkSize();
2917
- if (n < sz) { chunk = child; break; }
2918
- n -= sz;
2919
- }
2920
- }
2921
- return chunk.lines[n];
2922
- }
2923
- function lineNo(line) {
2924
- if (line.parent == null) return null;
2925
- var cur = line.parent, no = indexOf(cur.lines, line);
2926
- for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
2927
- for (var i = 0, e = chunk.children.length; ; ++i) {
2928
- if (chunk.children[i] == cur) break;
2929
- no += chunk.children[i].chunkSize();
2930
- }
2931
- }
2932
- return no;
2933
- }
2934
- function lineAtHeight(chunk, h) {
2935
- var n = 0;
2936
- outer: do {
2937
- for (var i = 0, e = chunk.children.length; i < e; ++i) {
2938
- var child = chunk.children[i], ch = child.height;
2939
- if (h < ch) { chunk = child; continue outer; }
2940
- h -= ch;
2941
- n += child.chunkSize();
2942
- }
2943
- return n;
2944
- } while (!chunk.lines);
2945
- for (var i = 0, e = chunk.lines.length; i < e; ++i) {
2946
- var line = chunk.lines[i], lh = line.height;
2947
- if (h < lh) break;
2948
- h -= lh;
2949
- }
2950
- return n + i;
2951
- }
2952
- function heightAtLine(chunk, n) {
2953
- var h = 0;
2954
- outer: do {
2955
- for (var i = 0, e = chunk.children.length; i < e; ++i) {
2956
- var child = chunk.children[i], sz = child.chunkSize();
2957
- if (n < sz) { chunk = child; continue outer; }
2958
- n -= sz;
2959
- h += child.height;
2960
- }
2961
- return h;
2962
- } while (!chunk.lines);
2963
- for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
2964
- return h;
2965
- }
2966
-
2967
- // The history object 'chunks' changes that are made close together
2968
- // and at almost the same time into bigger undoable units.
2969
- function History() {
2970
- this.time = 0;
2971
- this.done = []; this.undone = [];
2972
- this.compound = 0;
2973
- this.closed = false;
2974
- }
2975
- History.prototype = {
2976
- addChange: function(start, added, old) {
2977
- this.undone.length = 0;
2978
- var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1];
2979
- var dtime = time - this.time;
2980
-
2981
- if (this.compound && cur && !this.closed) {
2982
- cur.push({start: start, added: added, old: old});
2983
- } else if (dtime > 400 || !last || this.closed ||
2984
- last.start > start + old.length || last.start + last.added < start) {
2985
- this.done.push([{start: start, added: added, old: old}]);
2986
- this.closed = false;
2987
- } else {
2988
- var startBefore = Math.max(0, last.start - start),
2989
- endAfter = Math.max(0, (start + old.length) - (last.start + last.added));
2990
- for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]);
2991
- for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]);
2992
- if (startBefore) last.start = start;
2993
- last.added += added - (old.length - startBefore - endAfter);
2994
- }
2995
- this.time = time;
2996
- },
2997
- startCompound: function() {
2998
- if (!this.compound++) this.closed = true;
2999
- },
3000
- endCompound: function() {
3001
- if (!--this.compound) this.closed = true;
3002
- }
3003
- };
3004
-
3005
- function stopMethod() {e_stop(this);}
3006
- // Ensure an event has a stop method.
3007
- function addStop(event) {
3008
- if (!event.stop) event.stop = stopMethod;
3009
- return event;
3010
- }
3011
-
3012
- function e_preventDefault(e) {
3013
- if (e.preventDefault) e.preventDefault();
3014
- else e.returnValue = false;
3015
- }
3016
- function e_stopPropagation(e) {
3017
- if (e.stopPropagation) e.stopPropagation();
3018
- else e.cancelBubble = true;
3019
- }
3020
- function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
3021
- CodeMirror.e_stop = e_stop;
3022
- CodeMirror.e_preventDefault = e_preventDefault;
3023
- CodeMirror.e_stopPropagation = e_stopPropagation;
3024
-
3025
- function e_target(e) {return e.target || e.srcElement;}
3026
- function e_button(e) {
3027
- var b = e.which;
3028
- if (b == null) {
3029
- if (e.button & 1) b = 1;
3030
- else if (e.button & 2) b = 3;
3031
- else if (e.button & 4) b = 2;
3032
- }
3033
- if (mac && e.ctrlKey && b == 1) b = 3;
3034
- return b;
3035
- }
3036
-
3037
- // Allow 3rd-party code to override event properties by adding an override
3038
- // object to an event object.
3039
- function e_prop(e, prop) {
3040
- var overridden = e.override && e.override.hasOwnProperty(prop);
3041
- return overridden ? e.override[prop] : e[prop];
3042
- }
3043
-
3044
- // Event handler registration. If disconnect is true, it'll return a
3045
- // function that unregisters the handler.
3046
- function connect(node, type, handler, disconnect) {
3047
- if (typeof node.addEventListener == "function") {
3048
- node.addEventListener(type, handler, false);
3049
- if (disconnect) return function() {node.removeEventListener(type, handler, false);};
3050
- } else {
3051
- var wrapHandler = function(event) {handler(event || window.event);};
3052
- node.attachEvent("on" + type, wrapHandler);
3053
- if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);};
3054
- }
3055
- }
3056
- CodeMirror.connect = connect;
3057
-
3058
- function Delayed() {this.id = null;}
3059
- Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
3060
-
3061
- var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
3062
-
3063
- // Detect drag-and-drop
3064
- var dragAndDrop = function() {
3065
- // There is *some* kind of drag-and-drop support in IE6-8, but I
3066
- // couldn't get it to work yet.
3067
- if (ie_lt9) return false;
3068
- var div = elt('div');
3069
- return "draggable" in div || "dragDrop" in div;
3070
- }();
3071
-
3072
- // Feature-detect whether newlines in textareas are converted to \r\n
3073
- var lineSep = function () {
3074
- var te = elt("textarea");
3075
- te.value = "foo\nbar";
3076
- if (te.value.indexOf("\r") > -1) return "\r\n";
3077
- return "\n";
3078
- }();
3079
-
3080
- // For a reason I have yet to figure out, some browsers disallow
3081
- // word wrapping between certain characters *only* if a new inline
3082
- // element is started between them. This makes it hard to reliably
3083
- // measure the position of things, since that requires inserting an
3084
- // extra span. This terribly fragile set of regexps matches the
3085
- // character combinations that suffer from this phenomenon on the
3086
- // various browsers.
3087
- var spanAffectsWrapping = /^$/; // Won't match any two-character string
3088
- if (gecko) spanAffectsWrapping = /$'/;
3089
- else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
3090
- else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
3091
-
3092
- // Counts the column offset in a string, taking tabs into account.
3093
- // Used mostly to find indentation.
3094
- function countColumn(string, end, tabSize) {
3095
- if (end == null) {
3096
- end = string.search(/[^\s\u00a0]/);
3097
- if (end == -1) end = string.length;
3098
- }
3099
- for (var i = 0, n = 0; i < end; ++i) {
3100
- if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
3101
- else ++n;
3102
- }
3103
- return n;
3104
- }
3105
-
3106
- function eltOffset(node, screen) {
3107
- // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
3108
- // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
3109
- try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }
3110
- catch(e) { box = {top: 0, left: 0}; }
3111
- if (!screen) {
3112
- // Get the toplevel scroll, working around browser differences.
3113
- if (window.pageYOffset == null) {
3114
- var t = document.documentElement || document.body.parentNode;
3115
- if (t.scrollTop == null) t = document.body;
3116
- box.top += t.scrollTop; box.left += t.scrollLeft;
3117
- } else {
3118
- box.top += window.pageYOffset; box.left += window.pageXOffset;
3119
- }
3120
- }
3121
- return box;
3122
- }
3123
-
3124
- // Get a node's text content.
3125
- function eltText(node) {
3126
- return node.textContent || node.innerText || node.nodeValue || "";
3127
- }
3128
- function selectInput(node) {
3129
- if (ios) { // Mobile Safari apparently has a bug where select() is broken.
3130
- node.selectionStart = 0;
3131
- node.selectionEnd = node.value.length;
3132
- } else node.select();
3133
- }
3134
-
3135
- // Operations on {line, ch} objects.
3136
- function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
3137
- function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
3138
- function copyPos(x) {return {line: x.line, ch: x.ch};}
3139
-
3140
- function elt(tag, content, className, style) {
3141
- var e = document.createElement(tag);
3142
- if (className) e.className = className;
3143
- if (style) e.style.cssText = style;
3144
- if (typeof content == "string") setTextContent(e, content);
3145
- else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
3146
- return e;
3147
- }
3148
- function removeChildren(e) {
3149
- e.innerHTML = "";
3150
- return e;
3151
- }
3152
- function removeChildrenAndAdd(parent, e) {
3153
- removeChildren(parent).appendChild(e);
3154
- }
3155
- function setTextContent(e, str) {
3156
- if (ie_lt9) {
3157
- e.innerHTML = "";
3158
- e.appendChild(document.createTextNode(str));
3159
- } else e.textContent = str;
3160
- }
3161
- CodeMirror.setTextContent = setTextContent;
3162
-
3163
- // Used to position the cursor after an undo/redo by finding the
3164
- // last edited character.
3165
- function editEnd(from, to) {
3166
- if (!to) return 0;
3167
- if (!from) return to.length;
3168
- for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)
3169
- if (from.charAt(i) != to.charAt(j)) break;
3170
- return j + 1;
3171
- }
3172
-
3173
- function indexOf(collection, elt) {
3174
- if (collection.indexOf) return collection.indexOf(elt);
3175
- for (var i = 0, e = collection.length; i < e; ++i)
3176
- if (collection[i] == elt) return i;
3177
- return -1;
3178
- }
3179
- function isWordChar(ch) {
3180
- return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();
3181
- }
3182
-
3183
- // See if "".split is the broken IE version, if so, provide an
3184
- // alternative way to split lines.
3185
- var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
3186
- var pos = 0, result = [], l = string.length;
3187
- while (pos <= l) {
3188
- var nl = string.indexOf("\n", pos);
3189
- if (nl == -1) nl = string.length;
3190
- var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
3191
- var rt = line.indexOf("\r");
3192
- if (rt != -1) {
3193
- result.push(line.slice(0, rt));
3194
- pos += rt + 1;
3195
- } else {
3196
- result.push(line);
3197
- pos = nl + 1;
3198
- }
3199
- }
3200
- return result;
3201
- } : function(string){return string.split(/\r\n?|\n/);};
3202
- CodeMirror.splitLines = splitLines;
3203
-
3204
- var hasSelection = window.getSelection ? function(te) {
3205
- try { return te.selectionStart != te.selectionEnd; }
3206
- catch(e) { return false; }
3207
- } : function(te) {
3208
- try {var range = te.ownerDocument.selection.createRange();}
3209
- catch(e) {}
3210
- if (!range || range.parentElement() != te) return false;
3211
- return range.compareEndPoints("StartToEnd", range) != 0;
3212
- };
3213
-
3214
- CodeMirror.defineMode("null", function() {
3215
- return {token: function(stream) {stream.skipToEnd();}};
3216
- });
3217
- CodeMirror.defineMIME("text/plain", "null");
3218
-
3219
- var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
3220
- 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
3221
- 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
3222
- 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
3223
- 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
3224
- 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
3225
- 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
3226
- CodeMirror.keyNames = keyNames;
3227
- (function() {
3228
- // Number keys
3229
- for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
3230
- // Alphabetic keys
3231
- for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
3232
- // Function keys
3233
- for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
3234
- })();
3235
-
3236
- return CodeMirror;
3237
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/css.js DELETED
@@ -1,174 +0,0 @@
1
- CodeMirror.defineMode("css", function(config) {
2
- var indentUnit = config.indentUnit, type;
3
-
4
- var keywords = keySet(["above", "absolute", "activeborder", "activecaption", "afar", "after-white-space", "ahead", "alias", "all", "all-scroll",
5
- "alternate", "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", "arabic-indic", "armenian", "asterisks",
6
- "auto", "avoid", "background", "backwards", "baseline", "below", "bidi-override", "binary", "bengali", "blink",
7
- "block", "block-axis", "bold", "bolder", "border", "border-box", "both", "bottom", "break-all", "break-word", "button",
8
- "button-bevel", "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", "capitalize", "caps-lock-indicator",
9
- "caption", "captiontext", "caret", "cell", "center", "checkbox", "circle", "cjk-earthly-branch", "cjk-heavenly-stem", "cjk-ideographic",
10
- "clear", "clip", "close-quote", "col-resize", "collapse", "compact", "condensed", "contain", "content", "content-box", "context-menu",
11
- "continuous", "copy", "cover", "crop", "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", "decimal-leading-zero", "default",
12
- "default-button", "destination-atop", "destination-in", "destination-out", "destination-over", "devanagari", "disc", "discard", "document",
13
- "dot-dash", "dot-dot-dash", "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", "element",
14
- "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", "ethiopic-abegede-am-et", "ethiopic-abegede-gez",
15
- "ethiopic-abegede-ti-er", "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", "ethiopic-halehame-aa-et",
16
- "ethiopic-halehame-am-et", "ethiopic-halehame-gez", "ethiopic-halehame-om-et", "ethiopic-halehame-sid-et",
17
- "ethiopic-halehame-so-et", "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig", "ew-resize", "expanded",
18
- "extra-condensed", "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes", "forwards", "from", "geometricPrecision",
19
- "georgian", "graytext", "groove", "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", "help",
20
- "hidden", "hide", "higher", "highlight", "highlighttext", "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore",
21
- "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", "infobackground", "infotext", "inherit", "initial", "inline",
22
- "inline-axis", "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", "italic", "justify", "kannada", "katakana",
23
- "katakana-iroha", "khmer", "landscape", "lao", "large", "larger", "left", "level", "lighter", "line-through", "linear", "lines",
24
- "list-item", "listbox", "listitem", "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", "lower-greek",
25
- "lower-hexadecimal", "lower-latin", "lower-norwegian", "lower-roman", "lowercase", "ltr", "malayalam", "match", "media-controls-background",
26
- "media-current-time-display", "media-fullscreen-button", "media-mute-button", "media-play-button", "media-return-to-realtime-button",
27
- "media-rewind-button", "media-seek-back-button", "media-seek-forward-button", "media-slider", "media-sliderthumb", "media-time-remaining-display",
28
- "media-volume-slider", "media-volume-slider-container", "media-volume-sliderthumb", "medium", "menu", "menulist", "menulist-button",
29
- "menulist-text", "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", "mix", "mongolian", "monospace", "move", "multiple",
30
- "myanmar", "n-resize", "narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", "no-open-quote", "no-repeat", "none",
31
- "normal", "not-allowed", "nowrap", "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", "optimizeLegibility",
32
- "optimizeSpeed", "oriya", "oromo", "outset", "outside", "overlay", "overline", "padding", "padding-box", "painted", "paused",
33
- "persian", "plus-darker", "plus-lighter", "pointer", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress",
34
- "push-button", "radio", "read-only", "read-write", "read-write-plaintext-only", "relative", "repeat", "repeat-x",
35
- "repeat-y", "reset", "reverse", "rgb", "rgba", "ridge", "right", "round", "row-resize", "rtl", "run-in", "running", "s-resize", "sans-serif",
36
- "scroll", "scrollbar", "se-resize", "searchfield", "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button",
37
- "searchfield-results-decoration", "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", "single",
38
- "skip-white-space", "slide", "slider-horizontal", "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
39
- "small", "small-caps", "small-caption", "smaller", "solid", "somali", "source-atop", "source-in", "source-out", "source-over",
40
- "space", "square", "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub", "subpixel-antialiased", "super",
41
- "sw-resize", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group",
42
- "table-row", "table-row-group", "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", "thick", "thin",
43
- "threeddarkshadow", "threedface", "threedhighlight", "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", "tigrinya-er-abegede",
44
- "tigrinya-et", "tigrinya-et-abegede", "to", "top", "transparent", "ultra-condensed", "ultra-expanded", "underline", "up", "upper-alpha", "upper-armenian",
45
- "upper-greek", "upper-hexadecimal", "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", "vertical", "vertical-text", "visible",
46
- "visibleFill", "visiblePainted", "visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider", "window", "windowframe", "windowtext",
47
- "x-large", "x-small", "xor", "xx-large", "xx-small", "yellow", "-wap-marquee", "-webkit-activelink", "-webkit-auto", "-webkit-baseline-middle",
48
- "-webkit-body", "-webkit-box", "-webkit-center", "-webkit-control", "-webkit-focus-ring-color", "-webkit-grab", "-webkit-grabbing",
49
- "-webkit-gradient", "-webkit-inline-box", "-webkit-left", "-webkit-link", "-webkit-marquee", "-webkit-mini-control", "-webkit-nowrap", "-webkit-pictograph",
50
- "-webkit-right", "-webkit-small-control", "-webkit-text", "-webkit-xxx-large", "-webkit-zoom-in", "-webkit-zoom-out"]);
51
-
52
- function keySet(array) { var keys = {}; for (var i = 0; i < array.length; ++i) keys[array[i]] = true; return keys; }
53
- function ret(style, tp) {type = tp; return style;}
54
-
55
- function tokenBase(stream, state) {
56
- var ch = stream.next();
57
- if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("meta", stream.current());}
58
- else if (ch == "/" && stream.eat("*")) {
59
- state.tokenize = tokenCComment;
60
- return tokenCComment(stream, state);
61
- }
62
- else if (ch == "<" && stream.eat("!")) {
63
- state.tokenize = tokenSGMLComment;
64
- return tokenSGMLComment(stream, state);
65
- }
66
- else if (ch == "=") ret(null, "compare");
67
- else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare");
68
- else if (ch == "\"" || ch == "'") {
69
- state.tokenize = tokenString(ch);
70
- return state.tokenize(stream, state);
71
- }
72
- else if (ch == "#") {
73
- stream.eatWhile(/[\w\\\-]/);
74
- return ret("atom", "hash");
75
- }
76
- else if (ch == "!") {
77
- stream.match(/^\s*\w*/);
78
- return ret("keyword", "important");
79
- }
80
- else if (/\d/.test(ch)) {
81
- stream.eatWhile(/[\w.%]/);
82
- return ret("number", "unit");
83
- }
84
- else if (/[,.+>*\/]/.test(ch)) {
85
- return ret(null, "select-op");
86
- }
87
- else if (/[;{}:\[\]\(\)]/.test(ch)) {
88
- return ret(null, ch);
89
- }
90
- else {
91
- stream.eatWhile(/[\w\\\-]/);
92
- return ret("variable", "variable");
93
- }
94
- }
95
-
96
- function tokenCComment(stream, state) {
97
- var maybeEnd = false, ch;
98
- while ((ch = stream.next()) != null) {
99
- if (maybeEnd && ch == "/") {
100
- state.tokenize = tokenBase;
101
- break;
102
- }
103
- maybeEnd = (ch == "*");
104
- }
105
- return ret("comment", "comment");
106
- }
107
-
108
- function tokenSGMLComment(stream, state) {
109
- var dashes = 0, ch;
110
- while ((ch = stream.next()) != null) {
111
- if (dashes >= 2 && ch == ">") {
112
- state.tokenize = tokenBase;
113
- break;
114
- }
115
- dashes = (ch == "-") ? dashes + 1 : 0;
116
- }
117
- return ret("comment", "comment");
118
- }
119
-
120
- function tokenString(quote) {
121
- return function(stream, state) {
122
- var escaped = false, ch;
123
- while ((ch = stream.next()) != null) {
124
- if (ch == quote && !escaped)
125
- break;
126
- escaped = !escaped && ch == "\\";
127
- }
128
- if (!escaped) state.tokenize = tokenBase;
129
- return ret("string", "string");
130
- };
131
- }
132
-
133
- return {
134
- startState: function(base) {
135
- return {tokenize: tokenBase,
136
- baseIndent: base || 0,
137
- stack: []};
138
- },
139
-
140
- token: function(stream, state) {
141
- if (stream.eatSpace()) return null;
142
- var style = state.tokenize(stream, state);
143
-
144
- var context = state.stack[state.stack.length-1];
145
- if (type == "hash" && context != "rule") style = "string-2";
146
- else if (style == "variable") {
147
- if (context == "rule") style = keywords[stream.current()] ? "keyword" : "number";
148
- else if (!context || context == "@media{") style = "tag";
149
- }
150
-
151
- if (context == "rule" && /^[\{\};]$/.test(type))
152
- state.stack.pop();
153
- if (type == "{") {
154
- if (context == "@media") state.stack[state.stack.length-1] = "@media{";
155
- else state.stack.push("{");
156
- }
157
- else if (type == "}") state.stack.pop();
158
- else if (type == "@media") state.stack.push("@media");
159
- else if (context == "{" && type != "comment") state.stack.push("rule");
160
- return style;
161
- },
162
-
163
- indent: function(state, textAfter) {
164
- var n = state.stack.length;
165
- if (/^\}/.test(textAfter))
166
- n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1;
167
- return state.baseIndent + n * indentUnit;
168
- },
169
-
170
- electricChars: "}"
171
- };
172
- });
173
-
174
- CodeMirror.defineMIME("text/css", "css");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/php.js DELETED
@@ -1,150 +0,0 @@
1
- (function() {
2
- function keywords(str) {
3
- var obj = {}, words = str.split(" ");
4
- for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
5
- return obj;
6
- }
7
- function heredoc(delim) {
8
- return function(stream, state) {
9
- if (stream.match(delim)) state.tokenize = null;
10
- else stream.skipToEnd();
11
- return "string";
12
- };
13
- }
14
- var phpConfig = {
15
- name: "clike",
16
- keywords: keywords("abstract and array as break case catch class clone const continue declare default " +
17
- "do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final " +
18
- "for foreach function global goto if implements interface instanceof namespace " +
19
- "new or private protected public static switch throw trait try use var while xor " +
20
- "die echo empty exit eval include include_once isset list require require_once return " +
21
- "print unset __halt_compiler self static parent"),
22
- blockKeywords: keywords("catch do else elseif for foreach if switch try while"),
23
- atoms: keywords("true false null TRUE FALSE NULL"),
24
- multiLineStrings: true,
25
- hooks: {
26
- "$": function(stream, state) {
27
- stream.eatWhile(/[\w\$_]/);
28
- return "variable-2";
29
- },
30
- "<": function(stream, state) {
31
- if (stream.match(/<</)) {
32
- stream.eatWhile(/[\w\.]/);
33
- state.tokenize = heredoc(stream.current().slice(3));
34
- return state.tokenize(stream, state);
35
- }
36
- return false;
37
- },
38
- "#": function(stream, state) {
39
- while (!stream.eol() && !stream.match("?>", false)) stream.next();
40
- return "comment";
41
- },
42
- "/": function(stream, state) {
43
- if (stream.eat("/")) {
44
- while (!stream.eol() && !stream.match("?>", false)) stream.next();
45
- return "comment";
46
- }
47
- return false;
48
- }
49
- }
50
- };
51
-
52
- CodeMirror.defineMode("php", function(config, parserConfig) {
53
- var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true});
54
- var jsMode = CodeMirror.getMode(config, "javascript");
55
- var cssMode = CodeMirror.getMode(config, "css");
56
- var phpMode = CodeMirror.getMode(config, phpConfig);
57
-
58
- function dispatch(stream, state) { // TODO open PHP inside text/css
59
- var isPHP = state.mode == "php";
60
- if (stream.sol() && state.pending != '"') state.pending = null;
61
- if (state.curMode == htmlMode) {
62
- if (stream.match(/^<\?\w*/)) {
63
- state.curMode = phpMode;
64
- state.curState = state.php;
65
- state.curClose = "?>";
66
- state.mode = "php";
67
- return "meta";
68
- }
69
- if (state.pending == '"') {
70
- while (!stream.eol() && stream.next() != '"') {}
71
- var style = "string";
72
- } else if (state.pending && stream.pos < state.pending.end) {
73
- stream.pos = state.pending.end;
74
- var style = state.pending.style;
75
- } else {
76
- var style = htmlMode.token(stream, state.curState);
77
- }
78
- state.pending = null;
79
- var cur = stream.current(), openPHP = cur.search(/<\?/);
80
- if (openPHP != -1) {
81
- if (style == "string" && /\"$/.test(cur) && !/\?>/.test(cur)) state.pending = '"';
82
- else state.pending = {end: stream.pos, style: style};
83
- stream.backUp(cur.length - openPHP);
84
- } else if (style == "tag" && stream.current() == ">" && state.curState.context) {
85
- if (/^script$/i.test(state.curState.context.tagName)) {
86
- state.curMode = jsMode;
87
- state.curState = jsMode.startState(htmlMode.indent(state.curState, ""));
88
- state.curClose = /^<\/\s*script\s*>/i;
89
- state.mode = "javascript";
90
- }
91
- else if (/^style$/i.test(state.curState.context.tagName)) {
92
- state.curMode = cssMode;
93
- state.curState = cssMode.startState(htmlMode.indent(state.curState, ""));
94
- state.curClose = /^<\/\s*style\s*>/i;
95
- state.mode = "css";
96
- }
97
- }
98
- return style;
99
- } else if ((!isPHP || state.php.tokenize == null) &&
100
- stream.match(state.curClose, isPHP)) {
101
- state.curMode = htmlMode;
102
- state.curState = state.html;
103
- state.curClose = null;
104
- state.mode = "html";
105
- if (isPHP) return "meta";
106
- else return dispatch(stream, state);
107
- } else {
108
- return state.curMode.token(stream, state.curState);
109
- }
110
- }
111
-
112
- return {
113
- startState: function() {
114
- var html = htmlMode.startState();
115
- return {html: html,
116
- php: phpMode.startState(),
117
- curMode: parserConfig.startOpen ? phpMode : htmlMode,
118
- curState: parserConfig.startOpen ? phpMode.startState() : html,
119
- curClose: parserConfig.startOpen ? /^\?>/ : null,
120
- mode: parserConfig.startOpen ? "php" : "html",
121
- pending: null};
122
- },
123
-
124
- copyState: function(state) {
125
- var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html),
126
- php = state.php, phpNew = CodeMirror.copyState(phpMode, php), cur;
127
- if (state.curState == html) cur = htmlNew;
128
- else if (state.curState == php) cur = phpNew;
129
- else cur = CodeMirror.copyState(state.curMode, state.curState);
130
- return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur,
131
- curClose: state.curClose, mode: state.mode,
132
- pending: state.pending};
133
- },
134
-
135
- token: dispatch,
136
-
137
- indent: function(state, textAfter) {
138
- if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) ||
139
- (state.curMode == phpMode && /^\?>/.test(textAfter)))
140
- return htmlMode.indent(state.html, textAfter);
141
- return state.curMode.indent(state.curState, textAfter);
142
- },
143
-
144
- electricChars: "/{}:"
145
- };
146
- }, "xml", "clike", "javascript", "css");
147
- CodeMirror.defineMIME("application/x-httpd-php", "php");
148
- CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true});
149
- CodeMirror.defineMIME("text/x-php", phpConfig);
150
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
languages/code-snippets.pot CHANGED
@@ -2,112 +2,121 @@
2
  # This file is distributed under the same license as the Code Snippets package.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: Code Snippets 1.5\n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/support/plugin/code-snippets\n"
7
- "POT-Creation-Date: 2012-09-18 11:03:45+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
- "PO-Revision-Date: 2012-MO-DA HO:MI+ZONE\n"
12
- "Last-Translator: Shea Bunge <bungeshea@users.sourceforge.net>\n"
13
- "Language-Team: LANGUAGE <LL@li.org>\n"
14
 
15
- #: code-snippets.php:357 code-snippets.php:358 code-snippets.php:367
16
- #: code-snippets.php:404 code-snippets.php:405 code-snippets.php:414
 
 
 
 
 
 
 
 
 
 
17
  #: includes/admin/manage.php:29
18
  msgid "Snippets"
19
  msgstr ""
20
 
21
- #: code-snippets.php:368 code-snippets.php:415
22
  msgid "Manage Snippets"
23
  msgstr ""
24
 
25
- #: code-snippets.php:375 code-snippets.php:422 code-snippets.php:824
26
- #: includes/admin/single.php:36
27
  msgid "Add New Snippet"
28
  msgstr ""
29
 
30
- #: code-snippets.php:376 code-snippets.php:423
31
  msgid "Add New"
32
  msgstr ""
33
 
34
- #: code-snippets.php:383 code-snippets.php:430 includes/admin/import.php:16
35
  msgid "Import Snippets"
36
  msgstr ""
37
 
38
- #: code-snippets.php:384 code-snippets.php:431
39
  msgid "Import"
40
  msgstr ""
41
 
42
- #: code-snippets.php:825 includes/admin/single.php:28
43
  msgid "Edit Snippet"
44
  msgstr ""
45
 
46
- #: code-snippets.php:1001
47
  msgid "Manage your existing snippets"
48
  msgstr ""
49
 
50
- #: code-snippets.php:1002
51
  msgid "Manage"
52
  msgstr ""
53
 
54
- #: code-snippets.php:1021
55
  msgid "Visit the WordPress.org plugin page"
56
  msgstr ""
57
 
58
- #: code-snippets.php:1022
59
  msgid "About"
60
  msgstr ""
61
 
62
- #: code-snippets.php:1026
63
  msgid "Visit the support forums"
64
  msgstr ""
65
 
66
- #: code-snippets.php:1027
67
  msgid "Support"
68
  msgstr ""
69
 
70
- #: code-snippets.php:1031
71
  msgid "Support this plugin's development"
72
  msgstr ""
73
 
74
- #: code-snippets.php:1032
75
  msgid "Donate"
76
  msgstr ""
77
 
78
- #: includes/admin/import.php:4
79
- msgid "Imported <strong>%s</strong> snippet."
80
- msgid_plural "Imported <strong>%s</strong> snippets."
81
  msgstr[0] ""
82
  msgstr[1] ""
83
 
84
- #: includes/admin/import.php:18
85
  msgid ""
86
  "Howdy! Upload your Code Snippets export file and we&#8217;ll import the "
87
  "snippets to this site."
88
  msgstr ""
89
 
90
- #: includes/admin/import.php:19
91
  msgid ""
92
  "You will need to go to the <a href=\"%s\">Manage Snippets</a> page to "
93
  "activate the imported snippets."
94
  msgstr ""
95
 
96
- #: includes/admin/import.php:20
97
  msgid ""
98
  "Choose a Code Snippets (.xml) file to upload, then click Upload file and "
99
  "import."
100
  msgstr ""
101
 
102
- #: includes/admin/import.php:23
103
  msgid "Choose a file from your computer:"
104
  msgstr ""
105
 
106
- #: includes/admin/import.php:23
107
  msgid "(Maximum size: 8MB)"
108
  msgstr ""
109
 
110
- #: includes/admin/import.php:29
111
  msgid "Upload file and import"
112
  msgstr ""
113
 
@@ -148,19 +157,19 @@ msgstr ""
148
  msgid "Search Installed Snippets"
149
  msgstr ""
150
 
151
- #: includes/admin/single.php:10
152
- msgid "Sorry, you're not allowed to edit snippets"
153
  msgstr ""
154
 
155
- #: includes/admin/single.php:17
156
  msgid "Please provide a name for the snippet and its code."
157
  msgstr ""
158
 
159
- #: includes/admin/single.php:19
160
  msgid "Snippet <strong>updated</strong>."
161
  msgstr ""
162
 
163
- #: includes/admin/single.php:21
164
  msgid "Snippet <strong>added</strong>."
165
  msgstr ""
166
 
@@ -178,19 +187,19 @@ msgid ""
178
  "&gt;</code> tags."
179
  msgstr ""
180
 
181
- #: includes/admin/single.php:70 includes/class-list-table.php:124
182
  msgid "Description"
183
  msgstr ""
184
 
185
- #: includes/admin/single.php:70
186
  msgid "(Optional)"
187
  msgstr ""
188
 
189
- #: includes/admin/single.php:86
190
  msgid "Save"
191
  msgstr ""
192
 
193
- #: includes/admin/single.php:87
194
  msgid "Cancel"
195
  msgstr ""
196
 
@@ -198,71 +207,71 @@ msgstr ""
198
  msgid "Snippets per page"
199
  msgstr ""
200
 
201
- #: includes/class-list-table.php:69 includes/class-list-table.php:146
202
  msgid "Network Deactivate"
203
  msgstr ""
204
 
205
- #: includes/class-list-table.php:69 includes/class-list-table.php:146
206
  msgid "Deactivate"
207
  msgstr ""
208
 
209
- #: includes/class-list-table.php:79 includes/class-list-table.php:145
210
  msgid "Network Activate"
211
  msgstr ""
212
 
213
- #: includes/class-list-table.php:79 includes/class-list-table.php:145
214
  msgid "Activate"
215
  msgstr ""
216
 
217
- #: includes/class-list-table.php:122
218
  msgid "Name"
219
  msgstr ""
220
 
221
- #: includes/class-list-table.php:123
222
  msgid "ID"
223
  msgstr ""
224
 
225
- #: includes/class-list-table.php:147
226
  msgid "Export"
227
  msgstr ""
228
 
229
- #: includes/class-list-table.php:148
230
  msgid "Delete"
231
  msgstr ""
232
 
233
- #: includes/class-list-table.php:149
234
  msgid "Export to PHP"
235
  msgstr ""
236
 
237
- #: includes/class-list-table.php:168
238
  msgid "All <span class=\"count\">(%s)</span>"
239
  msgid_plural "All <span class=\"count\">(%s)</span>"
240
  msgstr[0] ""
241
  msgstr[1] ""
242
 
243
- #: includes/class-list-table.php:171
244
  msgid "Active <span class=\"count\">(%s)</span>"
245
  msgid_plural "Active <span class=\"count\">(%s)</span>"
246
  msgstr[0] ""
247
  msgstr[1] ""
248
 
249
- #: includes/class-list-table.php:174
250
  msgid "Recently Active <span class=\"count\">(%s)</span>"
251
  msgid_plural "Recently Active <span class=\"count\">(%s)</span>"
252
  msgstr[0] ""
253
  msgstr[1] ""
254
 
255
- #: includes/class-list-table.php:177
256
  msgid "Inactive <span class=\"count\">(%s)</span>"
257
  msgid_plural "Inactive <span class=\"count\">(%s)</span>"
258
  msgstr[0] ""
259
  msgstr[1] ""
260
 
261
- #: includes/class-list-table.php:204
262
  msgid "Clear List"
263
  msgstr ""
264
 
265
- #: includes/class-list-table.php:270
266
  msgid ""
267
  "You do not appear to have any snippets available at this time. <a href=\"%s"
268
  "\">Add New&rarr;</a>"
@@ -331,7 +340,8 @@ msgstr ""
331
  #: includes/help/import.php:29 includes/help/manage.php:30
332
  #: includes/help/single.php:35
333
  msgid ""
334
- "<a href=\"http://cs.bungeshea.com\" target=\"_blank\">Project Website</a>"
 
335
  msgstr ""
336
 
337
  #: includes/help/manage.php:7
@@ -357,10 +367,11 @@ msgstr ""
357
  #: includes/help/manage.php:15
358
  msgid ""
359
  "If something goes wrong with a snippet and you can't use WordPress, you can "
360
- "cause all snippets to stop executing by adding <code>define('CS_SAFE_MODE', "
361
- "true);</code> to your <code>wp-config.php</code> file. After you have "
362
- "deactivated the offending snippet, you can turn off safe mode by removing "
363
- "this line or replacing <strong>true</strong> with <strong>false</strong>."
 
364
  msgstr ""
365
 
366
  #: includes/help/manage.php:20
@@ -419,8 +430,8 @@ msgstr ""
419
  #: includes/help/single.php:20
420
  msgid ""
421
  "More places to find snippets, as well as a selection of example snippets, "
422
- "can be found in the <a href=\"http://cs.bungeshea.com/docs/finding-snippets/"
423
- "\">plugin documentation</a>"
424
  msgstr ""
425
 
426
  #: includes/help/single.php:24
@@ -449,12 +460,8 @@ msgid ""
449
  "snippet becoming active on your site."
450
  msgstr ""
451
 
452
- #. Plugin Name of the plugin/theme
453
- msgid "Code Snippets"
454
- msgstr ""
455
-
456
  #. Plugin URI of the plugin/theme
457
- msgid "http://cs.bungeshea.com"
458
  msgstr ""
459
 
460
  #. Description of the plugin/theme
2
  # This file is distributed under the same license as the Code Snippets package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: Code Snippets 1.6\n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/support/plugin/code-snippets\n"
7
+ "POT-Creation-Date: 2012-12-22 06:18:18+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2012-12-22 05:20+10\n"
12
+ "Last-Translator: Shea Bunge <code-snippets@bungeshea.com>\n"
 
13
 
14
+ #. #-#-#-#-# code-snippets.pot (Code Snippets 1.6) #-#-#-#-#
15
+ #. Plugin Name of the plugin/theme
16
+ #: code-snippets.php:217
17
+ msgid "Code Snippets"
18
+ msgstr ""
19
+
20
+ #: code-snippets.php:218
21
+ msgid "Import snippets from a <strong>Code Snippets</strong> export file"
22
+ msgstr ""
23
+
24
+ #: code-snippets.php:537 code-snippets.php:538 code-snippets.php:548
25
+ #: code-snippets.php:586 code-snippets.php:587 code-snippets.php:597
26
  #: includes/admin/manage.php:29
27
  msgid "Snippets"
28
  msgstr ""
29
 
30
+ #: code-snippets.php:549 code-snippets.php:598
31
  msgid "Manage Snippets"
32
  msgstr ""
33
 
34
+ #: code-snippets.php:557 code-snippets.php:606 code-snippets.php:1017
35
+ #: includes/admin/single.php:37
36
  msgid "Add New Snippet"
37
  msgstr ""
38
 
39
+ #: code-snippets.php:558 code-snippets.php:607
40
  msgid "Add New"
41
  msgstr ""
42
 
43
+ #: code-snippets.php:637 includes/admin/import.php:23
44
  msgid "Import Snippets"
45
  msgstr ""
46
 
47
+ #: code-snippets.php:638
48
  msgid "Import"
49
  msgstr ""
50
 
51
+ #: code-snippets.php:1018 includes/admin/single.php:29
52
  msgid "Edit Snippet"
53
  msgstr ""
54
 
55
+ #: code-snippets.php:1206
56
  msgid "Manage your existing snippets"
57
  msgstr ""
58
 
59
+ #: code-snippets.php:1207
60
  msgid "Manage"
61
  msgstr ""
62
 
63
+ #: code-snippets.php:1227
64
  msgid "Visit the WordPress.org plugin page"
65
  msgstr ""
66
 
67
+ #: code-snippets.php:1228
68
  msgid "About"
69
  msgstr ""
70
 
71
+ #: code-snippets.php:1232
72
  msgid "Visit the support forums"
73
  msgstr ""
74
 
75
+ #: code-snippets.php:1233
76
  msgid "Support"
77
  msgstr ""
78
 
79
+ #: code-snippets.php:1237
80
  msgid "Support this plugin's development"
81
  msgstr ""
82
 
83
+ #: code-snippets.php:1238
84
  msgid "Donate"
85
  msgstr ""
86
 
87
+ #: includes/admin/import.php:8
88
+ msgid "Imported <strong>%d</strong> snippet."
89
+ msgid_plural "Imported <strong>%d</strong> snippets."
90
  msgstr[0] ""
91
  msgstr[1] ""
92
 
93
+ #: includes/admin/import.php:27
94
  msgid ""
95
  "Howdy! Upload your Code Snippets export file and we&#8217;ll import the "
96
  "snippets to this site."
97
  msgstr ""
98
 
99
+ #: includes/admin/import.php:29
100
  msgid ""
101
  "You will need to go to the <a href=\"%s\">Manage Snippets</a> page to "
102
  "activate the imported snippets."
103
  msgstr ""
104
 
105
+ #: includes/admin/import.php:31
106
  msgid ""
107
  "Choose a Code Snippets (.xml) file to upload, then click Upload file and "
108
  "import."
109
  msgstr ""
110
 
111
+ #: includes/admin/import.php:34
112
  msgid "Choose a file from your computer:"
113
  msgstr ""
114
 
115
+ #: includes/admin/import.php:34
116
  msgid "(Maximum size: 8MB)"
117
  msgstr ""
118
 
119
+ #: includes/admin/import.php:40
120
  msgid "Upload file and import"
121
  msgstr ""
122
 
157
  msgid "Search Installed Snippets"
158
  msgstr ""
159
 
160
+ #: includes/admin/single.php:11
161
+ msgid "Sorry, you&#8217;re not allowed to edit snippets"
162
  msgstr ""
163
 
164
+ #: includes/admin/single.php:18
165
  msgid "Please provide a name for the snippet and its code."
166
  msgstr ""
167
 
168
+ #: includes/admin/single.php:20
169
  msgid "Snippet <strong>updated</strong>."
170
  msgstr ""
171
 
172
+ #: includes/admin/single.php:22
173
  msgid "Snippet <strong>added</strong>."
174
  msgstr ""
175
 
187
  "&gt;</code> tags."
188
  msgstr ""
189
 
190
+ #: includes/admin/single.php:70 includes/class-list-table.php:156
191
  msgid "Description"
192
  msgstr ""
193
 
194
+ #: includes/admin/single.php:71
195
  msgid "(Optional)"
196
  msgstr ""
197
 
198
+ #: includes/admin/single.php:87
199
  msgid "Save"
200
  msgstr ""
201
 
202
+ #: includes/admin/single.php:88
203
  msgid "Cancel"
204
  msgstr ""
205
 
207
  msgid "Snippets per page"
208
  msgstr ""
209
 
210
+ #: includes/class-list-table.php:101 includes/class-list-table.php:178
211
  msgid "Network Deactivate"
212
  msgstr ""
213
 
214
+ #: includes/class-list-table.php:101 includes/class-list-table.php:178
215
  msgid "Deactivate"
216
  msgstr ""
217
 
218
+ #: includes/class-list-table.php:111 includes/class-list-table.php:177
219
  msgid "Network Activate"
220
  msgstr ""
221
 
222
+ #: includes/class-list-table.php:111 includes/class-list-table.php:177
223
  msgid "Activate"
224
  msgstr ""
225
 
226
+ #: includes/class-list-table.php:154
227
  msgid "Name"
228
  msgstr ""
229
 
230
+ #: includes/class-list-table.php:155
231
  msgid "ID"
232
  msgstr ""
233
 
234
+ #: includes/class-list-table.php:179
235
  msgid "Export"
236
  msgstr ""
237
 
238
+ #: includes/class-list-table.php:180
239
  msgid "Delete"
240
  msgstr ""
241
 
242
+ #: includes/class-list-table.php:181
243
  msgid "Export to PHP"
244
  msgstr ""
245
 
246
+ #: includes/class-list-table.php:200
247
  msgid "All <span class=\"count\">(%s)</span>"
248
  msgid_plural "All <span class=\"count\">(%s)</span>"
249
  msgstr[0] ""
250
  msgstr[1] ""
251
 
252
+ #: includes/class-list-table.php:203
253
  msgid "Active <span class=\"count\">(%s)</span>"
254
  msgid_plural "Active <span class=\"count\">(%s)</span>"
255
  msgstr[0] ""
256
  msgstr[1] ""
257
 
258
+ #: includes/class-list-table.php:206
259
  msgid "Recently Active <span class=\"count\">(%s)</span>"
260
  msgid_plural "Recently Active <span class=\"count\">(%s)</span>"
261
  msgstr[0] ""
262
  msgstr[1] ""
263
 
264
+ #: includes/class-list-table.php:209
265
  msgid "Inactive <span class=\"count\">(%s)</span>"
266
  msgid_plural "Inactive <span class=\"count\">(%s)</span>"
267
  msgstr[0] ""
268
  msgstr[1] ""
269
 
270
+ #: includes/class-list-table.php:236
271
  msgid "Clear List"
272
  msgstr ""
273
 
274
+ #: includes/class-list-table.php:304
275
  msgid ""
276
  "You do not appear to have any snippets available at this time. <a href=\"%s"
277
  "\">Add New&rarr;</a>"
340
  #: includes/help/import.php:29 includes/help/manage.php:30
341
  #: includes/help/single.php:35
342
  msgid ""
343
+ "<a href=\"http://code-snippets.bungeshea.com/\" target=\"_blank\">Project "
344
+ "Website</a>"
345
  msgstr ""
346
 
347
  #: includes/help/manage.php:7
367
  #: includes/help/manage.php:15
368
  msgid ""
369
  "If something goes wrong with a snippet and you can't use WordPress, you can "
370
+ "cause all snippets to stop executing by adding <code>define"
371
+ "('CODE_SNIPPETS_SAFE_MODE', true);</code> to your <code>wp-config.php</code> "
372
+ "file. After you have deactivated the offending snippet, you can turn off "
373
+ "safe mode by removing this line or replacing <strong>true</strong> with "
374
+ "<strong>false</strong>."
375
  msgstr ""
376
 
377
  #: includes/help/manage.php:20
430
  #: includes/help/single.php:20
431
  msgid ""
432
  "More places to find snippets, as well as a selection of example snippets, "
433
+ "can be found in the <a href=\"http://code-snippets.bungeshea.com/docs/"
434
+ "finding-snippets/\">plugin documentation</a>"
435
  msgstr ""
436
 
437
  #: includes/help/single.php:24
460
  "snippet becoming active on your site."
461
  msgstr ""
462
 
 
 
 
 
463
  #. Plugin URI of the plugin/theme
464
+ msgid "http://code-snippets.bungeshea.com"
465
  msgstr ""
466
 
467
  #. Description of the plugin/theme
readme.txt CHANGED
@@ -1,10 +1,10 @@
1
  === Code Snippets ===
2
  Contributors: bungeshea
3
- Donate link: http://cs.bungeshea.com/donate/
4
  Tags: snippets, code, php, network, multisite
5
  Requires at least: 3.3
6
- Tested up to: 3.4.2
7
- Stable tag: 1.5
8
  License: GPLv3 or later
9
  License URI: http://www.gnu.org/copyleft/gpl.html
10
 
@@ -14,17 +14,19 @@ An easy, clean and simple way to add code snippets to your site.
14
 
15
  **Code Snippets** is an easy, clean and simple way to add code snippets to your site. No need to edit to your theme's `functions.php` file again!
16
 
17
- A snippet is a small chunk of PHP code that you can use to extend the functionality of a WordPress-powered website; essentially a mini-plugin with a lot less load on your site. Most snippet-hosting sites tell you to add snippet code to your active theme's `functions.php` file, which can get rather long and messy after a while. Code Snippets changes that by providing a GUI interface for adding snippets and actually running them on your site as if they were in your theme's `functions.php` file.
 
 
18
 
19
- You can use a graphical interface similar to the Plugins menu to manage, activate, deactivate, edit and delete your snippets. Easily organise your snippets by adding add a name and description using the visual editor. Code Snippets includes built-in syntax highlighting and other features to help you write your code. Snippets can be exported for transfer to another side, either in XML for later importing by the Code Snippets plugin, or in PHP for creating your own plugin or theme.
20
 
21
  Although Code Snippets is designed to be easy-to-use and its interface looks, feels and acts as if it was a native part of WordPress, each screen includes a help tab, just in case you get stuck.
22
 
23
- Further information, documentation and updates are available on the [plugin homepage](http://cs.bungeshea.com).
24
 
25
  [As featured on the WPMU blog](http://wpmu.org/wordpress-code-snippets)
26
 
27
- If you have any feedback, issues or suggestions for improvements please leave a topic in the [Support Forum](http://wordpress.org/support/plugin/code-snippets) and if you like the plugin please give it a perfect rating at [WordPress.org](http://wordpress.org/extend/plugins/code-snippets)
28
 
29
  == Installation ==
30
 
@@ -44,20 +46,31 @@ If you have any feedback, issues or suggestions for improvements please leave a
44
  3. Upload the contents of the zip file to the `wp-content/plugins/` folder of your WordPress installation
45
  4. Activate the Code Snippets plugin from 'Plugins' page.
46
 
47
- 'Network Activating' Code Snippets through the Network Dashboard will enable a special interface for running snippets across the entire network.
48
 
49
  == Frequently Asked Questions ==
50
 
51
- Further documentation available on the [plugin website](http://cs.bungeshea.com/docs/).
52
 
53
- = Do I need to include the `&lt;?php`, `&lt;?` or `?&gt;` tags in my snippet? =
54
  No, just copy all the content inside those tags.
55
 
56
  = Is there a way to add a snippet but not run it right away? =
57
  Yes. Just add it but do not activate it yet.
58
 
 
 
 
59
  = What do I use to write my snippets? =
60
- The [CodeMirror](http://codemirror.net) source-code editor will add line numbers, syntax highlighting, search, tabulate and other cool features to the code editor.
 
 
 
 
 
 
 
 
61
 
62
  = Will I lose my snippets if I change the theme or upgrade WordPress? =
63
  No, the snippets are added to the WordPress database so are independent of the theme and unaffected by WordPress upgrades.
@@ -69,16 +82,19 @@ Yes, when you delete Code Snippets using the 'Plugins' menu in WordPress it will
69
  Yes! You can individually export a single snippet using the link below the snippet name on the 'Manage Snippets' page or bulk export multiple snippets using the 'Bulk Actions' feature. Snippets can later be imported using the 'Import Snippets' page by uploading the export file.
70
 
71
  = Can I export my snippets to PHP for a site where I'm not using the Code Snippets plugin? =
72
- Yes. Click the checkboxes next to the snippets you want to export, and then choose 'Export to PHP' from the Bulk Actions menu and click Apply. The generated PHP file will contain the exported snippets' code, as well as their name and description in comments.
73
 
74
  = Can I run network-wide snippets on a multisite installation? =
75
- You can run snippets across an entire multisite network by 'Network Activating' Code Snippets through the Network Dashboard.
 
 
 
76
 
77
  = I have an idea for a cool feature for Code Snippets! =
78
- That's great! Let me know by starting (or adding to) a topic in the [Support Forums](http://wordpress.org/support/plugin/code-snippets/).
79
 
80
  = I want to contribute to and help develop the Code Snippets plugin! =
81
- That's fantastic! Join me on [GitHub](http://github.com/bungeshea/code-snippets), and also be sure to check out the [development page](http://cs.bungeshea.com/dev) on the [project website](http://cs.bungeshea.com).
82
 
83
  == Screenshots ==
84
 
@@ -86,10 +102,24 @@ That's fantastic! Join me on [GitHub](http://github.com/bungeshea/code-snippets)
86
  2. Managing network-wide snippets
87
  3. Adding a new snippet
88
  4. Editing a snippet
89
- 5. Importing snppets from an XML file
90
 
91
  == Changelog ==
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  = 1.5 =
94
  * Updated CodeMirror to version 2.33
95
  * Updated the 'Manage Snippets' page to use the WP_List_Table class
@@ -137,7 +167,7 @@ That's fantastic! Join me on [GitHub](http://github.com/bungeshea/code-snippets)
137
 
138
  == Other Notes ==
139
 
140
- Plugin updates will be posted on the [plugin's homepage](http://cs.bungeshea.com) ([RSS](http://cs.bungehea.tk/feed/)).
141
 
142
  * Snippets are stored in the `wp_snippets` table in the WordPress database (the table name may differ depending on what your table prefix is set to).
143
  * Code Snippets will automatically clean up its data when deleted through the WordPress dashboard.
@@ -146,6 +176,9 @@ You can also contribute to the code at [GitHub](https://github.com/bungeshea/cod
146
 
147
  == Upgrade Notice ==
148
 
 
 
 
149
  = 1.5 =
150
  Improvements on the 'Manage Snippets' page and localization
151
 
@@ -153,7 +186,7 @@ Improvements on the 'Manage Snippets' page and localization
153
  Better code highlighting and improved multisite support
154
 
155
  = 1.3.2 =
156
- Check out Code Snippet's new website: http://cs.bungeshea.com
157
 
158
  = 1.3 =
159
  Added import/export feature
1
  === Code Snippets ===
2
  Contributors: bungeshea
3
+ Donate link: http://code-snippets.bungeshea.com/donate/
4
  Tags: snippets, code, php, network, multisite
5
  Requires at least: 3.3
6
+ Tested up to: 3.5
7
+ Stable tag: 1.6
8
  License: GPLv3 or later
9
  License URI: http://www.gnu.org/copyleft/gpl.html
10
 
14
 
15
  **Code Snippets** is an easy, clean and simple way to add code snippets to your site. No need to edit to your theme's `functions.php` file again!
16
 
17
+ A snippet is a small chunk of PHP code that you can use to extend the functionality of a WordPress-powered website; essentially a mini-plugin with a *lot* less load on your site.
18
+ Most snippet-hosting sites tell you to add snippet code to your active theme's `functions.php` file, which can get rather long and messy after a while.
19
+ Code Snippets changes that by providing a GUI interface for adding snippets and **actually running them on your site** as if they were in your theme's `functions.php` file.
20
 
21
+ You can use a graphical interface, similar to the Plugins menu, to manage, activate, deactivate, edit and delete your snippets. Easily organise your snippets by adding a name and description using the visual editor. Code Snippets includes built-in syntax highlighting and other features to help you write your code. Snippets can be exported for transfer to another side, either in XML for later importing by the Code Snippets plugin, or in PHP for creating your own plugin or theme.
22
 
23
  Although Code Snippets is designed to be easy-to-use and its interface looks, feels and acts as if it was a native part of WordPress, each screen includes a help tab, just in case you get stuck.
24
 
25
+ Further information, documentation and updates are available on the [plugin homepage](http://code-snippets.bungeshea.com).
26
 
27
  [As featured on the WPMU blog](http://wpmu.org/wordpress-code-snippets)
28
 
29
+ If you have any feedback, issues, or suggestions for improvements please leave a topic in the [Support Forum](http://wordpress.org/support/plugin/code-snippets). If you like the plugin, or it is useful to you in any way, please review it on [WordPress.org](http://wordpress.org/support/view/plugin-reviews/code-snippets).
30
 
31
  == Installation ==
32
 
46
  3. Upload the contents of the zip file to the `wp-content/plugins/` folder of your WordPress installation
47
  4. Activate the Code Snippets plugin from 'Plugins' page.
48
 
49
+ **Network Activating** Code Snippets through the Network Dashboard will enable a special interface for running snippets across the entire network.
50
 
51
  == Frequently Asked Questions ==
52
 
53
+ Further documentation available on the [plugin website](http://code-snippets.bungeshea.com/docs/).
54
 
55
+ = Do I need to include the &lt;?php, &lt;? or ?&gt; tags in my snippet? =
56
  No, just copy all the content inside those tags.
57
 
58
  = Is there a way to add a snippet but not run it right away? =
59
  Yes. Just add it but do not activate it yet.
60
 
61
+ = How can I insert my snippet into the post text editor? =
62
+ Snippets that you add to this plugin are not ment to be inserted into the text editor. Instead, they are run on your site just as if they were added to your functions.php file.
63
+
64
  = What do I use to write my snippets? =
65
+ The [CodeMirror](http://codemirror.net) source-code editor will add line numbers, syntax highlighting, bracket matching, search, tabulate and other cool features to the code editor.
66
+
67
+ = Can I preform search and replace commands in the code editor? =
68
+
69
+ * __Ctrl-F / Cmd-F__ : Start searching
70
+ * __Ctrl-G / Cmd-G__ : Find next
71
+ * __Shift-Ctrl-G / Shift-Cmd-G__ : Find previous
72
+ * __Shift-Ctrl-F / Cmd-Option-F__ : Replace
73
+ * __Shift-Ctrl-R / Shift-Cmd-Option-F__ : Replace all
74
 
75
  = Will I lose my snippets if I change the theme or upgrade WordPress? =
76
  No, the snippets are added to the WordPress database so are independent of the theme and unaffected by WordPress upgrades.
82
  Yes! You can individually export a single snippet using the link below the snippet name on the 'Manage Snippets' page or bulk export multiple snippets using the 'Bulk Actions' feature. Snippets can later be imported using the 'Import Snippets' page by uploading the export file.
83
 
84
  = Can I export my snippets to PHP for a site where I'm not using the Code Snippets plugin? =
85
+ Yes. Click the checkboxes next to the snippets you want to export, and then choose **Export to PHP** from the Bulk Actions menu and click Apply. The generated PHP file will contain the exported snippets' code, as well as their name and description in comments.
86
 
87
  = Can I run network-wide snippets on a multisite installation? =
88
+ You can run snippets across an entire multisite network by **Network Activating** Code Snippets through the Network Dashboard.
89
+
90
+ = I need help with Code Snippets =
91
+ You can get help with Code Snippets either on the [WordPress Support Forums](http://wordpress.org/support/plugin/code-snippets/) or on [GithHub](https://github.com/bungeshea/code-snippets/issues).
92
 
93
  = I have an idea for a cool feature for Code Snippets! =
94
+ That's great! Let me know by starting (or adding to) a topic in the [Support Forums](http://wordpress.org/support/plugin/code-snippets/) or open an issue on [GitHub](https://github.com/bungeshea/code-snippets/issues).
95
 
96
  = I want to contribute to and help develop the Code Snippets plugin! =
97
+ That's fantastic! Join me on [GitHub](https://github.com/bungeshea/code-snippets), and also be sure to check out the [development page](http://code-snippets.bungeshea.com/development/) on the [project website](http://code-snippets.bungeshea.com).
98
 
99
  == Screenshots ==
100
 
102
  2. Managing network-wide snippets
103
  3. Adding a new snippet
104
  4. Editing a snippet
105
+ 5. Importing snippets from an XML file
106
 
107
  == Changelog ==
108
 
109
+ = 1.6 =
110
+ * Updated code editor to use CodeMirror 3
111
+ * Improved compatibility with Clean Options plugin
112
+ * Code improvements and optimization
113
+ * Changed namespace from `cs` to `code_snippets`
114
+ * Move css and js under assets
115
+ * Organized CodeMirror scripts
116
+ * Improved updating process
117
+ * Current line of code editor is now highlighted
118
+ * Highlight matches of selected text in code editor
119
+ * Only create snippet tables when needed
120
+ * Store multisite only options in site options table
121
+ * Fixed compatibility bugs with WordPress 3.5
122
+
123
  = 1.5 =
124
  * Updated CodeMirror to version 2.33
125
  * Updated the 'Manage Snippets' page to use the WP_List_Table class
167
 
168
  == Other Notes ==
169
 
170
+ Plugin updates will be posted on the [plugin's homepage](http://code-snippets.bungeshea.com/) ([RSS](http://code-snippets.bungeshea.com/feed/)).
171
 
172
  * Snippets are stored in the `wp_snippets` table in the WordPress database (the table name may differ depending on what your table prefix is set to).
173
  * Code Snippets will automatically clean up its data when deleted through the WordPress dashboard.
176
 
177
  == Upgrade Notice ==
178
 
179
+ = 1.6 =
180
+ Improvements and optimization with WordPress 3.5
181
+
182
  = 1.5 =
183
  Improvements on the 'Manage Snippets' page and localization
184
 
186
  Better code highlighting and improved multisite support
187
 
188
  = 1.3.2 =
189
+ Code Snippets has a new website: http://code-snippets.bungeshea.com/
190
 
191
  = 1.3 =
192
  Added import/export feature