Skip to content

Commit

Permalink
Update pygls + argparse, adding 0.6.0 features
Browse files Browse the repository at this point in the history
  • Loading branch information
pappasam committed May 21, 2021
1 parent 5239581 commit 3501846
Show file tree
Hide file tree
Showing 7 changed files with 352 additions and 225 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.6.0

### Added

- Configurable logging (including the option to log to a file)
- TCP support

### Changed

- Migrate from click to argparse
- Upgrade pygls to 0.10.3+, fixing breaking public api changes where applicable

## 0.5.1

### Fixed
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,32 @@ augroup end

Note: this list is non-exhaustive. If you know of a great choice not included in this list, please submit a PR!

## Command line

nginx-language-server can be run directly from the command line.

```console
$ nginx-language-server --help
usage: nginx-language-server [-h] [--version] [--tcp] [--host HOST]
[--port PORT] [--log-file LOG_FILE] [-v]

Nginx language server: an LSP server for nginx.conf.

optional arguments:
-h, --help show this help message and exit
--version display version information and exit
--tcp use TCP server instead of stdio
--host HOST host for TCP server (default 127.0.0.1)
--port PORT port for TCP server (default 2088)
--log-file LOG_FILE redirect logs to the given file instead of writing to
stderr
-v, --verbose increase verbosity of log output

Examples:

Run from stdio: nginx-language-server
```

## Inspiration

The useful language data for nginx is ported from [vscode-nginx-conf-hint](https://github.com/hangxingliu/vscode-nginx-conf-hint). I would have used this library directly, but alas! It's written only for VSCode and I use Neovim.
Expand Down
100 changes: 91 additions & 9 deletions nginx_language_server/cli.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,99 @@
"""Cli for nginx language server."""

import click
import argparse
import logging
import sys

from .server import SERVER


@click.command()
@click.version_option()
def cli() -> None:
"""Nginx language server.
def get_version() -> str:
"""Get the program version."""
# pylint: disable=import-outside-toplevel
try:
# Type checker for Python < 3.8 fails.
# Since this ony happens here, we just ignore.
from importlib.metadata import version # type: ignore
except ImportError:
try:
# Below ignored both because this a redefinition from above, and
# because importlib_metadata isn't known by mypy. Ignored because
# this is intentional.
from importlib_metadata import version # type: ignore
except ImportError:
print(
"Error: unable to get version. "
"If using Python < 3.8, you must install "
"`importlib_metadata` to get the version.",
file=sys.stderr,
)
sys.exit(1)
return version("nginx-language-server")


Examples:
def cli() -> None:
"""Nginx language server cli entrypoint."""
parser = argparse.ArgumentParser(
prog="nginx-language-server",
formatter_class=argparse.RawDescriptionHelpFormatter,
description="Nginx language server: an LSP server for nginx.conf.",
epilog="""\
Examples:
Run from stdio : nginx-language-server
"""
SERVER.start_io()
Run from stdio: nginx-language-server
""",
)
parser.add_argument(
"--version",
help="display version information and exit",
action="store_true",
)
parser.add_argument(
"--tcp",
help="use TCP server instead of stdio",
action="store_true",
)
parser.add_argument(
"--host",
help="host for TCP server (default 127.0.0.1)",
type=str,
default="127.0.0.1",
)
parser.add_argument(
"--port",
help="port for TCP server (default 2088)",
type=int,
default=2088,
)
parser.add_argument(
"--log-file",
help="redirect logs to the given file instead of writing to stderr",
type=str,
)
parser.add_argument(
"-v",
"--verbose",
help="increase verbosity of log output",
action="count",
default=0,
)
args = parser.parse_args()
if args.version:
print(get_version())
sys.exit(0)
log_level = {0: logging.INFO, 1: logging.DEBUG}.get(
args.verbose,
logging.DEBUG,
)
if args.log_file:
logging.basicConfig(
filename=args.log_file,
filemode="w",
level=log_level,
)
else:
logging.basicConfig(stream=sys.stderr, level=log_level)
if args.tcp:
SERVER.start_tcp(host=args.host, port=args.port)
else:
SERVER.start_io()
59 changes: 3 additions & 56 deletions nginx_language_server/pygls_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,10 @@
"""


import functools
import re
from typing import Optional

from pygls.types import Position, Range
from pygls.workspace import Document, position_from_utf16

_SENTINEL = object()

RE_END_WORD = re.compile(r'^[^ ;]*')
RE_START_WORD = re.compile(r'[^ ;]*$')


def rgetattr(obj: object, attr: str, default: object = None) -> object:
"""Get nested attributes, recursively.
Usage:
>> repr(my_object)
Object(hello=Object(world=2))
>> rgetattr(my_object, "hello.world")
2
>> rgetattr(my_object, "hello.world.space")
None
>> rgetattr(my_object, "hello.world.space", 20)
20
"""
result = _rgetattr(obj, attr)
return default if result is _SENTINEL else result


def _rgetattr(obj: object, attr: str) -> object:
"""Get nested attributes, recursively."""

def _getattr(obj, attr):
return getattr(obj, attr, _SENTINEL)

return functools.reduce(_getattr, [obj] + attr.split(".")) # type: ignore
from pygls.lsp.types import Position, Range
from pygls.workspace import Document


def char_before_cursor(
Expand All @@ -63,31 +30,11 @@ def char_after_cursor(
return default


def word_at_position(document: Document, position: Position) -> str:
"""Get the word under the cursor returning the start and end positions."""
lines = document.lines
if position.line >= len(lines):
return ""

row, col = position_from_utf16(lines, position)
line = lines[row]
# Split word in two
start = line[:col]
end = line[col:]

# Take end of start and start of end to find word
# These are guaranteed to match, even if they match the empty string
m_start = RE_START_WORD.findall(start)
m_end = RE_END_WORD.findall(end)

return m_start[0] + m_end[-1]


def current_word_range(
document: Document, position: Position
) -> Optional[Range]:
"""Get the range of the word under the cursor."""
word = word_at_position(document, position)
word = document.word_at_position(position)
word_len = len(word)
line: str = document.lines[position.line]
start = 0
Expand Down
19 changes: 12 additions & 7 deletions nginx_language_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@

from typing import Optional

from pygls.features import COMPLETION, HOVER
from pygls.server import LanguageServer
from pygls.types import (
from pygls.lsp.methods import COMPLETION, HOVER
from pygls.lsp.types import (
CompletionItem,
CompletionItemKind,
CompletionList,
CompletionOptions,
CompletionParams,
Hover,
InsertTextFormat,
MarkupContent,
MarkupKind,
TextDocumentPositionParams,
)
from pygls.server import LanguageServer

from . import pygls_utils
from .parser import DIRECTIVES, VARIABLES, nginxconf
Expand All @@ -34,12 +35,15 @@
# Server capabilities


@SERVER.feature(COMPLETION, trigger_characters=["$"])
@SERVER.feature(
COMPLETION,
CompletionOptions(trigger_characters=["$"]),
)
def completion(
server: LanguageServer, params: CompletionParams
) -> Optional[CompletionList]:
"""Returns completion items."""
document = server.workspace.get_document(params.textDocument.uri)
document = server.workspace.get_document(params.text_document.uri)
parsed = nginxconf.convert(document.source)
line = nginxconf.find(parsed, params.position.line)
if not line:
Expand Down Expand Up @@ -70,14 +74,15 @@ def completion(
else None
)


@SERVER.feature(HOVER)
def hover(
server: LanguageServer, params: TextDocumentPositionParams
) -> Optional[Hover]:
"""Support Hover."""
document = server.workspace.get_document(params.textDocument.uri)
document = server.workspace.get_document(params.text_document.uri)
parsed = nginxconf.convert(document.source)
word = pygls_utils.word_at_position(document, params.position)
word = document.word_at_position(params.position)
line = nginxconf.find(parsed, params.position.line)

# append "_name" to beginning of word
Expand Down
Loading

0 comments on commit 3501846

Please sign in to comment.