diff --git a/README.md b/README.md index ae527f0..f5b58d1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This plugin for Sublime Text 3 allows you to analyse OCaml source code, autocomp ## Installation -First of all, be sure to have [merlin](https://github.com/the-lambda-church/merlin) installed. +First of all, be sure to have [merlin](https://github.com/the-lambda-church/merlin) installed. The current supported version of merlin is 2.0. The shorter way of doing this is with [opam](opam.ocaml.org), an OCaml package manager: @@ -20,7 +20,7 @@ For the moment (during development), sublime-text-merlin is not listed in the Pa ## Work in Progress -This is an initial port of the vim plugin of merlin. Buffers can be synchronised with merlin, but any edit need a full refresh between ST and merlin. Projects are not supported. +This is an initial port of the vim plugin of merlin. Buffers can be synchronised with merlin, but any edit needs a full refresh between ST and merlin. Projects are not supported. If you want to use fully merlin with Sublime Text, fork this repository. diff --git a/merlin/helpers.py b/merlin/helpers.py index 830b13f..c4d2f58 100644 --- a/merlin/helpers.py +++ b/merlin/helpers.py @@ -59,9 +59,10 @@ def merlin_pos(view, pos): return view.text_point(pos['line'] - 1, pos['col']) + def clean_whitespace(text): """ Replace sequence of whitespaces by a single space """ - return ' '.join(text.split()) # yes, it's a feature, HAHAHA + return ' '.join(text.split()) diff --git a/merlin/process.py b/merlin/process.py index 0256501..9004d70 100644 --- a/merlin/process.py +++ b/merlin/process.py @@ -30,6 +30,7 @@ class MerlinException(MerlinExc): """ Standard exception. """ pass + class MerlinProcess(object): """ This class launches a merlin process and send/receive commands to @@ -119,7 +120,7 @@ def reset(self, kind="auto", name=None): self.find_use("ocamlbuild") return r - def _parse_cursor(self,result): + def _parse_cursor(self, result): """ Parser cursor values returned by merlin. """ position = result['cursor'] marker = result['marker'] @@ -148,14 +149,14 @@ def tell_source(self, content): def seek_start(self): """ Reset cursor to the beginning of the file. """ - return self.send_cursor_command("seek","before",{'line': 1, 'col': 0}) + return self.send_cursor_command("seek", "before", {'line': 1, 'col': 0}) def seek_marker(self): """ After satisfaying a tell marker, place the cursor immediately after the marker. """ - return self.send_cursor_command("seek","marker") + return self.send_cursor_command("seek", "marker") def complete_cursor(self, base, line, col): """ Return possible completions at the current cursor position. """ @@ -176,7 +177,7 @@ def find_use(self, *packages): """ Find and load external modules. """ return self.send_command('find', 'use', packages) - def project(): + def project(self): """ Returns a tuple (dot_merlins, failures) @@ -218,20 +219,26 @@ def sync_buffer(self, view): # Path management def add_build_path(self, path): return self.send_command("path", "add", "build", path) + def add_source_path(self, path): return self.send_command("path", "add", "source", path) + def remove_build_path(self, path): return self.send_command("path", "remove", "build", path) + def remove_source_path(self, path): return self.send_command("path", "remove", "source", path) + def list_build_path(self): return self.send_command("path", "list", "build") + def list_source_path(self): return self.send_command("path", "list", "source") # File selection def which_path(self, names): return self.send_command("which", "path", names) + def which_with_ext(self, extensions): return self.send_command("which", "with_ext", extensions) @@ -242,16 +249,16 @@ def type_enclosing(self, line, col): # Extensions management def extension_list(self, crit=None): - if crit: # "enabled" | "disabled" - return self.send_command("extension","list",crit) + if crit in ['enabled', 'disabled']: + return self.send_command("extension", "list", crit) else: - return self.send_command("extension","list") + return self.send_command("extension", "list") def extension_enable(self, exts): - self.send_command("extension","enable",exts) + self.send_command("extension", "enable", exts) def extension_disable(self, exts): - self.send_command("extension","disable",exts) + self.send_command("extension", "disable", exts) def locate(self, line, col, ident="", kind="mli"): if line is None or col is None: diff --git a/sublime-text-merlin.py b/sublime-text-merlin.py index 2adb778..2d8bf7b 100644 --- a/sublime-text-merlin.py +++ b/sublime-text-merlin.py @@ -2,7 +2,6 @@ This module allows you to analyse OCaml source code, autocomplete and infer types while writing """ -import subprocess import functools import sublime import sublime_plugin @@ -11,13 +10,15 @@ import sys if sys.version_info < (3, 0): - from merlin.process import MerlinProcess, merlin_bin + from merlin.process import MerlinProcess from merlin.helpers import merlin_pos, only_ocaml, clean_whitespace else: - from .merlin.process import MerlinProcess, merlin_bin + from .merlin.process import MerlinProcess from .merlin.helpers import merlin_pos, only_ocaml, clean_whitespace running_process = None + + def merlin_process(name): global running_process if running_process is None: @@ -38,8 +39,8 @@ def run(self): self.window.show_quick_panel(self.modules, self.on_done) def on_done(self, index): - if index == -1: return - self.process.find_use(self.modules[index]) + if index != -1: + self.process.find_use(self.modules[index]) class MerlinAddBuildPath(sublime_plugin.WindowCommand): @@ -97,8 +98,8 @@ def run(self): self.window.show_quick_panel(self.directories, self.on_done) def on_done(self, index): - if index == -1: return - self.process.remove_build_path(self.directories[index]) + if index != -1: + self.process.remove_build_path(self.directories[index]) class MerlinRemoveSourcePath(sublime_plugin.WindowCommand): @@ -114,8 +115,8 @@ def run(self): self.window.show_quick_panel(self.directories, self.on_done) def on_done(self, index): - if index == -1: return - self.process.remove_source_path(self.directories[index]) + if index != -1: + self.process.remove_source_path(self.directories[index]) class MerlinEnableExtension(sublime_plugin.WindowCommand): @@ -131,8 +132,8 @@ def run(self): self.window.show_quick_panel(self.extensions, self.on_done) def on_done(self, index): - if index == -1: return - self.process.extension_enable([self.extensions[index]]) + if index != -1: + self.process.extension_enable([self.extensions[index]]) class MerlinDisableExtension(sublime_plugin.WindowCommand): @@ -148,8 +149,8 @@ def run(self): self.window.show_quick_panel(self.extensions, self.on_done) def on_done(self, index): - if index == -1: return - self.process.extension_disable([self.extensions[index]]) + if index != -1: + self.process.extension_disable([self.extensions[index]]) class MerlinTypeEnclosing: @@ -157,7 +158,7 @@ class MerlinTypeEnclosing: Return type information around cursor. """ - def __init__(self,view): + def __init__(self, view): process = merlin_process(view.file_name()) process.sync_buffer_to_cursor(view) @@ -180,8 +181,10 @@ def _item_region(self, item): def _item_format(self, item): text = item['type'] - if item['tail'] == 'position': text = text + " (*tail-position*)" - if item['tail'] == 'call': text = text + " (*tail-call*)" + if item['tail'] == 'position': + text += " (*tail-position*)" + if item['tail'] == 'call': + text += " (*tail-call*)" return clean_whitespace(text) def _items(self): @@ -199,6 +202,7 @@ def on_done(self, index): sel.clear() sel.add(self._item_region(self.enclosing[index])) + class MerlinTypeCommand(sublime_plugin.WindowCommand): """ Return type information around cursor. @@ -207,6 +211,7 @@ def run(self): enclosing = MerlinTypeEnclosing(self.view) enclosing.show_panel() + class MerlinTypeMenu(sublime_plugin.TextCommand): """ Display type information in context menu @@ -215,6 +220,7 @@ def run(self, edit): enclosing = MerlinTypeEnclosing(self.view) enclosing.show_menu() + def merlin_locate_result(result, window): if isinstance(result, dict): pos = result['pos'] @@ -226,11 +232,12 @@ def merlin_locate_result(result, window): sel = view.sel() sel.clear() pos = merlin_pos(view, pos) - sel.add(sublime.Region(pos,pos)) + sel.add(sublime.Region(pos, pos)) view.show_at_center(pos) else: sublime.message_dialog(result) + class MerlinLocateMli(sublime_plugin.WindowCommand): """ Locate definition under cursor @@ -256,7 +263,7 @@ def run(self, edit): self.window.show_input_panel("Enter name", "", self.on_done, None, None) def kind(self): - return "mli" + return "mli" def on_done(self, name): view = self.window.active_view() @@ -267,14 +274,17 @@ def on_done(self, name): line, col = view.rowcol(pos[0].begin()) merlin_locate_result(process.locate(line + 1, col, ident=name), self.window) + class MerlinLocateMl(MerlinLocateMli): def kind(self): return "ml" + class MerlinLocateNameMl(MerlinLocateNameMli): def kind(self): return "ml" + class MerlinWhich(sublime_plugin.WindowCommand): """ Abstract command to quickly find a file. @@ -291,10 +301,10 @@ def run(self): self.window.show_quick_panel(self.files, self.on_done) def on_done(self, index): - if index == -1: return - module_name = self.files[index] - modules = map(lambda ext: module_name + ext, self.extensions()) - self.window.open_file(self.process.which_path(list(modules))) + if index != -1: + module_name = self.files[index] + modules = map(lambda ext: module_name + ext, self.extensions()) + self.window.open_file(self.process.which_path(list(modules))) class MerlinFindMl(MerlinWhich): @@ -303,7 +313,7 @@ class MerlinFindMl(MerlinWhich): """ def extensions(self): - return [".ml",".mli"] + return [".ml", ".mli"] class MerlinFindMli(MerlinWhich): @@ -312,7 +322,7 @@ class MerlinFindMli(MerlinWhich): """ def extensions(self): - return [".mli",".ml"] + return [".mli", ".ml"] class Autocomplete(sublime_plugin.EventListener): @@ -427,19 +437,20 @@ def show_errors(self, view): underlines = [] for e in errors: - pos_start = e['start'] - pos_stop = e['end'] - pnt_start = merlin_pos(view, pos_start) - pnt_stop = merlin_pos(view, pos_stop) - r = sublime.Region(pnt_start, pnt_stop) - line_r = view.full_line(r) - line_r = sublime.Region(line_r.a - 1, line_r.b) - underlines.append(r) - - # Remove line and character number - message = e['message'] - - error_messages.append((line_r, message)) + if 'start' in e and 'end' in e: + pos_start = e['start'] + pos_stop = e['end'] + pnt_start = merlin_pos(view, pos_start) + pnt_stop = merlin_pos(view, pos_stop) + r = sublime.Region(pnt_start, pnt_stop) + line_r = view.full_line(r) + line_r = sublime.Region(line_r.a - 1, line_r.b) + underlines.append(r) + + # Remove line and character number + message = e['message'] + + error_messages.append((line_r, message)) self.error_messages = error_messages flag = sublime.DRAW_OUTLINED