Skip to content

Commit

Permalink
Create initial project structure and add ServerNpmResource utility (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
rchl authored Mar 12, 2020
1 parent 8745db4 commit a300b10
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 2 deletions.
1 change: 1 addition & 0 deletions .sublime-dependency
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
10
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,29 @@
# LSP-utilities
Sublime Text library with common utilities used by LSP packages
# LSP utilities for Package Control

Module with LSP-related utilities for Sublime Text

## How to use

1. Create a `dependencies.json` file in your package root with the following contents:

```js
{
"*": {
"*": [
"lsp_utils",
"sublime_lib"
]
}
}
```

2. Run the **Package Control: Satisfy Dependencies** command via command palette

3. Import utility:

```python
from lsp_utils import ServerNpmResource
```

See also:
[Documentation on Dependencies](https://packagecontrol.io/docs/dependencies)
1 change: 1 addition & 0 deletions st3/lsp_utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .server_npm_resource import ServerNpmResource
106 changes: 106 additions & 0 deletions st3/lsp_utils/server_npm_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import json
import os
import shutil
import sublime
import subprocess
import threading

from sublime_lib import ResourcePath


def run_command(on_exit, on_error, popen_args):
"""
Runs the given args in a subprocess.Popen, and then calls the function
on_exit when the subprocess completes.
on_exit is a callable object, and popen_args is a list/tuple of args that
on_error when the subprocess throws an error
would give to subprocess.Popen.
"""
def run_in_thread(on_exit, on_error, popen_args):
try:
subprocess.check_call(popen_args, shell=sublime.platform() == 'windows')
on_exit()
except subprocess.CalledProcessError as error:
on_error(error)

thread = threading.Thread(target=run_in_thread, args=(on_exit, on_error, popen_args))
thread.start()
# returns immediately after the thread starts
return thread


def log_and_show_message(msg, additional_logs=None):
print(msg, '\n', additional_logs) if additional_logs else print(msg)
sublime.active_window().status_message(msg)


class ServerNpmResource(object):
"""Global object providing paths to server resources.
Also handles the installing and updating of the server in cache.
setup() needs to be called during (or after) plugin_loaded() for paths to be valid.
"""

def __init__(self, package_name, server_directory, server_binary_path):
self._initialized = False
self._package_name = package_name
self._server_directory = server_directory
self._binary_path = server_binary_path
self._package_cache_path = None

@property
def binary_path(self):
return os.path.join(self._package_cache_path, self._binary_path)

def setup(self):
if self._initialized:
return

self._initialized = True
self._package_cache_path = os.path.join(sublime.cache_path(), self._package_name)

self._copy_to_cache()

def cleanup(self):
if os.path.isdir(self._package_cache_path):
shutil.rmtree(self._package_cache_path)

def _copy_to_cache(self):
src_path = 'Packages/{}/{}/'.format(self._package_name, self._server_directory)
dst_path = 'Cache/{}/{}/'.format(self._package_name, self._server_directory)
cache_server_path = os.path.join(self._package_cache_path, self._server_directory)

if os.path.isdir(cache_server_path):
# Server already in cache. Check if version has changed and if so, delete existing copy in cache.
try:
src_package_json = json.loads(ResourcePath(src_path, 'package.json').read_text())
dst_package_json = json.loads(ResourcePath(dst_path, 'package.json').read_text())

if src_package_json['version'] != dst_package_json['version']:
shutil.rmtree(cache_server_path)
except FileNotFoundError:
shutil.rmtree(cache_server_path)

if not os.path.isdir(cache_server_path):
# create cache folder
ResourcePath(src_path).copytree(cache_server_path, exist_ok=True)

self._install_dependencies(cache_server_path)

def _install_dependencies(self, cache_server_path):
dependencies_installed = os.path.isdir(os.path.join(cache_server_path, 'node_modules'))
print('{}: Server {} installed.'.format(self._package_name, 'is' if dependencies_installed else 'is not'))

if not dependencies_installed:
# this will be called only when the plugin gets:
# - installed for the first time,
# - or when updated on package control
log_and_show_message('{}: Installing server.'.format(self._package_name))

run_command(
lambda: log_and_show_message(
'{}: Server installed.'.format(self._package_name)),
lambda error: log_and_show_message(
'{}: Error while installing the server.'.format(self._package_name), error),
["npm", "install", "--verbose", "--production", "--prefix", cache_server_path, cache_server_path]
)
19 changes: 19 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Tox (http://tox.testrun.org/) is a tool for running tests
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.

[tox]
envlist = py3
skipsdist = True

[pycodestyle]
max-line-length = 120

[flake8]
ignore =
F841 # local variable is assigned to but never used
E252 # missing whitespace around parameter equals
W504 # line break after binary operator
F821 # undefined name
max-line-length = 120

0 comments on commit a300b10

Please sign in to comment.