├── .gitignore ├── CHANGELOG.md ├── README.md ├── grammars ├── comments.cson └── elm.cson ├── lib ├── elmOracle.js ├── main.js └── provider.js ├── package.json ├── screenshot.png ├── settings └── language-elm.cson └── styles └── autocomplete-plus.less /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.0 - First Release 2 | * Every feature added 3 | * Every bug fixed 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # language-elm package 2 | 3 | Syntax highlighting and autocomplete for the [Elm language](http://elm-lang.org/). 4 | 5 | ## Installation 6 | 7 | There are two ways to install the Atom Elm Extension: using the editor, or using the command line. 8 | 9 | ### Install using the editor (Recommended) 10 | 11 | You can install packages within Atom. To do so: 12 | 13 | 1. Open the editor settings 14 | 2. Click the "Install" menu item 15 | 3. Search for "language-elm" 16 | 4. Click the install button on the "language-elm" result 17 | 18 | [More documentation for how to use the package manager](https://atom.io/docs/v1.5.3/using-atom-atom-packages) can be found on the official Atom site. 19 | 20 | #### Screenshot 21 | 22 | ![Screenshot of installation](https://raw.githubusercontent.com/edubkendo/atom-elm/better-instructions/screenshot.png) 23 | 24 | ### Install using the command line 25 | 26 | Run the following command in your terminal: 27 | 28 | ```apm install language-elm``` 29 | 30 | ## Autocomplete 31 | 32 | In order to get autocomplete working, please: 33 | 34 | 1. Open up a terminal 35 | 2. `npm install -g elm-oracle` 36 | 3. `which elm-oracle` on Unix/Linux or `where.exe elm-oracle` on Windows 37 | 4. Copy the path to elm-oracle 38 | 5. Open up the language-elm settings in Atom 39 | - Open up the Atom Settings / Preferences tab 40 | - Click on "Packages" 41 | - Find the "language-elm" package in the list 42 | - Click "Settings" 43 | 6. Paste the path into "The elm-oracle executable path" setting 44 | 45 | Thanks to the authors of [Elm Oracle](https://github.com/ElmCast/elm-oracle) 46 | 47 | ## Jump to Symbol 48 | 49 | Atom's native symbol-view package uses ctags, and thus doesn't support Elm without extra configuration. But this plugin https://atom.io/packages/goto uses langauge definition files to identify symbols, and does support Elm as long as atom-elm is installed. 50 | -------------------------------------------------------------------------------- /grammars/comments.cson: -------------------------------------------------------------------------------- 1 | 'name': 'Comments' 2 | 'scopeName': 'source.elm' 3 | 'settings': 4 | 'shellVariables': [ 5 | { 6 | 'name': 'TM_COMMENT_START_2' 7 | 'value': '{-' 8 | } 9 | { 10 | 'name': 'TM_COMMENT_END_2' 11 | 'value': '-}' 12 | } 13 | { 14 | 'name': 'TM_COMMENT_START' 15 | 'value': '--' 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /grammars/elm.cson: -------------------------------------------------------------------------------- 1 | 'fileTypes': [ 2 | 'elm' 3 | ] 4 | 'name': 'Elm' 5 | 'patterns': [ 6 | { 7 | 'captures': 8 | '1': 9 | 'name': 'punctuation.definition.entity.elm' 10 | '2': 11 | 'name': 'punctuation.definition.entity.elm' 12 | 'match': '(`)[a-zA-Z_\']*?(`)' 13 | 'name': 'keyword.operator.function.infix.elm' 14 | } 15 | { 16 | 'match': '\\(\\)' 17 | 'name': 'constant.language.unit.elm' 18 | } 19 | { 20 | 'match': '\\[\\]' 21 | 'name': 'constant.language.empty-list.elm' 22 | } 23 | { 24 | 'begin': '^\\b((effect|port)\\s+)?(module)\\s+' 25 | 'beginCaptures': 26 | '1': 27 | 'name': 'keyword.other.elm' 28 | '3': 29 | 'name': 'keyword.other.elm' 30 | 'end': '$|;' 31 | 'name': 'meta.declaration.module.elm' 32 | 'patterns': [ 33 | { 34 | 'include': '#module_name' 35 | } 36 | { 37 | 'begin': '(where)\\s*\\{' 38 | 'beginCaptures': 39 | '1': 40 | 'name': 'keyword.other.elm' 41 | 'end': '\\}' 42 | 'patterns': [ 43 | { 44 | 'include': '#type_signature' 45 | } 46 | ] 47 | } 48 | { 49 | 'match': '(exposing)' 50 | 'name': 'keyword.other.elm' 51 | } 52 | { 53 | 'include': '#module_exports' 54 | } 55 | { 56 | 'match': '(where)' 57 | 'name': 'keyword.other.elm' 58 | } 59 | { 60 | 'match': '[a-z]+' 61 | 'name': 'invalid' 62 | } 63 | ] 64 | } 65 | { 66 | 'begin': '^\\b(import)\\s+((open)\\s+)?' 67 | 'beginCaptures': 68 | '1': 69 | 'name': 'keyword.other.elm' 70 | '3': 71 | 'name': 'invalid' 72 | 'end': '($|;)' 73 | 'name': 'meta.import.elm' 74 | 'patterns': [ 75 | { 76 | 'match': '(as|exposing)' 77 | 'name': 'keyword.import.elm' 78 | } 79 | { 80 | 'match': '\\(\\.\\.\\)' 81 | 'name': 'keyword.import.elm' 82 | } 83 | { 84 | 'include': '#module_name' 85 | } 86 | { 87 | 'include': '#module_exports' 88 | } 89 | ] 90 | } 91 | { 92 | 'begin': '(\\[)(markdown)(\\|)' 93 | 'beginCaptures': 94 | '1': 95 | 'name': 'keyword.other.elm' 96 | '2': 97 | 'name': 'support.function.prelude.elm' 98 | '3': 99 | 'name': 'keyword.other.elm' 100 | 'end': '(\\|\\])' 101 | 'endCaptures': 102 | '1': 103 | 'name': 'keyword.other.elm' 104 | 'name': 'entity.markdown.elm' 105 | 'patterns': [ 106 | { 107 | 'include': 'text.html.basic' 108 | } 109 | ] 110 | } 111 | { 112 | 'match': '\\b(type|case|of|let|in|as|port|exposing|alias|infixl|infixr|infix|hiding|export|foreign|perform|deriving)\\s+' 113 | 'name': 'keyword.other.elm' 114 | } 115 | { 116 | 'match': '\\b(if|then|else)\\s+' 117 | 'name': 'keyword.control.elm' 118 | } 119 | { 120 | 'comment': 'Floats are always decimal' 121 | 'match': '\\b([0-9]+\\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+)\\b' 122 | 'name': 'constant.numeric.float.elm' 123 | } 124 | { 125 | 'match': '\\b([0-9]+)\\b' 126 | 'name': 'constant.numeric.elm' 127 | } 128 | { 129 | 'include': '#std_library' 130 | } 131 | { 132 | 'begin': '"""' 133 | 'beginCaptures': 134 | '0': 135 | 'name': 'punctuation.definition.string.begin.elm' 136 | 'end': '"""' 137 | 'endCaptures': 138 | '0': 139 | 'name': 'punctuation.definition.string.end.elm' 140 | 'name': 'string.quoted.triple.elm' 141 | 'patterns': [ 142 | { 143 | 'match': '\\\\(NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL|[abfnrtv\\\\\\"\'\\&]|x[0-9a-fA-F]{1,5})' 144 | 'name': 'constant.character.escape.elm' 145 | } 146 | { 147 | 'match': '\\^[A-Z@\\[\\]\\\\\\^_]' 148 | 'name': 'constant.character.escape.control.elm' 149 | } 150 | ] 151 | } 152 | { 153 | 'begin': '"' 154 | 'beginCaptures': 155 | '0': 156 | 'name': 'punctuation.definition.string.begin.elm' 157 | 'end': '"' 158 | 'endCaptures': 159 | '0': 160 | 'name': 'punctuation.definition.string.end.elm' 161 | 'name': 'string.quoted.double.elm' 162 | 'patterns': [ 163 | { 164 | 'match': '\\\\(NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL|[abfnrtv\\\\\\"\'\\&]|x[0-9a-fA-F]{1,5})' 165 | 'name': 'constant.character.escape.elm' 166 | } 167 | { 168 | 'match': '\\^[A-Z@\\[\\]\\\\\\^_]' 169 | 'name': 'constant.character.escape.control.elm' 170 | } 171 | ] 172 | } 173 | { 174 | 'captures': 175 | '1': 176 | 'name': 'punctuation.definition.string.begin.elm' 177 | '2': 178 | 'name': 'constant.character.escape.elm' 179 | '3': 180 | 'name': 'punctuation.definition.string.end.elm' 181 | 'match': '(?x)\n\t\t\t(\')\n\t\t\t(?:\n\t\t\t\t[\\ -\\[\\]-~]\t\t\t\t\t\t\t\t# Basic Char\n\t\t\t | (\\\\(?:NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE\n\t\t\t\t\t|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS\n\t\t\t\t\t|US|SP|DEL|[abfnrtv\\\\\\"\'\\&]|x[0-9a-fA-F]{1,5}))\t\t# Escapes\n\t\t\t | (\\^[A-Z@\\[\\]\\\\\\^_])\t\t\t\t\t\t# Control Chars\n\t\t\t)\n\t\t\t(\')\n\t\t\t' 182 | 'name': 'string.quoted.single.elm' 183 | } 184 | { 185 | 'begin': '^(port\\s+)?([a-z_][a-zA-Z0-9_\']*|\\([|!%$+\\-.,=]+\\))\\s*((:)([:]+)?)' 186 | 'beginCaptures': 187 | '1': 188 | 'name': 'keyword.other.port.elm' 189 | '2': 190 | 'name': 'entity.name.function.elm' 191 | '4': 192 | 'name': 'keyword.other.colon.elm' 193 | '5': 194 | 'name': 'invalid' 195 | 'end': '$\\n?' 196 | 'name': 'meta.function.type-declaration.elm' 197 | 'patterns': [ 198 | { 199 | 'include': '#type_signature' 200 | } 201 | ] 202 | } 203 | { 204 | 'match': '\\b(\\(\\)|\\[\\]|True|False|Int|Char|Bool|String|LT|EQ|GT|Color|Linear|Radial|Just|Nothing|Signal|Under|Over|Through|Text|FPath|FShape|FImage|FElement|FGroup|Solid|Texture|Grad|Flat|Round|Padded|Smooth|Sharp|Clipped|DUp|DDown|DLeft|DRight|DIn|DOut|Image|Container|Flow|Spacer|RawHtml|Custom|Plain|Fitted|Cropped|Tiled|Absolute|Relative|P|Z|N)\\b' 205 | 'name': 'support.constant.elm' 206 | } 207 | { 208 | 'match': '\\bport\\s+' 209 | 'name': 'keyword.other.port.elm' 210 | } 211 | { 212 | 'match': '\\b[A-Z]\\w*\\b' 213 | 'name': 'constant.other.elm' 214 | } 215 | { 216 | 'include': '#comments' 217 | } 218 | { 219 | 'match': '^[a-z][A-Za-z0-9_\']*\\s+' 220 | 'name': 'entity.name.function.elm' 221 | } 222 | { 223 | 'match': '\\b(show|abs|acos|always|asin|atan|atan2|ceiling|clamp|compare|cos|curry|degrees|div|e|flip|floor|fromPolar|fst|id|isInfinite|isNaN|logBase|max|min|mod|not|otherwise|pi|radians|rem|round|sin|snd|sqrt|tan|toFloat|toPolar|truncate|turns|uncurry|xor|black|blue|brown|charcoal|complement|darkBlue|darkBrown|darkCharcoal|darkGray|darkGreen|darkGrey|darkOrange|darkPurple|darkRed|darkYellow|gray|grayscale|green|grey|greyscale|hsv|hsva|lightBlue|lightBrown|lightCharcoal|lightGray|lightGreen|lightGrey|lightOrange|lightPurple|lightRed|lightYellow|linear|orange|purple|radial|red|rgb|rgba|white|yellow|all|and|any|concat|concatMap|drop|filter|foldl|foldl1|foldr|foldr1|head|intersperse|isEmpty|join|last|length|map|maximum|minimum|or|partition|product|repeat|reverse|scanl|scanl1|sort|sortBy|sortWith|sum|tail|take|unzip|zip|zipWith|cons|isJust|isNothing|justs|maybe|combine|constant|count|countIf|dropIf|dropRepeats|dropWhen|foldp|keepIf|keepWhen|lift|lift2|lift3|lift4|lift5|lift6|lift7|lift8|merge|merges|sampleOn|asText|bold|centered|color|defaultStyle|height|italic|justified|leftAligned|line|link|markdown|monospace|plainText|rightAligned|style|toText|typeface|delay|every|fps|fpsWhen|hour|inHours|inMilliseconds|inMinutes|inSeconds|millisecond|minute|second|since|timestamp|alpha|circle|collage|dashed|defaultLine|dotted|filled|form|gradient|group|groupTransform|move|moveX|moveY|ngon|outlined|oval|path|polygon|rect|rotate|scale|segment|solid|sprite|square|textured|toForm|traced|above|absolute|below|beside|bottomLeft|bottomLeftAt|bottomRight|bottomRightAt|color|container|croppedImage|down|empty|fittedImage|flow|height|heightOf|image|inward|layers|left|link|midBottom|midBottomAt|midLeft|midLeftAt|midRight|midRightAt|midTop|midTopAt|middle|middleAt|opacity|outward|relative|right|size|sizeOf|spacer|tag|tiledImage|topLeft|topLeftAt|topRight|topRightAt|up|width|widthOf)\\s+' 224 | 'name': 'support.function.prelude.elm' 225 | } 226 | { 227 | 'include': '#infix_op' 228 | } 229 | { 230 | 'match': '[|!%$?~+:\\-.=&\\\\*^]+' 231 | 'name': 'keyword.operator.elm' 232 | } 233 | { 234 | 'match': ',' 235 | 'name': 'punctuation.separator.comma.elm' 236 | } 237 | ] 238 | 'repository': 239 | 'block_comment': 240 | 'applyEndPatternLast': 1 241 | 'begin': '\\{-(?!#)' 242 | 'captures': 243 | '0': 244 | 'name': 'punctuation.definition.comment.elm' 245 | 'end': '-\\}' 246 | 'name': 'comment.block.elm' 247 | 'patterns': [ 248 | { 249 | 'include': '#block_comment' 250 | } 251 | ] 252 | 'comments': 253 | 'patterns': [ 254 | { 255 | 'captures': 256 | '1': 257 | 'name': 'punctuation.definition.comment.elm' 258 | 'begin': '--' 259 | 'end': '$' 260 | 'name': 'comment.line.double-dash.elm' 261 | } 262 | { 263 | 'include': '#block_comment' 264 | } 265 | ] 266 | 'infix_op': 267 | 'match': '(\\([|!%$+:\\-.=]+\\)|\\(,+\\))' 268 | 'name': 'entity.name.function.infix.elm' 269 | 'module_exports': 270 | 'begin': '\\(' 271 | 'end': '\\)' 272 | 'name': 'meta.declaration.exports.elm' 273 | 'patterns': [ 274 | { 275 | 'match': '\\b[a-z][a-zA-Z_\'0-9]*' 276 | 'name': 'entity.name.function.elm' 277 | } 278 | { 279 | 'match': '\\b[A-Z][A-Za-z_\'0-9]*' 280 | 'name': 'storage.type.elm' 281 | } 282 | { 283 | 'match': ',' 284 | 'name': 'punctuation.separator.comma.elm' 285 | } 286 | { 287 | 'include': '#infix_op' 288 | } 289 | { 290 | 'comment': 'So named because I don\'t know what to call this.' 291 | 'match': '\\(.*?\\)' 292 | 'name': 'meta.other.unknown.elm' 293 | } 294 | ] 295 | 'module_name': 296 | 'match': '[A-Z][A-Za-z._\']*' 297 | 'name': 'support.other.module.elm' 298 | 'type_signature': 299 | 'patterns': [ 300 | { 301 | 'captures': 302 | '1': 303 | 'name': 'entity.other.inherited-class.elm' 304 | '2': 305 | 'name': 'variable.other.generic-type.elm' 306 | '3': 307 | 'name': 'keyword.other.big-arrow.elm' 308 | 'match': '\\(\\s*([A-Z][A-Za-z]*)\\s+([a-z][A-Za-z_\']*)\\)\\s*(=>)' 309 | 'name': 'meta.class-constraint.elm' 310 | } 311 | { 312 | 'match': '->' 313 | 'name': 'keyword.other.arrow.elm' 314 | } 315 | { 316 | 'match': '=>' 317 | 'name': 'keyword.other.big-arrow.elm' 318 | } 319 | { 320 | 'match': '\\b(Int|Float|Char|Bool|String|True|False|Order|Color|Gradient|Maybe|Signal|Line|Text|Style|Time|BasicForm|FillStyle|LineCap|LineJoin|Form|LineStyle|Path|Shape|Direction|ElementPrim|ImageStyle|Pos|Three|Element|Position|Properties)\\b' 321 | 'name': 'support.type.prelude.elm' 322 | } 323 | { 324 | 'match': '\\b[a-z][a-zA-Z0-9_\']*\\b' 325 | 'name': 'variable.other.generic-type.elm' 326 | } 327 | { 328 | 'match': '\\b[A-Z][a-zA-Z0-9_\']*\\b' 329 | 'name': 'storage.type.elm' 330 | } 331 | { 332 | 'match': '\\(\\)' 333 | 'name': 'support.constant.unit.elm' 334 | } 335 | { 336 | 'include': '#comments' 337 | } 338 | ] 339 | 'std_library': 340 | 'patterns': [ 341 | { 342 | 'include': '#basics' 343 | } 344 | { 345 | 'include': '#bitwise' 346 | } 347 | { 348 | 'include': '#char' 349 | } 350 | { 351 | 'include': '#color' 352 | } 353 | { 354 | 'include': '#date' 355 | } 356 | { 357 | 'include': '#debug' 358 | } 359 | { 360 | 'include': '#dict' 361 | } 362 | { 363 | 'include': '#either' 364 | } 365 | { 366 | 'include': '#http' 367 | } 368 | { 369 | 'include': '#javascript' 370 | } 371 | { 372 | 'include': '#json' 373 | } 374 | { 375 | 'include': '#keyboard' 376 | } 377 | { 378 | 'include': '#list' 379 | } 380 | { 381 | 'include': '#maybe' 382 | } 383 | { 384 | 'include': '#mouse' 385 | } 386 | { 387 | 'include': '#random' 388 | } 389 | { 390 | 'include': '#regex' 391 | } 392 | { 393 | 'include': '#set' 394 | } 395 | { 396 | 'include': '#signal' 397 | } 398 | { 399 | 'include': '#string' 400 | } 401 | { 402 | 'include': '#text' 403 | } 404 | { 405 | 'include': '#time' 406 | } 407 | { 408 | 'include': '#touch' 409 | } 410 | { 411 | 'include': '#trampoline' 412 | } 413 | { 414 | 'include': '#transform2d' 415 | } 416 | { 417 | 'include': '#websocket' 418 | } 419 | { 420 | 'include': '#window' 421 | } 422 | { 423 | 'include': '#graphics.collage' 424 | } 425 | { 426 | 'include': '#graphics.element' 427 | } 428 | { 429 | 'include': '#graphics.input' 430 | } 431 | { 432 | 'include': '#graphics.input.field' 433 | } 434 | { 435 | 'include': '#javascript.experimental' 436 | } 437 | ] 438 | 'basics': 439 | 'captures': 440 | '1': 441 | 'name': 'variable.parameter' 442 | '2': 443 | 'name': 'variable.parameter' 444 | '3': 445 | 'name': 'support.function.elm' 446 | 'match': '\\b(Basics)(.)(abs|acos|always|asin|atan|atan2|ceiling|clamp|compare|cos|curry|degrees|div|e|flip|floor|fromPolar|fst|id|isInfinite|isNaN|logBase|max|min|mod|not|otherwise|pi|radians|rem|round|sin|snd|sqrt|tan|toFloat|toPolar|truncate|turns|uncurry|xor|Order)\\b' 447 | 'name': 'variable.parameter' 448 | 'bitwise': 449 | 'captures': 450 | '1': 451 | 'name': 'variable.parameter' 452 | '2': 453 | 'name': 'variable.parameter' 454 | '3': 455 | 'name': 'support.function.elm' 456 | 'match': '\\b(Bitwise)(.)(and|complement|or|shiftLeft|shiftRight|shiftRightLogical|xor)\\b' 457 | 'name': 'variable.parameter' 458 | 'char': 459 | 'captures': 460 | '1': 461 | 'name': 'variable.parameter' 462 | '2': 463 | 'name': 'variable.parameter' 464 | '3': 465 | 'name': 'support.function.elm' 466 | 'match': '\\b(Char)(.)(fromCode|isDigit|isHexDigit|isLower|isOctDigit|isUpper|toCode|toLocaleLower|toLocaleUpper|toLower|toUpper|KeyCode)\\b' 467 | 'name': 'variable.parameter' 468 | 'color': 469 | 'captures': 470 | '1': 471 | 'name': 'variable.parameter' 472 | '2': 473 | 'name': 'variable.parameter' 474 | '3': 475 | 'name': 'support.function.elm' 476 | 'match': '\\b(Color)(.)(black|blue|brown|charcoal|complement|darkBlue|darkBrown|darkCharcoal|darkGray|darkGreen|darkGrey|darkOrange|darkPurple|darkRed|darkYellow|gray|grayscale|green|grey|greyscale|hsv|hsva|lightBlue|lightBrown|lightCharcoal|lightGray|lightGreen|lightGrey|lightOrange|lightPurple|lightRed|lightYellow|linear|orange|purple|radial|red|rgb|rgba|white|yellow|Color|Gradient)\\b' 477 | 'name': 'variable.parameter' 478 | 'date': 479 | 'captures': 480 | '1': 481 | 'name': 'variable.parameter' 482 | '2': 483 | 'name': 'variable.parameter' 484 | '3': 485 | 'name': 'support.function.elm' 486 | 'match': '\\b(Date)(.)(day|dayOfWeek|fromTime|hour|minute|month|read|second|toTime|year|Date|Day|Month)\\b' 487 | 'name': 'variable.parameter' 488 | 'debug': 489 | 'captures': 490 | '1': 491 | 'name': 'variable.parameter' 492 | '2': 493 | 'name': 'variable.parameter' 494 | '3': 495 | 'name': 'support.function.elm' 496 | 'match': '\\b(Debug)(.)(log)\\b' 497 | 'name': 'variable.parameter' 498 | 'dict': 499 | 'captures': 500 | '1': 501 | 'name': 'variable.parameter' 502 | '2': 503 | 'name': 'variable.parameter' 504 | '3': 505 | 'name': 'support.function.elm' 506 | 'match': '\\b(Dict)(.)(diff|empty|findWithDefault|foldl|foldr|fromList|insert|intersect|keys|lookup|map|member|remove|singleton|toList|union|update|values)\\b' 507 | 'name': 'variable.parameter' 508 | 'either': 509 | 'captures': 510 | '1': 511 | 'name': 'variable.parameter' 512 | '2': 513 | 'name': 'variable.parameter' 514 | '3': 515 | 'name': 'support.function.elm' 516 | 'match': '\\b(Either)(.)(either|isLeft|isRight|lefts|partition|rights|Either)\\b' 517 | 'name': 'variable.parameter' 518 | 'http': 519 | 'captures': 520 | '1': 521 | 'name': 'variable.parameter' 522 | '2': 523 | 'name': 'variable.parameter' 524 | '3': 525 | 'name': 'support.function.elm' 526 | 'match': '\\b(Http)(.)(get|post|request|send|sendGet|Request|Response)\\b' 527 | 'name': 'variable.parameter' 528 | 'javascript': 529 | 'captures': 530 | '1': 531 | 'name': 'variable.parameter' 532 | '2': 533 | 'name': 'variable.parameter' 534 | '3': 535 | 'name': 'support.function.elm' 536 | 'match': '\\b(JavaScript)(.)(fromBool|fromFloat|fromInt|fromList|fromString|toBool|toFloat|toInt|toList|toString|JSArray|JSBool|JSDomNode|JSNumber|JSObject|JSString)\\b' 537 | 'name': 'variable.parameter' 538 | 'json': 539 | 'captures': 540 | '1': 541 | 'name': 'variable.parameter' 542 | '2': 543 | 'name': 'variable.parameter' 544 | '3': 545 | 'name': 'support.function.elm' 546 | 'match': '\\b(Json)(.)(fromJSObject|fromJSString|fromString|toJSObject|toJSString|toString|JsonValue)\\b' 547 | 'name': 'variable.parameter' 548 | 'keyboard': 549 | 'captures': 550 | '1': 551 | 'name': 'variable.parameter' 552 | '2': 553 | 'name': 'variable.parameter' 554 | '3': 555 | 'name': 'support.function.elm' 556 | 'match': '\\b(Keyboard)(.)(arrows|ctrl|directions|enter|isDown|keysDown|lastPressed|shift|space|wasd|KeyCode)\\b' 557 | 'name': 'variable.parameter' 558 | 'list': 559 | 'captures': 560 | '1': 561 | 'name': 'variable.parameter' 562 | '2': 563 | 'name': 'variable.parameter' 564 | '3': 565 | 'name': 'support.function.elm' 566 | 'match': '\\b(List)(.)(all|and|any|concat|concatMap|drop|filter|foldl|foldl1|foldr|foldr1|head|intersperse|isEmpty|join|last|length|map|maximum|minimum|or|partition|product|repeat|reverse|scanl|scanl1|sort|sortBy|sortWith|sum|tail|take|unzip|zip|zipWith)\\b' 567 | 'name': 'variable.parameter' 568 | 'maybe': 569 | 'captures': 570 | '1': 571 | 'name': 'variable.parameter' 572 | '2': 573 | 'name': 'variable.parameter' 574 | '3': 575 | 'name': 'support.function.elm' 576 | 'match': '\\b(Maybe)(.)(cons|isJust|isNothing|justs|maybe|Maybe)\\b' 577 | 'name': 'variable.parameter' 578 | 'mouse': 579 | 'captures': 580 | '1': 581 | 'name': 'variable.parameter' 582 | '2': 583 | 'name': 'variable.parameter' 584 | '3': 585 | 'name': 'support.function.elm' 586 | 'match': '\\b(Mouse)(.)(clicks|isDown|position|x|y)\\b' 587 | 'name': 'variable.parameter' 588 | 'random': 589 | 'captures': 590 | '1': 591 | 'name': 'variable.parameter' 592 | '2': 593 | 'name': 'variable.parameter' 594 | '3': 595 | 'name': 'support.function.elm' 596 | 'match': '\\b(Random)(.)(float|floatList|range)\\b' 597 | 'name': 'variable.parameter' 598 | 'regex': 599 | 'captures': 600 | '1': 601 | 'name': 'variable.parameter' 602 | '2': 603 | 'name': 'variable.parameter' 604 | '3': 605 | 'name': 'support.function.elm' 606 | 'match': '\\b(Regex)(.)(caseInsensitive|contains|escape|find|regex|replace|split|Match|HowMany|Regex)\\b' 607 | 'name': 'variable.parameter' 608 | 'set': 609 | 'captures': 610 | '1': 611 | 'name': 'variable.parameter' 612 | '2': 613 | 'name': 'variable.parameter' 614 | '3': 615 | 'name': 'support.function.elm' 616 | 'match': '\\b(Set)(.)(diff|empty|foldl|foldr|fromList|insert|intersect|map|member|remove|singleton|toList|union)\\b' 617 | 'name': 'variable.parameter' 618 | 'signal': 619 | 'captures': 620 | '1': 621 | 'name': 'variable.parameter' 622 | '2': 623 | 'name': 'variable.parameter' 624 | '3': 625 | 'name': 'support.function.elm' 626 | 'match': '\\b(Signal)(.)(combine|constant|count|countIf|dropIf|dropRepeats|dropWhen|foldp|keepIf|keepWhen|lift|lift2|lift3|lift4|lift5|lift6|lift7|lift8|merge|merges|sampleOn|Signal)\\b' 627 | 'name': 'variable.parameter' 628 | 'string': 629 | 'captures': 630 | '1': 631 | 'name': 'variable.parameter' 632 | '2': 633 | 'name': 'variable.parameter' 634 | '3': 635 | 'name': 'support.function.elm' 636 | 'match': '\\b(String)(.)(all|any|append|concat|cons|contains|dropLeft|dropRight|endsWith|filter|foldl|foldr|fromList|indexes|indices|isEmpty|join|left|length|lines|map|pad|padLeft|padRight|repeat|reverse|right|show|split|startsWith|sub|toFloat|toInt|toList|toLower|toUpper|trim|trimLeft|trimRight|uncons|words)\\b' 637 | 'name': 'variable.parameter' 638 | 'text': 639 | 'captures': 640 | '1': 641 | 'name': 'variable.parameter' 642 | '2': 643 | 'name': 'variable.parameter' 644 | '3': 645 | 'name': 'support.function.elm' 646 | 'match': '\\b(Text)(.)(asText|bold|centered|color|defaultStyle|height|italic|justified|leftAligned|line|link|markdown|monospace|plainText|rightAligned|style|toText|typeface|Style|Line|Text)\\b' 647 | 'name': 'variable.parameter' 648 | 'time': 649 | 'captures': 650 | '1': 651 | 'name': 'variable.parameter' 652 | '2': 653 | 'name': 'variable.parameter' 654 | '3': 655 | 'name': 'support.function.elm' 656 | 'match': '\\b(Time)(.)(delay|every|fps|fpsWhen|hour|inHours|inMilliseconds|inMinutes|inSeconds|millisecond|minute|second|since|timestamp|Time)\\b' 657 | 'name': 'variable.parameter' 658 | 'touch': 659 | 'captures': 660 | '1': 661 | 'name': 'variable.parameter' 662 | '2': 663 | 'name': 'variable.parameter' 664 | '3': 665 | 'name': 'support.function.elm' 666 | 'match': '\\b(Touch)(.)(taps|touches|Touch)\\b' 667 | 'name': 'variable.parameter' 668 | 'trampoline': 669 | 'captures': 670 | '1': 671 | 'name': 'variable.parameter' 672 | '2': 673 | 'name': 'variable.parameter' 674 | '3': 675 | 'name': 'support.function.elm' 676 | 'match': '\\b(Trampoline)(.)(trampoline|Trampoline)\\b' 677 | 'name': 'variable.parameter' 678 | 'transform2d': 679 | 'captures': 680 | '1': 681 | 'name': 'variable.parameter' 682 | '2': 683 | 'name': 'variable.parameter' 684 | '3': 685 | 'name': 'support.function.elm' 686 | 'match': '\\b(Transform2D)(.)(identity|matrix|multiply|rotation|scale|scaleX|scaleY|translation|Transform2D)\\b' 687 | 'name': 'variable.parameter' 688 | 'websocket': 689 | 'captures': 690 | '1': 691 | 'name': 'variable.parameter' 692 | '2': 693 | 'name': 'variable.parameter' 694 | '3': 695 | 'name': 'support.function.elm' 696 | 'match': '\\b(WebSocket)(.)(connect)\\b' 697 | 'name': 'variable.parameter' 698 | 'window': 699 | 'captures': 700 | '1': 701 | 'name': 'variable.parameter' 702 | '2': 703 | 'name': 'variable.parameter' 704 | '3': 705 | 'name': 'support.function.elm' 706 | 'match': '\\b(Window)(.)(dimensions|height|width)\\b' 707 | 'name': 'variable.parameter' 708 | 'graphics.collage': 709 | 'captures': 710 | '1': 711 | 'name': 'variable.parameter' 712 | '2': 713 | 'name': 'variable.parameter' 714 | '3': 715 | 'name': 'support.function.elm' 716 | 'match': '\\b(Graphics.Collage)(.)(alpha|circle|collage|dashed|defaultLine|dotted|filled|form|gradient|group|groupTransform|move|moveX|moveY|ngon|outlined|oval|path|polygon|rect|rotate|scale|segment|solid|sprite|square|textured|toForm|traced|Form|LineStyle|Path|Shape|BasicForm|FillStyle|LineCap|LineJoin)\\b' 717 | 'name': 'variable.parameter' 718 | 'graphics.element': 719 | 'captures': 720 | '1': 721 | 'name': 'variable.parameter' 722 | '2': 723 | 'name': 'variable.parameter' 724 | '3': 725 | 'name': 'support.function.elm' 726 | 'match': '\\b(Graphics.Element)(.)(above|absolute|below|beside|bottomLeft|bottomLeftAt|bottomRight|bottomRightAt|color|container|croppedImage|down|empty|fittedImage|flow|height|heightOf|image|inward|layers|left|link|midBottom|midBottomAt|midLeft|midLeftAt|midRight|midRightAt|midTop|midTopAt|middle|middleAt|opacity|outward|relative|right|size|sizeOf|spacer|tag|tiledImage|topLeft|topLeftAt|topRight|topRightAt|up|width|widthOf|Element|Position|Properties|Direction|ElementPrim|ImageStyle|Pos|Three)\\b' 727 | 'name': 'variable.parameter' 728 | 'graphics.input': 729 | 'captures': 730 | '1': 731 | 'name': 'variable.parameter' 732 | '2': 733 | 'name': 'variable.parameter' 734 | '3': 735 | 'name': 'support.function.elm' 736 | 'match': '\\b(Graphics.Input)(.)(button|checkbox|clickable|customButton|dropDown|hoverable|input|Input|Handle)\\b' 737 | 'name': 'variable.parameter' 738 | 'graphics.input.field': 739 | 'captures': 740 | '1': 741 | 'name': 'variable.parameter' 742 | '2': 743 | 'name': 'variable.parameter' 744 | '3': 745 | 'name': 'support.function.elm' 746 | 'match': '\\b(Graphics.Input.Field)(.)(defaultStyle|email|field|noContent|noHighlight|noOutline|password|uniformly|Content|Dimensions|Highlight|Outline|Selection|Style|Direction)\\b' 747 | 'name': 'variable.parameter' 748 | 'javascript.experimental': 749 | 'captures': 750 | '1': 751 | 'name': 'variable.parameter' 752 | '2': 753 | 'name': 'variable.parameter' 754 | '3': 755 | 'name': 'support.function.elm' 756 | 'match': '\\b(JavaScript.Experimental)(.)(fromRecord|toRecord)\\b' 757 | 'name': 'variable.parameter' 758 | 'scopeName': 'source.elm' 759 | -------------------------------------------------------------------------------- /lib/elmOracle.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | 3 | const { BufferedNodeProcess } = require('atom') 4 | const { spawn } = require('child_process') 5 | const { statSync } = require('fs') 6 | const path = require('path') 7 | 8 | const RELATIVE_EXACT_DEPS_PATH = path.join('elm-stuff', 'exact-dependencies.json') 9 | const PATH_CACHE = new Map() 10 | const LOG_PREFIX = 'Elm Autocomplete: ' 11 | 12 | module.exports = (prefix, filePath) => { 13 | return new Promise(resolve => { 14 | const lines = [] 15 | const elmProjectPath = findClosestElmProjectPath(filePath.split(path.sep).slice(0, -1)) 16 | const executablePath = atom.config.get('language-elm.elmOraclePath') 17 | const options = { 18 | cwd: elmProjectPath, 19 | env: process.env 20 | } 21 | 22 | const accumulateOutput = (line) => { 23 | lines.push(line) 24 | } 25 | 26 | const provideSuggestions = () => { 27 | resolve(JSON.parse((parseOutput(lines[0])))) 28 | } 29 | 30 | if (atom.inDevMode()) { 31 | console.log(`${LOG_PREFIX} Executing - ${executablePath} ${filePath} ${prefix}`) 32 | console.log(`${LOG_PREFIX} From Directory - ${elmProjectPath}`) 33 | } 34 | 35 | const onProcessError = ({error, handle}) => { 36 | if (atom.inDevMode()) { 37 | atom.notifications.addError('Elm Autocomplete Error', { 38 | detail: 'Failed to run:' + 39 | [executablePath, filePath, prefix].join(' ') + 40 | '\n\n' + 41 | 'From the following directory:' + 42 | elmProjectPath + 43 | '\n\n' + 44 | error.message 45 | }) 46 | } 47 | 48 | handle() 49 | 50 | throw error 51 | } 52 | 53 | // Fix for windows as BufferedNodeProcess doesn't spawn properly; See Atom issue 2887. 54 | if (process.platform === 'win32') { 55 | var results = spawn(getCmdPath(), ['/c', executablePath, filePath, prefix], options) 56 | 57 | results.stdout.on('data', function (data) { 58 | accumulateOutput(data.toString()) 59 | }) 60 | 61 | results.on('close', function () { 62 | provideSuggestions() 63 | }) 64 | 65 | results.on('error', function (err) { 66 | throw err 67 | }) 68 | } else { 69 | (new BufferedNodeProcess({ 70 | command: executablePath, 71 | args: [filePath, prefix], 72 | options: options, 73 | stdout: accumulateOutput, 74 | exit: provideSuggestions 75 | })).onWillThrowError(onProcessError) 76 | } 77 | }) 78 | } 79 | 80 | const recursivelyFindClosestElmProjectPath = (pathParts, startPath) => { 81 | const projectPath = startPath || 82 | pathParts.length 83 | ? buildAbsolutePath(pathParts) 84 | : '' 85 | 86 | let exactDependenciesPath 87 | 88 | if (projectPath) { 89 | exactDependenciesPath = path.join(projectPath, RELATIVE_EXACT_DEPS_PATH) 90 | 91 | try { 92 | statSync(exactDependenciesPath) 93 | return projectPath 94 | } catch (e) { 95 | return recursivelyFindClosestElmProjectPath(pathParts.slice(0, -1)) 96 | } 97 | } else { 98 | throw new Error('No elm project directory found') 99 | } 100 | } 101 | 102 | const buildAbsolutePath = (pathParts) => path.resolve(path.sep, ...pathParts) 103 | 104 | // Finds and caches the elm project path closest to the given path 105 | const findClosestElmProjectPath = (pathParts) => { 106 | const path = pathParts.length ? buildAbsolutePath(pathParts) : '' 107 | 108 | if (PATH_CACHE.has(path)) { 109 | return PATH_CACHE.get(path) 110 | } else { 111 | try { 112 | const elmProjectPath = recursivelyFindClosestElmProjectPath(pathParts, path) 113 | PATH_CACHE.set(path, elmProjectPath) 114 | return elmProjectPath 115 | } catch (error) { 116 | throw error 117 | } 118 | } 119 | } 120 | 121 | const parseOutput = (text) => { 122 | text = text && text.slice(0, text.indexOf('\n')) 123 | 124 | if (!text) { 125 | throw new Error('No elm-oracle suggestions') 126 | } else { 127 | return text 128 | } 129 | } 130 | 131 | const getCmdPath = () => { 132 | if (process.env.comspec) { 133 | return process.env.comspec 134 | } else if (process.env.SystemRoot) { 135 | return path.join(process.env.SystemRoot, 'System32', 'cmd.exe') 136 | } else { 137 | return 'cmd.exe' 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | 3 | const provider = require('./provider') 4 | const { join } = require('path') 5 | 6 | module.exports = { 7 | config: { 8 | autocompleteEnabled: { 9 | title: 'Enable autocomplete', 10 | type: 'boolean', 11 | default: true 12 | }, 13 | elmOraclePath: { 14 | title: 'The elm-oracle executable path (used for autocomplete)', 15 | type: 'string', 16 | default: join(__dirname, '..', 'node_modules', '.bin', 'elm-oracle') 17 | }, 18 | minCharsForAutocomplete: { 19 | title: 'The min number of characters to enter before autocomplete appears', 20 | type: 'number', 21 | default: 1 22 | } 23 | }, 24 | 25 | provide () { 26 | return [provider] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/provider.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | 3 | const { File } = require('atom') 4 | const getSuggestionsFromElmOracle = require('./elmOracle') 5 | 6 | module.exports = { 7 | selector: '.source.elm', 8 | disableForSelector: '.comment, .string', 9 | inclusionPriority: 1, 10 | excludeLowerPriority: false, 11 | 12 | getSuggestions ({editor, bufferPosition, scopeDescriptor}) { 13 | const prefix = getPrefix(editor, bufferPosition) 14 | const filePath = editor.getPath() 15 | 16 | return shouldProvideSuggestions(prefix, filePath) 17 | .then(getElmOracleSuggestionsIfNecessary(prefix, filePath)) 18 | .then(mapToAutocompletePlusSuggestions) 19 | .catch(onError) 20 | } 21 | } 22 | 23 | const getPrefix = (editor, bufferPosition) => { 24 | const regex = /[.\w0-9_-]+$/ 25 | const line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition]) 26 | 27 | return line.match(regex) ? line.match(regex)[0] : '' 28 | } 29 | 30 | const shouldProvideSuggestions = (prefix, filePath) => { 31 | const shouldNotProvideSuggestions = 32 | prefix.length < atom.config.get('language-elm.minCharsForAutocomplete') || 33 | !atom.config.get('language-elm.autocompleteEnabled') || 34 | !filePath 35 | 36 | if (shouldNotProvideSuggestions) { 37 | return Promise.resolve(false) 38 | } else { 39 | return new File(filePath).exists() 40 | } 41 | } 42 | 43 | const getElmOracleSuggestionsIfNecessary = (prefix, filePath) => { 44 | return (shouldProvideSuggestions) => { 45 | if (shouldProvideSuggestions) { 46 | return getSuggestionsFromElmOracle(prefix, filePath) 47 | } else { 48 | return [] 49 | } 50 | } 51 | } 52 | 53 | // -- Autocomplete Plus Formatting 54 | const mapToAutocompletePlusSuggestions = (oracleSuggestions) => { 55 | return oracleSuggestions.map(({comment, fullName, href, name, signature}) => { 56 | return { 57 | type: 'function', 58 | snippet: (name + ' ' + parseTabStops(signature)), 59 | displayText: name, 60 | rightLabel: signature, 61 | description: fullName + (comment ? ': ' + comment : ''), 62 | descriptionMoreURL: href 63 | } 64 | }) 65 | } 66 | 67 | /** 68 | * TODO: This function works for some cases, but needs a rework with unit tests. 69 | * Shouldn't block initial release, but requires follow up. 70 | * 71 | * Variety of signatures to parse: 72 | * - Signal#merge 73 | * - Signal#map5 74 | * - Html.Attributes#style 75 | * - Basics#curry 76 | * - List#partition 77 | * - Color#radial 78 | */ 79 | const parseTabStops = (signature) => { 80 | return signature.split(')') 81 | .filter((suggestion) => suggestion.trim().length) 82 | .reduce((acc, part) => { 83 | if ((/\(/g).test(part)) { 84 | acc.tabStops.push('${' + ++acc.position + ':(' + part.replace(/\(|^(\ ?->)\ /g, '') + ')}') 85 | } else { 86 | part 87 | .split('->') 88 | .filter((part) => part.trim().length) 89 | .slice(0, -1) 90 | .forEach((part) => { 91 | acc.tabStops.push('${' + ++acc.position + ':' + part.trim() + '}') 92 | }) 93 | } 94 | 95 | return acc 96 | }, { tabStops: [], position: 0 }).tabStops.join(' ') 97 | } 98 | 99 | // -- Error States 100 | const onError = (error) => { 101 | displayAutoCompletionsUnavailableWarning() 102 | 103 | if (atom.inDevMode()) { 104 | console.error(error) 105 | } 106 | } 107 | 108 | let seenUnavailableWarning = false 109 | const displayAutoCompletionsUnavailableWarning = () => { 110 | if (seenUnavailableWarning) { 111 | return 112 | } 113 | 114 | atom.notifications.addWarning('Elm AutoCompletions Unavailable', { 115 | detail: 'Please ensure you have:\n' + 116 | ' - Set the proper elm-oracle path\n' + 117 | ' - run `elm package install` within your project folder' 118 | }) 119 | 120 | seenUnavailableWarning = true 121 | } 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "language-elm", 3 | "version": "1.5.0", 4 | "description": "Syntax highlighting and autocompletion for the language Elm", 5 | "repository": "https://github.com/edubkendo/atom-elm", 6 | "license": "MIT", 7 | "engines": { 8 | "atom": ">0.50.0" 9 | }, 10 | "scripts": { 11 | "test": "./node_modules/.bin/standard" 12 | }, 13 | "dependencies": { 14 | "elm-oracle": "0.2.0" 15 | }, 16 | "devDependencies": { 17 | "babel-eslint": "4.1.6", 18 | "pre-commit": "1.1.2", 19 | "standard": "5.4.1" 20 | }, 21 | "main": "./lib/main", 22 | "providedServices": { 23 | "autocomplete.provider": { 24 | "versions": { 25 | "2.0.0": "provide" 26 | } 27 | } 28 | }, 29 | "standard": { 30 | "parser": "babel-eslint", 31 | "globals": [ 32 | "atom" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edubkendo/atom-elm/7bf63e6d57f77233c6708f7dda2bdff37c1ba685/screenshot.png -------------------------------------------------------------------------------- /settings/language-elm.cson: -------------------------------------------------------------------------------- 1 | '.source.elm': 2 | 'editor': 3 | 'autoIndentOnPaste': false 4 | 'commentStart': '-- ' 5 | 'increaseIndentPattern': '((^.*(=|[|!%$?~+:\\-.=&\\\\*^]+|\\bthen|\\belse|\\bof)\\s*$)|(^.*\\bif(?!.*\\bthen\\b.*\\belse\\b.*).*$))' 6 | -------------------------------------------------------------------------------- /styles/autocomplete-plus.less: -------------------------------------------------------------------------------- 1 | atom-text-editor[data-grammar="source elm"] autocomplete-suggestion-list .word { 2 | max-width: 145px; 3 | } 4 | 5 | atom-text-editor[data-grammar="source elm"] autocomplete-suggestion-list .right-label { 6 | max-width: 430px; 7 | } 8 | 9 | atom-text-editor[data-grammar="source elm"] autocomplete-suggestion-list.select-list.popover-list { 10 | max-width: 630px; 11 | } 12 | --------------------------------------------------------------------------------