├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── hyls ├── __init__.py ├── __main__.py ├── jedhy.hy └── server.hy └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /dist 3 | 4 | /hy_language_server.egg-info 5 | /hyls/__pycache__ 6 | /hyls/*.py 7 | !/hyls/__init__.py 8 | !/hyls/__main__.py 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Rintaro Okamura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | HY2PY ?= hy2py 2 | PYTHON ?= python 3 | PIP ?= pip 4 | 5 | HYSRCS := $(eval HYSRCS := $(shell find hyls -type f -regex ".*\.hy"))$(HYSRCS) 6 | PYSRCS = $(HYSRCS:%.hy=%.py) 7 | 8 | .PHONY: clean 9 | clean: 10 | rm -rf $(PYSRCS) dist build 11 | $(PYTHON) setup.py clean --all 12 | 13 | .PHONY: build 14 | build: \ 15 | $(PYSRCS) 16 | 17 | .PHONY: dev-install 18 | dev-install: \ 19 | build 20 | $(PIP) install -e . 21 | 22 | $(PYSRCS): $(HYSRCS) 23 | $(HY2PY) $(patsubst %.py,%.hy,$@) > $@ 24 | 25 | .PHONY: prepare-dist 26 | prepare-dist: \ 27 | build 28 | $(PIP) install --upgrade pip setuptools wheel twine 29 | $(PYTHON) setup.py sdist 30 | $(PYTHON) setup.py bdist_wheel 31 | 32 | .PHONY: publish 33 | publish: 34 | twine upload --repository pypi dist/* 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hy-language-server 2 | === 3 | 4 | [![PyPI version](https://badge.fury.io/py/hy-language-server.svg)](https://pypi.org/project/hy-language-server) 5 | 6 | [Hy](https://github.com/hylang/hy) Language Server built using [pygls](https://github.com/openlawlibrary/pygls) and [Jedhy](https://github.com/ekaschalk/jedhy). 7 | 8 | ## Supported Features 9 | 10 | Note: Currently, these features are available only for Hy's built-in core functions. 11 | 12 | - `textDocument/completion` 13 | - `textDocument/hover` 14 | 15 | ![hyls-with-nvim-example](https://user-images.githubusercontent.com/1588935/117307829-e2ac6b80-aebb-11eb-9d93-ab6087959d03.gif) 16 | 17 | ## Installation 18 | 19 | ```sh 20 | pip install hy-language-server 21 | ``` 22 | 23 | `hyls` will be installed under your PATH. 24 | 25 | If you are using Hy 1.0a1, please install the latest main branch. 26 | 27 | ```sh 28 | pip install git+https://github.com/rinx/jedhy.git@update/hy-1.0a1 29 | pip install git+https://github.com/rinx/hy-language-server.git 30 | ``` 31 | 32 | ## license 33 | 34 | MIT 35 | -------------------------------------------------------------------------------- /hyls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinx/hy-language-server/a1943d358b2b03728fe64c1c0755ff97298451d2/hyls/__init__.py -------------------------------------------------------------------------------- /hyls/__main__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | 4 | import hy 5 | 6 | from .server import Server 7 | 8 | logging.basicConfig(filename="/tmp/hyls.log", filemode="w", level=logging.DEBUG) 9 | logging.getLogger('hyls') 10 | 11 | def main(): 12 | parser = argparse.ArgumentParser() 13 | parser.description = 'hy language server' 14 | 15 | parser.add_argument( 16 | '--version', action='store_true', 17 | help='Print version and exit' 18 | ) 19 | 20 | args = parser.parse_args() 21 | 22 | if args.version: 23 | print('hy language server v0.0.7') 24 | return 25 | 26 | srv = Server() 27 | srv.start() 28 | 29 | if __name__ == '__main__': 30 | main() 31 | -------------------------------------------------------------------------------- /hyls/jedhy.hy: -------------------------------------------------------------------------------- 1 | (require [hy.contrib.walk [let]]) 2 | (import hy) 3 | 4 | (defclass Jedhy [] 5 | (defn __init__ [self jedhy [logger None]] 6 | (setv self.jedhy jedhy) 7 | (setv self.logger logger)) 8 | (defn refresh-ns [self __imports__] 9 | (for [__i__ __imports__] 10 | (self.logger.info (+ "import/require: " __i__)) 11 | (try 12 | (-> __i__ 13 | (hy.read-str) 14 | (hy.eval)) 15 | (except [e BaseException] 16 | (self.logger.info (+ "import/require failed: " (repr e)))))) 17 | (self.jedhy.set-namespace :locals- (locals) 18 | :globals- (globals) 19 | :macros- __macros__)) 20 | (defn complete [self prefix-str] 21 | (self.jedhy.complete prefix-str)) 22 | (defn docs [self candidate-str] 23 | (self.jedhy.docs candidate-str))) 24 | -------------------------------------------------------------------------------- /hyls/server.hy: -------------------------------------------------------------------------------- 1 | (require [hy.contrib.walk [let]]) 2 | 3 | (import logging) 4 | (import re) 5 | (import [jedhy.api [API]]) 6 | (import [pygls.lsp.methods [COMPLETION 7 | HOVER 8 | TEXT_DOCUMENT_DID_CHANGE 9 | TEXT_DOCUMENT_DID_CLOSE 10 | TEXT_DOCUMENT_DID_OPEN]]) 11 | (import [pygls.lsp.types [CompletionItem 12 | CompletionList 13 | CompletionOptions 14 | CompletionParams 15 | Hover 16 | MarkupContent 17 | MarkupKind]]) 18 | (import [pygls.server [LanguageServer]]) 19 | 20 | (import [.jedhy [Jedhy]]) 21 | 22 | (setv logger (logging.getLogger "hyls.server")) 23 | 24 | (defn cursor-line [ls uri ln] 25 | (let [doc (ls.workspace.get_document uri) 26 | content doc.source 27 | lines (content.split "\n")] 28 | (get lines ln))) 29 | 30 | (defn cursor-word [ls uri ln cn] 31 | (let [line (cursor-line ls uri ln)] 32 | (for [m (re.finditer r"[\.\?\-\w]+" line)] 33 | (when (and (<= (m.start) cn) (<= cn (m.end))) 34 | (return (cut line (m.start) cn)))))) 35 | 36 | (defn cursor-word-all [ls uri ln cn] 37 | (let [line (cursor-line ls uri ln)] 38 | (for [m (re.finditer r"[\.\?\-\w]+" line)] 39 | (when (and (<= (m.start) cn) (<= cn (m.end))) 40 | (return (cut line (m.start) (m.end))))))) 41 | 42 | (defclass Server [] 43 | (defn __init__ [self] 44 | (setv self.server (LanguageServer)) 45 | (setv self.jedhy (Jedhy (API) :logger logger)) 46 | (setv self.imports []) 47 | 48 | (with-decorator 49 | (self.server.feature 50 | COMPLETION 51 | (CompletionOptions :trigger_characters ["."])) 52 | (defn completions [params] 53 | (let [word (cursor-word self.server 54 | params.text_document.uri 55 | params.position.line 56 | params.position.character)] 57 | (setv complist (CompletionList 58 | :is_incomplete False 59 | :items [])) 60 | (when (not (none? word)) 61 | (for [candidate (self.jedhy.complete word)] 62 | (complist.add_item (CompletionItem :label candidate)))) 63 | complist))) 64 | (with-decorator 65 | (self.server.feature HOVER) 66 | (defn hover [params] 67 | (let [word (cursor-word-all self.server 68 | params.text_document.uri 69 | params.position.line 70 | params.position.character)] 71 | (when (not (none? word)) 72 | (let [docs (self.jedhy.docs word)] 73 | (when (!= docs "") 74 | (Hover 75 | :contents (MarkupContent 76 | :kind MarkupKind.PlainText 77 | :value docs)))))))) 78 | (with-decorator 79 | (self.server.feature TEXT_DOCUMENT_DID_OPEN) 80 | (defn did-open [params] 81 | (setv self.imports []) 82 | (self.find-and-eval-imports self.server params.text_document.uri) 83 | (self.jedhy.refresh-ns self.imports))) 84 | (with-decorator 85 | (self.server.feature TEXT_DOCUMENT_DID_CLOSE) 86 | (defn did-close [params] 87 | (setv self.imports []))) 88 | (with-decorator 89 | (self.server.feature TEXT_DOCUMENT_DID_CHANGE) 90 | (defn did-change [params] 91 | None))) 92 | (defn find-and-eval-imports [self ls uri] 93 | (let [doc (ls.workspace.get_document uri)] 94 | (for [m (re.finditer r"\(\s*(import|require)\s+([\w\.]+|\[[\w\.\s\*\?:\[\]]+\])\)" doc.source)] 95 | (logger.info (+ "try to evaluate: " (m.group))) 96 | (try 97 | (-> (m.group) 98 | (hy.read-str) 99 | (hy.eval)) 100 | (except [e BaseException] 101 | (logger.info (+ "cannot evaluate: " (repr e)))) 102 | (else 103 | (self.imports.append (m.group))))))) 104 | (defn start [self] 105 | (self.server.start_io))) 106 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open('README.md', 'r') as f: 4 | long_description = f.read() 5 | 6 | setup( 7 | name='hy-language-server', 8 | version='0.0.7', 9 | author='Rintaro Okamura', 10 | author_email='rintaro.okamura@gmail.com', 11 | description='hy language server using Jedhy', 12 | long_description=long_description, 13 | long_description_content_type='text/markdown', 14 | url='https://github.com/rinx/hy-language-server', 15 | packages=['hyls'], 16 | package_data={ 17 | 'hyls': ['*.hy', '__pycache__/*'] 18 | }, 19 | python_requires='>=3.6', 20 | install_requires=[ 21 | 'argparse', 22 | 'hy @ git+https://github.com/hylang/hy.git', 23 | 'pygls', 24 | 'jedhy @ git+https://github.com/rinx/jedhy.git@update/hy-1.0a1' 25 | ], 26 | entry_points={ 27 | 'console_scripts': [ 28 | 'hyls=hyls.__main__:main' 29 | ] 30 | } 31 | ) 32 | --------------------------------------------------------------------------------