ruby.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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. 'use strict';
  6. import IRichLanguageConfiguration = monaco.languages.LanguageConfiguration;
  7. import ILanguage = monaco.languages.IMonarchLanguage;
  8. export var conf:IRichLanguageConfiguration = {
  9. comments: {
  10. lineComment: '#',
  11. blockComment: ['=begin', '=end'],
  12. },
  13. brackets: [['(',')'],['{','}'], ['[',']']],
  14. autoClosingPairs: [
  15. { open: '"', close: '"', notIn: ['string', 'comment'] },
  16. { open: '\'', close: '\'', notIn: ['string', 'comment'] },
  17. { open: '(', close: ')', notIn: ['string', 'comment'] },
  18. { open: '{', close: '}', notIn: ['string', 'comment'] },
  19. { open: '[', close: ']', notIn: ['string', 'comment'] },
  20. ]
  21. };
  22. /*
  23. * Ruby language definition
  24. *
  25. * Quite a complex language due to elaborate escape sequences
  26. * and quoting of literate strings/regular expressions, and
  27. * an 'end' keyword that does not always apply to modifiers like until and while,
  28. * and a 'do' keyword that sometimes starts a block, but sometimes is part of
  29. * another statement (like 'while').
  30. *
  31. * (1) end blocks:
  32. * 'end' may end declarations like if or until, but sometimes 'if' or 'until'
  33. * are modifiers where there is no 'end'. Also, 'do' sometimes starts a block
  34. * that is ended by 'end', but sometimes it is part of a 'while', 'for', or 'until'
  35. * To do proper brace matching we do some elaborate state manipulation.
  36. * some examples:
  37. *
  38. * until bla do
  39. * work until tired
  40. * list.each do
  41. * something if test
  42. * end
  43. * end
  44. *
  45. * or
  46. *
  47. * if test
  48. * something (if test then x end)
  49. * bar if bla
  50. * end
  51. *
  52. * or, how about using class as a property..
  53. *
  54. * class Test
  55. * def endpoint
  56. * self.class.endpoint || routes
  57. * end
  58. * end
  59. *
  60. * (2) quoting:
  61. * there are many kinds of strings and escape sequences. But also, one can
  62. * start many string-like things as '%qx' where q specifies the kind of string
  63. * (like a command, escape expanded, regular expression, symbol etc.), and x is
  64. * some character and only another 'x' ends the sequence. Except for brackets
  65. * where the closing bracket ends the sequence.. and except for a nested bracket
  66. * inside the string like entity. Also, such strings can contain interpolated
  67. * ruby expressions again (and span multiple lines). Moreover, expanded
  68. * regular expression can also contain comments.
  69. */
  70. export var language = <ILanguage> {
  71. tokenPostfix: '.ruby',
  72. keywords: [
  73. '__LINE__', '__ENCODING__', '__FILE__', 'BEGIN', 'END', 'alias', 'and', 'begin',
  74. 'break', 'case', 'class', 'def', 'defined?', 'do', 'else', 'elsif', 'end',
  75. 'ensure', 'for', 'false', 'if', 'in', 'module', 'next', 'nil', 'not', 'or', 'redo',
  76. 'rescue', 'retry', 'return', 'self', 'super', 'then', 'true', 'undef', 'unless',
  77. 'until', 'when', 'while', 'yield',
  78. ],
  79. keywordops: [
  80. '::', '..', '...', '?', ':', '=>'
  81. ],
  82. builtins: [
  83. 'require', 'public', 'private', 'include', 'extend', 'attr_reader',
  84. 'protected', 'private_class_method', 'protected_class_method', 'new'
  85. ],
  86. // these are closed by 'end' (if, while and until are handled separately)
  87. declarations: [
  88. 'module','class','def','case','do','begin','for','if','while','until','unless'
  89. ],
  90. linedecls: [
  91. 'def','case','do','begin','for','if','while','until','unless'
  92. ],
  93. operators: [
  94. '^', '&', '|', '<=>', '==', '===', '!~', '=~', '>', '>=', '<', '<=', '<<', '>>', '+',
  95. '-', '*', '/', '%', '**', '~', '+@', '-@', '[]', '[]=', '`',
  96. '+=', '-=', '*=', '**=', '/=', '^=', '%=', '<<=', '>>=', '&=', '&&=', '||=', '|='
  97. ],
  98. brackets: [
  99. { open: '(', close: ')', token: 'delimiter.parenthesis'},
  100. { open: '{', close: '}', token: 'delimiter.curly'},
  101. { open: '[', close: ']', token: 'delimiter.square'}
  102. ],
  103. // we include these common regular expressions
  104. symbols: /[=><!~?:&|+\-*\/\^%\.]+/,
  105. // escape sequences
  106. escape: /(?:[abefnrstv\\"'\n\r]|[0-7]{1,3}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4})/,
  107. escapes: /\\(?:C\-(@escape|.)|c(@escape|.)|@escape)/,
  108. decpart: /\d(_?\d)*/,
  109. decimal: /0|@decpart/,
  110. delim: /[^a-zA-Z0-9\s\n\r]/,
  111. heredelim: /(?:\w+|'[^']*'|"[^"]*"|`[^`]*`)/,
  112. regexpctl: /[(){}\[\]\$\^|\-*+?\.]/,
  113. regexpesc: /\\(?:[AzZbBdDfnrstvwWn0\\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})?/,
  114. // The main tokenizer for our languages
  115. tokenizer: {
  116. // Main entry.
  117. // root.<decl> where decl is the current opening declaration (like 'class')
  118. root: [
  119. // identifiers and keywords
  120. // most complexity here is due to matching 'end' correctly with declarations.
  121. // We distinguish a declaration that comes first on a line, versus declarations further on a line (which are most likey modifiers)
  122. [/^(\s*)([a-z_]\w*[!?=]?)/, ['white',
  123. { cases: { 'for|until|while': { token: 'keyword.$2', next: '@dodecl.$2' },
  124. '@declarations': { token: 'keyword.$2', next: '@root.$2' },
  125. 'end': { token: 'keyword.$S2', next: '@pop' },
  126. '@keywords': 'keyword',
  127. '@builtins': 'predefined',
  128. '@default': 'identifier' } }]],
  129. [/[a-z_]\w*[!?=]?/,
  130. { cases: { 'if|unless|while|until': { token: 'keyword.$0x', next: '@modifier.$0x' },
  131. 'for': { token: 'keyword.$2', next: '@dodecl.$2' },
  132. '@linedecls': { token: 'keyword.$0', next: '@root.$0' },
  133. 'end': { token: 'keyword.$S2', next: '@pop' },
  134. '@keywords': 'keyword',
  135. '@builtins': 'predefined',
  136. '@default': 'identifier' } }],
  137. [/[A-Z][\w]*[!?=]?/, 'constructor.identifier' ], // constant
  138. [/\$[\w]*/, 'global.constant' ], // global
  139. [/@[\w]*/, 'namespace.instance.identifier' ], // instance
  140. [/@@[\w]*/, 'namespace.class.identifier' ], // class
  141. // here document
  142. [/<<-(@heredelim).*/, { token: 'string.heredoc.delimiter', next: '@heredoc.$1' } ],
  143. [/[ \t\r\n]+<<(@heredelim).*/, { token: 'string.heredoc.delimiter', next: '@heredoc.$1' } ],
  144. [/^<<(@heredelim).*/, { token: 'string.heredoc.delimiter', next: '@heredoc.$1' } ],
  145. // whitespace
  146. { include: '@whitespace' },
  147. // strings
  148. [/"/, { token: 'string.d.delim', next: '@dstring.d."'} ],
  149. [/'/, { token: 'string.sq.delim', next: '@sstring.sq' } ],
  150. // % literals. For efficiency, rematch in the 'pstring' state
  151. [/%([rsqxwW]|Q?)/, { token: '@rematch', next: 'pstring' } ],
  152. // commands and symbols
  153. [/`/, { token: 'string.x.delim', next: '@dstring.x.`' } ],
  154. [/:(\w|[$@])\w*[!?=]?/, 'string.s'],
  155. [/:"/, { token: 'string.s.delim', next: '@dstring.s."' } ],
  156. [/:'/, { token: 'string.s.delim', next: '@sstring.s' } ],
  157. // regular expressions. Lookahead for a (not escaped) closing forwardslash on the same line
  158. [/\/(?=(\\\/|[^\/\n])+\/)/, { token: 'regexp.delim', next: '@regexp' } ],
  159. // delimiters and operators
  160. [/[{}()\[\]]/, '@brackets'],
  161. [/@symbols/, { cases: { '@keywordops': 'keyword',
  162. '@operators' : 'operator',
  163. '@default' : '' } } ],
  164. [/[;,]/, 'delimiter'],
  165. // numbers
  166. [/0[xX][0-9a-fA-F](_?[0-9a-fA-F])*/, 'number.hex'],
  167. [/0[_oO][0-7](_?[0-7])*/, 'number.octal'],
  168. [/0[bB][01](_?[01])*/, 'number.binary'],
  169. [/0[dD]@decpart/, 'number'],
  170. [/@decimal((\.@decpart)?([eE][\-+]?@decpart)?)/, { cases: { '$1': 'number.float',
  171. '@default': 'number' }}],
  172. ],
  173. // used to not treat a 'do' as a block opener if it occurs on the same
  174. // line as a 'do' statement: 'while|until|for'
  175. // dodecl.<decl> where decl is the declarations started, like 'while'
  176. dodecl: [
  177. [/^/, { token: '', switchTo: '@root.$S2' }], // get out of do-skipping mode on a new line
  178. [/[a-z_]\w*[!?=]?/, { cases: { 'end': { token: 'keyword.$S2', next: '@pop' }, // end on same line
  179. 'do' : { token: 'keyword', switchTo: '@root.$S2' }, // do on same line: not an open bracket here
  180. '@linedecls': { token: '@rematch', switchTo: '@root.$S2' }, // other declaration on same line: rematch
  181. '@keywords': 'keyword',
  182. '@builtins': 'predefined',
  183. '@default': 'identifier' } }],
  184. { include: '@root' }
  185. ],
  186. // used to prevent potential modifiers ('if|until|while|unless') to match
  187. // with 'end' keywords.
  188. // modifier.<decl>x where decl is the declaration starter, like 'if'
  189. modifier: [
  190. [/^/, '', '@pop'], // it was a modifier: get out of modifier mode on a new line
  191. [/[a-z_]\w*[!?=]?/, { cases: { 'end': { token: 'keyword.$S2', next: '@pop' }, // end on same line
  192. 'then|else|elsif|do': { token: 'keyword', switchTo: '@root.$S2' }, // real declaration and not a modifier
  193. '@linedecls': { token: '@rematch', switchTo: '@root.$S2' }, // other declaration => not a modifier
  194. '@keywords': 'keyword',
  195. '@builtins': 'predefined',
  196. '@default': 'identifier' } }],
  197. { include: '@root' }
  198. ],
  199. // single quote strings (also used for symbols)
  200. // sstring.<kind> where kind is 'sq' (single quote) or 's' (symbol)
  201. sstring: [
  202. [/[^\\']+/, 'string.$S2' ],
  203. [/\\\\|\\'|\\$/, 'string.$S2.escape'],
  204. [/\\./, 'string.$S2.invalid'],
  205. [/'/, { token: 'string.$S2.delim', next: '@pop'} ]
  206. ],
  207. // double quoted "string".
  208. // dstring.<kind>.<delim> where kind is 'd' (double quoted), 'x' (command), or 's' (symbol)
  209. // and delim is the ending delimiter (" or `)
  210. dstring: [
  211. [/[^\\`"#]+/, 'string.$S2'],
  212. [/#/, 'string.$S2.escape', '@interpolated' ],
  213. [/\\$/, 'string.$S2.escape' ],
  214. [/@escapes/, 'string.$S2.escape'],
  215. [/\\./, 'string.$S2.escape.invalid'],
  216. [/[`"]/, { cases: { '$#==$S3': { token: 'string.$S2.delim', next: '@pop'},
  217. '@default': 'string.$S2' } } ]
  218. ],
  219. // literal documents
  220. // heredoc.<close> where close is the closing delimiter
  221. heredoc: [
  222. [/^(\s*)(@heredelim)$/, { cases: { '$2==$S2': ['string.heredoc', { token: 'string.heredoc.delimiter', next: '@pop' }],
  223. '@default': ['string.heredoc','string.heredoc'] }}],
  224. [/.*/, 'string.heredoc' ],
  225. ],
  226. // interpolated sequence
  227. interpolated: [
  228. [/\$\w*/, 'global.constant', '@pop' ],
  229. [/@\w*/, 'namespace.class.identifier', '@pop' ],
  230. [/@@\w*/, 'namespace.instance.identifier', '@pop' ],
  231. [/[{]/, { token: 'string.escape.curly', switchTo: '@interpolated_compound' }],
  232. ['', '', '@pop' ], // just a # is interpreted as a #
  233. ],
  234. // any code
  235. interpolated_compound: [
  236. [/[}]/, { token: 'string.escape.curly', next: '@pop'} ],
  237. { include: '@root' },
  238. ],
  239. // %r quoted regexp
  240. // pregexp.<open>.<close> where open/close are the open/close delimiter
  241. pregexp: [
  242. { include: '@whitespace' },
  243. // turns out that you can quote using regex control characters, aargh!
  244. // for example; %r|kgjgaj| is ok (even though | is used for alternation)
  245. // so, we need to match those first
  246. [/[^\(\{\[\\]/, { cases: { '$#==$S3' : { token: 'regexp.delim', next: '@pop' },
  247. '$#==$S2' : { token: 'regexp.delim', next: '@push' }, // nested delimiters are allowed..
  248. '~[)}\\]]' : '@brackets.regexp.escape.control',
  249. '~@regexpctl': 'regexp.escape.control',
  250. '@default': 'regexp' }}],
  251. { include: '@regexcontrol' },
  252. ],
  253. // We match regular expression quite precisely
  254. regexp: [
  255. { include: '@regexcontrol' },
  256. [/[^\\\/]/, 'regexp' ],
  257. ['/[ixmp]*', { token: 'regexp.delim'}, '@pop' ],
  258. ],
  259. regexcontrol: [
  260. [/(\{)(\d+(?:,\d*)?)(\})/, ['@brackets.regexp.escape.control', 'regexp.escape.control', '@brackets.regexp.escape.control'] ],
  261. [/(\[)(\^?)/, ['@brackets.regexp.escape.control',{ token: 'regexp.escape.control', next: '@regexrange'}]],
  262. [/(\()(\?[:=!])/, ['@brackets.regexp.escape.control', 'regexp.escape.control'] ],
  263. [/\(\?#/, { token: 'regexp.escape.control', next: '@regexpcomment' }],
  264. [/[()]/, '@brackets.regexp.escape.control'],
  265. [/@regexpctl/, 'regexp.escape.control'],
  266. [/\\$/, 'regexp.escape' ],
  267. [/@regexpesc/, 'regexp.escape' ],
  268. [/\\\./, 'regexp.invalid' ],
  269. [/#/, 'regexp.escape', '@interpolated' ],
  270. ],
  271. regexrange: [
  272. [/-/, 'regexp.escape.control'],
  273. [/\^/, 'regexp.invalid'],
  274. [/\\$/, 'regexp.escape' ],
  275. [/@regexpesc/, 'regexp.escape'],
  276. [/[^\]]/, 'regexp'],
  277. [/\]/, '@brackets.regexp.escape.control', '@pop'],
  278. ],
  279. regexpcomment: [
  280. [ /[^)]+/, 'comment' ],
  281. [ /\)/, { token: 'regexp.escape.control', next: '@pop' } ]
  282. ],
  283. // % quoted strings
  284. // A bit repetitive since we need to often special case the kind of ending delimiter
  285. pstring: [
  286. [/%([qws])\(/, { token: 'string.$1.delim', switchTo: '@qstring.$1.(.)' } ],
  287. [/%([qws])\[/, { token: 'string.$1.delim', switchTo: '@qstring.$1.[.]' } ],
  288. [/%([qws])\{/, { token: 'string.$1.delim', switchTo: '@qstring.$1.{.}' } ],
  289. [/%([qws])</, { token: 'string.$1.delim', switchTo: '@qstring.$1.<.>' } ],
  290. [/%([qws])(@delim)/, { token: 'string.$1.delim', switchTo: '@qstring.$1.$2.$2' } ],
  291. [/%r\(/, { token: 'regexp.delim', switchTo: '@pregexp.(.)' } ],
  292. [/%r\[/, { token: 'regexp.delim', switchTo: '@pregexp.[.]' } ],
  293. [/%r\{/, { token: 'regexp.delim', switchTo: '@pregexp.{.}' } ],
  294. [/%r</, { token: 'regexp.delim', switchTo: '@pregexp.<.>' } ],
  295. [/%r(@delim)/, { token: 'regexp.delim', switchTo: '@pregexp.$1.$1' } ],
  296. [/%(x|W|Q?)\(/, { token: 'string.$1.delim', switchTo: '@qqstring.$1.(.)' } ],
  297. [/%(x|W|Q?)\[/, { token: 'string.$1.delim', switchTo: '@qqstring.$1.[.]' } ],
  298. [/%(x|W|Q?)\{/, { token: 'string.$1.delim', switchTo: '@qqstring.$1.{.}' } ],
  299. [/%(x|W|Q?)</, { token: 'string.$1.delim', switchTo: '@qqstring.$1.<.>' } ],
  300. [/%(x|W|Q?)(@delim)/, { token: 'string.$1.delim', switchTo: '@qqstring.$1.$2.$2' } ],
  301. [/%([rqwsxW]|Q?)./, { token: 'invalid', next: '@pop' } ], // recover
  302. [/./, { token: 'invalid', next: '@pop' } ], // recover
  303. ],
  304. // non-expanded quoted string.
  305. // qstring.<kind>.<open>.<close>
  306. // kind = q|w|s (single quote, array, symbol)
  307. // open = open delimiter
  308. // close = close delimiter
  309. qstring: [
  310. [/\\$/, 'string.$S2.escape' ],
  311. [/\\./, 'string.$S2.escape' ],
  312. [/./, { cases: { '$#==$S4' : { token: 'string.$S2.delim', next: '@pop' },
  313. '$#==$S3' : { token: 'string.$S2.delim', next: '@push' }, // nested delimiters are allowed..
  314. '@default': 'string.$S2' }}],
  315. ],
  316. // expanded quoted string.
  317. // qqstring.<kind>.<open>.<close>
  318. // kind = Q|W|x (double quote, array, command)
  319. // open = open delimiter
  320. // close = close delimiter
  321. qqstring: [
  322. [/#/, 'string.$S2.escape', '@interpolated' ],
  323. { include: '@qstring' }
  324. ],
  325. // whitespace & comments
  326. whitespace: [
  327. [/[ \t\r\n]+/, ''],
  328. [/^\s*=begin\b/, 'comment', '@comment' ],
  329. [/#.*$/, 'comment'],
  330. ],
  331. comment: [
  332. [/[^=]+/, 'comment' ],
  333. [/^\s*=begin\b/, 'comment.invalid' ], // nested comment
  334. [/^\s*=end\b.*/, 'comment', '@pop' ],
  335. [/[=]/, 'comment' ]
  336. ],
  337. }
  338. };