add updates libs
This commit is contained in:
+149
-61
@@ -1,22 +1,27 @@
|
||||
import argparse
|
||||
import dataclasses
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from lsprotocol import types as lsp
|
||||
|
||||
from pygls.server import LanguageServer
|
||||
from pygls.workspace import TextDocument
|
||||
from pygls.uris import to_fs_path
|
||||
from pygls.workspace import TextDocument
|
||||
|
||||
from tclint.cli import tclint
|
||||
from tclint.config import get_config, DEFAULT_CONFIGS, RunConfig, Config, ConfigError
|
||||
from tclint.cli import tclint, utils
|
||||
from tclint.commands.plugins import PluginManager
|
||||
from tclint.config import (
|
||||
DEFAULT_CONFIGS,
|
||||
Config,
|
||||
ConfigError,
|
||||
SpacesInBraces,
|
||||
load_config_at,
|
||||
)
|
||||
from tclint.format import Formatter, FormatterOpts
|
||||
from tclint.lexer import TclSyntaxError
|
||||
from tclint.parser import Parser
|
||||
from tclint.cli import utils
|
||||
|
||||
try:
|
||||
from tclint._version import __version__ # type: ignore
|
||||
@@ -25,13 +30,14 @@ except ModuleNotFoundError:
|
||||
|
||||
|
||||
DIAGNOSTIC_SOURCE = "tclint"
|
||||
_DEFAULT_CONFIG = Config()
|
||||
|
||||
|
||||
def lint(source, config, path):
|
||||
def lint(source, config, plugin_manager, path):
|
||||
diagnostics = []
|
||||
|
||||
try:
|
||||
violations = tclint.lint(source, config, path)
|
||||
violations = tclint.lint(source, config, plugin_manager, path)
|
||||
except TclSyntaxError as e:
|
||||
return [
|
||||
lsp.Diagnostic(
|
||||
@@ -62,7 +68,7 @@ def lint(source, config, path):
|
||||
start=start,
|
||||
end=end,
|
||||
),
|
||||
code=violation.id,
|
||||
code=str(violation.id),
|
||||
source=DIAGNOSTIC_SOURCE,
|
||||
)
|
||||
)
|
||||
@@ -83,15 +89,26 @@ class TclspServer(LanguageServer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.diagnostics = {}
|
||||
self.global_config: RunConfig = None
|
||||
# Maps workspace roots to configs.
|
||||
self.configs: Dict[Path, RunConfig] = {}
|
||||
|
||||
# There are many config caches!!
|
||||
# Caches loaded config files specified in LSP settings.
|
||||
self.workspace_configs: dict[Path, Config] = {}
|
||||
# Caches loaded config files present in open workspaces.
|
||||
self.config_files: dict[Path, Config] = {}
|
||||
# Caches which config is used by each open file.
|
||||
self.source_configs: dict[Path, Config] = {}
|
||||
# Tracks which invalid configs we've already displayed an error for, to avoid
|
||||
# spam.
|
||||
self.invalid_configs: set[Path] = set()
|
||||
|
||||
self.client_supports_refresh = False
|
||||
|
||||
self.global_settings = ExtensionSettings()
|
||||
self.workspace_settings: Dict[Path, ExtensionSettings] = {}
|
||||
self.workspace_settings: dict[Path, ExtensionSettings] = {}
|
||||
|
||||
def get_roots(self) -> List[Path]:
|
||||
self.plugin_manager = PluginManager()
|
||||
|
||||
def get_roots(self) -> list[Path]:
|
||||
"""Returns root folders currently open in the workspace."""
|
||||
roots = []
|
||||
for uri in self.workspace.folders.keys():
|
||||
@@ -108,10 +125,8 @@ class TclspServer(LanguageServer):
|
||||
return roots
|
||||
|
||||
def get_root(self, path: Path) -> Optional[Path]:
|
||||
"""Returns workspace root folder that's closest to path.
|
||||
|
||||
Returns None if path is not in a workspace folder or if there are no workspace
|
||||
folders.
|
||||
"""Returns workspace root if path is in a workspace folder. Otherwise, returns
|
||||
None.
|
||||
"""
|
||||
roots = self.get_roots()
|
||||
closest_root = None
|
||||
@@ -124,55 +139,98 @@ class TclspServer(LanguageServer):
|
||||
if len(relpath.parts) < distance:
|
||||
distance = len(relpath.parts)
|
||||
closest_root = root
|
||||
|
||||
return closest_root
|
||||
|
||||
def get_config_file(self, workspace_root: Path) -> Optional[Path]:
|
||||
if workspace_root in self.workspace_settings:
|
||||
settings = self.workspace_settings[workspace_root]
|
||||
def get_config_file(self, root: Path) -> Optional[Path]:
|
||||
if root in self.workspace_settings:
|
||||
settings = self.workspace_settings[root]
|
||||
return settings.config_file
|
||||
return self.global_settings.config_file
|
||||
|
||||
def load_configs(self):
|
||||
self.configs = {}
|
||||
def show_config_error(self, msg: str, path: Path):
|
||||
if path not in self.invalid_configs:
|
||||
self.show_message(f"Error loading config file: {msg}")
|
||||
self.invalid_configs.add(path)
|
||||
|
||||
def load_config(self, path: Path, root: Path) -> Optional[Config]:
|
||||
try:
|
||||
return Config.from_path(path, root)
|
||||
except FileNotFoundError:
|
||||
self.show_config_error(f"{path} doesn't exist", path)
|
||||
return None
|
||||
except ConfigError as e:
|
||||
self.show_config_error(str(e), path)
|
||||
return None
|
||||
|
||||
def load_workspace_setting_configs(self):
|
||||
"""These may be used a lot if specified, so cache specially."""
|
||||
for root in self.get_roots():
|
||||
try:
|
||||
path = self.get_config_file(root)
|
||||
config = get_config(path, root)
|
||||
path = self.get_config_file(root)
|
||||
if path is None:
|
||||
continue
|
||||
config = self.load_config(path, root)
|
||||
if config is None:
|
||||
continue
|
||||
self.workspace_configs[root] = config
|
||||
|
||||
def _get_config(self, path: Path) -> Config:
|
||||
workspace_root = self.get_root(path)
|
||||
|
||||
# First, check for configs specified in the LSP settings.
|
||||
# If not in a workspace, our only shot is to use a global config. Otherwise, we
|
||||
# bail (no searching, since the LSP only searches up to the workspace root).
|
||||
if workspace_root is None:
|
||||
global_file = self.global_settings.config_file
|
||||
if global_file is not None:
|
||||
config = self.load_config(global_file, path.parent)
|
||||
if config is not None:
|
||||
self.configs[root] = config
|
||||
except ConfigError as e:
|
||||
self.show_message(f"Error loading config file: {e}")
|
||||
return config
|
||||
return _DEFAULT_CONFIG
|
||||
|
||||
# If a global config file exists, we apply it to any file not under a workspace
|
||||
# folder.
|
||||
global_path = self.global_settings.config_file
|
||||
if global_path is not None:
|
||||
# If file is in a workspace, and we've got a workspace config configured, use
|
||||
# that (this logic also handles global configs, since these are still
|
||||
# instantiated once per workspace to resolve relative paths).
|
||||
if workspace_root is not None and workspace_root in self.workspace_configs:
|
||||
return self.workspace_configs[workspace_root]
|
||||
|
||||
# Otherwise, walk upwards until root.
|
||||
# path is a file, which is a sneaky trick to guarantee we always run the first
|
||||
# iteration. It becomes a directory after the first statement in the loop.
|
||||
while path != workspace_root:
|
||||
path = path.parent
|
||||
try:
|
||||
config = get_config(global_path, global_path.parent)
|
||||
self.global_config = config
|
||||
config = load_config_at(path)
|
||||
except ConfigError as e:
|
||||
self.show_message(f"Error loading config file: {e}")
|
||||
self.show_config_error(str(e), path)
|
||||
return _DEFAULT_CONFIG
|
||||
|
||||
def get_config(self, path: Path, root: Optional[Path]) -> Config:
|
||||
if root in self.configs:
|
||||
return self.configs[root].get_for_path(path)
|
||||
if self.global_config is not None:
|
||||
return self.global_config.get_for_path(path)
|
||||
return Config()
|
||||
if config is not None:
|
||||
return config
|
||||
|
||||
def _compute_diagnostics(self, document: TextDocument) -> List[lsp.Diagnostic]:
|
||||
return _DEFAULT_CONFIG
|
||||
|
||||
def get_config(self, path: Path) -> Config:
|
||||
"""Return config object for a given path.
|
||||
|
||||
If no config has already been loaded for root (either by calling this function
|
||||
or load_configs), this function will search for and load a config file if found.
|
||||
"""
|
||||
if path in self.source_configs:
|
||||
return self.source_configs[path]
|
||||
config = self._get_config(path)
|
||||
self.source_configs[path] = config
|
||||
|
||||
return config
|
||||
|
||||
def _compute_diagnostics(self, document: TextDocument) -> list[lsp.Diagnostic]:
|
||||
path = Path(document.path)
|
||||
root = self.get_root(path)
|
||||
config = self.get_config(path, root)
|
||||
|
||||
if root is None:
|
||||
root = path.parent
|
||||
|
||||
config = self.get_config(path)
|
||||
is_excluded = utils.make_exclude_filter(config.exclude)
|
||||
if is_excluded(path, root):
|
||||
if is_excluded(path):
|
||||
return []
|
||||
|
||||
return lint(document.source, config, path)
|
||||
return lint(document.source, config, self.plugin_manager, path)
|
||||
|
||||
def compute_diagnostics(self, document: TextDocument):
|
||||
# `None` sentinel ensures that `diagnostics` gets updated if the URI is not
|
||||
@@ -189,11 +247,10 @@ class TclspServer(LanguageServer):
|
||||
self,
|
||||
document: TextDocument,
|
||||
options: lsp.FormattingOptions,
|
||||
range: Optional[Tuple[int, int]] = None,
|
||||
range: Optional[tuple[int, int]] = None,
|
||||
):
|
||||
path = Path(document.path)
|
||||
root = self.get_root(path)
|
||||
config = self.get_config(path, root)
|
||||
config = self.get_config(path)
|
||||
|
||||
parser = Parser()
|
||||
|
||||
@@ -205,9 +262,19 @@ class TclspServer(LanguageServer):
|
||||
formatter = Formatter(
|
||||
FormatterOpts(
|
||||
indent=indent,
|
||||
spaces_in_braces=config.style_spaces_in_braces,
|
||||
indent_mixed_tab_size=config.get_indent_mixed_tab_size(),
|
||||
spaces_in_braces=(
|
||||
config.style_spaces_in_braces == SpacesInBraces.ALWAYS
|
||||
or config.style_spaces_in_braces == SpacesInBraces.BALANCED_YES
|
||||
),
|
||||
balanced_spaces_in_braces=(
|
||||
config.style_spaces_in_braces == SpacesInBraces.BALANCED_NO
|
||||
or config.style_spaces_in_braces == SpacesInBraces.BALANCED_YES
|
||||
),
|
||||
max_blank_lines=config.style_max_blank_lines,
|
||||
indent_namespace_eval=config.style_indent_namespace_eval,
|
||||
emacs=False,
|
||||
debug_whitespace=False,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -230,13 +297,30 @@ def did_open(ls: TclspServer, params: lsp.DidOpenTextDocumentParams):
|
||||
|
||||
|
||||
@server.feature(lsp.TEXT_DOCUMENT_DID_CHANGE)
|
||||
def did_change(ls: TclspServer, params: lsp.DidOpenTextDocumentParams):
|
||||
def did_change(ls: TclspServer, params: lsp.DidChangeTextDocumentParams):
|
||||
"""Parse each document when it is changed"""
|
||||
logging.debug("Received %s: %s", lsp.TEXT_DOCUMENT_DID_CHANGE, params)
|
||||
doc = ls.workspace.get_text_document(params.text_document.uri)
|
||||
ls.compute_diagnostics(doc)
|
||||
|
||||
|
||||
@server.feature(lsp.TEXT_DOCUMENT_DID_CLOSE)
|
||||
def did_close(ls: TclspServer, params: lsp.DidCloseTextDocumentParams):
|
||||
"""Free up resources when a document is closed."""
|
||||
logging.debug("Received %s: %s", lsp.TEXT_DOCUMENT_DID_CLOSE, params)
|
||||
doc = ls.workspace.get_text_document(params.text_document.uri)
|
||||
|
||||
try:
|
||||
del ls.diagnostics[doc.uri]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
del ls.source_configs[Path(doc.path)]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
@server.feature(
|
||||
lsp.TEXT_DOCUMENT_DIAGNOSTIC,
|
||||
lsp.DiagnosticOptions(
|
||||
@@ -275,7 +359,11 @@ def change_watched_files(ls: TclspServer, params: lsp.DidChangeWatchedFilesParam
|
||||
# Clear diagnostics cache so they get recalculated when requested
|
||||
ls.diagnostics = {}
|
||||
|
||||
ls.load_configs()
|
||||
# Config files changed, clear the many caches!
|
||||
ls.config_files = {}
|
||||
ls.source_configs = {}
|
||||
ls.invalid_configs = set()
|
||||
|
||||
if ls.client_supports_refresh:
|
||||
ls.lsp.send_request(lsp.WORKSPACE_DIAGNOSTIC_REFRESH, None)
|
||||
|
||||
@@ -369,7 +457,7 @@ def init(ls: TclspServer, params: lsp.InitializeParams):
|
||||
capabilities = ls.client_capabilities.workspace
|
||||
|
||||
try:
|
||||
ls.client_supports_refresh = (
|
||||
ls.client_supports_refresh = bool(
|
||||
capabilities.diagnostics.refresh_support # type: ignore[union-attr]
|
||||
)
|
||||
except AttributeError:
|
||||
@@ -391,7 +479,7 @@ def init(ls: TclspServer, params: lsp.InitializeParams):
|
||||
for settings in (ls.global_settings, *ls.workspace_settings.values()):
|
||||
if settings.config_file is not None:
|
||||
watchers.append(
|
||||
lsp.FileSystemWatcher(glob_pattern=settings.config_file)
|
||||
lsp.FileSystemWatcher(glob_pattern=str(settings.config_file))
|
||||
)
|
||||
|
||||
ls.register_capability(
|
||||
@@ -408,7 +496,7 @@ def init(ls: TclspServer, params: lsp.InitializeParams):
|
||||
)
|
||||
)
|
||||
|
||||
ls.load_configs()
|
||||
ls.load_workspace_setting_configs()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
Reference in New Issue
Block a user