diff --git a/Build Systems/Elm Make.sublime-build b/Build Systems/Elm Make.sublime-build
index 176de84..1704606 100644
--- a/Build Systems/Elm Make.sublime-build
+++ b/Build Systems/Elm Make.sublime-build
@@ -3,67 +3,14 @@
"selector": "source.elm",
"cmd":
[
- "elm-make",
+ "{elm_binary}",
+ "make",
"$file",
- "--output={null}",
- "--report=json",
- "--yes"
+ "--output=/dev/null",
+ "--report=json"
],
- "working_dir": "$project_path",
- "file_regex": "^\\-\\- \\w+: (?=.+ \\- (.+?):(\\d+):(\\d+))(.+) \\- .*$",
- "error_format": "-- $type: $tag - $file:$line:$column\n$message\n",
- "info_format": ":: $info\n",
- "syntax": "Packages/Elm Language Support/Syntaxes/Elm Compile Messages.sublime-syntax",
- "null_device": "/dev/null",
- "warnings": "true",
- "osx":
+ "cancel":
{
- "path": "/usr/local/bin:$PATH"
- },
- "linux":
- {
- "path": "$HOME/.cabal/bin:/usr/local/bin:$PATH"
- },
- "variants":
- [
- {
- "name": "Run",
- "cmd":
- [
- "elm-make",
- "$file",
- "--output={output}",
- "--report=json",
- "--yes"
- ]
- },
- {
- "name": "Ignore Warnings",
- "warnings": "false"
- },
- {
- "name": "Run - debug",
- "cmd":
- [
- "elm-make",
- "$file",
- "--output={output}",
- "--report=json",
- "--debug",
- "--yes"
- ]
- },
- {
- "name": "Run - Ignore Warnings",
- "warnings": "false",
- "cmd":
- [
- "elm-make",
- "$file",
- "--output={output}",
- "--report=json",
- "--yes"
- ]
- }
- ]
+ "kill": true
+ }
}
diff --git a/Commands/Show Type.sublime-commands b/Commands/Show Type.sublime-commands
deleted file mode 100644
index 056d045..0000000
--- a/Commands/Show Type.sublime-commands
+++ /dev/null
@@ -1,11 +0,0 @@
-[
- {
- "caption": "Elm Language Support: Show type",
- "command": "elm_show_type",
- "args": { "panel": true }
- },
- {
- "caption": "Elm Language Support: Open type panel",
- "command": "elm_show_type_panel"
- }
-]
\ No newline at end of file
diff --git a/Keymaps/Default.sublime-keymap b/Keymaps/Default.sublime-keymap
deleted file mode 100644
index 4bff411..0000000
--- a/Keymaps/Default.sublime-keymap
+++ /dev/null
@@ -1,8 +0,0 @@
-[
- { "keys": ["alt+up"], "command": "elm_show_type_panel",
- "context":
- [ { "key": "selector", "operator": "equal", "operand": "source.elm" } ]
- },
- { "keys": ["alt+down"], "command": "hide_panel"
- }
-]
\ No newline at end of file
diff --git a/Menus/Context.sublime-menu b/Menus/Context.sublime-menu
deleted file mode 100644
index 82ebb89..0000000
--- a/Menus/Context.sublime-menu
+++ /dev/null
@@ -1,13 +0,0 @@
-[
- {
- "id": "elmlanguagesupport",
- "caption": "Elm Language Support",
- "children":
- [
- {
- "caption": "Open Type Panel",
- "command": "elm_show_type_panel"
- }
- ]
- }
-]
\ No newline at end of file
diff --git a/README.md b/README.md
index 9a2af81..14478ce 100644
--- a/README.md
+++ b/README.md
@@ -35,11 +35,6 @@
| type | ``type`` |
| typea | ``type alias (Record)`` |
-- 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`)
- 1. Bring up the type panel with `alt+up` or through the right-click context menu
- 2. Close the type panel with `alt+down`
- 3. If you don't like these keybindings, rebind them in your User packages directory
-
- Four standard build commands (Super+[Shift]+B or Super+[Shift]+F7)
1. `Build` just checks errors. Kudos to this [tweet][]!
2. `Run` additionally outputs your compiled program to an inferred path.
@@ -51,7 +46,6 @@
3. Compile message highlighting, embedded code highlighting, and color scheme for output panel. 
- Integration with popular plugins (installed separately)
1. [SublimeREPL][] — Run `elm-repl` in an editor tab with syntax highlighting. 
- 2. [Highlight Build Errors][] — Does what it says on the box … usually.
- Integration with [elm format](https://github.com/avh4/elm-format)
1. Make sure `elm-format` is in your PATH
2. Run the "Elm Language Support: Run elm-format" command from the Command Palette to run elm-format on the current file
diff --git a/Settings/Elm Language Support.sublime-settings b/Settings/Elm Language Support.sublime-settings
index abc419f..9dd5ed2 100644
--- a/Settings/Elm Language Support.sublime-settings
+++ b/Settings/Elm Language Support.sublime-settings
@@ -1,9 +1,9 @@
{
"debug": false,
"enabled": true,
+ "elm_binary": "elm",
"elm_format_binary": "elm-format",
"elm_format_on_save": true,
"elm_format_filename_filter": "",
- "elm_paths": "",
- "build_error_color_scheme": "Packages/Color Scheme - Default/Sunburst.tmTheme"
+ "elm_paths": ""
}
diff --git a/Settings/Elm User Strings.sublime-settings b/Settings/Elm User Strings.sublime-settings
index 6fb25d4..3b5125b 100644
--- a/Settings/Elm User Strings.sublime-settings
+++ b/Settings/Elm User Strings.sublime-settings
@@ -1,8 +1,9 @@
{
- "logging.prefix": "[Elm says]: ",
+ "logging.prefix": "Elm Language Support: ",
"logging.missing_plugin": "Missing plugin: {0}",
"make.missing_plugin": "To highlight build errors: Install with Package Control: Highlight Build Errors",
+ "make.logging.json": "JSON from elm-make: {0}",
"make.logging.invalid_json": "Invalid JSON from elm-make: {0}",
"open_in_browser.not_found": "HTML file NOT found to open: {0}",
diff --git a/Syntaxes/Elm Compile Messages.sublime-syntax b/Syntaxes/Elm Compile Messages.sublime-syntax
index 85327fb..cc667c0 100644
--- a/Syntaxes/Elm Compile Messages.sublime-syntax
+++ b/Syntaxes/Elm Compile Messages.sublime-syntax
@@ -5,8 +5,10 @@ name: Elm Compile Messages
hidden: true
file_extensions: []
scope: text.html.mediawiki.elm-build-output
+
contexts:
main:
+ - meta_scope: meta.report.elm-build-output
- match: "^(::) "
comment: "|> Unparsed Compile Message"
push:
@@ -20,7 +22,7 @@ contexts:
comment: Successfully generated
scope: constant.language.boolean.true.elm-build-output
- match: |-
- (?x) # Minimally modified `file_regex` from `Elm Make.sublime-build`
+ (?x) # Minimally modified `file_regex` from `Elm Make.sublime-build`
^\-\-[ ] # Leading delimiter
((error) # \2: error
|(warning) # \3: warning
@@ -32,7 +34,7 @@ contexts:
(\d+): # \6: $line
(\d+) # \7: $column
\n$ # End
- comment: '-- TAG - file:line:column\nOverview\nDetail\n'
+ comment: '-- type: TAG - file:line:column\nMessage\n'
captures:
0: markup.heading.4.elm-build-output
1: support.constant.type.elm-build-output
@@ -42,31 +44,39 @@ contexts:
5: markup.underline.link.elm-build-output
6: constant.numeric.elm-build-output
7: constant.numeric.elm-build-output
+ - match: (`)(?!`)
+ comment: Inline `variable`
+ scope: punctuation.definition.raw.elm-build-output
push:
- - meta_scope: meta.report.elm-build-output
- - meta_content_scope: string.unquoted.elm-build-output
- - match: ^\n$
- captures:
- 0: meta.separator.elm-build-output
- pop: true
- - match: (`)(?!`)
- comment: Inline `variable`
+ - meta_scope: markup.raw.inline.elm-build-output
+ - meta_content_scope: variable.other.elm.elm-build-output
+ - match: \1
captures:
0: punctuation.definition.raw.elm-build-output
- push:
- - meta_scope: markup.raw.inline.elm-build-output
- - meta_content_scope: variable.other.elm.elm-build-output
- - match: \1
- captures:
- 0: punctuation.definition.raw.elm-build-output
- pop: true
- - match: "(?m)^ {4}"
- comment: Code Block
- push:
- - meta_scope: markup.raw.block.elm-build-output
- - match: '\n+(?!^ {4})'
- pop: true
- - include: scope:source.elm
+ pop: true
+ - match: '(<)([^>]+)(>)'
+ comment: Inline
+ captures:
+ 1: punctuation.definition.link.elm-build-output
+ 2: markup.underline.link.elm-build-output
+ 3: punctuation.definition.link.elm-build-output
+ - match: '^Hint:'
+ scope: markup.heading.5.elm-build-output
+ - match: "^ +([x^]+)$"
+ comment: error jaggy underline
+ captures:
+ 1: punctuation.definition.raw.elm-build-output
+ - match: "^ {4}"
+ comment: code block
+ embed: Elm.sublime-syntax
+ escape: \n
+ - match: '^(\d+)(\|( |>))'
+ captures:
+ 1: constant.numeric.line-number.elm-build-output
+ 2: punctuation.definition.raw.elm-build-output
+ comment: line-numbered code block
+ embed: scope:source.elm
+ escape: \n
- match: ^\[
comment: '[Finished in 4.2s]'
push:
diff --git a/beta-repository.json b/beta-repository.json
deleted file mode 100644
index 18e5bf2..0000000
--- a/beta-repository.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "schema_version": "3.0.0",
- "packages": [
- {
- "name": "Elm Language Support",
- "details": "https://github.com/deadfoxygrandpa/Elm.tmLanguage",
- "releases": [
- {
- "sublime_text": "*",
- "branch": "beta"
- }
- ]
- }
- ],
- "dependencies": [
- ],
- "includes": [
- ]
-}
\ No newline at end of file
diff --git a/elm_generate.py b/elm_generate.py
deleted file mode 100644
index 2a97182..0000000
--- a/elm_generate.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import json
-import os
-import sys
-
-class Module(object):
- def __init__(self, data):
- self.name = data['name']
- self.values = [name(v['raw']) + ' : ' + signature(v['raw']) for v in data['values']]
- self.valueNames = [name(v) for v in self.values]
- self.datatypes = [v['name'] for v in data['datatypes']]
- self.constructors = [[v['name'] for v in x['constructors']] for x in data['datatypes']]
- self.aliases = [v['name'] for v in data['aliases']]
-
- def include_text(self):
- s = '\n\tinclude\n\t#{}\n'.format(self.name.lower())
- return s
-
- def moduleText(self):
- 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'
- values = '|'.join([n for n in self.valueNames if not n.startswith('(')])
- if self.aliases:
- values += '|' + '|'.join(self.aliases)
- if self.datatypes:
- values += '|' + '|'.join(self.datatypes)
- return s.format(nameLower=self.name.lower(), name=self.name, values=values)
-
- def snippets(self):
- base = 'Snippets'
- s = '\n\t\n\t\n\t{name}\n\t\n\tsource.elm\n\t{signature}\n'
- for v in [func for func in self.values if not name(func).startswith('(')]:
- subdirectories = self.name.split('.')
- path = '{}' + '\\{}'*(len(subdirectories))
- path = path.format(base, *subdirectories)
-
- if not os.path.exists(path):
- os.makedirs(path)
-
- path += '\\{}'
-
- with open(path.format(name(v) + '.sublime-snippet'), 'w') as f:
- f.write(s.format(autocomplete=make_autocomplete(v), name=name(v), signature=signature(v)))
-
- print('Wrote {}'.format(path.format(name(v) + '.sublime-snippet')))
-
-def name(t):
- return t.split(' : ')[0].strip()
-
-def signature(t):
- return t.split(' : ')[1].strip()
-
-def hintize(t):
- first = t[0].lower()
- t = t.replace(' ', '')
- return first + ''.join(t[1:])
-
-def typeFormat(t):
- if t[0] == '[':
- return 'ListOf' + typeFormat(t[1:-1])
- elif t[0] == '(':
- return ''.join([unicode(v.strip()) for v in t[1:-1].split(',')]) + 'Tuple'
- else:
- if len(t.split(' ')) == 1:
- return t
- else:
- x = t.split(' ')
- return x[0] + ''.join([typeFormat(v) for v in x[1:]])
-
-def tokenize(t):
- return [v.strip() for v in t.split('->')]
-
-def print_type(t):
- print(name(t))
- print([typeFormat(v) for v in tokenize(signature(t))])
-
-def make_autocomplete(t):
- s = '{}'.format(name(t))
- args = arguments(signature(t))
- for n, arg in enumerate(args):
- s += ' ${{{n}:{arg}}}'.format(n=n+1, arg=arg)
- return s
-
-def arguments(signature):
- args = [v.strip() for v in signature.split('->')][:-1]
- new_args = []
- open_parens = 0
- for arg in args:
- parens = arg.count('(') - arg.count(')')
- if parens and not open_parens:
- new_args.append('function')
- elif open_parens != 0:
- open_parens += parens
- continue
- else:
- new_args.append(argify(arg))
- open_parens += parens
- return new_args
-
-def argify(s):
- if s.startswith('('):
- return 'tuple'
- elif s.startswith('['):
- return 'list'
- elif len(s.split(' ')) > 1:
- return s.split(' ')[0].lower()
- else:
- return s.lower()
-
-def loadDocs(path):
- with open(path) as f:
- return json.load(f)
-
-
-if __name__ == '__main__':
- ## Usage: pass in docs.json from cabal's elm directory
- path = sys.argv[1]
- prelude = ['Basics', 'List', 'Signal', 'Text', 'Maybe', 'Time', 'Graphics.Element', 'Color', 'Graphics.Collage']
-
- modules = [Module(m) for m in loadDocs(path)]
-
- print('Prelude:')
- print('show|')
- for m in modules:
- if m.name in prelude:
- print('|'.join([n for n in m.valueNames if not n.startswith('(')]))
-
- print('\n'*5)
-
- print('Prelude Aliases and Datatypes:')
- print('Int|Float|Char|Bool|String|True|False')
- for m in modules:
- if m.name in prelude:
- print('|'.join([n for n in (m.datatypes + m.aliases) if not n.startswith('(')]) + '|')
-
- print('\n'*5)
-
- print('Includes:')
- for m in modules:
- print(m.include_text())
-
- print('\n'*5)
-
- print('Includes Continued:')
- for m in modules:
- print(m.moduleText())
-
- print('\n'*5)
-
- print('Constructors:')
- print('\(\)|\[\]|True|False|Int|Char|Bool|String|')
- for m in modules:
- if m.name in prelude:
- for c in m.constructors:
- print('|'.join(c) + '|')
-
- print('\n'*5)
-
- print('Writing Autocompletion Snippets...:')
- for m in modules:
- if m.name in prelude:
- m.snippets()
- print('\n'*2)
-
- with open('Snippets\\Basics\\markdown.sublime-snippet', 'w') as f:
- f.write('\n\n\nmarkdown\n\nsource.elm\nA markdown block\n')
- print('Wrote markdown.sublime-snippet')
diff --git a/elm_make.py b/elm_make.py
index cff5a23..e07237a 100644
--- a/elm_make.py
+++ b/elm_make.py
@@ -1,91 +1,277 @@
+import sublime
+import sublime_plugin
+
+import html
import json
-import re
+import os
import string
-import sublime
+import subprocess
+import threading
+
+from .elm_plugin import *
+from .elm_project import ElmProject
+
+# We need a custom build command so that we can take the JSON output from the
+# Elm compiler and render it in a format that works with Sublime Text’s build
+# output panel syntax highlighting and regexp-based error navigation.
+#
+# Based on Advanced Example: https://www.sublimetext.com/docs/3/build_systems.html#advanced_example
+class ElmMakeCommand(sublime_plugin.WindowCommand):
+
+ encoding = 'utf-8'
+ killed = False
+ proc = None
+ panel = None
+ panel_lock = threading.Lock()
+
+ errs_by_file = {}
+ phantom_sets_by_buffer = {}
+ show_errors_inline = True
-try: # ST3
- from .elm_plugin import *
- from .elm_project import ElmProject
-except: # ST2
- from elm_plugin import *
- from elm_project import ElmProject
-default_exec = import_module('Default.exec')
-
-@replace_base_class('Highlight Build Errors.HighlightBuildErrors.ExecCommand')
-class ElmMakeCommand(default_exec.ExecCommand):
-
- # inspired by: http://www.sublimetext.com/forum/viewtopic.php?t=12028
- def run(self, error_format, info_format, syntax, null_device, warnings, **kwargs):
- self.buffer = ''
- self.data_in_bytes = False # ST3 r3153 changed ExecCommand from bytes to str so we must detect which we get and handle appropriately: https://github.com/elm-community/SublimeElmLanguageSupport/issues/48
- self.warnings = warnings == "true"
- self.error_format = string.Template(error_format)
- self.info_format = string.Template(info_format)
- self.run_with_project(null_device=null_device, **kwargs)
- self.style_output(syntax)
-
- def run_with_project(self, cmd, working_dir, null_device, **kwargs):
- file_arg, output_arg = cmd[1:3]
- project = ElmProject(file_arg)
+ def is_enabled(self, kill=False):
+ # Cancel only available when the process is still running
+ if kill:
+ return self.proc is not None and self.proc.poll() is None
+ return True
+
+ def run(self, cmd=[], kill=False):
+ if kill:
+ if self.proc:
+ self.killed = True
+ self.proc.terminate()
+ return
+
+ working_dir = self.working_dir()
+ self.create_panel(working_dir)
+
+ if self.proc is not None:
+ self.proc.terminate()
+ self.proc = None
+
+ self.proc = subprocess.Popen(
+ self.format_cmd(cmd),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ cwd=working_dir
+ )
+ self.killed = False
+
+ threading.Thread(
+ target=self.read_handle,
+ args=(self.proc.stdout,)
+ ).start()
+
+ def working_dir(self):
+ vars = self.window.extract_variables()
+ project = ElmProject(vars['file'])
log_string('project.logging.settings', repr(project))
- if '{output}' in output_arg:
- cmd[1] = fs.expanduser(project.main_path)
- output_path = fs.expanduser(project.output_path)
- cmd[2] = output_arg.format(output=output_path)
- else:
- # cmd[1] builds active file rather than project main
- cmd[2] = output_arg.format(null=null_device)
- project_dir = project.working_dir or working_dir
- # ST2: TypeError: __init__() got an unexpected keyword argument 'syntax'
- super(ElmMakeCommand, self).run(cmd, working_dir=project_dir, **kwargs)
-
- def style_output(self, syntax):
- self.output_view.set_syntax_file(syntax)
- elm_setting = sublime.load_settings('Elm Language Support.sublime-settings')
- user_setting = sublime.load_settings('Preferences.sublime-settings')
- color_scheme = elm_setting.get('build_error_color_scheme') or user_setting.get('color_scheme')
- self.output_view.settings().set('color_scheme', color_scheme)
- if self.is_patched:
- self.debug_text = ''
- else:
- self.debug_text = get_string('make.missing_plugin')
+ return project.working_dir or vars['project_path'] or vars['file_path']
- def on_data(self, proc, data):
- if isinstance(data, str):
- self.buffer += data
- else:
- # ST3 r3153 changed ExecCommand from bytes to str so we must detect which we get and handle appropriately: https://github.com/elm-community/SublimeElmLanguageSupport/issues/48
- self.data_in_bytes = True
- self.buffer += data.decode(self.encoding)
-
- def on_finished(self, proc):
- result_strs = self.buffer.split('\n')
- flat_map = lambda f, xss: sum(map(f, xss), [])
- output_strs = flat_map(self.format_result, result_strs) + ['']
- output_data = '\n'.join(output_strs)
- # ST3 r3153 changed ExecCommand from bytes to str so we must detect which we get and handle appropriately: https://github.com/elm-community/SublimeElmLanguageSupport/issues/48
- output_data = output_data.encode(self.encoding) if self.data_in_bytes else output_data
- super(ElmMakeCommand, self).on_data(proc, output_data)
- super(ElmMakeCommand, self).on_finished(proc)
-
- def format_result(self, result_str):
- decode_error = lambda dict: self.format_error(**dict) if 'type' in dict else dict
+ def create_panel(self, working_dir):
+ # Only allow one thread to touch output panel at a time
+ with self.panel_lock:
+ # implicitly clears previous contents
+ self.panel = self.window.create_output_panel('exec')
+
+ settings = self.panel.settings()
+
+ self.panel.assign_syntax('Packages/Elm Language Support/Syntaxes/Elm Compile Messages.sublime-syntax')
+ settings.set('gutter', False)
+ settings.set('scroll_past_end', False)
+ settings.set('word_wrap', False)
+ settings.set('color_scheme', self.get_setting('build_output_color_scheme', 'color_scheme'))
+
+ # Enable result navigation
+ settings.set(
+ 'result_file_regex',
+ r'^\-\- \w+: (?=.+ \- (.+?):(\d+):(\d+))(.+) \- .*$'
+ )
+ settings.set('result_base_dir', working_dir)
+
+ preferences = sublime.load_settings('Preferences.sublime-settings')
+
+ self.hide_phantoms()
+ self.show_errors_inline = preferences.get('show_errors_inline', True)
+
+ show_panel_on_build = preferences.get('show_panel_on_build', True)
+ if show_panel_on_build:
+ self.window.run_command('show_panel', {'panel': 'output.exec'})
+
+ def format_cmd(self, cmd):
+ binary, command, file, output = cmd[0:4]
+
+ binary = binary.format(elm_binary=self.get_setting('elm_binary'))
+
+ return [binary, command, file, output] + cmd[4:]
+
+ def read_handle(self, handle):
+ chunk_size = 2 ** 13
+ output = b''
+ while True:
+ try:
+ chunk = os.read(handle.fileno(), chunk_size)
+ output += chunk
+
+ if chunk == b'':
+ if output != b'':
+ self.queue_write(self.format_output(output.decode(self.encoding)))
+ raise IOError('EOF')
+
+ except UnicodeDecodeError as e:
+ msg = 'Error decoding output using %s - %s'
+ self.queue_write(msg % (self.encoding, str(e)))
+ break
+
+ except IOError:
+ if self.killed:
+ msg = 'Cancelled'
+ else:
+ msg = 'Finished'
+ sublime.set_timeout(lambda: self.finish(), 0)
+ self.queue_write('[%s]' % msg)
+ break
+
+ def queue_write(self, text):
+ # Calling set_timeout inside this function rather than inline ensures
+ # that the value of text is captured for the lambda to use, and not
+ # mutated before it can run.
+ sublime.set_timeout(lambda: self.do_write(text), 1)
+
+ def do_write(self, text):
+ with self.panel_lock:
+ self.panel.set_read_only(False)
+ self.panel.run_command('append', {'characters': text})
+ self.panel.set_read_only(True)
+
+ if self.show_errors_inline and text.find('\n') >= 0:
+ errs = self.panel.find_all_results_with_text()
+ errs_by_file = {}
+ for file, line, column, text in errs:
+ if file not in errs_by_file:
+ errs_by_file[file] = []
+ errs_by_file[file].append((line, column, text))
+ self.errs_by_file = errs_by_file
+
+ self.update_phantoms()
+
+ def format_output(self, output):
try:
- data = json.loads(result_str, object_hook=decode_error)
- return [s for s in data if s is not None]
- except ValueError:
- log_string('make.logging.invalid_json', result_str)
- info_str = result_str.strip()
- return [self.info_format.substitute(info=info_str)] if info_str else []
-
- def format_error(shelf, type, file, region, tag, overview, details, **kwargs):
- if type == 'warning' and not shelf.warnings:
- return None
- line = region['start']['line']
- column = region['start']['column']
- message = overview
- if details:
- message += '\n' + re.sub(r'(\n)+', r'\1', details)
- # TypeError: substitute() got multiple values for argument 'self'
- # https://bugs.python.org/issue23671
- return shelf.error_format.substitute(**locals())
+ data = json.loads(output)
+ log_string('make.logging.json', output)
+ return self.format_errors(data['errors'])
+ except ValueError as e:
+ log_string('make.logging.invalid_json', output)
+ return ''
+
+ def format_errors(self, errors):
+ return '\n'.join(map(self.format_error, errors)) + '\n'
+
+ def format_error(self, error):
+ file = error['path']
+ return '\n'.join(map(lambda problem: self.format_problem(file, problem), error['problems']))
+
+ def format_problem(self, file, problem):
+ error_format = string.Template('-- $type: $title - $file:$line:$column\n\n$message\n')
+
+ type = 'error'
+ title = problem['title']
+ line = problem['region']['start']['line']
+ column = problem['region']['start']['column']
+ message = self.format_message(problem['message'])
+
+ vars = locals()
+ vars.pop('self') # https://bugs.python.org/issue23671
+ return error_format.substitute(**vars)
+
+ def format_message(self, message):
+ format = lambda msg: msg['string'] if 'string' in msg else msg
+
+ return ''.join(map(format, message))
+
+ def finish(self):
+ errs = self.panel.find_all_results()
+ if len(errs) == 0:
+ sublime.status_message('Build finished')
+ else:
+ sublime.status_message('Build finished with %d errors' % len(errs))
+
+ # Borrowed from Sublime’s ExecCommand: https://github.com/twolfson/sublime-files/blob/master/Packages/Default/exec.py
+ def update_phantoms(self):
+ stylesheet = '''
+
+ '''
+
+ for file, errs in self.errs_by_file.items():
+ view = self.window.find_open_file(file)
+ if view:
+
+ buffer_id = view.buffer_id()
+ if buffer_id not in self.phantom_sets_by_buffer:
+ phantom_set = sublime.PhantomSet(view, "exec")
+ self.phantom_sets_by_buffer[buffer_id] = phantom_set
+ else:
+ phantom_set = self.phantom_sets_by_buffer[buffer_id]
+
+ phantoms = []
+
+ for line, column, text in errs:
+ pt = view.text_point(line - 1, column - 1)
+ phantoms.append(sublime.Phantom(
+ sublime.Region(pt, view.line(pt).b),
+ ('' + stylesheet +
+ '' +
+ ''),
+ sublime.LAYOUT_BELOW,
+ on_navigate=self.on_phantom_navigate))
+
+ phantom_set.update(phantoms)
+
+ def hide_phantoms(self):
+ for file, errs in self.errs_by_file.items():
+ view = self.window.find_open_file(file)
+ if view:
+ view.erase_phantoms('elm_make')
+
+ self.errs_by_file = {}
+ self.phantom_sets_by_buffer = {}
+ self.show_errors_inline = False
+
+ def on_phantom_navigate(self, url):
+ self.hide_phantoms()
+
+ def get_setting(self, key, user_key=None):
+ package_settings = sublime.load_settings('Elm Language Support.sublime-settings')
+ user_settings = self.window.active_view().settings()
+
+ return user_settings.get(user_key or ('elm_language_support_' + key), package_settings.get(key))
diff --git a/elm_open_in_browser.py b/elm_open_in_browser.py
index 41ac5dc..f27eaa0 100644
--- a/elm_open_in_browser.py
+++ b/elm_open_in_browser.py
@@ -1,17 +1,10 @@
import webbrowser
-try: # ST3
- import urllib.parse as urlparse
- import urllib.request as urllib
+import urllib.parse as urlparse
+import urllib.request as urllib
- from .elm_plugin import *
- from .elm_project import ElmProject
-except: # ST2
- import urlparse
- import urllib
-
- from elm_plugin import *
- from elm_project import ElmProject
+from .elm_plugin import *
+from .elm_project import ElmProject
class ElmOpenInBrowserCommand(sublime_plugin.TextCommand):
diff --git a/elm_project.py b/elm_project.py
index 0157127..b61aee1 100644
--- a/elm_project.py
+++ b/elm_project.py
@@ -1,10 +1,7 @@
import collections
import json
-try: # ST3
- from .elm_plugin import *
-except: # ST2
- from elm_plugin import *
+from .elm_plugin import *
class ElmProjectCommand(sublime_plugin.TextCommand):
diff --git a/elm_show_type.py b/elm_show_type.py
deleted file mode 100644
index 5634fab..0000000
--- a/elm_show_type.py
+++ /dev/null
@@ -1,279 +0,0 @@
-from __future__ import print_function
-
-import webbrowser
-import os, os.path
-import subprocess
-import json
-import re
-from difflib import SequenceMatcher
-
-import sublime, sublime_plugin
-
-try: # ST3
- from .elm_project import ElmProject
-except: # ST2
- from elm_project import ElmProject
-
-LOOKUPS = {}
-
-def join_qualified(region, view):
- """
- Given a region, expand outward on periods to return a new region defining
- the entire word, in the context of Elm syntax.
-
- For example, when the region encompasses the 'map' part of a larger
- 'Dict.map' word, this function will return the entire region encompassing
- 'Dict.map'. The same is true if the region is encompassing 'Dict'.
-
- Recursively expands outward in both directions, correctly returning longer
- constructions such as 'Graphics.Input.button'
- """
- starting_region = region
- prefix = view.substr(region.a - 1)
- suffix = view.substr(region.b)
- if prefix == '.':
- region = region.cover(view.word(region.a - 2))
- if suffix == '.':
- region = region.cover(view.word(region.b + 1))
-
- if region == starting_region:
- return region
- else:
- return join_qualified(region, view)
-
-def get_word_under_cursor(view):
- sel = view.sel()[0]
- region = join_qualified(view.word(sel), view)
- return view.substr(region).strip()
-
-def get_type(view, panel):
- """
- Given a view, return the type signature of the word under the cursor,
- if found. If no type is found, return an empty string. Write the info
- to an output panel.
- """
- sel = view.sel()[0]
- region = join_qualified(view.word(sel), view)
- scope = view.scope_name(region.b)
- if scope.find('source.elm') != -1 and scope.find('string') == -1 and scope.find('comment') == -1:
- filename = view.file_name()
- word = view.substr(region).strip()
- sublime.set_timeout_async(lambda: search_and_set_status_message(filename, word, panel, 0), 0)
-
-def search_and_set_status_message(filename, query, panel, tries):
- """
- Given a filename and a query, look up in the in-memory dict of values
- pulled from elm oracle to find a match. If a match is found, display
- the type signature in the status bar and set it in the output panel.
- """
- global LOOKUPS
- if len(query) == 0:
- return None
- if filename not in LOOKUPS.keys():
- if tries >= 10:
- return None
- else:
- # if the filename is not found loaded into memory, it's probably being
- # loaded into memory right now. Try 10 more times at 100ms intervals
- # and if it still isn't loaded, there's likely a problem we can't fix
- # here.
- sublime.set_timeout_async(search_and_set_status_message(filename, query, panel, tries + 1), 100)
- else:
- data = LOOKUPS[filename]
- if len(data) > 0:
- matches = [item for item in data if item['name'] == query.split('.')[-1]]
- if len(matches) == 0:
- return None
- else:
- # sort matches by similarity to query
- matches.sort(key=lambda x: SequenceMatcher(None, query, x['fullName']).ratio(), reverse=True)
- item = matches[0]
- type_signature = item['fullName'] + ' : ' + item['signature']
- sublime.status_message(type_signature)
- panel.run_command('erase_view')
- # add full name and type annotation
- panel_output = '`' + type_signature + '`' + '\n\n' + item['comment'][1:]
- # replace backticks with no-width space for syntax highlighting
- panel_output = panel_output.replace('`', '\uFEFF')
- # add no-width space to beginning and end of code blocks for syntax highlighting
- panel_output = re.sub('\n( {4}[\s\S]+?)((?=\n\S)\n|\Z)', '\uFEFF\n\\1\uFEFF\n', panel_output)
- # remove first four spaces on each line from code blocks
- panel_output = re.sub('\n {4}', '\n', panel_output)
- panel.run_command('append', {'characters': panel_output})
- return None
-
-def get_matching_names(filename, prefix):
- """
- Given a file name and a search prefix, return a list of matching
- completions from elm oracle.
- """
- def skip_chars(full_name):
- # Sublime Text seems to have odd behavior on completions. If the full
- # name is at the same "path level" as the prefix, then the completion
- # will replace the entire entry, otherwise it will only replace after
- # the final period separator
- full_name_path = full_name.split('.')[:-1]
- prefix_path = prefix.split('.')[:-1]
- if full_name_path == prefix_path:
- return full_name
- else:
- # get the characters to remove from the completion to avoid duplication
- # of paths. If it's 0, then stay at 0, otherwise add a period back
- chars_to_skip = len('.'.join(prefix_path))
- if chars_to_skip > 0:
- chars_to_skip += 1
- return full_name[chars_to_skip:]
-
- global LOOKUPS
- if filename not in LOOKUPS.keys():
- return None
- else:
- data = LOOKUPS[filename]
- completions = {(v['fullName'] + '\t' + v['signature'], skip_chars(v['fullName']))
- for v in data
- if v['fullName'].startswith(prefix) or v['name'].startswith(prefix)}
- return [[v[0], v[1]] for v in completions]
-
-def explore_package(filename, package_name):
- global LOOKUPS
- if filename not in LOOKUPS.keys() or len(package_name) == 0:
- return None
- elif package_name[0].upper() != package_name[0]:
- sublime.status_message('This is not a package!')
- return None
- else:
- def open_link(items, i):
- if i == -1:
- return None
- else:
- open_in_browser(items[i][3])
- data = [[v['fullName'], v['signature'], v['comment'], v['href']]
- for v in LOOKUPS[filename]
- if v['fullName'].startswith(package_name)]
- # all items must be the same number of rows
- n = 75
- panel_items = [v[:2] + [v[2][:n]] + [v[2][n:2*n]] + [v[2][2*n:]] for v in data]
- sublime.active_window().show_quick_panel(panel_items, lambda i: open_link(data, i))
-
-def open_in_browser(url):
- webbrowser.open_new_tab(url)
-
-def load_from_oracle(filename):
- """
- Loads all data about the current file from elm oracle and adds it
- to the LOOKUPS global dictionary.
- """
- global LOOKUPS
- project = ElmProject(filename)
- if project.working_dir is None:
- return
- os.chdir(project.working_dir)
-
- # Hide the console window on Windows
- shell = False
- path_separator = ':'
- if os.name == "nt":
- shell = True
- path_separator = ';'
-
- settings = sublime.load_settings('Elm Language Support.sublime-settings')
- path = settings.get('elm_paths', '')
- if path:
- old_path = os.environ['PATH']
- os.environ["PATH"] = os.path.expandvars(path + path_separator + '$PATH')
-
- p = subprocess.Popen(['elm-oracle', filename, ''], stdout=subprocess.PIPE,
- stderr=subprocess.PIPE, shell=shell)
-
- if path:
- os.environ['PATH'] = old_path
-
- output, errors = p.communicate()
- output = output.strip()
- if settings.get('debug', False):
- string_settings = sublime.load_settings('Elm User Strings.sublime-settings')
- print(string_settings.get('logging.prefix', '') + '(elm-oracle) ' + str(output), '\nerrors: ' + str(errors.strip()))
- if str(errors.strip()):
- print('Your PATH is: ', os.environ['PATH'])
- try:
- data = json.loads(output.decode('utf-8'))
- except ValueError:
- return None
- LOOKUPS[filename] = data
-
-def view_load(view):
- """
- Selectively calls load_from_oracle based on the current scope.
- """
-
- if view.file_name() is None:
- return;
-
- sel = view.sel()[0]
- region = join_qualified(view.word(sel), view)
- scope = view.scope_name(region.b)
- if scope.find('source.elm') != -1:
- load_from_oracle(view.file_name())
-
-
-class ElmOracleListener(sublime_plugin.EventListener):
- """
- An event listener to load and search through data from elm oracle.
- """
-
- def on_selection_modified_async(self, view):
- sel = view.sel()[0]
- region = join_qualified(view.word(sel), view)
- scope = view.scope_name(region.b)
- if scope.find('source.elm') != -1:
- view.run_command('elm_show_type')
-
- def on_activated_async(self, view):
- view_load(view)
-
- def on_post_save_async(self, view):
- view_load(view)
-
- def on_query_completions(self, view, prefix, locations):
- word = get_word_under_cursor(view)
- return get_matching_names(view.file_name(), word)
-
-
-class ElmShowType(sublime_plugin.TextCommand):
- """
- A text command to lookup the type signature of the function under the
- cursor, and display it in the status bar if found.
- """
- type_panel = None
-
- def run(self, edit, panel=False):
- if self.type_panel is None:
- self.type_panel = self.view.window().create_output_panel('elm_type')
- self.type_panel.set_syntax_file('Packages/Elm Language Support/Syntaxes/Elm Documentation.sublime-syntax')
- get_type(self.view, self.type_panel)
- if panel:
- self.view.window().run_command('elm_show_type_panel')
-
-
-class ElmShowTypePanel(sublime_plugin.WindowCommand):
- """
- Turns on the type output panel
- """
- def run(self):
- self.window.run_command("show_panel", {"panel": "output.elm_type"})
-
-
-class ElmOracleExplore(sublime_plugin.TextCommand):
- def run(self, edit):
- word = get_word_under_cursor(self.view)
- parts = [part for part in word.split('.') if part[0].upper() == part[0]]
- package_name = '.'.join(parts)
- explore_package(self.view.file_name(), package_name)
-
-
-class EraseView(sublime_plugin.TextCommand):
- """
- Erases a view
- """
- def run(self, edit):
- self.view.erase(edit, sublime.Region(0, self.view.size()))
diff --git a/images/completions.png b/images/completions.png
deleted file mode 100644
index b519ce2..0000000
Binary files a/images/completions.png and /dev/null differ
diff --git a/images/elm_types.png b/images/elm_types.png
deleted file mode 100644
index a17681b..0000000
Binary files a/images/elm_types.png and /dev/null differ
diff --git a/images/type_panel.png b/images/type_panel.png
deleted file mode 100644
index 8b96566..0000000
Binary files a/images/type_panel.png and /dev/null differ