Skip to content

Commit

Permalink
Add support for more languages, per-project lookup paths (#31)
Browse files Browse the repository at this point in the history
* Refactor the generic resolver

- Replaces the resolvers for Less, PHP and HTML with a new generic resolver.
- Removes the old generic resolver.
- Adds support for CSS, Pug, SugarSS, SugarML, Nunjucks, Jinja2, Twig, all using the new generic resolver.

* Add project settings support

- Add `"lookup_paths"` support for languages that use the generic resolver
- Add support for a `"hyper_click"` setting in project-settings, used for per-project lookup paths
- Also fixes a bug with files opened through the quick panel
  • Loading branch information
cristianl authored Jan 11, 2018
1 parent a45e125 commit ab37554
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 122 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ files. `Go to Definition` functionality of Sublime, trying to be a generic solut
falls short for most languages since jumping between these required files needs
some knowledge about how the language or package manager of the language is working.

HyperClick tries to solve this issue. Currently, it knows how to jump between files
in **Javascript**, **Sass**, **Less**, **Stylus**, **PHP**, **HTML (imports)** but can be easily
extended to support more languages.
HyperClick tries to solve this issue. Currently, it knows how to jump between files in the following languages, but it can easily be extended to add more languages:

## Supported Languages and Syntaxes

Expand All @@ -25,6 +23,13 @@ extended to support more languages.
| PHP | `PHP.sublime-syntax` <br> `PHP Source.sublime-syntax` |
| HTML | `HTML.sublime-syntax` |
| JSTL | `JSTL.sublime-syntax` |
| CSS/PostCSS | `CSS.sublime-syntax` <br> `PostCSS.tmLanguage` |
| Pug | `Pug.tmLanguage` |
| SugarSS | `Sss.tmLanguage` <br> `SugarSS.tmLanguage` |
| SugarML | `SugarML.tmLanguage` |
| Nunjucks | `Nunjucks.tmLanguage` |
| Jinja2 | `Jinja2.tmLanguage` |
| Twig | `HTML (Twig).tmLanguage` <br> `Craft-Twig.tmLanguage` |

*You can contribute and add more languages by adding a path resolver like [SassPathResolver](https://github.com/aziz/SublimeHyperClick/blob/master/hyper_click/sass_path_resolver.py)*

Expand Down
67 changes: 66 additions & 1 deletion hyper_click.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,30 @@
],
"jstl": [
"jstl.tmLanguage"
],
"css": [
"CSS.sublime-syntax",
"PostCSS.tmLanguage"
],
"pug": [
"Pug.tmLanguage"
],
"sugarss": [
"Sss.tmLanguage",
"SugarSS.tmLanguage"
],
"sugarml": [
"SugarML.tmLanguage"
],
"nunjucks": [
"Nunjucks.tmLanguage"
],
"jinja2": [
"Jinja2.tmLanguage"
],
"twig": [
"HTML (Twig).tmLanguage",
"Craft-Twig.tmLanguage"
]
},
"import_line_regex": {
Expand Down Expand Up @@ -53,6 +77,40 @@
],
"jstl": [
"(<([\\w-]*)\\:([\\w-]*))",
],
"css": [
"^@import\\s+['\"](.+)['\"].*?;$",
"^@import\\s+url\\(['\"](.+)['\"]\\).*?;$"
],
"pug": [
"^include\\s+(.+)$",
"^extends\\s+(.+)$"
],
"sugarss": [
"^@import\\s+['\"](.+)['\"]$"
],
"sugarml": [
"^include\\(src=['\"]\/?(.+)['\"]\\)$",
"^extends\\(src=['\"]\/?(.+)['\"]\\)$",
],
"nunjucks": [
"^{%\\s+extends\\s+['\"](.+)['\"].*?%}$",
"^{%\\s+include\\s+['\"](.+)['\"].*?%}$",
"^{%\\s+import\\s+['\"](.+)['\"].*?%}$",
"^{%\\s+from\\s+['\"](.+)['\"]\\s+import.*?%}$"
],
"jinja2": [
"^{%\\s+extends\\s+['\"](.+)['\"].*?%}$",
"^{%\\s+include\\s+['\"](.+)['\"].*?%}$",
"^{%\\s+import\\s+['\"](.+)['\"].*?%}$",
"^{%\\s+from\\s+['\"](.+)['\"]\\s+import.*?%}$"
],
"twig": [
"^{%\\s+extends\\s+['\"](.+)['\"].*?%}$",
"^{%\\s+include\\s+['\"](.+)['\"].*?%}$",
"^{%\\s+import\\s+['\"](.+)['\"].*?%}$",
"^{%\\s+from\\s+['\"](.+)['\"]\\s+import.*?%}$",
"^{%\\s+embed\\s+['\"](.+)['\"].*?%}$",
]
},
"valid_extensions": {
Expand All @@ -62,7 +120,14 @@
"php": ["php"],
"stylus": ["styl", "stylus"],
"html": ["html"],
"jstl": ["jsp", "tag"]
"jstl": ["jsp", "tag"],
"css": ["css", "pcss", "postcss"],
"pug": ["pug", "jade"],
"sugarss": ["sss"],
"sugarml": ["sgr"],
"nunjucks": ["njk", "nunjucks", "njs", "html"],
"jinja2": ["j2"],
"twig": ["twig"]
},
"default_filenames": {
"js": ["index"]
Expand Down
23 changes: 0 additions & 23 deletions hyper_click/html_path_resolver.py

This file was deleted.

2 changes: 1 addition & 1 deletion hyper_click/js_path_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def walkup_dir(start_path, vendor_dirs, endpath = '/'):
# doc: https://nodejs.org/dist/latest-v8.x/docs/api/modules.html#modules_all_together

class JsPathResolver:
def __init__(self, str_path, current_dir, roots, lang, settings):
def __init__(self, str_path, current_dir, roots, lang, settings, proj_settings):
self.str_path = str_path
self.current_dir = current_dir
self.lang = lang
Expand Down
26 changes: 0 additions & 26 deletions hyper_click/less_path_resolver.py

This file was deleted.

59 changes: 59 additions & 0 deletions hyper_click/path_generic_resolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
from os import path


# A default resolver that resolves to a subfolder along along with one of the valid extensions
class GenericPathResolver:
def __init__(self, str_path, current_dir, roots, lang, settings, proj_settings):
self.str_path = str_path
self.current_dir = current_dir
self.lang = lang
self.settings = settings
self.roots = roots
self.valid_extensions = settings.get('valid_extensions', {})[lang]
self.proj_settings = proj_settings

self.matching_root = [root for root in self.roots if self.current_dir.startswith(root)]
self.current_root = self.matching_root[0]
self.lookup_paths = self.proj_settings.get('lookup_paths', {}).get(lang, False) or settings.get('lookup_paths', {}).get(lang, False) or []

def resolve(self):
combined = path.realpath(path.join(self.current_dir, self.str_path))

# Try to match the literal filename referenced
# match: '../variables/palette.less'
if path.isfile(combined):
return combined

# Try to use the path given in project settings for this language
result = self.resolve_in_lookup_paths(self.str_path)
if result:
return result

# Loop all allowed extensions, and try matching those
# match '../variables/palette' to '../variables/palette.less'
for ext in self.valid_extensions:
file_path = combined + '.' + ext
if path.isfile(file_path):
return file_path

return ''

def resolve_relative_to_dir(self, target, directory):
combined = path.realpath(path.join(directory, target))
return self.resolve_as_file(combined)

def resolve_in_lookup_paths(self, target):
for lookup_path in self.lookup_paths:
result = self.resolve_relative_to_dir(target, path.join(self.current_root, lookup_path))
if result:
return result

def resolve_as_file(self, path_name):
if path.isfile(path_name):
return path_name
# match imports without extension
for ext in self.valid_extensions:
file_path = path_name + '.' + ext
if path.isfile(file_path):
return file_path
23 changes: 0 additions & 23 deletions hyper_click/path_generic_subfolder_resolver.py

This file was deleted.

20 changes: 6 additions & 14 deletions hyper_click/path_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,23 @@
from os import path
from .js_path_resolver import JsPathResolver
from .sass_path_resolver import SassPathResolver
from .less_path_resolver import LessPathResolver
from .jstl_path_resolver import JstlPathResolver
from .php_path_resolver import PhpPathResolver
from .html_path_resolver import HTMLPathResolver
from .path_generic_subfolder_resolver import GenericSubfolderResolver
from .path_generic_resolver import GenericPathResolver


class HyperClickPathResolver:
def __init__(self, view, str_path, roots, lang, settings):
def __init__(self, view, str_path, roots, lang, settings,
proj_settings):
current_file = view.file_name()
current_dir = path.dirname(path.realpath(current_file))
if lang == 'js':
self.resolver = JsPathResolver(str_path, current_dir, roots, lang, settings)
self.resolver = JsPathResolver(str_path, current_dir, roots, lang, settings, proj_settings)
elif lang == 'sass':
self.resolver = SassPathResolver(str_path, current_dir, roots, lang, settings)
elif lang == 'less':
self.resolver = LessPathResolver(str_path, current_dir, roots, lang, settings)
elif lang == 'php':
self.resolver = PhpPathResolver(str_path, current_dir, roots, lang, settings)
elif lang == 'html':
self.resolver = HTMLPathResolver(str_path, current_dir, roots, lang, settings)
self.resolver = SassPathResolver(str_path, current_dir, roots, lang, settings, proj_settings)
elif lang == 'jstl':
self.resolver = JstlPathResolver(view, str_path, current_dir, roots, lang, settings)
else:
self.resolver = GenericSubfolderResolver(str_path, current_dir, roots, lang, settings)
self.resolver = GenericPathResolver(str_path, current_dir, roots, lang, settings, proj_settings)

def resolve(self):
return self.resolver.resolve()
23 changes: 0 additions & 23 deletions hyper_click/php_path_resolver.py

This file was deleted.

2 changes: 1 addition & 1 deletion hyper_click/sass_path_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@


class SassPathResolver:
def __init__(self, str_path, current_dir, roots, lang, settings):
def __init__(self, str_path, current_dir, roots, lang, settings, proj_settings):
self.str_path = str_path
self.current_dir = current_dir
self.lang = lang
Expand Down
11 changes: 8 additions & 3 deletions hyper_click_annotator.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ def annotate(self, point):
self.roots = self.view.window().folders()
self.syntax = self.view.settings().get('syntax')
self.lang = self.get_lang(self.syntax)

# Per-project settings are optional
self.proj_settings = self.window.project_data().get('hyper_click', {})

v = self.view
line_range = v.line(point)

Expand All @@ -72,9 +76,10 @@ def annotate(self, point):

if matched:
destination_str = matched.group(1)
file_path = HyperClickPathResolver(v,
destination_str,
self.roots, self.lang, self.settings
file_path = HyperClickPathResolver(
v, destination_str,
self.roots, self.lang, self.settings,
self.proj_settings
)
region = sublime.Region(line_range.b, line_range.b)
self.current_line = v.line(line_range.b)
Expand Down
13 changes: 9 additions & 4 deletions hyper_click_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ def __init__(self, view):
self.view = view
self.settings = sublime.load_settings('hyper_click.sublime-settings')
self.window = view.window()
self.roots = self.window and self.window.folders()
self.syntax = self.view.settings().get('syntax')
self.lang = self.get_lang(self.syntax)

Expand All @@ -24,15 +23,21 @@ def run(self, edit):
if len(v.sel()) != 1:
return

# Setting self.roots here (instead of in `__init__`) fixes a bug with files opened through the quick panel
self.roots = self.window and self.window.folders()
# Per-project settings are optional
self.proj_settings = self.window.project_data().get('hyper_click', {})

cursor = v.sel()[0].a
line_range = v.line(cursor)
line_content = v.substr(line_range).strip()
matched = self.is_valid_line(line_content)
if matched:
destination_str = matched.group(1)
file_path = HyperClickPathResolver(v,
destination_str,
self.roots, self.lang, self.settings
file_path = HyperClickPathResolver(
v, destination_str,
self.roots, self.lang, self.settings,
self.proj_settings
)
resolved_path = file_path.resolve()
if resolved_path:
Expand Down

0 comments on commit ab37554

Please sign in to comment.