elixir.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import type { languages } from '../../fillers/monaco-editor-core';
  6. export const conf: languages.LanguageConfiguration = {
  7. comments: {
  8. lineComment: '#'
  9. },
  10. brackets: [
  11. ['{', '}'],
  12. ['[', ']'],
  13. ['(', ')']
  14. ],
  15. surroundingPairs: [
  16. { open: '{', close: '}' },
  17. { open: '[', close: ']' },
  18. { open: '(', close: ')' },
  19. { open: "'", close: "'" },
  20. { open: '"', close: '"' }
  21. ],
  22. autoClosingPairs: [
  23. { open: "'", close: "'", notIn: ['string', 'comment'] },
  24. { open: '"', close: '"', notIn: ['comment'] },
  25. { open: '"""', close: '"""' },
  26. { open: '`', close: '`', notIn: ['string', 'comment'] },
  27. { open: '(', close: ')' },
  28. { open: '{', close: '}' },
  29. { open: '[', close: ']' },
  30. { open: '<<', close: '>>' }
  31. ],
  32. indentationRules: {
  33. increaseIndentPattern: /^\s*(after|else|catch|rescue|fn|[^#]*(do|<\-|\->|\{|\[|\=))\s*$/,
  34. decreaseIndentPattern: /^\s*((\}|\])\s*$|(after|else|catch|rescue|end)\b)/
  35. }
  36. };
  37. /**
  38. * A Monarch lexer for the Elixir language.
  39. *
  40. * References:
  41. *
  42. * * Monarch documentation - https://microsoft.github.io/monaco-editor/monarch.html
  43. * * Elixir lexer - https://github.com/elixir-makeup/makeup_elixir/blob/master/lib/makeup/lexers/elixir_lexer.ex
  44. * * TextMate lexer (elixir-tmbundle) - https://github.com/elixir-editors/elixir-tmbundle/blob/master/Syntaxes/Elixir.tmLanguage
  45. * * TextMate lexer (vscode-elixir-ls) - https://github.com/elixir-lsp/vscode-elixir-ls/blob/master/syntaxes/elixir.json
  46. */
  47. export const language = <languages.IMonarchLanguage>{
  48. defaultToken: 'source',
  49. tokenPostfix: '.elixir',
  50. brackets: [
  51. { open: '[', close: ']', token: 'delimiter.square' },
  52. { open: '(', close: ')', token: 'delimiter.parenthesis' },
  53. { open: '{', close: '}', token: 'delimiter.curly' },
  54. { open: '<<', close: '>>', token: 'delimiter.angle.special' }
  55. ],
  56. // Below are lists/regexps to which we reference later.
  57. declarationKeywords: [
  58. 'def',
  59. 'defp',
  60. 'defn',
  61. 'defnp',
  62. 'defguard',
  63. 'defguardp',
  64. 'defmacro',
  65. 'defmacrop',
  66. 'defdelegate',
  67. 'defcallback',
  68. 'defmacrocallback',
  69. 'defmodule',
  70. 'defprotocol',
  71. 'defexception',
  72. 'defimpl',
  73. 'defstruct'
  74. ],
  75. operatorKeywords: ['and', 'in', 'not', 'or', 'when'],
  76. namespaceKeywords: ['alias', 'import', 'require', 'use'],
  77. otherKeywords: [
  78. 'after',
  79. 'case',
  80. 'catch',
  81. 'cond',
  82. 'do',
  83. 'else',
  84. 'end',
  85. 'fn',
  86. 'for',
  87. 'if',
  88. 'quote',
  89. 'raise',
  90. 'receive',
  91. 'rescue',
  92. 'super',
  93. 'throw',
  94. 'try',
  95. 'unless',
  96. 'unquote_splicing',
  97. 'unquote',
  98. 'with'
  99. ],
  100. constants: ['true', 'false', 'nil'],
  101. nameBuiltin: ['__MODULE__', '__DIR__', '__ENV__', '__CALLER__', '__STACKTRACE__'],
  102. // Matches any of the operator names:
  103. // <<< >>> ||| &&& ^^^ ~~~ === !== ~>> <~> |~> <|> == != <= >= && || \\ <> ++ -- |> =~ -> <- ~> <~ :: .. = < > + - * / | . ^ & !
  104. operator:
  105. /-[->]?|!={0,2}|\*{1,2}|\/|\\\\|&{1,3}|\.\.?|\^(?:\^\^)?|\+\+?|<(?:-|<<|=|>|\|>|~>?)?|=~|={1,3}|>(?:=|>>)?|\|~>|\|>|\|{1,3}|~>>?|~~~|::/,
  106. // See https://hexdocs.pm/elixir/syntax-reference.html#variables
  107. variableName: /[a-z_][a-zA-Z0-9_]*[?!]?/,
  108. // See https://hexdocs.pm/elixir/syntax-reference.html#atoms
  109. atomName: /[a-zA-Z_][a-zA-Z0-9_@]*[?!]?|@specialAtomName|@operator/,
  110. specialAtomName: /\.\.\.|<<>>|%\{\}|%|\{\}/,
  111. aliasPart: /[A-Z][a-zA-Z0-9_]*/,
  112. moduleName: /@aliasPart(?:\.@aliasPart)*/,
  113. // Sigil pairs are: """ """, ''' ''', " ", ' ', / /, | |, < >, { }, [ ], ( )
  114. sigilSymmetricDelimiter: /"""|'''|"|'|\/|\|/,
  115. sigilStartDelimiter: /@sigilSymmetricDelimiter|<|\{|\[|\(/,
  116. sigilEndDelimiter: /@sigilSymmetricDelimiter|>|\}|\]|\)/,
  117. sigilModifiers: /[a-zA-Z0-9]*/,
  118. decimal: /\d(?:_?\d)*/,
  119. hex: /[0-9a-fA-F](_?[0-9a-fA-F])*/,
  120. octal: /[0-7](_?[0-7])*/,
  121. binary: /[01](_?[01])*/,
  122. // See https://hexdocs.pm/elixir/master/String.html#module-escape-characters
  123. escape: /\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2}|\\./,
  124. // The keys below correspond to tokenizer states.
  125. // We start from the root state and match against its rules
  126. // until we explicitly transition into another state.
  127. // The `include` simply brings in all operations from the given state
  128. // and is useful for improving readability.
  129. tokenizer: {
  130. root: [
  131. { include: '@whitespace' },
  132. { include: '@comments' },
  133. // Keywords start as either an identifier or a string,
  134. // but end with a : so it's important to match this first.
  135. { include: '@keywordsShorthand' },
  136. { include: '@numbers' },
  137. { include: '@identifiers' },
  138. { include: '@strings' },
  139. { include: '@atoms' },
  140. { include: '@sigils' },
  141. { include: '@attributes' },
  142. { include: '@symbols' }
  143. ],
  144. // Whitespace
  145. whitespace: [[/\s+/, 'white']],
  146. // Comments
  147. comments: [[/(#)(.*)/, ['comment.punctuation', 'comment']]],
  148. // Keyword list shorthand
  149. keywordsShorthand: [
  150. [/(@atomName)(:)(\s+)/, ['constant', 'constant.punctuation', 'white']],
  151. // Use positive look-ahead to ensure the string is followed by :
  152. // and should be considered a keyword.
  153. [
  154. /"(?=([^"]|#\{.*?\}|\\")*":)/,
  155. { token: 'constant.delimiter', next: '@doubleQuotedStringKeyword' }
  156. ],
  157. [
  158. /'(?=([^']|#\{.*?\}|\\')*':)/,
  159. { token: 'constant.delimiter', next: '@singleQuotedStringKeyword' }
  160. ]
  161. ],
  162. doubleQuotedStringKeyword: [
  163. [/":/, { token: 'constant.delimiter', next: '@pop' }],
  164. { include: '@stringConstantContentInterpol' }
  165. ],
  166. singleQuotedStringKeyword: [
  167. [/':/, { token: 'constant.delimiter', next: '@pop' }],
  168. { include: '@stringConstantContentInterpol' }
  169. ],
  170. // Numbers
  171. numbers: [
  172. [/0b@binary/, 'number.binary'],
  173. [/0o@octal/, 'number.octal'],
  174. [/0x@hex/, 'number.hex'],
  175. [/@decimal\.@decimal([eE]-?@decimal)?/, 'number.float'],
  176. [/@decimal/, 'number']
  177. ],
  178. // Identifiers
  179. identifiers: [
  180. // Tokenize identifier name in function-like definitions.
  181. // Note: given `def a + b, do: nil`, `a` is not a function name,
  182. // so we use negative look-ahead to ensure there's no operator.
  183. [
  184. /\b(defp?|defnp?|defmacrop?|defguardp?|defdelegate)(\s+)(@variableName)(?!\s+@operator)/,
  185. [
  186. 'keyword.declaration',
  187. 'white',
  188. {
  189. cases: {
  190. unquote: 'keyword',
  191. '@default': 'function'
  192. }
  193. }
  194. ]
  195. ],
  196. // Tokenize function calls
  197. [
  198. // In-scope call - an identifier followed by ( or .(
  199. /(@variableName)(?=\s*\.?\s*\()/,
  200. {
  201. cases: {
  202. // Tokenize as keyword in cases like `if(..., do: ..., else: ...)`
  203. '@declarationKeywords': 'keyword.declaration',
  204. '@namespaceKeywords': 'keyword',
  205. '@otherKeywords': 'keyword',
  206. '@default': 'function.call'
  207. }
  208. }
  209. ],
  210. [
  211. // Referencing function in a module
  212. /(@moduleName)(\s*)(\.)(\s*)(@variableName)/,
  213. ['type.identifier', 'white', 'operator', 'white', 'function.call']
  214. ],
  215. [
  216. // Referencing function in an Erlang module
  217. /(:)(@atomName)(\s*)(\.)(\s*)(@variableName)/,
  218. ['constant.punctuation', 'constant', 'white', 'operator', 'white', 'function.call']
  219. ],
  220. [
  221. // Piping into a function (tokenized separately as it may not have parentheses)
  222. /(\|>)(\s*)(@variableName)/,
  223. [
  224. 'operator',
  225. 'white',
  226. {
  227. cases: {
  228. '@otherKeywords': 'keyword',
  229. '@default': 'function.call'
  230. }
  231. }
  232. ]
  233. ],
  234. [
  235. // Function reference passed to another function
  236. /(&)(\s*)(@variableName)/,
  237. ['operator', 'white', 'function.call']
  238. ],
  239. // Language keywords, builtins, constants and variables
  240. [
  241. /@variableName/,
  242. {
  243. cases: {
  244. '@declarationKeywords': 'keyword.declaration',
  245. '@operatorKeywords': 'keyword.operator',
  246. '@namespaceKeywords': 'keyword',
  247. '@otherKeywords': 'keyword',
  248. '@constants': 'constant.language',
  249. '@nameBuiltin': 'variable.language',
  250. '_.*': 'comment.unused',
  251. '@default': 'identifier'
  252. }
  253. }
  254. ],
  255. // Module names
  256. [/@moduleName/, 'type.identifier']
  257. ],
  258. // Strings
  259. strings: [
  260. [/"""/, { token: 'string.delimiter', next: '@doubleQuotedHeredoc' }],
  261. [/'''/, { token: 'string.delimiter', next: '@singleQuotedHeredoc' }],
  262. [/"/, { token: 'string.delimiter', next: '@doubleQuotedString' }],
  263. [/'/, { token: 'string.delimiter', next: '@singleQuotedString' }]
  264. ],
  265. doubleQuotedHeredoc: [
  266. [/"""/, { token: 'string.delimiter', next: '@pop' }],
  267. { include: '@stringContentInterpol' }
  268. ],
  269. singleQuotedHeredoc: [
  270. [/'''/, { token: 'string.delimiter', next: '@pop' }],
  271. { include: '@stringContentInterpol' }
  272. ],
  273. doubleQuotedString: [
  274. [/"/, { token: 'string.delimiter', next: '@pop' }],
  275. { include: '@stringContentInterpol' }
  276. ],
  277. singleQuotedString: [
  278. [/'/, { token: 'string.delimiter', next: '@pop' }],
  279. { include: '@stringContentInterpol' }
  280. ],
  281. // Atoms
  282. atoms: [
  283. [/(:)(@atomName)/, ['constant.punctuation', 'constant']],
  284. [/:"/, { token: 'constant.delimiter', next: '@doubleQuotedStringAtom' }],
  285. [/:'/, { token: 'constant.delimiter', next: '@singleQuotedStringAtom' }]
  286. ],
  287. doubleQuotedStringAtom: [
  288. [/"/, { token: 'constant.delimiter', next: '@pop' }],
  289. { include: '@stringConstantContentInterpol' }
  290. ],
  291. singleQuotedStringAtom: [
  292. [/'/, { token: 'constant.delimiter', next: '@pop' }],
  293. { include: '@stringConstantContentInterpol' }
  294. ],
  295. // Sigils
  296. // See https://elixir-lang.org/getting-started/sigils.html
  297. // Sigils allow for typing values using their textual representation.
  298. // All sigils start with ~ followed by a letter indicating sigil type
  299. // and then a delimiter pair enclosing the textual representation.
  300. // Optional modifiers are allowed after the closing delimiter.
  301. // For instance a regular expressions can be written as:
  302. // ~r/foo|bar/ ~r{foo|bar} ~r/foo|bar/g
  303. //
  304. // In general lowercase sigils allow for interpolation
  305. // and escaped characters, whereas uppercase sigils don't
  306. //
  307. // During tokenization we want to distinguish some
  308. // specific sigil types, namely string and regexp,
  309. // so that they cen be themed separately.
  310. //
  311. // To reasonably handle all those combinations we leverage
  312. // dot-separated states, so if we transition to @sigilStart.interpol.s.{.}
  313. // then "sigilStart.interpol.s" state will match and also all
  314. // the individual dot-separated parameters can be accessed.
  315. sigils: [
  316. [/~[a-z]@sigilStartDelimiter/, { token: '@rematch', next: '@sigil.interpol' }],
  317. [/~[A-Z]@sigilStartDelimiter/, { token: '@rematch', next: '@sigil.noInterpol' }]
  318. ],
  319. sigil: [
  320. [/~([a-zA-Z])\{/, { token: '@rematch', switchTo: '@sigilStart.$S2.$1.{.}' }],
  321. [/~([a-zA-Z])\[/, { token: '@rematch', switchTo: '@sigilStart.$S2.$1.[.]' }],
  322. [/~([a-zA-Z])\(/, { token: '@rematch', switchTo: '@sigilStart.$S2.$1.(.)' }],
  323. [/~([a-zA-Z])\</, { token: '@rematch', switchTo: '@sigilStart.$S2.$1.<.>' }],
  324. [
  325. /~([a-zA-Z])(@sigilSymmetricDelimiter)/,
  326. { token: '@rematch', switchTo: '@sigilStart.$S2.$1.$2.$2' }
  327. ]
  328. ],
  329. // The definitions below expect states to be of the form:
  330. //
  331. // sigilStart.<interpol-or-noInterpol>.<sigil-letter>.<start-delimiter>.<end-delimiter>
  332. // sigilContinue.<interpol-or-noInterpol>.<sigil-letter>.<start-delimiter>.<end-delimiter>
  333. //
  334. // The sigilStart state is used only to properly classify the token (as string/regex/sigil)
  335. // and immediately switches to the sigilContinue sate, which handles the actual content
  336. // and waits for the corresponding end delimiter.
  337. 'sigilStart.interpol.s': [
  338. [
  339. /~s@sigilStartDelimiter/,
  340. {
  341. token: 'string.delimiter',
  342. switchTo: '@sigilContinue.$S2.$S3.$S4.$S5'
  343. }
  344. ]
  345. ],
  346. 'sigilContinue.interpol.s': [
  347. [
  348. /(@sigilEndDelimiter)@sigilModifiers/,
  349. {
  350. cases: {
  351. '$1==$S5': { token: 'string.delimiter', next: '@pop' },
  352. '@default': 'string'
  353. }
  354. }
  355. ],
  356. { include: '@stringContentInterpol' }
  357. ],
  358. 'sigilStart.noInterpol.S': [
  359. [
  360. /~S@sigilStartDelimiter/,
  361. {
  362. token: 'string.delimiter',
  363. switchTo: '@sigilContinue.$S2.$S3.$S4.$S5'
  364. }
  365. ]
  366. ],
  367. 'sigilContinue.noInterpol.S': [
  368. // Ignore escaped sigil end
  369. [/(^|[^\\])\\@sigilEndDelimiter/, 'string'],
  370. [
  371. /(@sigilEndDelimiter)@sigilModifiers/,
  372. {
  373. cases: {
  374. '$1==$S5': { token: 'string.delimiter', next: '@pop' },
  375. '@default': 'string'
  376. }
  377. }
  378. ],
  379. { include: '@stringContent' }
  380. ],
  381. 'sigilStart.interpol.r': [
  382. [
  383. /~r@sigilStartDelimiter/,
  384. {
  385. token: 'regexp.delimiter',
  386. switchTo: '@sigilContinue.$S2.$S3.$S4.$S5'
  387. }
  388. ]
  389. ],
  390. 'sigilContinue.interpol.r': [
  391. [
  392. /(@sigilEndDelimiter)@sigilModifiers/,
  393. {
  394. cases: {
  395. '$1==$S5': { token: 'regexp.delimiter', next: '@pop' },
  396. '@default': 'regexp'
  397. }
  398. }
  399. ],
  400. { include: '@regexpContentInterpol' }
  401. ],
  402. 'sigilStart.noInterpol.R': [
  403. [
  404. /~R@sigilStartDelimiter/,
  405. {
  406. token: 'regexp.delimiter',
  407. switchTo: '@sigilContinue.$S2.$S3.$S4.$S5'
  408. }
  409. ]
  410. ],
  411. 'sigilContinue.noInterpol.R': [
  412. // Ignore escaped sigil end
  413. [/(^|[^\\])\\@sigilEndDelimiter/, 'regexp'],
  414. [
  415. /(@sigilEndDelimiter)@sigilModifiers/,
  416. {
  417. cases: {
  418. '$1==$S5': { token: 'regexp.delimiter', next: '@pop' },
  419. '@default': 'regexp'
  420. }
  421. }
  422. ],
  423. { include: '@regexpContent' }
  424. ],
  425. // Fallback to the generic sigil by default
  426. 'sigilStart.interpol': [
  427. [
  428. /~([a-zA-Z])@sigilStartDelimiter/,
  429. {
  430. token: 'sigil.delimiter',
  431. switchTo: '@sigilContinue.$S2.$S3.$S4.$S5'
  432. }
  433. ]
  434. ],
  435. 'sigilContinue.interpol': [
  436. [
  437. /(@sigilEndDelimiter)@sigilModifiers/,
  438. {
  439. cases: {
  440. '$1==$S5': { token: 'sigil.delimiter', next: '@pop' },
  441. '@default': 'sigil'
  442. }
  443. }
  444. ],
  445. { include: '@sigilContentInterpol' }
  446. ],
  447. 'sigilStart.noInterpol': [
  448. [
  449. /~([a-zA-Z])@sigilStartDelimiter/,
  450. {
  451. token: 'sigil.delimiter',
  452. switchTo: '@sigilContinue.$S2.$S3.$S4.$S5'
  453. }
  454. ]
  455. ],
  456. 'sigilContinue.noInterpol': [
  457. // Ignore escaped sigil end
  458. [/(^|[^\\])\\@sigilEndDelimiter/, 'sigil'],
  459. [
  460. /(@sigilEndDelimiter)@sigilModifiers/,
  461. {
  462. cases: {
  463. '$1==$S5': { token: 'sigil.delimiter', next: '@pop' },
  464. '@default': 'sigil'
  465. }
  466. }
  467. ],
  468. { include: '@sigilContent' }
  469. ],
  470. // Attributes
  471. attributes: [
  472. // Module @doc* attributes - tokenized as comments
  473. [
  474. /\@(module|type)?doc (~[sS])?"""/,
  475. {
  476. token: 'comment.block.documentation',
  477. next: '@doubleQuotedHeredocDocstring'
  478. }
  479. ],
  480. [
  481. /\@(module|type)?doc (~[sS])?'''/,
  482. {
  483. token: 'comment.block.documentation',
  484. next: '@singleQuotedHeredocDocstring'
  485. }
  486. ],
  487. [
  488. /\@(module|type)?doc (~[sS])?"/,
  489. {
  490. token: 'comment.block.documentation',
  491. next: '@doubleQuotedStringDocstring'
  492. }
  493. ],
  494. [
  495. /\@(module|type)?doc (~[sS])?'/,
  496. {
  497. token: 'comment.block.documentation',
  498. next: '@singleQuotedStringDocstring'
  499. }
  500. ],
  501. [/\@(module|type)?doc false/, 'comment.block.documentation'],
  502. // Module attributes
  503. [/\@(@variableName)/, 'variable']
  504. ],
  505. doubleQuotedHeredocDocstring: [
  506. [/"""/, { token: 'comment.block.documentation', next: '@pop' }],
  507. { include: '@docstringContent' }
  508. ],
  509. singleQuotedHeredocDocstring: [
  510. [/'''/, { token: 'comment.block.documentation', next: '@pop' }],
  511. { include: '@docstringContent' }
  512. ],
  513. doubleQuotedStringDocstring: [
  514. [/"/, { token: 'comment.block.documentation', next: '@pop' }],
  515. { include: '@docstringContent' }
  516. ],
  517. singleQuotedStringDocstring: [
  518. [/'/, { token: 'comment.block.documentation', next: '@pop' }],
  519. { include: '@docstringContent' }
  520. ],
  521. // Operators, punctuation, brackets
  522. symbols: [
  523. // Code point operator (either with regular character ?a or an escaped one ?\n)
  524. [/\?(\\.|[^\\\s])/, 'number.constant'],
  525. // Anonymous function arguments
  526. [/&\d+/, 'operator'],
  527. // Bitshift operators (must go before delimiters, so that << >> don't match first)
  528. [/<<<|>>>/, 'operator'],
  529. // Delimiter pairs
  530. [/[()\[\]\{\}]|<<|>>/, '@brackets'],
  531. // Triple dot is a valid name (must go before operators, so that .. doesn't match instead)
  532. [/\.\.\./, 'identifier'],
  533. // Punctuation => (must go before operators, so it's not tokenized as = then >)
  534. [/=>/, 'punctuation'],
  535. // Operators
  536. [/@operator/, 'operator'],
  537. // Punctuation
  538. [/[:;,.%]/, 'punctuation']
  539. ],
  540. // Generic helpers
  541. stringContentInterpol: [
  542. { include: '@interpolation' },
  543. { include: '@escapeChar' },
  544. { include: '@stringContent' }
  545. ],
  546. stringContent: [[/./, 'string']],
  547. stringConstantContentInterpol: [
  548. { include: '@interpolation' },
  549. { include: '@escapeChar' },
  550. { include: '@stringConstantContent' }
  551. ],
  552. stringConstantContent: [[/./, 'constant']],
  553. regexpContentInterpol: [
  554. { include: '@interpolation' },
  555. { include: '@escapeChar' },
  556. { include: '@regexpContent' }
  557. ],
  558. regexpContent: [
  559. // # may be a regular regexp char, so we use a heuristic
  560. // assuming a # surrounded by whitespace is actually a comment.
  561. [/(\s)(#)(\s.*)$/, ['white', 'comment.punctuation', 'comment']],
  562. [/./, 'regexp']
  563. ],
  564. sigilContentInterpol: [
  565. { include: '@interpolation' },
  566. { include: '@escapeChar' },
  567. { include: '@sigilContent' }
  568. ],
  569. sigilContent: [[/./, 'sigil']],
  570. docstringContent: [[/./, 'comment.block.documentation']],
  571. escapeChar: [[/@escape/, 'constant.character.escape']],
  572. interpolation: [[/#{/, { token: 'delimiter.bracket.embed', next: '@interpolationContinue' }]],
  573. interpolationContinue: [
  574. [/}/, { token: 'delimiter.bracket.embed', next: '@pop' }],
  575. // Interpolation brackets may contain arbitrary code,
  576. // so we simply match against all the root rules,
  577. // until we reach interpolation end (the above matches).
  578. { include: '@root' }
  579. ]
  580. }
  581. };