├── .no-sublime-package ├── .gitignore ├── images ├── logo.png ├── elm_format.png ├── elm_make.jpg ├── elm_repl.jpg ├── elm_types.png ├── type_panel.png ├── completions.png └── elm_project.jpg ├── Settings ├── Elm.sublime-settings ├── Elm Language Support.sublime-settings └── Elm User Strings.sublime-settings ├── messages ├── 0.17.0.md ├── 0.16.2.md ├── 0.19.0.txt ├── 0.17.1.txt ├── 0.15.0.md ├── 0.18.0.txt ├── 0.16.1.md └── 0.16.3.md ├── Commands ├── Elm Format.sublime-commands ├── Open In Browser.sublime-commands ├── Show Type.sublime-commands ├── REPL.sublime-commands └── Project.sublime-commands ├── Snippets ├── module.sublime-snippet ├── type.sublime-snippet ├── import.sublime-snippet ├── let.sublime-snippet ├── import_as.sublime-snippet ├── case_of.sublime-snippet ├── function.sublime-snippet ├── type_alias.sublime-snippet ├── case_of_maybe.sublime-snippet ├── case_of_result.sublime-snippet ├── function_2_aguments.sublime-snippet ├── function_3_aguments.sublime-snippet └── function_4_aguments.sublime-snippet ├── Keymaps └── Default.sublime-keymap ├── Menus ├── Context.sublime-menu └── Main.sublime-menu ├── messages.json ├── beta-repository.json ├── Syntaxes ├── Elm Documentation.YAML-tmLanguage ├── Elm Documentation.hidden-tmLanguage ├── Elm Documentation.hide-tmLanguage ├── Elm Compile Messages.YAML-tmLanguage ├── Elm Compile Messages.hidden-tmLanguage ├── Elm.YAML-tmLanguage └── Elm.tmLanguage ├── Preferences ├── Metadata.YAML-tmLanguage └── Metadata.tmPreferences ├── LICENSE ├── elm_open_in_browser.py ├── Build Systems └── Elm Make.sublime-build ├── elm_format.py ├── elm_plugin.py ├── elm_make.py ├── README.md ├── elm_generate.py ├── elm_project.py └── elm_show_type.py /.no-sublime-package: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.cache 2 | *.pyc 3 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/Elm.tmLanguage/master/images/logo.png -------------------------------------------------------------------------------- /images/elm_format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/Elm.tmLanguage/master/images/elm_format.png -------------------------------------------------------------------------------- /images/elm_make.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/Elm.tmLanguage/master/images/elm_make.jpg -------------------------------------------------------------------------------- /images/elm_repl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/Elm.tmLanguage/master/images/elm_repl.jpg -------------------------------------------------------------------------------- /images/elm_types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/Elm.tmLanguage/master/images/elm_types.png -------------------------------------------------------------------------------- /images/type_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/Elm.tmLanguage/master/images/type_panel.png -------------------------------------------------------------------------------- /images/completions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/Elm.tmLanguage/master/images/completions.png -------------------------------------------------------------------------------- /images/elm_project.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/Elm.tmLanguage/master/images/elm_project.jpg -------------------------------------------------------------------------------- /Settings/Elm.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "word_separators": "/\\()\"-:,.;<>!@#$%^&*|+=[]{}`~?", 3 | "translate_tabs_to_spaces": true 4 | } 5 | -------------------------------------------------------------------------------- /messages/0.17.0.md: -------------------------------------------------------------------------------- 1 | ## What's new 2 | 3 | - Added support for new Elm 0.17 module declaration syntax. 4 | - Package adopted by elm-community. 5 | -------------------------------------------------------------------------------- /Commands/Elm Format.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Elm Language Support: Run elm-format", 4 | "command": "elm_format" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /Commands/Open In Browser.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Elm Build System: Open in Browser", 4 | "command": "elm_open_in_browser" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /messages/0.16.2.md: -------------------------------------------------------------------------------- 1 | ## What's new 2 | 3 | This is just a small update to fix an issue where the Elm Make command was no longer compatible with the elm-make command line utility on Windows. This update will not change anything on Linux or Mac. 4 | -------------------------------------------------------------------------------- /Snippets/module.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | module 7 | mod 8 | source.elm 9 | 10 | -------------------------------------------------------------------------------- /Snippets/type.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | type 9 | type 10 | source.elm 11 | 12 | -------------------------------------------------------------------------------- /Keymaps/Default.sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["alt+up"], "command": "elm_show_type_panel", 3 | "context": 4 | [ { "key": "selector", "operator": "equal", "operand": "source.elm" } ] 5 | }, 6 | { "keys": ["alt+down"], "command": "hide_panel" 7 | } 8 | ] -------------------------------------------------------------------------------- /Snippets/import.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | import 7 | imp 8 | source.elm 9 | 10 | -------------------------------------------------------------------------------- /Snippets/let.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | let … in … 11 | let 12 | source.elm 13 | 14 | -------------------------------------------------------------------------------- /Snippets/import_as.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | import … as 7 | impas 8 | source.elm 9 | 10 | -------------------------------------------------------------------------------- /Snippets/case_of.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | $3 6 | ${4:_} -> 7 | $5 8 | ]]> 9 | 10 | case … of 11 | cof 12 | source.elm 13 | 14 | -------------------------------------------------------------------------------- /messages/0.19.0.txt: -------------------------------------------------------------------------------- 1 | New in Elm Language Support 0.19.0 (May 28, 2017) 2 | 3 | - Improvement: Side-by-side editing of package user settings (requires Sublime 4 | Text 3 Build 3124). (Thanks, @stoivo!) 5 | 6 | - Fix: Blank menu item when SublimeREPL is not installed. (Thanks, @Bernardoow!) 7 | -------------------------------------------------------------------------------- /Settings/Elm Language Support.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "debug": false, 3 | "enabled": true, 4 | "elm_docs_path": "docs.json", 5 | "elm_format_binary": "elm-format", 6 | "elm_format_on_save": true, 7 | "elm_format_filename_filter": "", 8 | "elm_paths": "" 9 | } 10 | -------------------------------------------------------------------------------- /Snippets/function.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | ${2:Int} 4 | ${1:fn} ${4:count} = 5 | ${5:-- body} 6 | ]]> 7 | Function (a -> b) 8 | fn 9 | source.elm 10 | 11 | -------------------------------------------------------------------------------- /Snippets/type_alias.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | type alias (Record) 10 | typea 11 | source.elm 12 | 13 | -------------------------------------------------------------------------------- /Snippets/case_of_maybe.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | $3 6 | Nothing -> 7 | $4 8 | ]]> 9 | 10 | case … of (Maybe) 11 | cofm 12 | source.elm 13 | 14 | -------------------------------------------------------------------------------- /Snippets/case_of_result.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | $3 6 | Error ${4:error} -> 7 | $5 8 | ]]> 9 | 10 | case … of (Result) 11 | cofr 12 | source.elm 13 | 14 | -------------------------------------------------------------------------------- /Commands/Show Type.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Elm Language Support: Show type", 4 | "command": "elm_show_type", 5 | "args": { "panel": true } 6 | }, 7 | { 8 | "caption": "Elm Language Support: Open type panel", 9 | "command": "elm_show_type_panel" 10 | } 11 | ] -------------------------------------------------------------------------------- /Snippets/function_2_aguments.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | ${4:Int} -> ${2:Int} 4 | ${1:fn} ${5:a} ${6:a} = 5 | ${7:-- body} 6 | ]]> 7 | Function (a -> b -> c) 8 | fn2 9 | source.elm 10 | 11 | -------------------------------------------------------------------------------- /Menus/Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "elmlanguagesupport", 4 | "caption": "Elm Language Support", 5 | "children": 6 | [ 7 | { 8 | "caption": "Open Type Panel", 9 | "command": "elm_show_type_panel" 10 | } 11 | ] 12 | } 13 | ] -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "README.md", 3 | "0.15.0": "messages/0.15.0.md", 4 | "0.16.1": "messages/0.16.1.md", 5 | "0.16.2": "messages/0.16.2.md", 6 | "0.16.3": "messages/0.16.3.md", 7 | "0.17.0": "messages/0.17.0.md", 8 | "0.17.1": "messages/0.17.1.txt", 9 | "0.18.0": "messages/0.18.0.txt" 10 | } 11 | -------------------------------------------------------------------------------- /Snippets/function_3_aguments.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | ${4:Int} -> ${5:Int} -> ${2:Int} 4 | ${1:fn} ${6:a} ${7:b} ${8:c} = 5 | ${9:-- body} 6 | ]]> 7 | Function (a -> b -> c -> d) 8 | fn3 9 | source.elm 10 | 11 | -------------------------------------------------------------------------------- /Snippets/function_4_aguments.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | ${4:Int} -> ${5:Int} -> ${6:Int} -> ${2:Int} 4 | ${1:fn} ${7:a} ${8:b} ${9:c} ${10:d}= 5 | ${11:-- body} 6 | ]]> 7 | Function (a -> b -> c -> d -> e) 8 | fn4 9 | source.elm 10 | 11 | -------------------------------------------------------------------------------- /beta-repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema_version": "3.0.0", 3 | "packages": [ 4 | { 5 | "name": "Elm Language Support", 6 | "details": "https://github.com/deadfoxygrandpa/Elm.tmLanguage", 7 | "releases": [ 8 | { 9 | "sublime_text": "*", 10 | "branch": "beta" 11 | } 12 | ] 13 | } 14 | ], 15 | "dependencies": [ 16 | ], 17 | "includes": [ 18 | ] 19 | } -------------------------------------------------------------------------------- /messages/0.17.1.txt: -------------------------------------------------------------------------------- 1 | New in Elm Language Support 0.17.1 (January 19, 2017) 2 | 3 | - New: New build output format, with support for new inline build error 4 | indicators (phantoms) introduced in Sublime Text 3 build 3118 5 | (see: https://forum.sublimetext.com/t/dev-build-3118/21270). 6 | - Fixed: F4/Shift-F4 navigation for build errors. 7 | - Fixed: Double-click build error heading to jump to location in editor. 8 | - New maintainer: Kevin Yank (@sentience) 9 | -------------------------------------------------------------------------------- /Syntaxes/Elm Documentation.YAML-tmLanguage: -------------------------------------------------------------------------------- 1 | # [PackageDev] target_format: plist, ext: hidden-tmLanguage 2 | name: Elm Documentation 3 | scopeName: text.html.mediawiki.elm-documentation 4 | fileTypes: [] 5 | uuid: c4a06e11-4259-4698-aeb2-1e87799984cc 6 | 7 | patterns: 8 | - comment: Code Block 9 | name: markup.raw.block.elm-documentation 10 | contentName: markup.raw.block.elm-documentation 11 | begin: \x{FEFF} 12 | end: \x{FEFF} 13 | patterns: 14 | - include: source.elm 15 | -------------------------------------------------------------------------------- /messages/0.15.0.md: -------------------------------------------------------------------------------- 1 | Thank you for continuing to use Elm Language Support. We've been busy lately. Here are a few features since we last checked in: 2 | 3 | ## What's New 4 | 5 | - This message ;) 6 | - Improved ST3 compatibility 7 | - Two build commands 8 | - Compile messages 9 | - Integration with four (4!) external plugins 10 | - One new contributor — @dnalot aka "Texas" 11 | - A brand new [README][] with further details 12 | 13 | [README]: https://github.com/deadfoxygrandpa/Elm.tmLanguage/blob/master/README.md 14 | -------------------------------------------------------------------------------- /Preferences/Metadata.YAML-tmLanguage: -------------------------------------------------------------------------------- 1 | # [PackageDev] target_format: plist, ext: tmPreferences 2 | name: Metadata 3 | uuid: 64598216-cf92-4b00-961f-2b1478921c3e 4 | scope: source.elm 5 | settings: 6 | increaseIndentPattern: |- 7 | (?x) 8 | ^.* 9 | (= 10 | |[|!%$?~+:\-.=&\\*^]+ 11 | |\bthen 12 | |\belse 13 | |\bof 14 | ) 15 | \s*$ 16 | shellVariables: 17 | - name: TM_COMMENT_START 18 | value: -- 19 | - name: TM_COMMENT_START_2 20 | value: '{-' 21 | - name: TM_COMMENT_END_2 22 | value: -} 23 | -------------------------------------------------------------------------------- /messages/0.18.0.txt: -------------------------------------------------------------------------------- 1 | New in Elm Language Support 0.18.0 (May 13, 2017) 2 | 3 | - New: Snippets that provide shortcuts for typing common Elm syntax: case … of, 4 | case … of (Maybe), case … of (Result), function (1-4 arguments), import, 5 | import … as, let … in, module, type, and type alias. 6 | 7 | See http://docs.sublimetext.info/en/latest/extensibility/snippets.html for 8 | more info on using snippets in Sublime Text. 9 | 10 | - Improvement: Option to specify the name of the elm-format binary, for systems 11 | where the version number is included in the filename (e.g. elm-format-0.18). 12 | -------------------------------------------------------------------------------- /Commands/REPL.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "SublimeREPL: Elm", 4 | "command": "repl_open", "args": 5 | { 6 | "type": "subprocess", 7 | "encoding": "utf8", 8 | "cmd": ["elm-repl"], 9 | "cwd": "$file_path", 10 | "syntax": "Packages/Elm Language Support/Syntaxes/Elm.tmLanguage", 11 | "external_id": "elm", 12 | "extend_env": 13 | { 14 | "osx": {"PATH": "{PATH}:/usr/local/bin"}, 15 | "linux": {"PATH": "{PATH}:/usr/local/bin:{HOME}/.cabal/bin"}, 16 | "windows": {} 17 | } 18 | } 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /Settings/Elm User Strings.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "logging.prefix": "[Elm says]: ", 3 | "logging.missing_plugin": "Missing plugin: {0}", 4 | 5 | "make.missing_plugin": "To highlight build errors: Install with Package Control: Highlight Build Errors", 6 | "make.logging.invalid_json": "Invalid JSON from elm-make: {0}", 7 | 8 | "open_in_browser.not_found": "HTML file NOT found to open: {0}", 9 | 10 | "project.not_found": "Valid elm-package.json NOT found to update", 11 | "project.updated": "elm-package.json updated: {0} = {1}", 12 | "project.logging.invalid_choice": "Invalid choice in elm-package.json: {0}", 13 | "project.logging.invalid_json": "Invalid elm-package.json: {0}", 14 | "project.logging.settings": "Detected settings: {0}" 15 | } 16 | -------------------------------------------------------------------------------- /messages/0.16.1.md: -------------------------------------------------------------------------------- 1 | ## What's new 2 | 3 | Added support for elm-format, the new tool for automatically formatting your Elm source code (you need to install this separately. Check here for details: https://github.com/avh4/elm-format) 4 | - elm-format needs to be installed and in your PATH in order to work 5 | - There is a new command on the command palette named `Elm Language Support: Run elm-format`. This command will run elm-format on the currently open file. 6 | - There is also a new User setting, which you can set by going to Preferences -> Package Settings -> Elm Language Support -> User and adding `"elm_format_on_save": true`. This will automatically run elm-format on your Elm source files before they are saved. 7 | - If there are some Elm source files you want to exclude from auto-formatting, you can set a regex-based filename filter with the "elm_format_filename_filter" User setting. Enter a regex and any file names, including their paths, the regex matches against will be excluded from the "elm_format_on_save" setting previously mentioned. 8 | -------------------------------------------------------------------------------- /Syntaxes/Elm Documentation.hidden-tmLanguage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fileTypes 6 | 7 | name 8 | Elm Documentation 9 | patterns 10 | 11 | 12 | begin 13 | \x{FEFF} 14 | comment 15 | Code Block 16 | contentName 17 | markup.raw.block.elm-documentation 18 | end 19 | \x{FEFF} 20 | name 21 | markup.raw.block.elm-documentation 22 | patterns 23 | 24 | 25 | include 26 | source.elm 27 | 28 | 29 | 30 | 31 | scopeName 32 | text.html.mediawiki.elm-documentation 33 | uuid 34 | c4a06e11-4259-4698-aeb2-1e87799984cc 35 | 36 | 37 | -------------------------------------------------------------------------------- /Syntaxes/Elm Documentation.hide-tmLanguage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fileTypes 6 | 7 | name 8 | Elm Documentation 9 | patterns 10 | 11 | 12 | begin 13 | \x{FEFF} 14 | comment 15 | Code Block 16 | contentName 17 | markup.raw.block.elm-documentation 18 | end 19 | \x{FEFF} 20 | name 21 | markup.raw.block.elm-documentation 22 | patterns 23 | 24 | 25 | include 26 | source.elm 27 | 28 | 29 | 30 | 31 | scopeName 32 | text.html.mediawiki.elm-documentation 33 | uuid 34 | c4a06e11-4259-4698-aeb2-1e87799984cc 35 | 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2014 Alex Neslusan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Preferences/Metadata.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | Metadata 7 | scope 8 | source.elm 9 | settings 10 | 11 | increaseIndentPattern 12 | (?x) 13 | ^.* 14 | (= 15 | |[|!%$?~+:\-.=</>&\\*^]+ 16 | |\bthen 17 | |\belse 18 | |\bof 19 | ) 20 | \s*$ 21 | shellVariables 22 | 23 | 24 | name 25 | TM_COMMENT_START 26 | value 27 | -- 28 | 29 | 30 | name 31 | TM_COMMENT_START_2 32 | value 33 | {- 34 | 35 | 36 | name 37 | TM_COMMENT_END_2 38 | value 39 | -} 40 | 41 | 42 | 43 | uuid 44 | 64598216-cf92-4b00-961f-2b1478921c3e 45 | 46 | 47 | -------------------------------------------------------------------------------- /elm_open_in_browser.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | 3 | try: # ST3 4 | import urllib.parse as urlparse 5 | import urllib.request as urllib 6 | 7 | from .elm_plugin import * 8 | from .elm_project import ElmProject 9 | except: # ST2 10 | import urlparse 11 | import urllib 12 | 13 | from elm_plugin import * 14 | from elm_project import ElmProject 15 | 16 | class ElmOpenInBrowserCommand(sublime_plugin.TextCommand): 17 | 18 | def is_enabled(self): 19 | self.project = ElmProject(self.view.file_name()) 20 | return self.project.exists 21 | 22 | def run(self, edit): 23 | norm_path = fs.join(self.project.working_dir, fs.expanduser(self.project.html_path)) 24 | file_path = fs.abspath(norm_path) 25 | if fs.isfile(file_path): 26 | # http://stackoverflow.com/questions/11687478/convert-a-filename-to-a-file-url#comment32679033_14298190 27 | file_url = urlparse.urljoin('file:', urllib.pathname2url(file_path)) 28 | # inspired by https://github.com/noahcoad/open-url 29 | webbrowser.open_new_tab(file_url) 30 | else: 31 | sublime.status_message(get_string('open_in_browser.not_found', html_path)) 32 | -------------------------------------------------------------------------------- /messages/0.16.3.md: -------------------------------------------------------------------------------- 1 | ## What's new 2 | 3 | Added support for elm-oracle to add autocompletions, type information, and in-editor documentation for external packages (you need to install this separately, npm install -g elm-oracle) 4 | - Moving the cursor over any function imported from an external package defined in your elm-package.json file will show the type signature in the status bar 5 | - Bring up the type panel with alt+up or through the right click context menu, or by running the `Elm Language Support: Show type` command from the command palette 6 | - Shrinking the type panel to a height of 1 line will show just an enhanced type signature, better than the status bar, while expanding its height will show you documentation 7 | - Close the type panel with Escape or alt+down 8 | - If the elm-oracle features aren't working, elm-oracle needs to either be on your PATH, or you can add additional directories to the Elm Language Support > "elm_paths" setting 9 | 10 | Other than that: 11 | - elm_format_on_save now defaults to true. If you have elm-format installed, you probably want to be using it more often than not. You can still disable it by changing elm_format_on_save to false 12 | - Fixed a couple minor bugs, the most notable being Elm Make was broken on Windows with the most recent version of Elm 13 | -------------------------------------------------------------------------------- /Build Systems/Elm Make.sublime-build: -------------------------------------------------------------------------------- 1 | { 2 | "target": "elm_make", 3 | "selector": "source.elm", 4 | "cmd": 5 | [ 6 | "elm-make", 7 | "$file", 8 | "--output={null}", 9 | "--report=json", 10 | "--yes" 11 | ], 12 | "working_dir": "$project_path", 13 | "file_regex": "^\\-\\- \\w+: (?=.+ \\- (.+?):(\\d+):(\\d+))(.+) \\- .*$", 14 | "error_format": "-- $type: $tag - $file:$line:$column\n$message\n", 15 | "info_format": ":: $info\n", 16 | "syntax": "Packages/Elm Language Support/Syntaxes/Elm Compile Messages.hidden-tmLanguage", 17 | "color_scheme": "Packages/Color Scheme - Default/Sunburst.tmTheme", 18 | "null_device": "/dev/null", 19 | "warnings": "true", 20 | "osx": 21 | { 22 | "path": "/usr/local/bin:$PATH" 23 | }, 24 | "linux": 25 | { 26 | "path": "$HOME/.cabal/bin:/usr/local/bin:$PATH" 27 | }, 28 | "variants": 29 | [ 30 | { 31 | "name": "Run", 32 | "cmd": 33 | [ 34 | "elm-make", 35 | "$file", 36 | "--output={output}", 37 | "--report=json", 38 | "--yes" 39 | ] 40 | }, 41 | { 42 | "name": "Ignore Warnings", 43 | "warnings": "false" 44 | }, 45 | { 46 | "name": "Run - Ignore Warnings", 47 | "warnings": "false", 48 | "cmd": 49 | [ 50 | "elm-make", 51 | "$file", 52 | "--output={output}", 53 | "--report=json", 54 | "--yes" 55 | ] 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /Commands/Project.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Elm Build System: View elm-package.json", 4 | "command": "elm_project" 5 | }, 6 | { 7 | "caption": "Elm Build System: Set Main Path", 8 | "command": "elm_project", "args": 9 | { 10 | "prop_name": "main_path", 11 | "caption": "Enter main path to build from: " 12 | } 13 | }, 14 | { 15 | "caption": "Elm Build System: Set HTML Path", 16 | "command": "elm_project", "args": 17 | { 18 | "prop_name": "html_path", 19 | "caption": "Enter HTML path to open in browser: " 20 | } 21 | }, 22 | { 23 | "caption": "Elm Build System: Set Output Path", 24 | "command": "elm_project", "args": 25 | { 26 | "prop_name": "output_path", 27 | "caption": "Enter path to build output to: " 28 | } 29 | }, 30 | { 31 | "caption": "Elm Build System: Set Output Directory", 32 | "command": "elm_project", "args": 33 | { 34 | "prop_name": "output_dir", 35 | "caption": "Enter directory to build output to: " 36 | } 37 | }, 38 | { 39 | "caption": "Elm Build System: Set Output Base Name", 40 | "command": "elm_project", "args": 41 | { 42 | "prop_name": "output_name", 43 | "caption": "Enter base name to build output with: " 44 | } 45 | }, 46 | { 47 | "caption": "Elm Build System: Set Output Extension", 48 | "command": "elm_project", "args": 49 | { 50 | "prop_name": "output_ext", 51 | "choices": 52 | [ 53 | "HTML", 54 | "JS" 55 | ] 56 | } 57 | } 58 | ] 59 | -------------------------------------------------------------------------------- /elm_format.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import subprocess 4 | import os, os.path 5 | import re 6 | import sublime, sublime_plugin 7 | 8 | 9 | class ElmFormatCommand(sublime_plugin.TextCommand): 10 | def run(self, edit): 11 | 12 | # Hide the console window on Windows 13 | shell = False 14 | path_separator = ':' 15 | if os.name == "nt": 16 | shell = True 17 | path_separator = ';' 18 | 19 | settings = sublime.load_settings('Elm Language Support.sublime-settings') 20 | binary = settings.get('elm_format_binary', 'elm-format') 21 | path = settings.get('elm_paths', '') 22 | if path: 23 | old_path = os.environ['PATH'] 24 | os.environ['PATH'] = os.path.expandvars(path + path_separator + '$PATH') 25 | 26 | command = [binary, self.view.file_name(), '--yes'] 27 | p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=shell) 28 | 29 | if path: 30 | os.environ['PATH'] = old_path 31 | 32 | output, errors = p.communicate() 33 | 34 | if settings.get('debug', False): 35 | string_settings = sublime.load_settings('Elm User Strings.sublime-settings') 36 | print(string_settings.get('logging.prefix', '') + '(' + binary + ') ' + str(output.strip()), '\nerrors: ' + str(errors.strip())) 37 | if str(errors.strip()): 38 | print('Your PATH is: ', os.environ['PATH']) 39 | 40 | 41 | class ElmFormatOnSave(sublime_plugin.EventListener): 42 | def on_post_save(self, view): 43 | sel = view.sel()[0] 44 | region = view.word(sel) 45 | scope = view.scope_name(region.b) 46 | if scope.find('source.elm') != -1: 47 | settings = sublime.load_settings('Elm Language Support.sublime-settings') 48 | if settings.get('elm_format_on_save', True): 49 | regex = settings.get('elm_format_filename_filter', '') 50 | if not (len(regex) > 0 and re.search(regex, view.file_name()) is not None): 51 | view.run_command('elm_format') 52 | -------------------------------------------------------------------------------- /Menus/Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "tools", 4 | "children": 5 | [{ 6 | "id": "SublimeREPL", 7 | "caption": "SublimeREPL", 8 | "mnemonic": "R", 9 | "children": 10 | [ 11 | { 12 | "caption": "Elm", 13 | "id": "repl_elm", 14 | "command": "repl_open", "args": 15 | { 16 | "type": "subprocess", 17 | "encoding": "utf8", 18 | "cmd": ["elm-repl"], 19 | "cwd": "$file_path", 20 | "syntax": "Packages/Elm Language Support/Syntaxes/Elm.tmLanguage", 21 | "external_id": "elm", 22 | "extend_env": 23 | { 24 | "osx": {"PATH": "{PATH}:/usr/local/bin"}, 25 | "linux": {"PATH": "{PATH}:/usr/local/bin:{HOME}/.cabal/bin"}, 26 | "windows": {} 27 | } 28 | } 29 | } 30 | ] 31 | }] 32 | }, 33 | { 34 | "id": "preferences", 35 | "children": 36 | [{ 37 | "id": "package-settings", 38 | "children": 39 | [ 40 | { 41 | "caption": "Elm Language Support", 42 | "children": 43 | [ 44 | { 45 | "caption": "Settings", 46 | "command": "edit_settings", 47 | "args": { 48 | "base_file": "${packages}/Elm Language Support/Settings/Elm Language Support.sublime-settings", 49 | "default": "// Settings in here override those in \"Elm Language Support/Settings/Elm Language Support.sublime-settings\",\n\n{\n\t$0\n}\n" 50 | } 51 | } 52 | ] 53 | } 54 | ] 55 | }] 56 | } 57 | ] 58 | -------------------------------------------------------------------------------- /elm_plugin.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import os.path as fs 4 | 5 | def is_ST2(): 6 | return sublime.version().startswith('2') 7 | 8 | def get_string(key, *args): 9 | strings = sublime.load_settings('Elm User Strings.sublime-settings') 10 | return strings.get('logging.prefix') + strings.get(key).format(*args) 11 | 12 | def log_string(key, *args): 13 | def log_string_with_retry(retry): 14 | try: 15 | # ST2: RuntimeError: Must call on main thread 16 | settings = sublime.load_settings('Elm Language Support.sublime-settings') 17 | except RuntimeError: 18 | if retry: 19 | sublime.set_timeout(lambda: log_string_with_retry(False), 0) 20 | else: 21 | import traceback 22 | traceback.print_exc() 23 | else: 24 | if settings.get('debug'): 25 | print(get_string(key, *args)) 26 | 27 | log_string_with_retry(True) 28 | 29 | def import_module(path): 30 | names = path.split('.') 31 | index = 1 if is_ST2() else 0 32 | base = __import__(names[index]) 33 | for name in names[index + 1:]: 34 | base = getattr(base, name) 35 | return base 36 | 37 | # defer import as long as possible in case plugin not loaded 38 | def replace_base_class(path): 39 | def splice_bases(old_base, *extra_bases): 40 | try: 41 | new_base = import_module(path) 42 | except ImportError: 43 | module_name = path[:path.index('.')] 44 | log_string('logging.missing_plugin', module_name) 45 | return None 46 | else: 47 | return (new_base,) + extra_bases 48 | 49 | def monkey_patch(target_cls): 50 | if not hasattr(target_cls, 'is_patched'): 51 | new_bases = splice_bases(target_cls.__bases__) 52 | target_cls.is_patched = bool(new_bases) 53 | if new_bases: 54 | target_cls.__bases__ = new_bases 55 | 56 | def decorator(target_cls): 57 | def new(cls, *args, **kwargs): 58 | monkey_patch(target_cls) 59 | super_ = super(target_cls, cls).__new__ 60 | # TypeError: object() takes no parameters 61 | return super_(cls) if super_ is object.__new__ else super_(cls, *args, **kwargs) 62 | 63 | assert '__new__' not in target_cls.__dict__ 64 | # ST2: TypeError: unbound method new() 65 | target_cls.__new__ = classmethod(new) 66 | return target_cls 67 | 68 | return decorator 69 | -------------------------------------------------------------------------------- /Syntaxes/Elm Compile Messages.YAML-tmLanguage: -------------------------------------------------------------------------------- 1 | # [PackageDev] target_format: plist, ext: hidden-tmLanguage 2 | --- 3 | name: Elm Compile Messages 4 | scopeName: text.html.mediawiki.elm-build-output 5 | fileTypes: [] 6 | uuid: 0e1a8891-7cc0-4991-8018-252d6f591f8f 7 | 8 | patterns: 9 | - comment: "|> Unparsed Compile Message" 10 | name: comment.line.heading.3.elm-build-output 11 | begin: '^(::) ' 12 | end: '^\n$' 13 | patterns: 14 | - comment: elm-lang/core OR build\index.html 15 | name: markup.underline.link.elm-build-output 16 | match: \S+[/\.]\S+ 17 | - comment: Successfully generated 18 | name: constant.language.boolean.true.elm-build-output 19 | match: (?i)\bsuccess\w+ 20 | - comment: -- TAG - file:line:column\nOverview\nDetail\n 21 | name: meta.report.elm-build-output 22 | contentName: string.unquoted.elm-build-output 23 | begin: |- 24 | (?x) # Minimally modified `file_regex` from `Elm Make.sublime-build` 25 | ^\-\-[ ] # Leading delimiter 26 | ((error) # \2: error 27 | |(warning) # \3: warning 28 | |\w+ # \1: any $type 29 | )[:][ ] # separator 30 | (.+) # \4: tag 31 | [ ][-][ ] # separator 32 | (.+?): # \5: $file 33 | (\d+): # \6: $line 34 | (\d+) # \7: $column 35 | \n$ # End 36 | beginCaptures: 37 | '0': {name: markup.heading.4.elm-build-output} 38 | '1': {name: support.constant.type.elm-build-output} 39 | '2': {name: invalid.illegal.error.elm-build-output} 40 | '3': {name: invalid.deprecated.warning.elm-build-output} 41 | '4': {name: support.constant.type.elm-build-output} 42 | '5': {name: markup.underline.link.elm-build-output} 43 | '6': {name: constant.numeric.elm-build-output} 44 | '7': {name: constant.numeric.elm-build-output} 45 | end: ^\n$ 46 | endCaptures: 47 | '0': {name: meta.separator.elm-build-output} 48 | patterns: 49 | - comment: 'Inline `variable`' 50 | name: markup.raw.inline.elm-build-output 51 | contentName: variable.other.elm.elm-build-output 52 | begin: (`)(?!`) 53 | end: \1 54 | captures: 55 | '0': {name: punctuation.definition.raw.elm-build-output} 56 | - comment: Code Block 57 | name: markup.raw.block.elm-build-output 58 | begin: (?m)^ {4} 59 | end: \n+(?!^ {4}) 60 | patterns: 61 | - include: source.elm 62 | - comment: [Finished in 4.2s] 63 | name: comment.line.brackets.elm-build-output 64 | begin: ^\[ 65 | end: \]$ 66 | patterns: 67 | - comment: 4.2s 68 | name: constant.numeric.elm-build-output 69 | match: \b\d+\.\d+(s)\b 70 | captures: 71 | '1': {name: keyword.other.unit.elm-build-output} 72 | ... 73 | -------------------------------------------------------------------------------- /elm_make.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | import string 4 | 5 | try: # ST3 6 | from .elm_plugin import * 7 | from .elm_project import ElmProject 8 | except: # ST2 9 | from elm_plugin import * 10 | from elm_project import ElmProject 11 | default_exec = import_module('Default.exec') 12 | 13 | @replace_base_class('Highlight Build Errors.HighlightBuildErrors.ExecCommand') 14 | class ElmMakeCommand(default_exec.ExecCommand): 15 | 16 | # inspired by: http://www.sublimetext.com/forum/viewtopic.php?t=12028 17 | def run(self, error_format, info_format, syntax, color_scheme, null_device, warnings, **kwargs): 18 | self.buffer = b'' 19 | self.warnings = warnings == "true" 20 | self.error_format = string.Template(error_format) 21 | self.info_format = string.Template(info_format) 22 | self.run_with_project(null_device=null_device, **kwargs) 23 | self.style_output(syntax, color_scheme) 24 | 25 | def run_with_project(self, cmd, working_dir, null_device, **kwargs): 26 | file_arg, output_arg = cmd[1:3] 27 | project = ElmProject(file_arg) 28 | log_string('project.logging.settings', repr(project)) 29 | if '{output}' in output_arg: 30 | cmd[1] = fs.expanduser(project.main_path) 31 | output_path = fs.expanduser(project.output_path) 32 | cmd[2] = output_arg.format(output=output_path) 33 | else: 34 | # cmd[1] builds active file rather than project main 35 | cmd[2] = output_arg.format(null=null_device) 36 | project_dir = project.working_dir or working_dir 37 | # ST2: TypeError: __init__() got an unexpected keyword argument 'syntax' 38 | super(ElmMakeCommand, self).run(cmd, working_dir=project_dir, **kwargs) 39 | 40 | def style_output(self, syntax, color_scheme): 41 | self.output_view.set_syntax_file(syntax) 42 | self.output_view.settings().set('color_scheme', color_scheme) 43 | if self.is_patched: 44 | self.debug_text = '' 45 | else: 46 | self.debug_text = get_string('make.missing_plugin') 47 | 48 | def on_data(self, proc, data): 49 | self.buffer += data 50 | 51 | def on_finished(self, proc): 52 | result_strs = self.buffer.decode(self.encoding).split('\n') 53 | flat_map = lambda f ,xss: sum(map(f, xss), []) 54 | output_strs = flat_map(self.format_result, result_strs) + [''] 55 | output_data = '\n'.join(output_strs).encode(self.encoding) 56 | super(ElmMakeCommand, self).on_data(proc, output_data) 57 | super(ElmMakeCommand, self).on_finished(proc) 58 | 59 | def format_result(self, result_str): 60 | decode_error = lambda dict: self.format_error(**dict) if 'type' in dict else dict 61 | try: 62 | data = json.loads(result_str, object_hook=decode_error) 63 | return [s for s in data if s is not None] 64 | except ValueError: 65 | log_string('make.logging.invalid_json', result_str) 66 | info_str = result_str.strip() 67 | return [self.info_format.substitute(info=info_str)] if info_str else [] 68 | 69 | def format_error(shelf, type, file, region, tag, overview, details, **kwargs): 70 | if type == 'warning' and not shelf.warnings: 71 | return None 72 | line = region['start']['line'] 73 | column = region['start']['column'] 74 | message = overview 75 | if details: 76 | message += '\n' + re.sub(r'(\n)+', r'\1', details) 77 | # TypeError: substitute() got multiple values for argument 'self' 78 | # https://bugs.python.org/issue23671 79 | return shelf.error_format.substitute(**locals()) 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Elm Language Support logo](images/logo.png) 2 | # The Sublime Elm Language Package 3 | 4 | ## Installation 5 | 6 | 1. Install [Package Control][] 7 | 2. Run `Package Control: Install Package` in the Command Palette (Super+Shift+P) 8 | 3. Install [Elm][] or use [NPM][] (`npm i -g elm`) 9 | 10 | ## Features 11 | 12 | - Compatible with [Sublime Text 2] and [Sublime Text 3] 13 | - Syntax highlighting 14 | - Snippets for common Elm syntax (function, `case` … `of`, `let` … `in`, etc.) 15 | - Autocompletions plus type signature and documentation display for all functions inside packages in your `elm-package.json` file (requires [elm-oracle](https://www.npmjs.com/package/elm-oracle), which you can install with `npm install -g elm-oracle`) 16 | 1. Bring up the type panel with `alt+up` or through the right-click context menu 17 | 2. Close the type panel with `alt+down` 18 | 3. If you don't like these keybindings, rebind them in your User packages directory 19 | ![autocompletions screenshot](images/completions.png)![type signature screenshot](images/elm_types.png)![type panel screenshot](images/type_panel.png) 20 | - Four standard build commands (Super+[Shift]+B or Super+[Shift]+F7) 21 | 1. `Build` just checks errors. Kudos to this [tweet][]! 22 | 2. `Run` additionally outputs your compiled program to an inferred path. 23 | 3. The same as the above two, but ignoring warnings 24 | 4. Output path is configurable in `elm-package.json` or `Elm Build System: …` in the Command Palette. Elm build system only requires a valid config in any ancestor directory of the active file. ![compile messages screenshot](images/elm_project.jpg) 25 | - Compile messages 26 | 1. Navigate errors and warnings (Super+[Shift]+F4). 27 | 2. Formatted for build output panel. 28 | 3. Compile message highlighting, embedded code highlighting, and color scheme for output panel. ![compile messages screenshot](images/elm_make.jpg) 29 | - Integration with popular plugins (installed separately) 30 | 1. [SublimeREPL][] — Run `elm-repl` in an editor tab with syntax highlighting. ![SublimeREPL screenshot](images/elm_repl.jpg) 31 | 2. [Highlight Build Errors][] — Does what it says on the box … usually. 32 | - Integration with [elm format](https://github.com/avh4/elm-format) 33 | 1. Make sure `elm-format` is in your PATH 34 | 2. Run the "Elm Language Support: Run elm-format" command from the Command Palette to run elm-format on the current file 35 | 3. To enable automatic formatting on every save, Go to Preferences -> Package Settings -> Elm Language Support -> User and add this setting: 36 | `"elm_format_on_save": true` 37 | 4. If there are certain Elm source files you don't want to automatically run `elm-format` on, for example elm-css based files, you can set a regex filter which will search the full filename (including the path to the file). If the regex matches, then it will not automatically run `elm-format` on the file when you save. For example, the following filter would prevent automatic `elm-format` on a file named `elm-css/src/Css/TopBar.elm`: 38 | `"elm_format_filename_filter": "elm-css/src/Css/.*\\.elm$"`![elm-format screenshot](images/elm_format.png) 39 | 40 | ## Troubleshooting 41 | 42 | - I have `elm-oracle` installed, but completions, type signature display, and the type panel don't work 43 | 1. Make sure `elm-oracle` is on your PATH, or 44 | 2. Add the absolute path of the directory containing `elm-oracle` to the `elm_paths` setting in your Elm Language Support User settings 45 | - I have `elm-format` installed, but it's not working 46 | 1. Make sure `elm-format` is on your PATH, or 47 | 2. If using an alternate name for the binary (`elm-format-0.17` or `elm-format-0.18`) add it to the `elm_format_binary` setting in your Elm Language Support User settings; an example might be `"elm_format_binary": "elm-format-0.18",`, or 48 | 3. Add the absolute path of the directory containing `elm-format` to the `elm_paths` setting in your Elm Language Support User settings. Note that you can combine paths with the above, so an example might be `"elm_paths": "/users/alex/elm-format:/users/alex/elm-oracle"` 49 | - Elm format automatically runs every time I save a file, but there are some files I don't want it to run on 50 | 1. If there are certain Elm source files you don't want to automatically run `elm-format` on, for example elm-css based files, you can set a regex filter which will search the full filename (including the path to the file). If the regex matches, then it will not automatically run `elm-format` on the file when you save. For example, the following filter would prevent automatic `elm-format` on a file named `elm-css/src/Css/TopBar.elm`: 51 | `"elm_format_filename_filter": "elm-css/src/Css/.*\\.elm$"` 52 | 53 | ## Learning 54 | 55 | Don't know Elm? Great first step! 56 | 57 | - [Elm Website][] 58 | - [elm-discuss group][] 59 | - [Pragmatic Studio: Building Web Apps with Elm][pragmatic] 60 | - [Elm Town Podcast][] 61 | 62 | [elm-discuss group]: https://groups.google.com/d/forum/elm-discuss 63 | [Elm]: http://elm-lang.org/install 64 | [Elm Town Podcast]: https://elmtown.github.io 65 | [Elm Website]: http://elm-lang.org 66 | [Highlight Build Errors]: https://packagecontrol.io/packages/Highlight%20Build%20Errors 67 | [NPM]: https://nodejs.org 68 | [Package Control]: https://packagecontrol.io/installation 69 | [pragmatic]: https://pragmaticstudio.com/elm 70 | [SublimeREPL]: https://packagecontrol.io/packages/SublimeREPL 71 | [Sublime Text 2]: http://www.sublimetext.com/2 72 | [Sublime Text 3]: http://www.sublimetext.com/3 73 | [tweet]: https://twitter.com/rtfeldman/status/624026168652660740 74 | -------------------------------------------------------------------------------- /Syntaxes/Elm Compile Messages.hidden-tmLanguage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fileTypes 6 | 7 | name 8 | Elm Compile Messages 9 | patterns 10 | 11 | 12 | begin 13 | ^(::) 14 | comment 15 | |> Unparsed Compile Message 16 | end 17 | ^\n$ 18 | name 19 | comment.line.heading.3.elm-build-output 20 | patterns 21 | 22 | 23 | comment 24 | elm-lang/core OR build\index.html 25 | match 26 | \S+[/\.]\S+ 27 | name 28 | markup.underline.link.elm-build-output 29 | 30 | 31 | comment 32 | Successfully generated 33 | match 34 | (?i)\bsuccess\w+ 35 | name 36 | constant.language.boolean.true.elm-build-output 37 | 38 | 39 | 40 | 41 | begin 42 | (?x) # Minimally modified `file_regex` from `Elm Make.sublime-build` 43 | ^\-\-[ ] # Leading delimiter 44 | ((error) # \2: error 45 | |(warning) # \3: warning 46 | |\w+ # \1: any $type 47 | )[:][ ] # separator 48 | (.+) # \4: tag 49 | [ ][-][ ] # separator 50 | (.+?): # \5: $file 51 | (\d+): # \6: $line 52 | (\d+) # \7: $column 53 | \n$ # End 54 | beginCaptures 55 | 56 | 0 57 | 58 | name 59 | markup.heading.4.elm-build-output 60 | 61 | 1 62 | 63 | name 64 | support.constant.type.elm-build-output 65 | 66 | 2 67 | 68 | name 69 | invalid.illegal.error.elm-build-output 70 | 71 | 3 72 | 73 | name 74 | invalid.deprecated.warning.elm-build-output 75 | 76 | 4 77 | 78 | name 79 | support.constant.type.elm-build-output 80 | 81 | 5 82 | 83 | name 84 | markup.underline.link.elm-build-output 85 | 86 | 6 87 | 88 | name 89 | constant.numeric.elm-build-output 90 | 91 | 7 92 | 93 | name 94 | constant.numeric.elm-build-output 95 | 96 | 97 | comment 98 | -- TAG - file:line:column\nOverview\nDetail\n 99 | contentName 100 | string.unquoted.elm-build-output 101 | end 102 | ^\n$ 103 | endCaptures 104 | 105 | 0 106 | 107 | name 108 | meta.separator.elm-build-output 109 | 110 | 111 | name 112 | meta.report.elm-build-output 113 | patterns 114 | 115 | 116 | begin 117 | (`)(?!`) 118 | captures 119 | 120 | 0 121 | 122 | name 123 | punctuation.definition.raw.elm-build-output 124 | 125 | 126 | comment 127 | Inline `variable` 128 | contentName 129 | variable.other.elm.elm-build-output 130 | end 131 | \1 132 | name 133 | markup.raw.inline.elm-build-output 134 | 135 | 136 | begin 137 | (?m)^ {4} 138 | comment 139 | Code Block 140 | end 141 | \n+(?!^ {4}) 142 | name 143 | markup.raw.block.elm-build-output 144 | patterns 145 | 146 | 147 | include 148 | source.elm 149 | 150 | 151 | 152 | 153 | 154 | 155 | begin 156 | ^\[ 157 | comment 158 | 159 | Finished in 4.2s 160 | 161 | end 162 | \]$ 163 | name 164 | comment.line.brackets.elm-build-output 165 | patterns 166 | 167 | 168 | captures 169 | 170 | 1 171 | 172 | name 173 | keyword.other.unit.elm-build-output 174 | 175 | 176 | comment 177 | 4.2s 178 | match 179 | \b\d+\.\d+(s)\b 180 | name 181 | constant.numeric.elm-build-output 182 | 183 | 184 | 185 | 186 | scopeName 187 | text.html.mediawiki.elm-build-output 188 | uuid 189 | 0e1a8891-7cc0-4991-8018-252d6f591f8f 190 | 191 | 192 | -------------------------------------------------------------------------------- /elm_generate.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import sys 4 | 5 | class Module(object): 6 | def __init__(self, data): 7 | self.name = data['name'] 8 | self.values = [name(v['raw']) + ' : ' + signature(v['raw']) for v in data['values']] 9 | self.valueNames = [name(v) for v in self.values] 10 | self.datatypes = [v['name'] for v in data['datatypes']] 11 | self.constructors = [[v['name'] for v in x['constructors']] for x in data['datatypes']] 12 | self.aliases = [v['name'] for v in data['aliases']] 13 | 14 | def include_text(self): 15 | s = '\n\tinclude\n\t#{}\n'.format(self.name.lower()) 16 | return s 17 | 18 | def moduleText(self): 19 | s = '{nameLower}\n\n\tcaptures\n\t\n\t\t1\n\t\t\n\t\t\tname\n\t\t\tvariable.parameter\n\t\t\n\t\t2\n\t\t\n\t\t\tname\n\t\t\tvariable.parameter\n\t\t\n\t\t3\n\t\t\n\t\t\tname\n\t\t\tsupport.function.elm\n\t\t\n\t\n\tmatch\n\t\\b({name})(.)({values})\\b\n\tname\n\tvariable.parameter\n' 20 | values = '|'.join([n for n in self.valueNames if not n.startswith('(')]) 21 | if self.aliases: 22 | values += '|' + '|'.join(self.aliases) 23 | if self.datatypes: 24 | values += '|' + '|'.join(self.datatypes) 25 | return s.format(nameLower=self.name.lower(), name=self.name, values=values) 26 | 27 | def snippets(self): 28 | base = 'Snippets' 29 | s = '\n\t\n\t\n\t{name}\n\t\n\tsource.elm\n\t{signature}\n' 30 | for v in [func for func in self.values if not name(func).startswith('(')]: 31 | subdirectories = self.name.split('.') 32 | path = '{}' + '\\{}'*(len(subdirectories)) 33 | path = path.format(base, *subdirectories) 34 | 35 | if not os.path.exists(path): 36 | os.makedirs(path) 37 | 38 | path += '\\{}' 39 | 40 | with open(path.format(name(v) + '.sublime-snippet'), 'w') as f: 41 | f.write(s.format(autocomplete=make_autocomplete(v), name=name(v), signature=signature(v))) 42 | 43 | print('Wrote {}'.format(path.format(name(v) + '.sublime-snippet'))) 44 | 45 | def name(t): 46 | return t.split(' : ')[0].strip() 47 | 48 | def signature(t): 49 | return t.split(' : ')[1].strip() 50 | 51 | def hintize(t): 52 | first = t[0].lower() 53 | t = t.replace(' ', '') 54 | return first + ''.join(t[1:]) 55 | 56 | def typeFormat(t): 57 | if t[0] == '[': 58 | return 'ListOf' + typeFormat(t[1:-1]) 59 | elif t[0] == '(': 60 | return ''.join([unicode(v.strip()) for v in t[1:-1].split(',')]) + 'Tuple' 61 | else: 62 | if len(t.split(' ')) == 1: 63 | return t 64 | else: 65 | x = t.split(' ') 66 | return x[0] + ''.join([typeFormat(v) for v in x[1:]]) 67 | 68 | def tokenize(t): 69 | return [v.strip() for v in t.split('->')] 70 | 71 | def print_type(t): 72 | print(name(t)) 73 | print([typeFormat(v) for v in tokenize(signature(t))]) 74 | 75 | def make_autocomplete(t): 76 | s = '{}'.format(name(t)) 77 | args = arguments(signature(t)) 78 | for n, arg in enumerate(args): 79 | s += ' ${{{n}:{arg}}}'.format(n=n+1, arg=arg) 80 | return s 81 | 82 | def arguments(signature): 83 | args = [v.strip() for v in signature.split('->')][:-1] 84 | new_args = [] 85 | open_parens = 0 86 | for arg in args: 87 | parens = arg.count('(') - arg.count(')') 88 | if parens and not open_parens: 89 | new_args.append('function') 90 | elif open_parens != 0: 91 | open_parens += parens 92 | continue 93 | else: 94 | new_args.append(argify(arg)) 95 | open_parens += parens 96 | return new_args 97 | 98 | def argify(s): 99 | if s.startswith('('): 100 | return 'tuple' 101 | elif s.startswith('['): 102 | return 'list' 103 | elif len(s.split(' ')) > 1: 104 | return s.split(' ')[0].lower() 105 | else: 106 | return s.lower() 107 | 108 | def loadDocs(path): 109 | with open(path) as f: 110 | return json.load(f) 111 | 112 | 113 | if __name__ == '__main__': 114 | ## Usage: pass in docs.json from cabal's elm directory 115 | path = sys.argv[1] 116 | prelude = ['Basics', 'List', 'Signal', 'Text', 'Maybe', 'Time', 'Graphics.Element', 'Color', 'Graphics.Collage'] 117 | 118 | modules = [Module(m) for m in loadDocs(path)] 119 | 120 | print('Prelude:') 121 | print('show|') 122 | for m in modules: 123 | if m.name in prelude: 124 | print('|'.join([n for n in m.valueNames if not n.startswith('(')])) 125 | 126 | print('\n'*5) 127 | 128 | print('Prelude Aliases and Datatypes:') 129 | print('Int|Float|Char|Bool|String|True|False') 130 | for m in modules: 131 | if m.name in prelude: 132 | print('|'.join([n for n in (m.datatypes + m.aliases) if not n.startswith('(')]) + '|') 133 | 134 | print('\n'*5) 135 | 136 | print('Includes:') 137 | for m in modules: 138 | print(m.include_text()) 139 | 140 | print('\n'*5) 141 | 142 | print('Includes Continued:') 143 | for m in modules: 144 | print(m.moduleText()) 145 | 146 | print('\n'*5) 147 | 148 | print('Constructors:') 149 | print('\(\)|\[\]|True|False|Int|Char|Bool|String|') 150 | for m in modules: 151 | if m.name in prelude: 152 | for c in m.constructors: 153 | print('|'.join(c) + '|') 154 | 155 | print('\n'*5) 156 | 157 | print('Writing Autocompletion Snippets...:') 158 | for m in modules: 159 | if m.name in prelude: 160 | m.snippets() 161 | print('\n'*2) 162 | 163 | with open('Snippets\\Basics\\markdown.sublime-snippet', 'w') as f: 164 | f.write('\n\n\nmarkdown\n\nsource.elm\nA markdown block\n') 165 | print('Wrote markdown.sublime-snippet') 166 | -------------------------------------------------------------------------------- /elm_project.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import json 3 | 4 | try: # ST3 5 | from .elm_plugin import * 6 | except: # ST2 7 | from elm_plugin import * 8 | 9 | class ElmProjectCommand(sublime_plugin.TextCommand): 10 | 11 | def is_enabled(self): 12 | self.project = ElmProject(self.view.file_name()) 13 | return self.project.exists 14 | 15 | def run(self, edit, prop_name=None, choices=None, caption=None): 16 | self.window = self.view.window() 17 | if not prop_name: 18 | self.window.open_file(self.project.json_path, sublime.TRANSIENT) 19 | return 20 | self.prop_name = prop_name 21 | initial_value = getattr(self.project, prop_name) 22 | if choices: 23 | self.show_choices(choices, initial_value) 24 | else: 25 | self.window.show_input_panel(caption, initial_value, self.on_finished, None, None) 26 | 27 | def show_choices(self, choices, initial_value): 28 | self.norm_choices = [choice.lower() for choice in choices] 29 | try: 30 | # ValueError: $initial_value is not in list 31 | initial_index = self.norm_choices.index(initial_value.lower()) 32 | # ST2: Boost.Python.ArgumentError: Python argument types 33 | self.window.show_quick_panel(choices, self.on_choice, selected_index=initial_index) 34 | except: # simplest control flow 35 | if not is_ST2(): 36 | log_string('project.logging.invalid_choice', initial_value) 37 | self.window.show_quick_panel(choices, self.on_choice) 38 | 39 | def on_choice(self, index): 40 | if index != -1: 41 | self.on_finished(self.norm_choices[index]) 42 | 43 | def on_finished(self, value): 44 | setattr(self.project, self.prop_name, value) 45 | keys = self.project._last_updated_key_path 46 | if keys: 47 | sublime.status_message(get_string('project.updated', '.'.join(keys), value)) 48 | 49 | BUILD_KEY = ('sublime-build',) 50 | MAIN_KEY = BUILD_KEY + ('main',) 51 | HTML_KEY = BUILD_KEY + ('html',) 52 | OUTPUT_KEY = BUILD_KEY + ('output',) 53 | OUTPUT_PATH_KEY = OUTPUT_KEY + ('path',) 54 | OUTPUT_COMP_KEY = OUTPUT_KEY + ('components',) 55 | OUTPUT_DIR_KEY = OUTPUT_COMP_KEY + ('dir',) 56 | OUTPUT_NAME_KEY = OUTPUT_COMP_KEY + ('name',) 57 | OUTPUT_EXT_KEY = OUTPUT_COMP_KEY + ('ext',) 58 | 59 | class ElmProject(object): 60 | 61 | @classmethod 62 | def find_json(cls, dir_path): 63 | if not fs.isdir(fs.abspath(dir_path)): 64 | return None 65 | file_path = fs.abspath(fs.join(dir_path, 'elm-package.json')) 66 | if fs.isfile(file_path): 67 | return file_path 68 | parent_path = fs.join(dir_path, fs.pardir) 69 | if fs.abspath(parent_path) == fs.abspath(dir_path): 70 | return None 71 | return cls.find_json(parent_path) 72 | 73 | def __init__(self, file_path): 74 | self.file_path = file_path 75 | self.json_path = self.find_json(fs.dirname(file_path or '')) 76 | self.data_dict = self.load_json() 77 | 78 | def __getitem__(self, keys): 79 | if not self.exists: 80 | return None 81 | item = self.data_dict 82 | for key in keys: 83 | item = item.get(key) 84 | if not item: 85 | break 86 | return item 87 | 88 | def __setitem__(self, keys, value): 89 | self._last_updated_key_path = None 90 | if not self.exists: 91 | sublime.error_message(get_string('project.not_found')) 92 | return 93 | item = self.data_dict 94 | for key in keys[0:-1]: 95 | item = item.setdefault(key, {}) 96 | item[keys[-1]] = value 97 | self.save_json() 98 | self._last_updated_key_path = keys 99 | 100 | def __repr__(self): 101 | members = [(name, getattr(self, name), ' ' * 4) 102 | for name in dir(self) if name[0] != '_'] 103 | properties = ["{indent}{name}={value},".format(**locals()) 104 | for name, value, indent in members if not callable(value)] 105 | return "{0}(\n{1}\n)".format(self.__class__.__name__, '\n'.join(properties)) 106 | 107 | def load_json(self): 108 | try: 109 | with open(self.json_path) as json_file: 110 | if is_ST2(): # AttributeError: 'module' object has no attribute 'OrderedDict' 111 | return json.load(json_file) 112 | else: 113 | return json.load(json_file, object_pairs_hook=collections.OrderedDict) 114 | except TypeError: # self.json_path == None 115 | pass 116 | except ValueError: 117 | log_string('project.logging.invalid_json', self.json_path) 118 | return None 119 | 120 | def save_json(self): 121 | with open(self.json_path, 'w') as json_file: 122 | json.dump(self.data_dict, json_file, 123 | indent=4, 124 | separators=(',', ': '), 125 | sort_keys=is_ST2()) 126 | 127 | @property 128 | def exists(self): 129 | return bool(self.data_dict) 130 | 131 | @property 132 | def working_dir(self): 133 | return fs.dirname(self.json_path) if self.json_path else None 134 | 135 | @property 136 | def main_path(self): 137 | return self[MAIN_KEY] or fs.relpath(self.file_path, self.working_dir) 138 | 139 | @main_path.setter 140 | def main_path(self, value): 141 | self[MAIN_KEY] = value 142 | 143 | @property 144 | def html_path(self): 145 | return self[HTML_KEY] or self.output_path 146 | 147 | @html_path.setter 148 | def html_path(self, value): 149 | self[HTML_KEY] = value 150 | 151 | @property 152 | def output_path(self): 153 | output_path = fs.join(self.output_dir, self.output_name + '.' + self.output_ext) 154 | return self[OUTPUT_PATH_KEY] or fs.normpath(output_path) 155 | 156 | @output_path.setter 157 | def output_path(self, value): 158 | self[OUTPUT_PATH_KEY] = value 159 | 160 | @property 161 | def output_dir(self): 162 | return self[OUTPUT_DIR_KEY] or 'build' 163 | 164 | @output_dir.setter 165 | def output_dir(self, value): 166 | self[OUTPUT_DIR_KEY] = value 167 | 168 | @property 169 | def output_name(self): 170 | return self[OUTPUT_NAME_KEY] or fs.splitext(fs.basename(self.main_path))[0] 171 | 172 | @output_name.setter 173 | def output_name(self, value): 174 | self[OUTPUT_NAME_KEY] = value 175 | 176 | @property 177 | def output_ext(self): 178 | return self[OUTPUT_EXT_KEY] or 'html' 179 | 180 | @output_ext.setter 181 | def output_ext(self, value): 182 | self[OUTPUT_EXT_KEY] = value 183 | -------------------------------------------------------------------------------- /Syntaxes/Elm.YAML-tmLanguage: -------------------------------------------------------------------------------- 1 | # [PackageDev] target_format: plist, ext: tmLanguage 2 | name: Elm 3 | scopeName: source.elm 4 | fileTypes: [elm] 5 | uuid: 2cb90e5e-6e98-456d-9a8a-b59935dbc4b0 6 | 7 | patterns: 8 | - name: keyword.operator.function.infix.elm 9 | match: (`)[a-zA-Z_']*?(`) 10 | captures: 11 | '1': {name: punctuation.definition.entity.elm} 12 | '2': {name: punctuation.definition.entity.elm} 13 | 14 | - name: constant.language.unit.elm 15 | match: \(\) 16 | 17 | - name: meta.declaration.module.elm 18 | begin: ^\b((effect|port)\s+)?(module)\s+ 19 | beginCaptures: 20 | '1': {name: keyword.other.elm} 21 | '3': {name: keyword.other.elm} 22 | end: $|; 23 | endCaptures: 24 | '1': {name: keyword.other.elm} 25 | patterns: 26 | - include: '#module_name' 27 | - begin: (where)\s*\{ 28 | beginCaptures: 29 | '1': {name: keyword.other.elm} 30 | end: \} 31 | patterns: 32 | - include: '#type_signature' 33 | - name: keyword.other.elm 34 | match: (exposing) 35 | - include: '#module_exports' 36 | - name: keyword.other.elm 37 | match: (where) 38 | - name: invalid 39 | match: '[a-z]+' 40 | 41 | - name: meta.import.elm 42 | begin: ^\b(import)\s+((open)\s+)? 43 | beginCaptures: 44 | '1': {name: keyword.other.elm} 45 | '3': {name: invalid} 46 | end: ($|;) 47 | patterns: 48 | - name: keyword.import.elm 49 | match: (as|exposing) 50 | - include: '#module_name' 51 | - include: '#module_exports' 52 | 53 | - name: entity.glsl.elm 54 | begin: (\[)(glsl)(\|) 55 | beginCaptures: 56 | '1': {name: keyword.other.elm} 57 | '2': {name: support.function.prelude.elm} 58 | '3': {name: keyword.other.elm} 59 | end: (\|\]) 60 | endCaptures: 61 | '1': {name: keyword.other.elm} 62 | patterns: 63 | - include: source.glsl 64 | 65 | - name: keyword.other.elm 66 | match: \b(type alias|type|case|of|let|in|as)\s+ 67 | 68 | - name: keyword.control.elm 69 | match: \b(if|then|else)\s+ 70 | 71 | - comment: Floats are always decimal 72 | name: constant.numeric.float.elm 73 | match: \b([0-9]+\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+)\b 74 | 75 | - name: constant.numeric.elm 76 | match: \b([0-9]+)\b 77 | 78 | - name: string.quoted.double.elm 79 | begin: '"""' 80 | beginCaptures: 81 | '0': {name: punctuation.definition.string.begin.elm} 82 | end: '"""' 83 | endCaptures: 84 | '0': {name: punctuation.definition.string.end.elm} 85 | patterns: 86 | - name: constant.character.escape.elm 87 | 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\\'\&]) 88 | - name: constant.character.escape.control.elm 89 | match: \^[A-Z@\[\]\\\^_] 90 | 91 | - name: string.quoted.double.elm 92 | begin: '"' 93 | beginCaptures: 94 | '0': {name: punctuation.definition.string.begin.elm} 95 | end: '"' 96 | endCaptures: 97 | '0': {name: punctuation.definition.string.end.elm} 98 | patterns: 99 | - name: constant.character.escape.elm 100 | 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\\\"'\&]) 101 | - name: constant.character.escape.control.elm 102 | match: \^[A-Z@\[\]\\\^_] 103 | 104 | - name: string.quoted.single.elm 105 | match: "(?x)\n(')\n(?:\n\t[\\ -\\[\\]-~]\t\t\t\t\t\t\t\t# Basic Char\n | (\\\\\ 106 | (?:NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE\n\t\t|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS\n\ 107 | \t\t|US|SP|DEL|[abfnrtv\\\\\\\"'\\&]))\t\t# Escapes\n | (\\^[A-Z@\\[\\]\\\\\\\ 108 | ^_])\t\t\t\t\t\t# Control Chars\n)\n(')" 109 | captures: 110 | '1': {name: punctuation.definition.string.begin.elm} 111 | '2': {name: constant.character.escape.elm} 112 | '3': {name: punctuation.definition.string.end.elm} 113 | 114 | - name: meta.function.type-declaration.elm 115 | begin: ^(port\s+)?([a-z_][a-zA-Z0-9_']*|\([|!%$+\-.,=]+\))\s*((:)([:]+)?) 116 | beginCaptures: 117 | '1': {name: keyword.other.port.elm} 118 | '2': {name: entity.name.function.elm} 119 | '4': {name: keyword.other.colon.elm} 120 | '5': {name: invalid} 121 | end: $\n? 122 | patterns: 123 | - include: '#type_signature' 124 | 125 | - name: keyword.other.port.elm 126 | match: \bport\s+ 127 | 128 | - name: constant.other.elm 129 | match: \b[A-Z]\w*\b 130 | 131 | - include: '#comments' 132 | 133 | - name: entity.name.function.elm 134 | match: ^[a-z][A-Za-z0-9_']*\s+ 135 | 136 | - include: '#infix_op' 137 | 138 | - name: keyword.operator.elm 139 | match: '[|!%$?~+:\-.=&\\*^]+' 140 | 141 | # Note: Sublime color schemes consistently decline to apply colors to 142 | # delimiters of literals. This does Elm users a disservice since 143 | # Elm's function application syntax thrives on a clear visual distinction 144 | # between literal delimiters and expressions. 145 | # We therefore go out of our way to make sure delimiters are colorized. 146 | - name: constant.language.delimiter.elm 147 | match: '([\[\]\{\},])' 148 | captures: 149 | '1': {name: support.function.delimiter.elm} 150 | 151 | - name: keyword.other.parenthesis.elm 152 | match: '([\(\)])' 153 | 154 | repository: 155 | block_comment: 156 | name: comment.block.elm 157 | begin: \{-(?!#) 158 | end: -\} 159 | captures: 160 | '0': {name: punctuation.definition.comment.elm} 161 | patterns: 162 | - include: '#block_comment' 163 | applyEndPatternLast: 1 164 | 165 | comments: 166 | patterns: 167 | - name: comment.line.double-dash.elm 168 | match: (--).*$\n? 169 | captures: 170 | '1': {name: punctuation.definition.comment.elm} 171 | - include: '#block_comment' 172 | 173 | infix_op: 174 | name: entity.name.function.infix.elm 175 | match: (\([|!%$+:\-.=]+\)|\(,+\)) 176 | 177 | module_exports: 178 | name: meta.declaration.exports.elm 179 | begin: \( 180 | end: \) 181 | patterns: 182 | - name: entity.name.function.elm 183 | match: \b[a-z][a-zA-Z_'0-9]* 184 | - name: storage.type.elm 185 | match: \b[A-Z][A-Za-z_'0-9]* 186 | - name: punctuation.separator.comma.elm 187 | match: ',' 188 | - include: '#infix_op' 189 | - comment: So named because I don't know what to call this. 190 | name: meta.other.unknown.elm 191 | match: \(.*?\) 192 | 193 | module_name: 194 | name: support.other.module.elm 195 | match: '[A-Z][A-Za-z._'']*' 196 | 197 | type_signature: 198 | patterns: 199 | - name: meta.class-constraint.elm 200 | match: \(\s*([A-Z][A-Za-z]*)\s+([a-z][A-Za-z_']*)\)\s*(=>) 201 | captures: 202 | '1': {name: entity.other.inherited-class.elm} 203 | '2': {name: variable.other.generic-type.elm} 204 | '3': {name: keyword.other.big-arrow.elm} 205 | - name: keyword.other.arrow.elm 206 | match: -> 207 | - name: keyword.other.big-arrow.elm 208 | match: => 209 | - name: variable.other.generic-type.elm 210 | match: \b[a-z][a-zA-Z0-9_']*\b 211 | - name: storage.type.elm 212 | match: \b[A-Z][a-zA-Z0-9_']*\b 213 | - name: support.constant.unit.elm 214 | match: \(\) 215 | - include: '#comments' 216 | -------------------------------------------------------------------------------- /elm_show_type.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import webbrowser 4 | import os, os.path 5 | import subprocess 6 | import json 7 | import re 8 | from difflib import SequenceMatcher 9 | 10 | import sublime, sublime_plugin 11 | 12 | try: # ST3 13 | from .elm_project import ElmProject 14 | except: # ST2 15 | from elm_project import ElmProject 16 | 17 | LOOKUPS = {} 18 | 19 | def join_qualified(region, view): 20 | """ 21 | Given a region, expand outward on periods to return a new region defining 22 | the entire word, in the context of Elm syntax. 23 | 24 | For example, when the region encompasses the 'map' part of a larger 25 | 'Dict.map' word, this function will return the entire region encompassing 26 | 'Dict.map'. The same is true if the region is encompassing 'Dict'. 27 | 28 | Recursively expands outward in both directions, correctly returning longer 29 | constructions such as 'Graphics.Input.button' 30 | """ 31 | starting_region = region 32 | prefix = view.substr(region.a - 1) 33 | suffix = view.substr(region.b) 34 | if prefix == '.': 35 | region = region.cover(view.word(region.a - 2)) 36 | if suffix == '.': 37 | region = region.cover(view.word(region.b + 1)) 38 | 39 | if region == starting_region: 40 | return region 41 | else: 42 | return join_qualified(region, view) 43 | 44 | def get_word_under_cursor(view): 45 | sel = view.sel()[0] 46 | region = join_qualified(view.word(sel), view) 47 | return view.substr(region).strip() 48 | 49 | def get_type(view, panel): 50 | """ 51 | Given a view, return the type signature of the word under the cursor, 52 | if found. If no type is found, return an empty string. Write the info 53 | to an output panel. 54 | """ 55 | sel = view.sel()[0] 56 | region = join_qualified(view.word(sel), view) 57 | scope = view.scope_name(region.b) 58 | if scope.find('source.elm') != -1 and scope.find('string') == -1 and scope.find('comment') == -1: 59 | filename = view.file_name() 60 | word = view.substr(region).strip() 61 | sublime.set_timeout_async(lambda: search_and_set_status_message(filename, word, panel, 0), 0) 62 | 63 | def search_and_set_status_message(filename, query, panel, tries): 64 | """ 65 | Given a filename and a query, look up in the in-memory dict of values 66 | pulled from elm oracle to find a match. If a match is found, display 67 | the type signature in the status bar and set it in the output panel. 68 | """ 69 | global LOOKUPS 70 | if len(query) == 0: 71 | return None 72 | if filename not in LOOKUPS.keys(): 73 | if tries >= 10: 74 | return None 75 | else: 76 | # if the filename is not found loaded into memory, it's probably being 77 | # loaded into memory right now. Try 10 more times at 100ms intervals 78 | # and if it still isn't loaded, there's likely a problem we can't fix 79 | # here. 80 | sublime.set_timeout_async(search_and_set_status_message(filename, query, panel, tries + 1), 100) 81 | else: 82 | data = LOOKUPS[filename] 83 | if len(data) > 0: 84 | matches = [item for item in data if item['name'] == query.split('.')[-1]] 85 | if len(matches) == 0: 86 | return None 87 | else: 88 | # sort matches by similarity to query 89 | matches.sort(key=lambda x: SequenceMatcher(None, query, x['fullName']).ratio(), reverse=True) 90 | item = matches[0] 91 | type_signature = item['fullName'] + ' : ' + item['signature'] 92 | sublime.status_message(type_signature) 93 | panel.run_command('erase_view') 94 | # add full name and type annotation 95 | panel_output = '`' + type_signature + '`' + '\n\n' + item['comment'][1:] 96 | # replace backticks with no-width space for syntax highlighting 97 | panel_output = panel_output.replace('`', '\uFEFF') 98 | # add no-width space to beginning and end of code blocks for syntax highlighting 99 | panel_output = re.sub('\n( {4}[\s\S]+?)((?=\n\S)\n|\Z)', '\uFEFF\n\\1\uFEFF\n', panel_output) 100 | # remove first four spaces on each line from code blocks 101 | panel_output = re.sub('\n {4}', '\n', panel_output) 102 | panel.run_command('append', {'characters': panel_output}) 103 | return None 104 | 105 | def get_matching_names(filename, prefix): 106 | """ 107 | Given a file name and a search prefix, return a list of matching 108 | completions from elm oracle. 109 | """ 110 | def skip_chars(full_name): 111 | # Sublime Text seems to have odd behavior on completions. If the full 112 | # name is at the same "path level" as the prefix, then the completion 113 | # will replace the entire entry, otherwise it will only replace after 114 | # the final period separator 115 | full_name_path = full_name.split('.')[:-1] 116 | prefix_path = prefix.split('.')[:-1] 117 | if full_name_path == prefix_path: 118 | return full_name 119 | else: 120 | # get the characters to remove from the completion to avoid duplication 121 | # of paths. If it's 0, then stay at 0, otherwise add a period back 122 | chars_to_skip = len('.'.join(prefix_path)) 123 | if chars_to_skip > 0: 124 | chars_to_skip += 1 125 | return full_name[chars_to_skip:] 126 | 127 | global LOOKUPS 128 | if filename not in LOOKUPS.keys(): 129 | return None 130 | else: 131 | data = LOOKUPS[filename] 132 | completions = {(v['fullName'] + '\t' + v['signature'], skip_chars(v['fullName'])) 133 | for v in data 134 | if v['fullName'].startswith(prefix) or v['name'].startswith(prefix)} 135 | return [[v[0], v[1]] for v in completions] 136 | 137 | def explore_package(filename, package_name): 138 | global LOOKUPS 139 | if filename not in LOOKUPS.keys() or len(package_name) == 0: 140 | return None 141 | elif package_name[0].upper() != package_name[0]: 142 | sublime.status_message('This is not a package!') 143 | return None 144 | else: 145 | def open_link(items, i): 146 | if i == -1: 147 | return None 148 | else: 149 | open_in_browser(items[i][3]) 150 | data = [[v['fullName'], v['signature'], v['comment'], v['href']] 151 | for v in LOOKUPS[filename] 152 | if v['fullName'].startswith(package_name)] 153 | # all items must be the same number of rows 154 | n = 75 155 | panel_items = [v[:2] + [v[2][:n]] + [v[2][n:2*n]] + [v[2][2*n:]] for v in data] 156 | sublime.active_window().show_quick_panel(panel_items, lambda i: open_link(data, i)) 157 | 158 | def open_in_browser(url): 159 | webbrowser.open_new_tab(url) 160 | 161 | def load_from_oracle(filename): 162 | """ 163 | Loads all data about the current file from elm oracle and adds it 164 | to the LOOKUPS global dictionary. 165 | """ 166 | global LOOKUPS 167 | project = ElmProject(filename) 168 | if project.working_dir is None: 169 | return 170 | os.chdir(project.working_dir) 171 | 172 | # Hide the console window on Windows 173 | shell = False 174 | path_separator = ':' 175 | if os.name == "nt": 176 | shell = True 177 | path_separator = ';' 178 | 179 | settings = sublime.load_settings('Elm Language Support.sublime-settings') 180 | path = settings.get('elm_paths', '') 181 | if path: 182 | old_path = os.environ['PATH'] 183 | os.environ["PATH"] = os.path.expandvars(path + path_separator + '$PATH') 184 | 185 | p = subprocess.Popen(['elm-oracle', filename, ''], stdout=subprocess.PIPE, 186 | stderr=subprocess.PIPE, shell=shell) 187 | 188 | if path: 189 | os.environ['PATH'] = old_path 190 | 191 | output, errors = p.communicate() 192 | output = output.strip() 193 | if settings.get('debug', False): 194 | string_settings = sublime.load_settings('Elm User Strings.sublime-settings') 195 | print(string_settings.get('logging.prefix', '') + '(elm-oracle) ' + str(output), '\nerrors: ' + str(errors.strip())) 196 | if str(errors.strip()): 197 | print('Your PATH is: ', os.environ['PATH']) 198 | try: 199 | data = json.loads(output.decode('utf-8')) 200 | except ValueError: 201 | return None 202 | LOOKUPS[filename] = data 203 | 204 | def view_load(view): 205 | """ 206 | Selectively calls load_from_oracle based on the current scope. 207 | """ 208 | 209 | if view.file_name() is None: 210 | return; 211 | 212 | sel = view.sel()[0] 213 | region = join_qualified(view.word(sel), view) 214 | scope = view.scope_name(region.b) 215 | if scope.find('source.elm') != -1: 216 | load_from_oracle(view.file_name()) 217 | 218 | 219 | class ElmOracleListener(sublime_plugin.EventListener): 220 | """ 221 | An event listener to load and search through data from elm oracle. 222 | """ 223 | 224 | def on_selection_modified_async(self, view): 225 | sel = view.sel()[0] 226 | region = join_qualified(view.word(sel), view) 227 | scope = view.scope_name(region.b) 228 | if scope.find('source.elm') != -1: 229 | view.run_command('elm_show_type') 230 | 231 | def on_activated_async(self, view): 232 | view_load(view) 233 | 234 | def on_post_save_async(self, view): 235 | view_load(view) 236 | 237 | def on_query_completions(self, view, prefix, locations): 238 | word = get_word_under_cursor(view) 239 | return get_matching_names(view.file_name(), word) 240 | 241 | 242 | class ElmShowType(sublime_plugin.TextCommand): 243 | """ 244 | A text command to lookup the type signature of the function under the 245 | cursor, and display it in the status bar if found. 246 | """ 247 | type_panel = None 248 | 249 | def run(self, edit, panel=False): 250 | if self.type_panel is None: 251 | self.type_panel = self.view.window().create_output_panel('elm_type') 252 | if os.name == "nt": 253 | # using extension hide-tmLanguage because hidden-tmLanguage doesn't work correctly 254 | self.type_panel.set_syntax_file('Packages/Elm Language Support/Syntaxes/Elm Documentation.hide-tmLanguage') 255 | else: 256 | self.type_panel.set_syntax_file('Packages/Elm Language Support/Syntaxes/Elm Documentation.hidden-tmLanguage') 257 | get_type(self.view, self.type_panel) 258 | if panel: 259 | self.view.window().run_command('elm_show_type_panel') 260 | 261 | 262 | class ElmShowTypePanel(sublime_plugin.WindowCommand): 263 | """ 264 | Turns on the type output panel 265 | """ 266 | def run(self): 267 | self.window.run_command("show_panel", {"panel": "output.elm_type"}) 268 | 269 | 270 | class ElmOracleExplore(sublime_plugin.TextCommand): 271 | def run(self, edit): 272 | word = get_word_under_cursor(self.view) 273 | parts = [part for part in word.split('.') if part[0].upper() == part[0]] 274 | package_name = '.'.join(parts) 275 | explore_package(self.view.file_name(), package_name) 276 | 277 | 278 | class EraseView(sublime_plugin.TextCommand): 279 | """ 280 | Erases a view 281 | """ 282 | def run(self, edit): 283 | self.view.erase(edit, sublime.Region(0, self.view.size())) 284 | -------------------------------------------------------------------------------- /Syntaxes/Elm.tmLanguage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fileTypes 6 | 7 | elm 8 | 9 | name 10 | Elm 11 | patterns 12 | 13 | 14 | captures 15 | 16 | 1 17 | 18 | name 19 | punctuation.definition.entity.elm 20 | 21 | 2 22 | 23 | name 24 | punctuation.definition.entity.elm 25 | 26 | 27 | match 28 | (`)[a-zA-Z_']*?(`) 29 | name 30 | keyword.operator.function.infix.elm 31 | 32 | 33 | match 34 | \(\) 35 | name 36 | constant.language.unit.elm 37 | 38 | 39 | begin 40 | ^\b((effect|port)\s+)?(module)\s+ 41 | beginCaptures 42 | 43 | 1 44 | 45 | name 46 | keyword.other.elm 47 | 48 | 3 49 | 50 | name 51 | keyword.other.elm 52 | 53 | 54 | end 55 | $|; 56 | endCaptures 57 | 58 | 1 59 | 60 | name 61 | keyword.other.elm 62 | 63 | 64 | name 65 | meta.declaration.module.elm 66 | patterns 67 | 68 | 69 | include 70 | #module_name 71 | 72 | 73 | begin 74 | (where)\s*\{ 75 | beginCaptures 76 | 77 | 1 78 | 79 | name 80 | keyword.other.elm 81 | 82 | 83 | end 84 | \} 85 | patterns 86 | 87 | 88 | include 89 | #type_signature 90 | 91 | 92 | 93 | 94 | match 95 | (exposing) 96 | name 97 | keyword.other.elm 98 | 99 | 100 | include 101 | #module_exports 102 | 103 | 104 | match 105 | (where) 106 | name 107 | keyword.other.elm 108 | 109 | 110 | match 111 | [a-z]+ 112 | name 113 | invalid 114 | 115 | 116 | 117 | 118 | begin 119 | ^\b(import)\s+((open)\s+)? 120 | beginCaptures 121 | 122 | 1 123 | 124 | name 125 | keyword.other.elm 126 | 127 | 3 128 | 129 | name 130 | invalid 131 | 132 | 133 | end 134 | ($|;) 135 | name 136 | meta.import.elm 137 | patterns 138 | 139 | 140 | match 141 | (as|exposing) 142 | name 143 | keyword.import.elm 144 | 145 | 146 | include 147 | #module_name 148 | 149 | 150 | include 151 | #module_exports 152 | 153 | 154 | 155 | 156 | begin 157 | (\[)(glsl)(\|) 158 | beginCaptures 159 | 160 | 1 161 | 162 | name 163 | keyword.other.elm 164 | 165 | 2 166 | 167 | name 168 | support.function.prelude.elm 169 | 170 | 3 171 | 172 | name 173 | keyword.other.elm 174 | 175 | 176 | end 177 | (\|\]) 178 | endCaptures 179 | 180 | 1 181 | 182 | name 183 | keyword.other.elm 184 | 185 | 186 | name 187 | entity.glsl.elm 188 | patterns 189 | 190 | 191 | include 192 | source.glsl 193 | 194 | 195 | 196 | 197 | match 198 | \b(type alias|type|case|of|let|in|as)\s+ 199 | name 200 | keyword.other.elm 201 | 202 | 203 | match 204 | \b(if|then|else)\s+ 205 | name 206 | keyword.control.elm 207 | 208 | 209 | comment 210 | Floats are always decimal 211 | match 212 | \b([0-9]+\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+)\b 213 | name 214 | constant.numeric.float.elm 215 | 216 | 217 | match 218 | \b([0-9]+)\b 219 | name 220 | constant.numeric.elm 221 | 222 | 223 | begin 224 | """ 225 | beginCaptures 226 | 227 | 0 228 | 229 | name 230 | punctuation.definition.string.begin.elm 231 | 232 | 233 | end 234 | """ 235 | endCaptures 236 | 237 | 0 238 | 239 | name 240 | punctuation.definition.string.end.elm 241 | 242 | 243 | name 244 | string.quoted.double.elm 245 | patterns 246 | 247 | 248 | match 249 | \\(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\\'\&]) 250 | name 251 | constant.character.escape.elm 252 | 253 | 254 | match 255 | \^[A-Z@\[\]\\\^_] 256 | name 257 | constant.character.escape.control.elm 258 | 259 | 260 | 261 | 262 | begin 263 | " 264 | beginCaptures 265 | 266 | 0 267 | 268 | name 269 | punctuation.definition.string.begin.elm 270 | 271 | 272 | end 273 | " 274 | endCaptures 275 | 276 | 0 277 | 278 | name 279 | punctuation.definition.string.end.elm 280 | 281 | 282 | name 283 | string.quoted.double.elm 284 | patterns 285 | 286 | 287 | match 288 | \\(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\\\"'\&]) 289 | name 290 | constant.character.escape.elm 291 | 292 | 293 | match 294 | \^[A-Z@\[\]\\\^_] 295 | name 296 | constant.character.escape.control.elm 297 | 298 | 299 | 300 | 301 | captures 302 | 303 | 1 304 | 305 | name 306 | punctuation.definition.string.begin.elm 307 | 308 | 2 309 | 310 | name 311 | constant.character.escape.elm 312 | 313 | 3 314 | 315 | name 316 | punctuation.definition.string.end.elm 317 | 318 | 319 | match 320 | (?x) 321 | (') 322 | (?: 323 | [\ -\[\]-~] # Basic Char 324 | | (\\(?:NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE 325 | |DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS 326 | |US|SP|DEL|[abfnrtv\\\"'\&])) # Escapes 327 | | (\^[A-Z@\[\]\\\^_]) # Control Chars 328 | ) 329 | (') 330 | name 331 | string.quoted.single.elm 332 | 333 | 334 | begin 335 | ^(port\s+)?([a-z_][a-zA-Z0-9_']*|\([|!%$+\-.,=</>]+\))\s*((:)([:]+)?) 336 | beginCaptures 337 | 338 | 1 339 | 340 | name 341 | keyword.other.port.elm 342 | 343 | 2 344 | 345 | name 346 | entity.name.function.elm 347 | 348 | 4 349 | 350 | name 351 | keyword.other.colon.elm 352 | 353 | 5 354 | 355 | name 356 | invalid 357 | 358 | 359 | end 360 | $\n? 361 | name 362 | meta.function.type-declaration.elm 363 | patterns 364 | 365 | 366 | include 367 | #type_signature 368 | 369 | 370 | 371 | 372 | match 373 | \bport\s+ 374 | name 375 | keyword.other.port.elm 376 | 377 | 378 | match 379 | \b[A-Z]\w*\b 380 | name 381 | constant.other.elm 382 | 383 | 384 | include 385 | #comments 386 | 387 | 388 | match 389 | ^[a-z][A-Za-z0-9_']*\s+ 390 | name 391 | entity.name.function.elm 392 | 393 | 394 | include 395 | #infix_op 396 | 397 | 398 | match 399 | [|!%$?~+:\-.=</>&\\*^]+ 400 | name 401 | keyword.operator.elm 402 | 403 | 404 | captures 405 | 406 | 1 407 | 408 | name 409 | support.function.delimiter.elm 410 | 411 | 412 | match 413 | ([\[\]\{\},]) 414 | name 415 | constant.language.delimiter.elm 416 | 417 | 418 | match 419 | ([\(\)]) 420 | name 421 | keyword.other.parenthesis.elm 422 | 423 | 424 | repository 425 | 426 | block_comment 427 | 428 | applyEndPatternLast 429 | 1 430 | begin 431 | \{-(?!#) 432 | captures 433 | 434 | 0 435 | 436 | name 437 | punctuation.definition.comment.elm 438 | 439 | 440 | end 441 | -\} 442 | name 443 | comment.block.elm 444 | patterns 445 | 446 | 447 | include 448 | #block_comment 449 | 450 | 451 | 452 | comments 453 | 454 | patterns 455 | 456 | 457 | captures 458 | 459 | 1 460 | 461 | name 462 | punctuation.definition.comment.elm 463 | 464 | 465 | match 466 | (--).*$\n? 467 | name 468 | comment.line.double-dash.elm 469 | 470 | 471 | include 472 | #block_comment 473 | 474 | 475 | 476 | infix_op 477 | 478 | match 479 | (\([|!%$+:\-.=</>]+\)|\(,+\)) 480 | name 481 | entity.name.function.infix.elm 482 | 483 | module_exports 484 | 485 | begin 486 | \( 487 | end 488 | \) 489 | name 490 | meta.declaration.exports.elm 491 | patterns 492 | 493 | 494 | match 495 | \b[a-z][a-zA-Z_'0-9]* 496 | name 497 | entity.name.function.elm 498 | 499 | 500 | match 501 | \b[A-Z][A-Za-z_'0-9]* 502 | name 503 | storage.type.elm 504 | 505 | 506 | match 507 | , 508 | name 509 | punctuation.separator.comma.elm 510 | 511 | 512 | include 513 | #infix_op 514 | 515 | 516 | comment 517 | So named because I don't know what to call this. 518 | match 519 | \(.*?\) 520 | name 521 | meta.other.unknown.elm 522 | 523 | 524 | 525 | module_name 526 | 527 | match 528 | [A-Z][A-Za-z0-9._']* 529 | name 530 | support.other.module.elm 531 | 532 | type_signature 533 | 534 | patterns 535 | 536 | 537 | captures 538 | 539 | 1 540 | 541 | name 542 | entity.other.inherited-class.elm 543 | 544 | 2 545 | 546 | name 547 | variable.other.generic-type.elm 548 | 549 | 3 550 | 551 | name 552 | keyword.other.big-arrow.elm 553 | 554 | 555 | match 556 | \(\s*([A-Z][A-Za-z]*)\s+([a-z][A-Za-z_']*)\)\s*(=>) 557 | name 558 | meta.class-constraint.elm 559 | 560 | 561 | match 562 | -> 563 | name 564 | keyword.other.arrow.elm 565 | 566 | 567 | match 568 | => 569 | name 570 | keyword.other.big-arrow.elm 571 | 572 | 573 | match 574 | \b[a-z][a-zA-Z0-9_']*\b 575 | name 576 | variable.other.generic-type.elm 577 | 578 | 579 | match 580 | \b[A-Z][a-zA-Z0-9_']*\b 581 | name 582 | storage.type.elm 583 | 584 | 585 | match 586 | \(\) 587 | name 588 | support.constant.unit.elm 589 | 590 | 591 | include 592 | #comments 593 | 594 | 595 | 596 | 597 | scopeName 598 | source.elm 599 | uuid 600 | 2cb90e5e-6e98-456d-9a8a-b59935dbc4b0 601 | 602 | 603 | --------------------------------------------------------------------------------