-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathElixirFormatter.py
More file actions
145 lines (121 loc) · 5.1 KB
/
ElixirFormatter.py
File metadata and controls
145 lines (121 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import os
import sublime
import sublime_plugin
import subprocess
import threading
import re
from .MixFormatError import MixFormatError
PLUGIN_NAME = "ElixirFormatter"
PLUGIN_CMD_NAME = "elixir_formatter_format_file"
class ElixirFormatter:
@staticmethod
def run(view, edit, file_name):
project_root_with_mix = ElixirFormatter.find_project(file_name)
project_root = project_root_with_mix or os.path.dirname(file_name)
file_name_rel = file_name.replace(project_root + "/", "")
blacklisted = ElixirFormatter.check_blacklisted_in_config(project_root, file_name_rel)
if blacklisted:
print("{0} skipped '{1}' due to :inputs key in '.formatter.exs'".
format(PLUGIN_NAME, file_name_rel))
return
region = sublime.Region(0, view.size())
[stdout, stderr, exit_code] = ElixirFormatter.run_command(project_root, ["mix", "format", file_name_rel])
if exit_code == 0:
previous_position = view.viewport_position()
Utils.indent(view)
Utils.restore_position(view, previous_position)
Utils.st_status_message("file formatted")
else:
error = MixFormatError(stdout, stderr)
if error.did_match:
print("{0}: {1}".format(PLUGIN_NAME, error.full_message))
Utils.st_status_message(error.status_message)
if error.line is not None: view.run_command("goto_line", {"line": error.line})
else:
print("{0} stdout: {1}".format(PLUGIN_NAME, stdout))
print("{0} stderr: {1}".format(PLUGIN_NAME, stderr))
Utils.st_status_message("unknown error - check console for details")
@staticmethod
def find_project(cwd = None):
cwd = cwd or os.getcwd()
if cwd == os.path.realpath('/'):
return None
elif os.path.exists(os.path.join(cwd, 'mix.exs')):
return cwd
else:
return ElixirFormatter.find_project(os.path.dirname(cwd))
@staticmethod
def run_command(project_root, task_args):
settings = sublime.load_settings('Preferences.sublime-settings')
env = os.environ.copy()
env['MIX_ENV'] = 'test'
try:
env['PATH'] = os.pathsep.join([settings.get('env')['PATH'], env['PATH']])
except (TypeError, ValueError, KeyError):
pass
if sublime.platform() == "windows":
launcher = ['cmd', '/c']
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
else:
launcher = []
startupinfo = None
process = subprocess.Popen(
launcher + task_args,
cwd = project_root,
env = env,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
startupinfo = startupinfo)
stdout, stderr = process.communicate()
stdout = stdout.decode('utf-8')
stderr = stderr.decode('utf-8')
exit_code = process.returncode
return [stderr, stdout, exit_code]
check_blacklisted_script_template = """
file = \"[[file]]\"
formatter = \".formatter.exs\"
with true <- File.exists?(formatter),
{formatter_opts, _} <- Code.eval_file(formatter),
{:ok, inputs} <- Keyword.fetch(formatter_opts, :inputs) do
IO.puts("Check result: #{file in Enum.flat_map(inputs, &Path.wildcard/1)}")
end
"""
@staticmethod
def check_blacklisted_in_config(project_root, file_name):
if not os.path.isfile(os.path.join(project_root, ".formatter.exs")):
return
script = ElixirFormatter.check_blacklisted_script_template.replace("[[file]]", file_name)
[stderr, stdout, exit_code] = ElixirFormatter.run_command(project_root, ["elixir", "-e", script])
return "Check result: false" in stdout
class ElixirFormatterFormatFileCommand(sublime_plugin.TextCommand):
def run(self, edit):
file_name = self.view.file_name()
extension = os.path.splitext(file_name)[1][1:]
syntax = self.view.settings().get("syntax")
if extension in ["ex", "exs"] or "Elixir" in syntax:
threading.Thread(target=ElixirFormatter.run, args=(self.view, edit, file_name,)).start()
class ElixirFormatterEventListeners(sublime_plugin.EventListener):
@staticmethod
def on_pre_save(view):
view.run_command(PLUGIN_CMD_NAME)
class Utils:
@staticmethod
def trim_trailing_ws_and_lines(val):
if val is None:
return val
val = re.sub(r'\s+\Z', '', val)
return val
@staticmethod
def indent(view):
view.run_command('detect_indentation')
@staticmethod
def restore_position(view, previous_position):
view.set_viewport_position((0, 0), False)
view.set_viewport_position(previous_position, False)
@staticmethod
def replace(view, edit, region, text):
view.replace(edit, region, text)
@staticmethod
def st_status_message(msg):
sublime.set_timeout(lambda: sublime.status_message('{0}: {1}'.format(PLUGIN_NAME, msg)), 0)