diff --git a/README.rst b/README.rst index d51ea0b..5265d47 100644 --- a/README.rst +++ b/README.rst @@ -24,6 +24,9 @@ Install into the same virtualenv as python-lsp-server itself. Configuration ------------- +Options +~~~~~~~ + ``live_mode`` (default is True) provides type checking as you type. This writes to a tempfile every time a check is done. Turning off ``live_mode`` means you must save your changes for mypy diagnostics to update correctly. @@ -33,8 +36,8 @@ Configuration ``strict`` (default is False) refers to the ``strict`` option of ``mypy``. This option often is too strict to be useful. -``overrides`` (default is ``[True]``) specifies a list of alternate or supplemental command-line options. - This modifies the options passed to ``mypy`` or the mypy-specific ones passed to ``dmypy run``. When present, the special boolean member ``True`` is replaced with the command-line options that would've been passed had ``overrides`` not been specified. Later options take precedence, which allows for replacing or negating individual default options (see ``mypy.main:process_options`` and ``mypy --help | grep inverse``). +``overrides`` (default is ``[]``) specifies a list of supplemental command-line options. + This modifies the options passed to ``mypy`` or the mypy-specific ones passed to ``dmypy run``. Later options take precedence, which allows for replacing or negating individual default options (see ``mypy.main:process_options`` and ``mypy --help | grep inverse``). ``dmypy_status_file`` (Default is ``.dmypy.json``) specifies which status file dmypy should use. This modifies the ``--status-file`` option passed to ``dmypy`` given ``dmypy`` is active. @@ -48,6 +51,20 @@ Configuration ``exclude`` (default is ``[]``) A list of regular expressions which should be ignored. The ``mypy`` runner wil not be invoked when a document path is matched by one of the expressions. Note that this differs from the ``exclude`` directive of a ``mypy`` config which is only used for recursively discovering files when mypy is invoked on a whole directory. For both windows or unix platforms you should use forward slashes (``/``) to indicate paths. +Overrides +~~~~~~~~~ + +The plugin invokes both ``mypy`` and ``dmypy`` with the following options + +- ``--show-error-end`` which allows us to report which characters need to be highlighted by the LSP. +- ``--config-file`` with the resolved configuration file + +For ``mypy``, the plugin additionally adds ``--incremental`` and ``--follow-imports=silent`` options. These can be overriden using the ``overrides`` configuration option. + + +Configuration file +~~~~~~~~~~~~~~~~~~ + This project supports the use of ``pyproject.toml`` for configuration. It is in fact the preferred way. Using that your configuration could look like this: :: @@ -87,7 +104,7 @@ With ``overrides`` specified (for example to tell mypy to use a different python { "enabled": True, - "overrides": ["--python-executable", "/home/me/bin/python", True] + "overrides": ["--python-executable", "/home/me/bin/python"] } With ``dmypy_status_file`` your config could look like this: diff --git a/pylsp_mypy/plugin.py b/pylsp_mypy/plugin.py index 0ca74cc..545e1d8 100644 --- a/pylsp_mypy/plugin.py +++ b/pylsp_mypy/plugin.py @@ -31,17 +31,13 @@ from pylsp.workspace import Document, Workspace line_pattern = re.compile( - ( - r"^(?P.+):(?P\d+):(?P\d*):(?P\d*):(?P\d*): " - r"(?P\w+): (?P.+?)(?: +\[(?P.+)\])?$" - ) + r"^(?P.+):(?P\d+):(?P\d*):(?P\d*):(?P\d*): " + r"(?P\w+): (?P.+?)(?: +\[(?P.+)\])?$" ) whole_line_pattern = re.compile( # certain mypy warnings do not report start-end ranges - ( - r"^(?P.+):(?P\d+): " - r"(?P\w+): (?P.+?)(?: +\[(?P.+)\])?$" - ) + r"^(?P.+?):(?P\d+):(?:(?P\d+):)? " + r"(?P\w+): (?P.+?)(?: +\[(?P.+)\])?$" ) log = logging.getLogger(__name__) @@ -89,11 +85,13 @@ def parse_line(line: str, document: Optional[Document] = None) -> Optional[Dict[ The dict with the lint data. """ - result = line_pattern.match(line) or whole_line_pattern.match(line) + line_match = line_pattern.match(line) or whole_line_pattern.match(line) - if not result: + if not line_match: return None + result = line_match.groupdict() + file_path = result["file"] if file_path != "": # live mode # results from other files can be included, but we cannot return @@ -103,9 +101,9 @@ def parse_line(line: str, document: Optional[Document] = None) -> Optional[Dict[ return None lineno = int(result["start_line"]) - 1 # 0-based line number - offset = int(result.groupdict().get("start_col", 1)) - 1 # 0-based offset - end_lineno = int(result.groupdict().get("end_line", lineno + 1)) - 1 - end_offset = int(result.groupdict().get("end_col", 1)) # end is exclusive + offset = int(result.get("start_col", 1)) - 1 # 0-based offset + end_lineno = int(result.get("end_line", lineno + 1)) - 1 + end_offset = int(result.get("end_col", 1)) # end is exclusive severity = result["severity"] if severity not in ("error", "note"): @@ -124,18 +122,6 @@ def parse_line(line: str, document: Optional[Document] = None) -> Optional[Dict[ } -def apply_overrides(args: List[str], overrides: List[Any]) -> List[str]: - """Replace or combine default command-line options with overrides.""" - overrides_iterator = iter(overrides) - if True not in overrides_iterator: - return overrides - # If True is in the list, the if above leaves the iterator at the element after True, - # therefore, the list below only contains the elements after the True - rest = list(overrides_iterator) - # slice of the True and the rest, add the args, add the rest - return overrides[: -(len(rest) + 1)] + args + rest - - def didSettingsChange(workspace: str, settings: Dict[str, Any]) -> None: """Handle relevant changes to the settings between runs.""" configSubPaths = settings.get("config_sub_paths", []) @@ -266,7 +252,7 @@ def get_diagnostics( if dmypy: dmypy_status_file = settings.get("dmypy_status_file", ".dmypy.json") - args = ["--show-error-end", "--no-error-summary"] + args = ["--show-error-end"] global tmpFile if live_mode and not is_saved: @@ -297,12 +283,11 @@ def get_diagnostics( if settings.get("strict", False): args.append("--strict") - overrides = settings.get("overrides", [True]) + args.extend(settings.get("overrides", [])) exit_status = 0 if not dmypy: - args.extend(["--incremental", "--follow-imports", "silent"]) - args = apply_overrides(args, overrides) + args = ["--incremental", "--follow-imports", "silent", *args] if shutil.which("mypy"): # mypy exists on path @@ -326,11 +311,12 @@ def get_diagnostics( # If daemon is dead/absent, kill will no-op. # In either case, reset to fresh state + dmypy_args = ["--status-file", dmypy_status_file] if shutil.which("dmypy"): # dmypy exists on path # -> use dmypy on path completed_process = subprocess.run( - ["dmypy", "--status-file", dmypy_status_file, "status"], + ["dmypy", *dmypy_args, "status"], capture_output=True, **windows_flag, encoding="utf-8", @@ -344,7 +330,7 @@ def get_diagnostics( errors.strip(), ) subprocess.run( - ["dmypy", "--status-file", dmypy_status_file, "restart"], + ["dmypy", *dmypy_args, "restart"], capture_output=True, **windows_flag, encoding="utf-8", @@ -352,19 +338,17 @@ def get_diagnostics( else: # dmypy does not exist on path, but must exist in the env pylsp-mypy is installed in # -> use dmypy via api - _, errors, exit_status = mypy_api.run_dmypy( - ["--status-file", dmypy_status_file, "status"] - ) + _, errors, exit_status = mypy_api.run_dmypy([*dmypy_args, "status"]) if exit_status != 0: log.info( "restarting dmypy from status: %s message: %s via api", exit_status, errors.strip(), ) - mypy_api.run_dmypy(["--status-file", dmypy_status_file, "restart"]) + mypy_api.run_dmypy([*dmypy_args, "restart"]) # run to use existing daemon or restart if required - args = ["--status-file", dmypy_status_file, "run", "--"] + apply_overrides(args, overrides) + args = [*dmypy_args, "run", "--", *args] if shutil.which("dmypy"): # dmypy exists on path # -> use mypy on path