LexCSS.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. // Scintilla source code edit control
  2. /** @file LexCSS.cxx
  3. ** Lexer for Cascading Style Sheets
  4. ** Written by Jakub Vrána
  5. ** Improved by Philippe Lhoste (CSS2)
  6. ** Improved by Ross McKay (SCSS mode; see http://sass-lang.com/ )
  7. **/
  8. // Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
  9. // The License.txt file describes the conditions under which this software may be distributed.
  10. // TODO: handle SCSS nested properties like font: { weight: bold; size: 1em; }
  11. // TODO: handle SCSS interpolation: #{}
  12. // TODO: add features for Less if somebody feels like contributing; http://lesscss.org/
  13. // TODO: refactor this monster so that the next poor slob can read it!
  14. #include <stdlib.h>
  15. #include <string.h>
  16. #include <stdio.h>
  17. #include <stdarg.h>
  18. #include <assert.h>
  19. #include <ctype.h>
  20. #include "ILexer.h"
  21. #include "Scintilla.h"
  22. #include "SciLexer.h"
  23. #include "WordList.h"
  24. #include "LexAccessor.h"
  25. #include "Accessor.h"
  26. #include "StyleContext.h"
  27. #include "CharacterSet.h"
  28. #include "LexerModule.h"
  29. #ifdef SCI_NAMESPACE
  30. using namespace Scintilla;
  31. #endif
  32. static inline bool IsAWordChar(const unsigned int ch) {
  33. /* FIXME:
  34. * The CSS spec allows "ISO 10646 characters U+00A1 and higher" to be treated as word chars.
  35. * Unfortunately, we are only getting string bytes here, and not full unicode characters. We cannot guarantee
  36. * that our byte is between U+0080 - U+00A0 (to return false), so we have to allow all characters U+0080 and higher
  37. */
  38. return ch >= 0x80 || isalnum(ch) || ch == '-' || ch == '_';
  39. }
  40. inline bool IsCssOperator(const int ch) {
  41. if (!((ch < 0x80) && isalnum(ch)) &&
  42. (ch == '{' || ch == '}' || ch == ':' || ch == ',' || ch == ';' ||
  43. ch == '.' || ch == '#' || ch == '!' || ch == '@' ||
  44. /* CSS2 */
  45. ch == '*' || ch == '>' || ch == '+' || ch == '=' || ch == '~' || ch == '|' ||
  46. ch == '[' || ch == ']' || ch == '(' || ch == ')')) {
  47. return true;
  48. }
  49. return false;
  50. }
  51. // look behind (from start of document to our start position) to determine current nesting level
  52. inline int NestingLevelLookBehind(Sci_PositionU startPos, Accessor &styler) {
  53. int ch;
  54. int nestingLevel = 0;
  55. for (Sci_PositionU i = 0; i < startPos; i++) {
  56. ch = styler.SafeGetCharAt(i);
  57. if (ch == '{')
  58. nestingLevel++;
  59. else if (ch == '}')
  60. nestingLevel--;
  61. }
  62. return nestingLevel;
  63. }
  64. static void ColouriseCssDoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *keywordlists[], Accessor &styler) {
  65. WordList &css1Props = *keywordlists[0];
  66. WordList &pseudoClasses = *keywordlists[1];
  67. WordList &css2Props = *keywordlists[2];
  68. WordList &css3Props = *keywordlists[3];
  69. WordList &pseudoElements = *keywordlists[4];
  70. WordList &exProps = *keywordlists[5];
  71. WordList &exPseudoClasses = *keywordlists[6];
  72. WordList &exPseudoElements = *keywordlists[7];
  73. StyleContext sc(startPos, length, initStyle, styler);
  74. int lastState = -1; // before operator
  75. int lastStateC = -1; // before comment
  76. int lastStateS = -1; // before single-quoted/double-quoted string
  77. int lastStateVar = -1; // before variable (SCSS)
  78. int lastStateVal = -1; // before value (SCSS)
  79. int op = ' '; // last operator
  80. int opPrev = ' '; // last operator
  81. bool insideParentheses = false; // true if currently in a CSS url() or similar construct
  82. // property lexer.css.scss.language
  83. // Set to 1 for Sassy CSS (.scss)
  84. bool isScssDocument = styler.GetPropertyInt("lexer.css.scss.language") != 0;
  85. // property lexer.css.less.language
  86. // Set to 1 for Less CSS (.less)
  87. bool isLessDocument = styler.GetPropertyInt("lexer.css.less.language") != 0;
  88. // property lexer.css.hss.language
  89. // Set to 1 for HSS (.hss)
  90. bool isHssDocument = styler.GetPropertyInt("lexer.css.hss.language") != 0;
  91. // SCSS/LESS/HSS have the concept of variable
  92. bool hasVariables = isScssDocument || isLessDocument || isHssDocument;
  93. char varPrefix = 0;
  94. if (hasVariables)
  95. varPrefix = isLessDocument ? '@' : '$';
  96. // SCSS/LESS/HSS support single-line comments
  97. typedef enum _CommentModes { eCommentBlock = 0, eCommentLine = 1} CommentMode;
  98. CommentMode comment_mode = eCommentBlock;
  99. bool hasSingleLineComments = isScssDocument || isLessDocument || isHssDocument;
  100. // must keep track of nesting level in document types that support it (SCSS/LESS/HSS)
  101. bool hasNesting = false;
  102. int nestingLevel = 0;
  103. if (isScssDocument || isLessDocument || isHssDocument) {
  104. hasNesting = true;
  105. nestingLevel = NestingLevelLookBehind(startPos, styler);
  106. }
  107. // "the loop"
  108. for (; sc.More(); sc.Forward()) {
  109. if (sc.state == SCE_CSS_COMMENT && ((comment_mode == eCommentBlock && sc.Match('*', '/')) || (comment_mode == eCommentLine && sc.atLineEnd))) {
  110. if (lastStateC == -1) {
  111. // backtrack to get last state:
  112. // comments are like whitespace, so we must return to the previous state
  113. Sci_PositionU i = startPos;
  114. for (; i > 0; i--) {
  115. if ((lastStateC = styler.StyleAt(i-1)) != SCE_CSS_COMMENT) {
  116. if (lastStateC == SCE_CSS_OPERATOR) {
  117. op = styler.SafeGetCharAt(i-1);
  118. opPrev = styler.SafeGetCharAt(i-2);
  119. while (--i) {
  120. lastState = styler.StyleAt(i-1);
  121. if (lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT)
  122. break;
  123. }
  124. if (i == 0)
  125. lastState = SCE_CSS_DEFAULT;
  126. }
  127. break;
  128. }
  129. }
  130. if (i == 0)
  131. lastStateC = SCE_CSS_DEFAULT;
  132. }
  133. if (comment_mode == eCommentBlock) {
  134. sc.Forward();
  135. sc.ForwardSetState(lastStateC);
  136. } else /* eCommentLine */ {
  137. sc.SetState(lastStateC);
  138. }
  139. }
  140. if (sc.state == SCE_CSS_COMMENT)
  141. continue;
  142. if (sc.state == SCE_CSS_DOUBLESTRING || sc.state == SCE_CSS_SINGLESTRING) {
  143. if (sc.ch != (sc.state == SCE_CSS_DOUBLESTRING ? '\"' : '\''))
  144. continue;
  145. Sci_PositionU i = sc.currentPos;
  146. while (i && styler[i-1] == '\\')
  147. i--;
  148. if ((sc.currentPos - i) % 2 == 1)
  149. continue;
  150. sc.ForwardSetState(lastStateS);
  151. }
  152. if (sc.state == SCE_CSS_OPERATOR) {
  153. if (op == ' ') {
  154. Sci_PositionU i = startPos;
  155. op = styler.SafeGetCharAt(i-1);
  156. opPrev = styler.SafeGetCharAt(i-2);
  157. while (--i) {
  158. lastState = styler.StyleAt(i-1);
  159. if (lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT)
  160. break;
  161. }
  162. }
  163. switch (op) {
  164. case '@':
  165. if (lastState == SCE_CSS_DEFAULT || hasNesting)
  166. sc.SetState(SCE_CSS_DIRECTIVE);
  167. break;
  168. case '>':
  169. case '+':
  170. if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
  171. lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
  172. sc.SetState(SCE_CSS_DEFAULT);
  173. break;
  174. case '[':
  175. if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
  176. lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
  177. sc.SetState(SCE_CSS_ATTRIBUTE);
  178. break;
  179. case ']':
  180. if (lastState == SCE_CSS_ATTRIBUTE)
  181. sc.SetState(SCE_CSS_TAG);
  182. break;
  183. case '{':
  184. nestingLevel++;
  185. switch (lastState) {
  186. case SCE_CSS_MEDIA:
  187. sc.SetState(SCE_CSS_DEFAULT);
  188. break;
  189. case SCE_CSS_TAG:
  190. case SCE_CSS_DIRECTIVE:
  191. sc.SetState(SCE_CSS_IDENTIFIER);
  192. break;
  193. }
  194. break;
  195. case '}':
  196. if (--nestingLevel < 0)
  197. nestingLevel = 0;
  198. switch (lastState) {
  199. case SCE_CSS_DEFAULT:
  200. case SCE_CSS_VALUE:
  201. case SCE_CSS_IMPORTANT:
  202. case SCE_CSS_IDENTIFIER:
  203. case SCE_CSS_IDENTIFIER2:
  204. case SCE_CSS_IDENTIFIER3:
  205. if (hasNesting)
  206. sc.SetState(nestingLevel > 0 ? SCE_CSS_IDENTIFIER : SCE_CSS_DEFAULT);
  207. else
  208. sc.SetState(SCE_CSS_DEFAULT);
  209. break;
  210. }
  211. break;
  212. case '(':
  213. if (lastState == SCE_CSS_PSEUDOCLASS)
  214. sc.SetState(SCE_CSS_TAG);
  215. else if (lastState == SCE_CSS_EXTENDED_PSEUDOCLASS)
  216. sc.SetState(SCE_CSS_EXTENDED_PSEUDOCLASS);
  217. break;
  218. case ')':
  219. if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
  220. lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS ||
  221. lastState == SCE_CSS_PSEUDOELEMENT || lastState == SCE_CSS_EXTENDED_PSEUDOELEMENT)
  222. sc.SetState(SCE_CSS_TAG);
  223. break;
  224. case ':':
  225. switch (lastState) {
  226. case SCE_CSS_TAG:
  227. case SCE_CSS_DEFAULT:
  228. case SCE_CSS_CLASS:
  229. case SCE_CSS_ID:
  230. case SCE_CSS_PSEUDOCLASS:
  231. case SCE_CSS_EXTENDED_PSEUDOCLASS:
  232. case SCE_CSS_UNKNOWN_PSEUDOCLASS:
  233. case SCE_CSS_PSEUDOELEMENT:
  234. case SCE_CSS_EXTENDED_PSEUDOELEMENT:
  235. sc.SetState(SCE_CSS_PSEUDOCLASS);
  236. break;
  237. case SCE_CSS_IDENTIFIER:
  238. case SCE_CSS_IDENTIFIER2:
  239. case SCE_CSS_IDENTIFIER3:
  240. case SCE_CSS_EXTENDED_IDENTIFIER:
  241. case SCE_CSS_UNKNOWN_IDENTIFIER:
  242. case SCE_CSS_VARIABLE:
  243. sc.SetState(SCE_CSS_VALUE);
  244. lastStateVal = lastState;
  245. break;
  246. }
  247. break;
  248. case '.':
  249. if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
  250. lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
  251. sc.SetState(SCE_CSS_CLASS);
  252. break;
  253. case '#':
  254. if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
  255. lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
  256. sc.SetState(SCE_CSS_ID);
  257. break;
  258. case ',':
  259. case '|':
  260. case '~':
  261. if (lastState == SCE_CSS_TAG)
  262. sc.SetState(SCE_CSS_DEFAULT);
  263. break;
  264. case ';':
  265. switch (lastState) {
  266. case SCE_CSS_DIRECTIVE:
  267. if (hasNesting) {
  268. sc.SetState(nestingLevel > 0 ? SCE_CSS_IDENTIFIER : SCE_CSS_DEFAULT);
  269. } else {
  270. sc.SetState(SCE_CSS_DEFAULT);
  271. }
  272. break;
  273. case SCE_CSS_VALUE:
  274. case SCE_CSS_IMPORTANT:
  275. // data URLs can have semicolons; simplistically check for wrapping parentheses and move along
  276. if (insideParentheses) {
  277. sc.SetState(lastState);
  278. } else {
  279. if (lastStateVal == SCE_CSS_VARIABLE) {
  280. sc.SetState(SCE_CSS_DEFAULT);
  281. } else {
  282. sc.SetState(SCE_CSS_IDENTIFIER);
  283. }
  284. }
  285. break;
  286. case SCE_CSS_VARIABLE:
  287. if (lastStateVar == SCE_CSS_VALUE) {
  288. // data URLs can have semicolons; simplistically check for wrapping parentheses and move along
  289. if (insideParentheses) {
  290. sc.SetState(SCE_CSS_VALUE);
  291. } else {
  292. sc.SetState(SCE_CSS_IDENTIFIER);
  293. }
  294. } else {
  295. sc.SetState(SCE_CSS_DEFAULT);
  296. }
  297. break;
  298. }
  299. break;
  300. case '!':
  301. if (lastState == SCE_CSS_VALUE)
  302. sc.SetState(SCE_CSS_IMPORTANT);
  303. break;
  304. }
  305. }
  306. if (sc.ch == '*' && sc.state == SCE_CSS_DEFAULT) {
  307. sc.SetState(SCE_CSS_TAG);
  308. continue;
  309. }
  310. // check for inside parentheses (whether part of an "operator" or not)
  311. if (sc.ch == '(')
  312. insideParentheses = true;
  313. else if (sc.ch == ')')
  314. insideParentheses = false;
  315. // SCSS special modes
  316. if (hasVariables) {
  317. // variable name
  318. if (sc.ch == varPrefix) {
  319. switch (sc.state) {
  320. case SCE_CSS_DEFAULT:
  321. if (isLessDocument) // give priority to pseudo elements
  322. break;
  323. case SCE_CSS_VALUE:
  324. lastStateVar = sc.state;
  325. sc.SetState(SCE_CSS_VARIABLE);
  326. continue;
  327. }
  328. }
  329. if (sc.state == SCE_CSS_VARIABLE) {
  330. if (IsAWordChar(sc.ch)) {
  331. // still looking at the variable name
  332. continue;
  333. }
  334. if (lastStateVar == SCE_CSS_VALUE) {
  335. // not looking at the variable name any more, and it was part of a value
  336. sc.SetState(SCE_CSS_VALUE);
  337. }
  338. }
  339. // nested rule parent selector
  340. if (sc.ch == '&') {
  341. switch (sc.state) {
  342. case SCE_CSS_DEFAULT:
  343. case SCE_CSS_IDENTIFIER:
  344. sc.SetState(SCE_CSS_TAG);
  345. continue;
  346. }
  347. }
  348. }
  349. // nesting rules that apply to SCSS and Less
  350. if (hasNesting) {
  351. // check for nested rule selector
  352. if (sc.state == SCE_CSS_IDENTIFIER && (IsAWordChar(sc.ch) || sc.ch == ':' || sc.ch == '.' || sc.ch == '#')) {
  353. // look ahead to see whether { comes before next ; and }
  354. Sci_PositionU endPos = startPos + length;
  355. int ch;
  356. for (Sci_PositionU i = sc.currentPos; i < endPos; i++) {
  357. ch = styler.SafeGetCharAt(i);
  358. if (ch == ';' || ch == '}')
  359. break;
  360. if (ch == '{') {
  361. sc.SetState(SCE_CSS_DEFAULT);
  362. continue;
  363. }
  364. }
  365. }
  366. }
  367. if (IsAWordChar(sc.ch)) {
  368. if (sc.state == SCE_CSS_DEFAULT)
  369. sc.SetState(SCE_CSS_TAG);
  370. continue;
  371. }
  372. if (IsAWordChar(sc.chPrev) && (
  373. sc.state == SCE_CSS_IDENTIFIER || sc.state == SCE_CSS_IDENTIFIER2 ||
  374. sc.state == SCE_CSS_IDENTIFIER3 || sc.state == SCE_CSS_EXTENDED_IDENTIFIER ||
  375. sc.state == SCE_CSS_UNKNOWN_IDENTIFIER ||
  376. sc.state == SCE_CSS_PSEUDOCLASS || sc.state == SCE_CSS_PSEUDOELEMENT ||
  377. sc.state == SCE_CSS_EXTENDED_PSEUDOCLASS || sc.state == SCE_CSS_EXTENDED_PSEUDOELEMENT ||
  378. sc.state == SCE_CSS_UNKNOWN_PSEUDOCLASS ||
  379. sc.state == SCE_CSS_IMPORTANT ||
  380. sc.state == SCE_CSS_DIRECTIVE
  381. )) {
  382. char s[100];
  383. sc.GetCurrentLowered(s, sizeof(s));
  384. char *s2 = s;
  385. while (*s2 && !IsAWordChar(*s2))
  386. s2++;
  387. switch (sc.state) {
  388. case SCE_CSS_IDENTIFIER:
  389. case SCE_CSS_IDENTIFIER2:
  390. case SCE_CSS_IDENTIFIER3:
  391. case SCE_CSS_EXTENDED_IDENTIFIER:
  392. case SCE_CSS_UNKNOWN_IDENTIFIER:
  393. if (css1Props.InList(s2))
  394. sc.ChangeState(SCE_CSS_IDENTIFIER);
  395. else if (css2Props.InList(s2))
  396. sc.ChangeState(SCE_CSS_IDENTIFIER2);
  397. else if (css3Props.InList(s2))
  398. sc.ChangeState(SCE_CSS_IDENTIFIER3);
  399. else if (exProps.InList(s2))
  400. sc.ChangeState(SCE_CSS_EXTENDED_IDENTIFIER);
  401. else
  402. sc.ChangeState(SCE_CSS_UNKNOWN_IDENTIFIER);
  403. break;
  404. case SCE_CSS_PSEUDOCLASS:
  405. case SCE_CSS_PSEUDOELEMENT:
  406. case SCE_CSS_EXTENDED_PSEUDOCLASS:
  407. case SCE_CSS_EXTENDED_PSEUDOELEMENT:
  408. case SCE_CSS_UNKNOWN_PSEUDOCLASS:
  409. if (op == ':' && opPrev != ':' && pseudoClasses.InList(s2))
  410. sc.ChangeState(SCE_CSS_PSEUDOCLASS);
  411. else if (opPrev == ':' && pseudoElements.InList(s2))
  412. sc.ChangeState(SCE_CSS_PSEUDOELEMENT);
  413. else if ((op == ':' || (op == '(' && lastState == SCE_CSS_EXTENDED_PSEUDOCLASS)) && opPrev != ':' && exPseudoClasses.InList(s2))
  414. sc.ChangeState(SCE_CSS_EXTENDED_PSEUDOCLASS);
  415. else if (opPrev == ':' && exPseudoElements.InList(s2))
  416. sc.ChangeState(SCE_CSS_EXTENDED_PSEUDOELEMENT);
  417. else
  418. sc.ChangeState(SCE_CSS_UNKNOWN_PSEUDOCLASS);
  419. break;
  420. case SCE_CSS_IMPORTANT:
  421. if (strcmp(s2, "important") != 0)
  422. sc.ChangeState(SCE_CSS_VALUE);
  423. break;
  424. case SCE_CSS_DIRECTIVE:
  425. if (op == '@' && strcmp(s2, "media") == 0)
  426. sc.ChangeState(SCE_CSS_MEDIA);
  427. break;
  428. }
  429. }
  430. if (sc.ch != '.' && sc.ch != ':' && sc.ch != '#' && (
  431. sc.state == SCE_CSS_CLASS || sc.state == SCE_CSS_ID ||
  432. (sc.ch != '(' && sc.ch != ')' && ( /* This line of the condition makes it possible to extend pseudo-classes with parentheses */
  433. sc.state == SCE_CSS_PSEUDOCLASS || sc.state == SCE_CSS_PSEUDOELEMENT ||
  434. sc.state == SCE_CSS_EXTENDED_PSEUDOCLASS || sc.state == SCE_CSS_EXTENDED_PSEUDOELEMENT ||
  435. sc.state == SCE_CSS_UNKNOWN_PSEUDOCLASS
  436. ))
  437. ))
  438. sc.SetState(SCE_CSS_TAG);
  439. if (sc.Match('/', '*')) {
  440. lastStateC = sc.state;
  441. comment_mode = eCommentBlock;
  442. sc.SetState(SCE_CSS_COMMENT);
  443. sc.Forward();
  444. } else if (hasSingleLineComments && sc.Match('/', '/') && !insideParentheses) {
  445. // note that we've had to treat ([...]// as the start of a URL not a comment, e.g. url(http://example.com), url(//example.com)
  446. lastStateC = sc.state;
  447. comment_mode = eCommentLine;
  448. sc.SetState(SCE_CSS_COMMENT);
  449. sc.Forward();
  450. } else if ((sc.state == SCE_CSS_VALUE || sc.state == SCE_CSS_ATTRIBUTE)
  451. && (sc.ch == '\"' || sc.ch == '\'')) {
  452. lastStateS = sc.state;
  453. sc.SetState((sc.ch == '\"' ? SCE_CSS_DOUBLESTRING : SCE_CSS_SINGLESTRING));
  454. } else if (IsCssOperator(sc.ch)
  455. && (sc.state != SCE_CSS_ATTRIBUTE || sc.ch == ']')
  456. && (sc.state != SCE_CSS_VALUE || sc.ch == ';' || sc.ch == '}' || sc.ch == '!')
  457. && ((sc.state != SCE_CSS_DIRECTIVE && sc.state != SCE_CSS_MEDIA) || sc.ch == ';' || sc.ch == '{')
  458. ) {
  459. if (sc.state != SCE_CSS_OPERATOR)
  460. lastState = sc.state;
  461. sc.SetState(SCE_CSS_OPERATOR);
  462. op = sc.ch;
  463. opPrev = sc.chPrev;
  464. }
  465. }
  466. sc.Complete();
  467. }
  468. static void FoldCSSDoc(Sci_PositionU startPos, Sci_Position length, int, WordList *[], Accessor &styler) {
  469. bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
  470. bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
  471. Sci_PositionU endPos = startPos + length;
  472. int visibleChars = 0;
  473. Sci_Position lineCurrent = styler.GetLine(startPos);
  474. int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
  475. int levelCurrent = levelPrev;
  476. char chNext = styler[startPos];
  477. bool inComment = (styler.StyleAt(startPos-1) == SCE_CSS_COMMENT);
  478. for (Sci_PositionU i = startPos; i < endPos; i++) {
  479. char ch = chNext;
  480. chNext = styler.SafeGetCharAt(i + 1);
  481. int style = styler.StyleAt(i);
  482. bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
  483. if (foldComment) {
  484. if (!inComment && (style == SCE_CSS_COMMENT))
  485. levelCurrent++;
  486. else if (inComment && (style != SCE_CSS_COMMENT))
  487. levelCurrent--;
  488. inComment = (style == SCE_CSS_COMMENT);
  489. }
  490. if (style == SCE_CSS_OPERATOR) {
  491. if (ch == '{') {
  492. levelCurrent++;
  493. } else if (ch == '}') {
  494. levelCurrent--;
  495. }
  496. }
  497. if (atEOL) {
  498. int lev = levelPrev;
  499. if (visibleChars == 0 && foldCompact)
  500. lev |= SC_FOLDLEVELWHITEFLAG;
  501. if ((levelCurrent > levelPrev) && (visibleChars > 0))
  502. lev |= SC_FOLDLEVELHEADERFLAG;
  503. if (lev != styler.LevelAt(lineCurrent)) {
  504. styler.SetLevel(lineCurrent, lev);
  505. }
  506. lineCurrent++;
  507. levelPrev = levelCurrent;
  508. visibleChars = 0;
  509. }
  510. if (!isspacechar(ch))
  511. visibleChars++;
  512. }
  513. // Fill in the real level of the next line, keeping the current flags as they will be filled in later
  514. int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
  515. styler.SetLevel(lineCurrent, levelPrev | flagsNext);
  516. }
  517. static const char * const cssWordListDesc[] = {
  518. "CSS1 Properties",
  519. "Pseudo-classes",
  520. "CSS2 Properties",
  521. "CSS3 Properties",
  522. "Pseudo-elements",
  523. "Browser-Specific CSS Properties",
  524. "Browser-Specific Pseudo-classes",
  525. "Browser-Specific Pseudo-elements",
  526. 0
  527. };
  528. LexerModule lmCss(SCLEX_CSS, ColouriseCssDoc, "css", FoldCSSDoc, cssWordListDesc);