Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Using hdocs

  • Loading branch information...
commit 870cf1abe33d1c8d5a8d94650b6e2abcc0f8e879 1 parent ad29ebc
@mvoidex mvoidex authored
View
180 autocomplete.py
@@ -11,6 +11,7 @@
import symbols
import cache
import util
+ import hdocs
from ghci import ghci_info
from haskell_docs import haskell_docs
from hdevtools import start_hdevtools, stop_hdevtools
@@ -19,6 +20,7 @@
import SublimeHaskell.symbols as symbols
import SublimeHaskell.cache as cache
import SublimeHaskell.util as util
+ import SublimeHaskell.hdocs as hdocs
from SublimeHaskell.ghci import ghci_info
from SublimeHaskell.haskell_docs import haskell_docs
from SublimeHaskell.hdevtools import start_hdevtools, stop_hdevtools
@@ -428,54 +430,35 @@ def run(self, edit, filename = None, module_name = None, decl = None):
imported_symbol_not_found = False
with autocompletion.database.symbols as decl_symbols:
- if ident not in decl_symbols:
- # Sometimes ghc-mod returns no info about module, but module exists
- # So there are no info about valid symbol
- # But if user sure, that symbol exists, he can force to call for ghci to get info
- import_list = []
- with autocompletion.database.files as files:
- if current_file_name in files:
- import_list.extend(files[current_file_name].imports.keys())
+ if ident in decl_symbols:
- if module_word:
- # Full qualified name, just call to ghci_info
- info = ghci_info(module_word, ident)
- if info:
- self.show_symbol_info(info)
- return
- elif import_list:
- # Allow user to select module
- self.candidates = [(m, ident) for m in import_list]
- self.view.window().show_quick_panel(['{0}.{1}'.format(m, ident) for m in import_list], self.on_candidate_selected)
- return
+ decls = decl_symbols[ident] if not module_word else [d for d in decl_symbols[ident] if d.full_name() == full_name]
- show_status_message('Symbol {0} not found'.format(ident), False)
- return
-
- decls = decl_symbols[ident]
+ modules_dict = symbols.declarations_modules(decls, lambda ms: symbols.get_visible_module(ms, current_file_name)).values()
- modules_dict = symbols.declarations_modules(decls, lambda ms: symbols.get_visible_module(ms, current_file_name)).values()
-
- with autocompletion.database.files as files:
- if current_file_name in files:
- cur_info = files[current_file_name]
-
- if not module_word:
- # this module declaration
- candidates.extend([m.declarations[ident] for m in modules_dict if symbols.is_this_module(cur_info, m) and ident in m.declarations])
- if not candidates:
- # declarations from imported modules
- candidates.extend([m.declarations[ident] for m in modules_dict if symbols.is_imported_module(cur_info, m, module_word) and ident in m.declarations])
- if not candidates:
- imported_symbol_not_found = True
- # show all possible candidates
+ with autocompletion.database.files as files:
+ if current_file_name in files:
+ cur_info = files[current_file_name]
+
+ if not module_word:
+ # this module declaration
+ candidates.extend([m.declarations[ident] for m in modules_dict if symbols.is_this_module(cur_info, m) and ident in m.declarations])
+ if not candidates:
+ # declarations from imported modules
+ candidates.extend([m.declarations[ident] for m in modules_dict if symbols.is_imported_module(cur_info, m, module_word) and ident in m.declarations])
+ if not candidates:
+ imported_symbol_not_found = True
+ # show all possible candidates
+ candidates.extend([m.declarations[ident] for m in modules_dict if ident in m.declarations])
+
+ # No info about imports for this file, just add all declarations
+ else:
candidates.extend([m.declarations[ident] for m in modules_dict if ident in m.declarations])
- # No info about imports for this file, just add all declarations
- else:
- candidates.extend([m.declarations[ident] for m in modules_dict if ident in m.declarations])
+ else:
+ imported_symbol_not_found = True
- if imported_symbol_not_found:
+ if imported_symbol_not_found or not candidates:
browse_for_module = False
browse_module_candidate = None
with autocompletion.database.modules as modules:
@@ -493,7 +476,29 @@ def run(self, edit, filename = None, module_name = None, decl = None):
else:
show_status_message("No info about module {0}".format(full_name))
return
+ elif not candidates:
+ # Sometimes ghc-mod returns no info about module, but module exists
+ # So there are no info about valid symbol
+ # But if user sure, that symbol exists, he can force to call for ghci to get info
+ import_list = []
+ with autocompletion.database.files as files:
+ if current_file_name in files:
+ import_list.extend(files[current_file_name].imports.keys())
+
+ if module_word:
+ # Full qualified name, just call to ghci_info
+ info = ghci_info(module_word, ident)
+ if info:
+ self.show_symbol_info(info)
+ return
+ elif import_list:
+ # Allow user to select module
+ self.candidates = [(m, ident) for m in import_list]
+ self.view.window().show_quick_panel(['{0}.{1}'.format(m, ident) for m in import_list], self.on_candidate_selected)
+ return
+ show_status_message('Symbol {0} not found'.format(ident), False)
+ return
if not candidates:
show_status_message('Symbol {0} not found'.format(ident), False)
@@ -542,7 +547,7 @@ def browse_module(self, module):
with autocompletion.database.modules as modules:
decls = list(module.declarations.values())
self.candidates = decls
- self.view.window().show_quick_panel([[decl.brief(), decl.docs] if decl.docs else [decl.brief()] for decl in decls], self.on_done)
+ self.view.window().show_quick_panel([[decl.brief(), decl.docs.splitlines()[0]] if decl.docs else [decl.brief()] for decl in decls], self.on_done)
def is_enabled(self):
return is_enabled_haskell_command(self.view, False)
@@ -564,10 +569,14 @@ def run(self, module_name = None, filename = None):
module_candidate = symbols.get_preferred_module(modules[module_name], current_file_name)
+ if hdocs.load_module_docs(module_candidate):
+ # FIXME: Not here!
+ cache.dump_cabal_cache(autocompletion.database, module_candidate.cabal)
+
decls = list(module_candidate.declarations.values())
self.candidates = decls
- self.window.show_quick_panel([[decl.brief(), decl.docs] if decl.docs else [decl.brief()] for decl in decls], self.on_symbol_selected)
+ self.window.show_quick_panel([[decl.brief(), decl.docs.splitlines()[0]] if decl.docs else [decl.brief()] for decl in decls], self.on_symbol_selected)
return
cur_cabal = current_cabal()
@@ -696,6 +705,8 @@ def __init__(self):
self.cabal_lock = threading.Lock()
self.cabal_to_load = []
+ self.module_docs = LockedObject([])
+
self.update_event = threading.Event()
def run(self):
@@ -708,13 +719,25 @@ def run(self):
load_modules = self.modules_to_load
self.modules_to_load = []
+ cabal = current_cabal()
+
if len(load_modules) > 0:
try:
for m in load_modules:
- self._load_standard_module(m)
+ self._load_standard_module(m, cabal)
+ # self._load_standard_module_docs(m, cabal)
except:
continue
+ load_module_docs = []
+ with self.module_docs as module_docs:
+ load_module_docs = module_docs[:]
+ module_docs[:] = []
+
+ if len(load_module_docs) > 0:
+ for m in load_module_docs:
+ self._load_standard_module_docs(m, cabal)
+
with self.cabal_lock:
load_cabal = self.cabal_to_load
self.cabal_to_load = []
@@ -726,6 +749,9 @@ def run(self):
except:
continue
+ if len(load_modules) > 0:
+ cache.dump_cabal_cache(autocompletion.database)
+
self.update_event.wait(AGENT_SLEEP_TIMEOUT)
self.update_event.clear()
@@ -734,6 +760,11 @@ def load_module_info(self, module_name):
self.modules_to_load.append(module_name)
self.update_event.set()
+ def load_module_docs(self, module_name):
+ with self.module_docs as module_docs:
+ module_docs.append(module_name)
+ self.update_event.set()
+
def load_cabal_info(self, cabal_name = None):
if not cabal_name:
cabal_name = current_cabal()
@@ -777,6 +808,7 @@ def load_module_completions(self, cabal = None):
loaded_modules = 0
for m in modules:
self._load_standard_module(m, cabal)
+ # self._load_standard_module_docs(m, cabal)
loaded_modules += 1
s.percentage_message(loaded_modules, len(modules))
@@ -793,7 +825,7 @@ def _load_standard_module(self, module_name, cabal = None):
if not cabal:
cabal = current_cabal()
- if module_name not in autocompletion.database.get_cabal_modules():
+ if module_name not in autocompletion.database.get_cabal_modules(cabal):
try:
m = util.browse_module(module_name, cabal = cabal)
autocompletion.database.add_module(m)
@@ -801,22 +833,19 @@ def _load_standard_module(self, module_name, cabal = None):
except Exception as e:
log('Inspecting in-cabal module {0} failed: {1}'.format(module_name, e))
- def _load_standard_module_docs(self, module_name):
- if module_name in autocompletion.database.get_cabal_modules():
- try:
- msg = 'Loading docs for {0}'.format(module_name)
- begin_time = time.clock()
- log('loading docs for standard module {0}'.format(module_name))
+ def _load_standard_module_docs(self, module_name, cabal = None):
+ if not cabal:
+ cabal = current_cabal()
- with status_message(msg):
- in_module = autocompletion.database.get_cabal_modules()[module_name]
- for decl in in_module.declarations.values():
- decl.docs = haskell_docs(module_name, decl.name)
+ with autocompletion.database.cabal_modules as cabal_modules:
+ if module_name in cabal_modules[cabal]:
+ try:
+ have_docs = all(list(d.docs for d in cabal_modules[cabal][module_name].declarations.values()))
+ if not have_docs:
+ hdocs.load_module_docs(cabal_modules[cabal][module_name])
- end_time = time.clock()
- log('loaded docs for standard module {0} within {1} seconds'.format(module_name, end_time - begin_time))
- except Exception as e:
- log('Loading docs for in-cabal module {0} failed: {1}'.format(module_name, e))
+ except Exception as e:
+ log('Loading docs for in-cabal module {0} failed: {1}'.format(module_name, e))
@@ -835,6 +864,8 @@ def __init__(self):
self.dirty_files_lock = threading.Lock()
self.dirty_files = []
+ self.active_files = LockedObject([])
+
self.reinspect_event = threading.Event()
CABALMSG = 'Compiling Haskell CabalInspector'
@@ -873,14 +904,19 @@ def run(self):
# For first time, inspect all open folders and files
wait_for_window(lambda w: self.mark_all_files(w))
+ self.mark_active_files()
# TODO: If compilation failed, we can't proceed; handle this.
# Periodically wake up and see if there is anything to inspect.
while True:
files_to_reinspect = []
+ files_to_doc = []
with self.dirty_files_lock:
files_to_reinspect = self.dirty_files
self.dirty_files = []
+ with self.active_files as active_files:
+ files_to_doc = active_files[:]
+ active_files[:] = []
# Find the cabal project corresponding to each "dirty" file:
cabal_dirs = []
standalone_files = []
@@ -897,9 +933,23 @@ def run(self):
self._refresh_all_module_info(d, i + 1, len(cabal_dirs))
for f in standalone_files:
self._refresh_module_info(f)
+ for f in files_to_doc:
+ with autocompletion.database.files as files:
+ if f in files:
+ for i in files[f].imports.values():
+ std_inspector.load_module_docs(i.module)
self.reinspect_event.wait(AGENT_SLEEP_TIMEOUT)
self.reinspect_event.clear()
+ def mark_active_files(self):
+ def mark_active_files_():
+ for w in sublime.windows():
+ for v in w.views():
+ with self.active_files as active_files:
+ active_files.append(v.file_name())
+ sublime.set_timeout(lambda: mark_active_files_, 0)
+ self.reinspect_event.set()
+
def mark_all_files(self, window):
folder_files = []
for folder in window.folders():
@@ -911,6 +961,11 @@ def mark_all_files(self, window):
def show_errors(self, window, error_text):
sublime.set_timeout(lambda: output_error(window, error_text), 0)
+ def mark_file_active(self, filename):
+ with self.active_files as active_files:
+ active_files.append(filename)
+ self.reinspect_event.set()
+
def mark_file_dirty(self, filename):
"Report that a file should be reinspected."
with self.dirty_files_lock:
@@ -1152,9 +1207,14 @@ def on_new(self, view):
filename = view.file_name()
if filename:
inspector.mark_file_dirty(filename)
+ inspector.mark_file_active(filename)
def on_load(self, view):
self.set_cabal_status(view)
+ filename = view.file_name()
+ if filename:
+ inspector.mark_file_dirty(filename)
+ inspector.mark_file_active(filename)
def on_activated(self, view):
self.set_cabal_status(view)
View
4 hdevtools.py
@@ -20,7 +20,7 @@ def call_hdevtools_and_wait(arg_list, filename = None, cabal = None):
Shows a sublime error message if hdevtools is not available.
"""
- ghc_opts_args = get_ghc_opts_args(filename)
+ ghc_opts_args = get_ghc_opts_args(filename, cabal = cabal)
hdevtools_socket = get_setting_async('hdevtools_socket')
source_dir = get_source_dir(filename)
@@ -28,8 +28,6 @@ def call_hdevtools_and_wait(arg_list, filename = None, cabal = None):
arg_list.append('--socket={0}'.format(hdevtools_socket))
try:
- command = ['hdevtools'] + arg_list + ghc_opts_args
-
exit_code, out, err = call_and_wait(['hdevtools'] + arg_list + ghc_opts_args, cwd = source_dir)
if exit_code != 0:
View
66 hdocs.py
@@ -0,0 +1,66 @@
+import sublime
+import json
+import time
+
+if int(sublime.version()) < 3000:
+ from sublime_haskell_common import *
+else:
+ from SublimeHaskell.sublime_haskell_common import *
+
+def call_hdocs_and_wait(args, filename = None, cabal = None):
+ ghc_opts_args = get_ghc_opts_args(filename, cabal = cabal)
+ source_dir = get_source_dir(filename)
+
+ try:
+ command = ['hdocs'] + args + ghc_opts_args
+ log(command)
+
+ exit_code, out, err = call_and_wait(command, cwd = source_dir)
+
+ if exit_code != 0:
+ raise Exception("hdocs exited with status %d and stderr: %s" % (exit_code, err))
+
+ return crlf2lf(out)
+
+ except OSError as e:
+ if e.errno == errno.ENOENT:
+ output_error(sublime.active_window(), "SublimeHaskell: hdocs was not found!")
+
+ return None
+
+ except Exception as e:
+ log('calling to hdocs fails with {0}'.format(e))
+ return None
+
+def module_docs(module_name, cabal = None):
+ contents = call_hdocs_and_wait(['module', module_name], cabal = cabal)
+ return json.loads(contents)
+
+def symbol_docs(module_name, symbol_name, cabal = None):
+ contents = call_hdocs_and_wait(['name', symbol_name, module_name], cabal = cabal)
+ return contents
+
+def load_module_docs(module):
+ if module.location:
+ return False
+ if 'hdocs' in module.tags:
+ return False
+
+ docs = module_docs(module.name, module.cabal)
+ module.tags['hdocs'] = time.clock()
+ for decl in module.declarations.values():
+ if decl.name in docs:
+ decl.docs = docs[decl.name]
+
+ return True
+
+def start_server():
+ call_hdocs_and_wait(['start'])
+
+def start_hdocs():
+ thread = threading.Thread(
+ target=start_server)
+ thread.start()
+
+def stop_hdocs():
+ hdocs(['stop'])
View
30 sublime_haskell_common.py
@@ -1,6 +1,7 @@
import errno
import fnmatch
import os
+import re
import json
import sublime
import sublime_plugin
@@ -324,17 +325,19 @@ def set_setting(key, value):
def set_setting_async(key, value):
sublime.set_timeout(lambda: set_setting(key, value), 0)
-def ghci_package_db():
- dev = get_setting_async('use_cabal_dev')
- box = get_setting_async('cabal_dev_sandbox')
+def ghci_package_db(cabal = None):
+ if cabal == 'cabal':
+ return None
+ dev = True if cabal else get_setting_async('use_cabal_dev')
+ box = cabal if cabal else get_setting_async('cabal_dev_sandbox')
if dev and box:
package_conf = (filter(lambda x: re.match('packages-(.*)\.conf', x), os.listdir(box)) + [None])[0]
if package_conf:
return os.path.join(box, package_conf)
return None
-def ghci_append_package_db(cmd):
- package_conf = ghci_package_db()
+def ghci_append_package_db(cmd, cabal = None):
+ package_conf = ghci_package_db(cabal)
if package_conf:
cmd.extend(['-package-db', package_conf])
return cmd
@@ -378,7 +381,7 @@ def get_cwd(filename = None):
cwd = (get_cabal_project_dir_of_file(filename) or os.path.dirname(filename)) if filename else os.getcwd()
return cwd
-def get_ghc_opts(filename = None, add_package_db = True):
+def get_ghc_opts(filename = None, add_package_db = True, cabal = None):
"""
Gets ghc_opts, used in several tools, as list with extra '-package-db' option and '-i' option if filename passed
"""
@@ -386,7 +389,7 @@ def get_ghc_opts(filename = None, add_package_db = True):
if not ghc_opts:
ghc_opts = []
if add_package_db:
- package_db = ghci_package_db()
+ package_db = ghci_package_db(cabal = cabal)
if package_db:
ghc_opts.append('-package-db {0}'.format(package_db))
@@ -395,11 +398,11 @@ def get_ghc_opts(filename = None, add_package_db = True):
return ghc_opts
-def get_ghc_opts_args(filename = None, add_package_db = True):
+def get_ghc_opts_args(filename = None, add_package_db = True, cabal = None):
"""
Same as ghc_opts, but uses '-g' option for each option
"""
- opts = get_ghc_opts(filename, add_package_db)
+ opts = get_ghc_opts(filename, add_package_db, cabal)
args = []
for opt in opts:
args.extend(["-g", opt])
@@ -411,7 +414,7 @@ def call_ghcmod_and_wait(arg_list, filename=None, cabal = None):
Shows a sublime error message if ghc-mod is not available.
"""
- ghc_opts_args = get_ghc_opts_args(filename, add_package_db = False)
+ ghc_opts_args = get_ghc_opts_args(filename, add_package_db = False, cabal = cabal)
try:
command = attach_cabal_sandbox(['ghc-mod'] + arg_list + ghc_opts_args, cabal)
@@ -527,6 +530,7 @@ class StatusMessage(threading.Thread):
def __init__(self, msg, timeout, priority):
super(StatusMessage, self).__init__()
self.interval = 0.5
+ self.start_timeout = timeout
self.timeout = timeout
self.priority = priority
self.msg = msg
@@ -579,9 +583,11 @@ def is_highest_priority(self):
return False
def change_message(self, new_msg):
+ # There's progress, don't timeout
+ self.timeout = self.start_timeout
self.msg = new_msg
-def show_status_message_process(msg, isok = None, timeout = 60, priority = 0):
+def show_status_message_process(msg, isok = None, timeout = 300, priority = 0):
"""
Same as show_status_message, but shows permanently until called with isok not None
There can be only one message process in time, message with highest priority is shown
@@ -634,7 +640,7 @@ def percentage_message(self, current, total = 100):
def status_message(msg, isok = True):
return with_status_message(msg, isok, show_status_message)
-def status_message_process(msg, isok = True, timeout = 60, priority = 0):
+def status_message_process(msg, isok = True, timeout = 300, priority = 0):
return with_status_message(msg, isok, lambda m, ok = None: show_status_message_process(m, ok, timeout, priority))
def sublime_haskell_package_path():
View
5 symbols.py
@@ -39,6 +39,11 @@ def __init__(self, symbol_type, name, docs = None, location = None, module = Non
self.docs = docs
self.location = location
+ self.tags = {}
+
+ def full_name(self):
+ return self.module.name + '.' + self.name
+
class Import(object):
"""
Haskell import of module
Please sign in to comment.
Something went wrong with that request. Please try again.