CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/codemirror/mode/less.js
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/*
7
LESS mode - http://www.lesscss.org/
8
Ported to CodeMirror by Peter Kroon <[email protected]>
9
Report bugs/issues here: https://github.com/marijnh/CodeMirror/issues
10
GitHub: @peterkroon
11
*/
12
13
import * as CodeMirror from "codemirror";
14
15
CodeMirror.defineMode("less", function(config) {
16
var indentUnit = config.indentUnit, type;
17
function ret(style, tp) {type = tp; return style;}
18
19
var selectors = /(^\:root$|^\:nth\-child$|^\:nth\-last\-child$|^\:nth\-of\-type$|^\:nth\-last\-of\-type$|^\:first\-child$|^\:last\-child$|^\:first\-of\-type$|^\:last\-of\-type$|^\:only\-child$|^\:only\-of\-type$|^\:empty$|^\:link|^\:visited$|^\:active$|^\:hover$|^\:focus$|^\:target$|^\:lang$|^\:enabled^\:disabled$|^\:checked$|^\:first\-line$|^\:first\-letter$|^\:before$|^\:after$|^\:not$|^\:required$|^\:invalid$)/;
20
21
function tokenBase(stream, state) {
22
var ch = stream.next();
23
24
if (ch == "@") {stream.eatWhile(/[\w\-]/); return ret("meta", stream.current());}
25
else if (ch == "/" && stream.eat("*")) {
26
state.tokenize = tokenCComment;
27
return tokenCComment(stream, state);
28
} else if (ch == "<" && stream.eat("!")) {
29
state.tokenize = tokenSGMLComment;
30
return tokenSGMLComment(stream, state);
31
} else if (ch == "=") ret(null, "compare");
32
else if (ch == "|" && stream.eat("=")) return ret(null, "compare");
33
else if (ch == "\"" || ch == "'") {
34
state.tokenize = tokenString(ch);
35
return state.tokenize(stream, state);
36
} else if (ch == "/") { // e.g.: .png will not be parsed as a class
37
if(stream.eat("/")){
38
state.tokenize = tokenSComment;
39
return tokenSComment(stream, state);
40
} else {
41
if(type == "string" || type == "(") return ret("string", "string");
42
if(state.stack[state.stack.length-1] !== undefined) return ret(null, ch);
43
stream.eatWhile(/[\a-zA-Z0-9\-_.\s]/);
44
if( /\/|\)|#/.test(stream.peek() || (stream.eatSpace() && stream.peek() === ")")) || stream.eol() )return ret("string", "string"); // let url(/images/logo.png) without quotes return as string
45
}
46
} else if (ch == "!") {
47
stream.match(/^\s*\w*/);
48
return ret("keyword", "important");
49
} else if (/\d/.test(ch)) {
50
stream.eatWhile(/[\w.%]/);
51
return ret("number", "unit");
52
} else if (/[,+<>*\/]/.test(ch)) {
53
if(stream.peek() == "=" || type == "a")return ret("string", "string");
54
if(ch === ",")return ret(null, ch);
55
return ret(null, "select-op");
56
} else if (/[;{}:\[\]()~\|]/.test(ch)) {
57
if(ch == ":"){
58
stream.eatWhile(/[a-z\\\-]/);
59
if( selectors.test(stream.current()) ){
60
return ret("tag", "tag");
61
} else if(stream.peek() == ":"){//::-webkit-search-decoration
62
stream.next();
63
stream.eatWhile(/[a-z\\\-]/);
64
if(stream.current().match(/\:\:\-(o|ms|moz|webkit)\-/))return ret("string", "string");
65
if( selectors.test(stream.current().substring(1)) )return ret("tag", "tag");
66
return ret(null, ch);
67
} else {
68
return ret(null, ch);
69
}
70
} else if(ch == "~"){
71
if(type == "r")return ret("string", "string");
72
} else {
73
return ret(null, ch);
74
}
75
} else if (ch == ".") {
76
if(type == "(")return ret("string", "string"); // allow url(../image.png)
77
stream.eatWhile(/[\a-zA-Z0-9\-_]/);
78
if(stream.peek() === " ")stream.eatSpace();
79
if(stream.peek() === ")" || type === ":")return ret("number", "unit");//rgba(0,0,0,.25);
80
else if(stream.current().length >1){
81
if(state.stack[state.stack.length-1] === "rule" && stream.peek().match(/{|,|\+|\(/) === null)return ret("number", "unit");
82
}
83
return ret("tag", "tag");
84
} else if (ch == "#") {
85
//we don't eat white-space, we want the hex color and or id only
86
stream.eatWhile(/[A-Za-z0-9]/);
87
//check if there is a proper hex color length e.g. #eee || #eeeEEE
88
if(stream.current().length == 4 || stream.current().length == 7){
89
if(stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false) != null){//is there a valid hex color value present in the current stream
90
//when not a valid hex value, parse as id
91
if(stream.current().substring(1) != stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false))return ret("atom", "tag");
92
//eat white-space
93
stream.eatSpace();
94
//when hex value declaration doesn't end with [;,] but is does with a slash/cc comment treat it as an id, just like the other hex values that don't end with[;,]
95
if( /[\/<>.(){!$%^&*_\-\\?=+\|#'~`]/.test(stream.peek()) ){
96
if(type === "select-op")return ret("number", "unit"); else return ret("atom", "tag");
97
}
98
//#time { color: #aaa }
99
else if(stream.peek() == "}" )return ret("number", "unit");
100
//we have a valid hex color value, parse as id whenever an element/class is defined after the hex(id) value e.g. #eee aaa || #eee .aaa
101
else if( /[a-zA-Z\\]/.test(stream.peek()) )return ret("atom", "tag");
102
//when a hex value is on the end of a line, parse as id
103
else if(stream.eol())return ret("atom", "tag");
104
//default
105
else return ret("number", "unit");
106
} else {//when not a valid hexvalue in the current stream e.g. #footer
107
stream.eatWhile(/[\w\\\-]/);
108
return ret("atom", stream.current());
109
}
110
} else {//when not a valid hexvalue length
111
stream.eatWhile(/[\w\\\-]/);
112
if(state.stack[state.stack.length-1] === "rule")return ret("atom", stream.current());return ret("atom", stream.current());
113
return ret("atom", "tag");
114
}
115
} else if (ch == "&") {
116
stream.eatWhile(/[\w\-]/);
117
return ret(null, ch);
118
} else {
119
stream.eatWhile(/[\w\\\-_%.{]/);
120
if(stream.current().match(/\\/) !== null){
121
if(stream.current().charAt(stream.current().length-1) === "\\"){
122
stream.eat(/\'|\"|\)|\(/);
123
while(stream.eatWhile(/[\w\\\-_%.{]/)){
124
stream.eat(/\'|\"|\)|\(/);
125
}
126
return ret("string", stream.current());
127
}
128
} //else if(type === "tag")return ret("tag", "tag");
129
else if(type == "string"){
130
if(state.stack[state.stack.length-1] === "{" && stream.peek() === ":")return ret("variable", "variable");
131
if(stream.peek() === "/")stream.eatWhile(/[\w\\\-_%.{:\/]/);
132
return ret(type, stream.current());
133
} else if(stream.current().match(/(^http$|^https$)/) != null){
134
stream.eatWhile(/[\w\\\-_%.{:\/]/);
135
if(stream.peek() === "/")stream.eatWhile(/[\w\\\-_%.{:\/]/);
136
return ret("string", "string");
137
} else if(stream.peek() == "<" || stream.peek() == ">" || stream.peek() == "+"){
138
if(type === "(" && (stream.current() === "n" || stream.current() === "-n"))return ret("string", stream.current());
139
return ret("tag", "tag");
140
} else if( /\(/.test(stream.peek()) ){
141
if(stream.current() === "when")return ret("variable","variable");
142
else if(state.stack[state.stack.length-1] === "@media" && stream.current() === "and")return ret("variable",stream.current());
143
return ret(null, ch);
144
} else if (stream.peek() == "/" && state.stack[state.stack.length-1] !== undefined){ // url(dir/center/image.png)
145
if(stream.peek() === "/")stream.eatWhile(/[\w\\\-_%.{:\/]/);
146
return ret("string", stream.current());
147
} else if( stream.current().match(/\-\d|\-.\d/) ){ // match e.g.: -5px -0.4 etc... only colorize the minus sign
148
//commment out these 2 comment if you want the minus sign to be parsed as null -500px
149
//stream.backUp(stream.current().length-1);
150
//return ret(null, ch);
151
return ret("number", "unit");
152
} else if( /\/|[\s\)]/.test(stream.peek() || stream.eol() || (stream.eatSpace() && stream.peek() == "/")) && stream.current().indexOf(".") !== -1){
153
if(stream.current().substring(stream.current().length-1,stream.current().length) == "{"){
154
stream.backUp(1);
155
return ret("tag", "tag");
156
}//end if
157
stream.eatSpace();
158
if( /[{<>.a-zA-Z\/]/.test(stream.peek()) || stream.eol() )return ret("tag", "tag"); // e.g. button.icon-plus
159
return ret("string", "string"); // let url(/images/logo.png) without quotes return as string
160
} else if( stream.eol() || stream.peek() == "[" || stream.peek() == "#" || type == "tag" ){
161
162
if(stream.current().substring(stream.current().length-1,stream.current().length) == "{")stream.backUp(1);
163
else if(state.stack[state.stack.length-1] === "border-color" || state.stack[state.stack.length-1] === "background-position" || state.stack[state.stack.length-1] === "font-family")return ret(null, stream.current());
164
else if(type === "tag")return ret("tag", "tag");
165
else if((type === ":" || type === "unit") && state.stack[state.stack.length-1] === "rule")return ret(null, stream.current());
166
else if(state.stack[state.stack.length-1] === "rule" && type === "tag")return ret("string", stream.current());
167
else if(state.stack[state.stack.length-1] === ";" && type === ":")return ret(null, stream.current());
168
//else if(state.stack[state.stack.length-1] === ";" || type === "")return ret("variable", stream.current());
169
else if(stream.peek() === "#" && type !== undefined && type.match(/\+|,|tag|select\-op|}|{|;/g) === null)return ret("string", stream.current());
170
else if(type === "variable")return ret(null, stream.current());
171
else if(state.stack[state.stack.length-1] === "{" && type === "comment")return ret("variable", stream.current());
172
else if(state.stack.length === 0 && (type === ";" || type === "comment"))return ret("tag", stream.current());
173
else if((state.stack[state.stack.length-1] === "{" || type === ";") && state.stack[state.stack.length-1] !== "@media{")return ret("variable", stream.current());
174
else if(state.stack[state.stack.length-2] === "{" && state.stack[state.stack.length-1] === ";")return ret("variable", stream.current());
175
176
return ret("tag", "tag");
177
} else if(type == "compare" || type == "a" || type == "("){
178
return ret("string", "string");
179
} else if(type == "|" || stream.current() == "-" || type == "["){
180
if(type == "|" && stream.peek().match(/\]|=|\~/) !== null)return ret("number", stream.current());
181
else if(type == "|" )return ret("tag", "tag");
182
else if(type == "["){
183
stream.eatWhile(/\w\-/);
184
return ret("number", stream.current());
185
}
186
return ret(null, ch);
187
} else if((stream.peek() == ":") || ( stream.eatSpace() && stream.peek() == ":")) {
188
stream.next();
189
var t_v = stream.peek() == ":" ? true : false;
190
if(!t_v){
191
var old_pos = stream.pos;
192
var sc = stream.current().length;
193
stream.eatWhile(/[a-z\\\-]/);
194
var new_pos = stream.pos;
195
if(stream.current().substring(sc-1).match(selectors) != null){
196
stream.backUp(new_pos-(old_pos-1));
197
return ret("tag", "tag");
198
} else stream.backUp(new_pos-(old_pos-1));
199
} else {
200
stream.backUp(1);
201
}
202
if(t_v)return ret("tag", "tag"); else return ret("variable", "variable");
203
} else if(state.stack[state.stack.length-1] === "font-family" || state.stack[state.stack.length-1] === "background-position" || state.stack[state.stack.length-1] === "border-color"){
204
return ret(null, null);
205
} else {
206
207
if(state.stack[state.stack.length-1] === null && type === ":")return ret(null, stream.current());
208
209
//else if((type === ")" && state.stack[state.stack.length-1] === "rule") || (state.stack[state.stack.length-2] === "{" && state.stack[state.stack.length-1] === "rule" && type === "variable"))return ret(null, stream.current());
210
211
else if(/\^|\$/.test(stream.current()) && stream.peek().match(/\~|=/) !== null)return ret("string", "string");//att^=val
212
213
else if(type === "unit" && state.stack[state.stack.length-1] === "rule")return ret(null, "unit");
214
else if(type === "unit" && state.stack[state.stack.length-1] === ";")return ret(null, "unit");
215
else if(type === ")" && state.stack[state.stack.length-1] === "rule")return ret(null, "unit");
216
else if(type && type.match("@") !== null && state.stack[state.stack.length-1] === "rule")return ret(null, "unit");
217
//else if(type === "unit" && state.stack[state.stack.length-1] === "rule")return ret(null, stream.current());
218
219
else if((type === ";" || type === "}" || type === ",") && state.stack[state.stack.length-1] === ";")return ret("tag", stream.current());
220
else if((type === ";" && stream.peek() !== undefined && stream.peek().match(/{|./) === null) || (type === ";" && stream.eatSpace() && stream.peek().match(/{|./) === null))return ret("variable", stream.current());
221
else if((type === "@media" && state.stack[state.stack.length-1] === "@media") || type === "@namespace")return ret("tag", stream.current());
222
223
else if(type === "{" && state.stack[state.stack.length-1] === ";" && stream.peek() === "{")return ret("tag", "tag");
224
else if((type === "{" || type === ":") && state.stack[state.stack.length-1] === ";")return ret(null, stream.current());
225
else if((state.stack[state.stack.length-1] === "{" && stream.eatSpace() && stream.peek().match(/.|#/) === null) || type === "select-op" || (state.stack[state.stack.length-1] === "rule" && type === ",") )return ret("tag", "tag");
226
else if(type === "variable" && state.stack[state.stack.length-1] === "rule")return ret("tag", "tag");
227
else if((stream.eatSpace() && stream.peek() === "{") || stream.eol() || stream.peek() === "{")return ret("tag", "tag");
228
//this one messes up indentation
229
//else if((type === "}" && stream.peek() !== ":") || (type === "}" && stream.eatSpace() && stream.peek() !== ":"))return(type, "tag");
230
231
else if(type === ")" && (stream.current() == "and" || stream.current() == "and "))return ret("variable", "variable");
232
else if(type === ")" && (stream.current() == "when" || stream.current() == "when "))return ret("variable", "variable");
233
else if(type === ")" || type === "comment" || type === "{")return ret("tag", "tag");
234
else if(stream.sol())return ret("tag", "tag");
235
else if((stream.eatSpace() && stream.peek() === "#") || stream.peek() === "#")return ret("tag", "tag");
236
else if(state.stack.length === 0)return ret("tag", "tag");
237
else if(type === ";" && stream.peek() !== undefined && stream.peek().match(/^[.|\#]/g) !== null)return ret("tag", "tag");
238
239
else if(type === ":"){stream.eatSpace();return ret(null, stream.current());}
240
241
else if(stream.current() === "and " || stream.current() === "and")return ret("variable", stream.current());
242
else if(type === ";" && state.stack[state.stack.length-1] === "{")return ret("variable", stream.current());
243
244
else if(state.stack[state.stack.length-1] === "rule")return ret(null, stream.current());
245
246
return ret("tag", stream.current());
247
}
248
}
249
}
250
251
function tokenSComment(stream, state) { // SComment = Slash comment
252
stream.skipToEnd();
253
state.tokenize = tokenBase;
254
return ret("comment", "comment");
255
}
256
257
function tokenCComment(stream, state) {
258
var maybeEnd = false, ch;
259
while ((ch = stream.next()) != null) {
260
if (maybeEnd && ch == "/") {
261
state.tokenize = tokenBase;
262
break;
263
}
264
maybeEnd = (ch == "*");
265
}
266
return ret("comment", "comment");
267
}
268
269
function tokenSGMLComment(stream, state) {
270
var dashes = 0, ch;
271
while ((ch = stream.next()) != null) {
272
if (dashes >= 2 && ch == ">") {
273
state.tokenize = tokenBase;
274
break;
275
}
276
dashes = (ch == "-") ? dashes + 1 : 0;
277
}
278
return ret("comment", "comment");
279
}
280
281
function tokenString(quote) {
282
return function(stream, state) {
283
var escaped = false, ch;
284
while ((ch = stream.next()) != null) {
285
if (ch == quote && !escaped)
286
break;
287
escaped = !escaped && ch == "\\";
288
}
289
if (!escaped) state.tokenize = tokenBase;
290
return ret("string", "string");
291
};
292
}
293
294
return {
295
startState: function(base) {
296
return {tokenize: tokenBase,
297
baseIndent: base || 0,
298
stack: []};
299
},
300
301
token: function(stream, state) {
302
if (stream.eatSpace()) return null;
303
var style = state.tokenize(stream, state);
304
305
var context = state.stack[state.stack.length-1];
306
if (type == "hash" && context == "rule") style = "atom";
307
else if (style == "variable") {
308
if (context == "rule") style = null; //"tag"
309
else if (!context || context == "@media{") {
310
style = stream.current() == "when" ? "variable" :
311
/[\s,|\s\)|\s]/.test(stream.peek()) ? "tag" : type;
312
}
313
}
314
315
if (context == "rule" && /^[\{\};]$/.test(type))
316
state.stack.pop();
317
if (type == "{") {
318
if (context == "@media") state.stack[state.stack.length-1] = "@media{";
319
else state.stack.push("{");
320
}
321
else if (type == "}") state.stack.pop();
322
else if (type == "@media") state.stack.push("@media");
323
else if (stream.current() === "font-family") state.stack[state.stack.length-1] = "font-family";
324
else if (stream.current() === "background-position") state.stack[state.stack.length-1] = "background-position";
325
else if (stream.current() === "border-color") state.stack[state.stack.length-1] = "border-color";
326
else if (context == "{" && type != "comment" && type !== "tag") state.stack.push("rule");
327
else if (stream.peek() === ":" && stream.current().match(/@|#/) === null) style = type;
328
if(type === ";" && (state.stack[state.stack.length-1] == "font-family" || state.stack[state.stack.length-1] == "background-position" || state.stack[state.stack.length-1] == "border-color"))state.stack[state.stack.length-1] = stream.current();
329
else if(type === "tag" && stream.peek() === ")" && stream.current().match(/\:/) === null){type = null; style = null;}
330
// ????
331
else if((type === "variable" && stream.peek() === ")") || (type === "variable" && stream.eatSpace() && stream.peek() === ")"))return ret(null,stream.current());
332
return style;
333
},
334
335
indent: function(state, textAfter) {
336
var n = state.stack.length;
337
if (/^\}/.test(textAfter))
338
n -= state.stack[state.stack.length-1] === "rule" ? 2 : 1;
339
else if (state.stack[state.stack.length-2] === "{")
340
n -= state.stack[state.stack.length-1] === "rule" ? 1 : 0;
341
return state.baseIndent + n * indentUnit;
342
},
343
344
electricChars: "}",
345
blockCommentStart: "/*",
346
blockCommentEnd: "*/",
347
lineComment: "//"
348
};
349
});
350
351
CodeMirror.defineMIME("text/x-less", "less");
352
if (!CodeMirror.mimeModes.hasOwnProperty("text/css"))
353
CodeMirror.defineMIME("text/css", "less");
354
355