summaryrefslogtreecommitdiffstats
path: root/src/debputy/commands/debputy_cmd/lint_and_lsp_cmds.py
blob: 35b5f6a8d82c7e79c3a339c5b0408e7e03de8ea8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
import textwrap
from argparse import BooleanOptionalAction

from debputy.commands.debputy_cmd.context import ROOT_COMMAND, CommandContext, add_arg
from debputy.util import _error


_EDITOR_SNIPPETS = {
    "emacs": "emacs+eglot",
    "emacs+eglot": textwrap.dedent(
        """\
        ;; `deputy lsp server` glue for emacs eglot (eglot is built-in these days)
        ;;
        ;; Add to ~/.emacs or ~/.emacs.d/init.el and then activate via `M-x eglot`.
        ;;
        ;; Requires: apt install elpa-dpkg-dev-el elpa-yaml-mode
        ;; Recommends: apt install elpa-markdown-mode

        ;; Make emacs recognize debian/debputy.manifest as a YAML file
        (add-to-list 'auto-mode-alist '("/debian/debputy.manifest\\'" . yaml-mode))
        ;; Inform eglot about the debputy LSP
        (with-eval-after-load 'eglot
            (add-to-list 'eglot-server-programs
                    '(debian-control-mode . ("debputy" "lsp" "server")))
            (add-to-list 'eglot-server-programs
                    '(debian-changelog-mode . ("debputy" "lsp" "server")))
            (add-to-list 'eglot-server-programs
                    '(debian-copyright-mode . ("debputy" "lsp" "server")))
        ;; Requires elpa-dpkg-dev-el (>> 37.11)
        ;;    (add-to-list 'eglot-server-programs
        ;;            '(debian-autopkgtest-control-mode . ("debputy" "lsp" "server")))
            ;; The debian/rules file uses the qmake mode.
            (add-to-list 'eglot-server-programs
                    '(makefile-gmake-mode . ("debputy" "lsp" "server")))
            (add-to-list 'eglot-server-programs
                    '(yaml-mode . ("debputy" "lsp" "server")))
        )

        ;; Auto-start eglot for the relevant modes.
        (add-hook 'debian-control-mode-hook 'eglot-ensure)
        ;; NOTE: changelog disabled by default because for some reason it
        ;;       this hook causes perceivable delay (several seconds) when
        ;;       opening the first changelog. It seems to be related to imenu.
        ;; (add-hook 'debian-changelog-mode-hook 'eglot-ensure)
        (add-hook 'debian-copyright-mode-hook 'eglot-ensure)
        ;; Requires elpa-dpkg-dev-el (>> 37.11)
        ;; (add-hook 'debian-autopkgtest-control-mode-hook 'eglot-ensure)
        (add-hook 'makefile-gmake-mode-hook 'eglot-ensure)
        (add-hook 'yaml-mode-hook 'eglot-ensure)
    """
    ),
    "vim": "vim+youcompleteme",
    "vim+youcompleteme": textwrap.dedent(
        """\
        # debputy lsp server glue for vim with vim-youcompleteme. Add to ~/.vimrc
        #
        # Requires: apt install vim-youcompleteme

        # Make vim recognize debputy.manifest as YAML file
        au BufNewFile,BufRead debputy.manifest          setf yaml
        # Inform vim/ycm about the debputy LSP
        # - NB: No known support for debian/tests/control that we can hook into.
        #   Feel free to provide one :)
        let g:ycm_language_server = [
          \\   { 'name': 'debputy',
          \\     'filetypes': [ 'debcontrol', 'debcopyright', 'debchangelog', 'make', 'yaml'],
          \\     'cmdline': [ 'debputy', 'lsp', 'server' ]
          \\   },
          \\ ]

        packadd! youcompleteme
        # Add relevant ycm keybinding such as:
        # nmap <leader>d <plug>(YCMHover)
    """
    ),
}


lsp_command = ROOT_COMMAND.add_dispatching_subcommand(
    "lsp",
    dest="lsp_command",
    help_description="Language server related subcommands",
)


@lsp_command.register_subcommand(
    "server",
    log_only_to_stderr=True,
    help_description="Start the language server",
    argparser=[
        add_arg(
            "--tcp",
            action="store_true",
            help="Use TCP server",
        ),
        add_arg(
            "--ws",
            action="store_true",
            help="Use WebSocket server",
        ),
        add_arg(
            "--host",
            default="127.0.0.1",
            help="Bind to this address (Use with --tcp / --ws)",
        ),
        add_arg(
            "--port",
            type=int,
            default=2087,
            help="Bind to this port (Use with --tcp / --ws)",
        ),
    ],
)
def lsp_server_cmd(context: CommandContext) -> None:
    parsed_args = context.parsed_args

    try:
        import lsprotocol
        import pygls
    except ImportError:
        _error(
            "This feature requires lsprotocol and pygls (apt-get install python3-lsprotocol python3-pygls)"
        )

    feature_set = context.load_plugins()

    from debputy.lsp.lsp_features import (
        ensure_lsp_features_are_loaded,
        lsp_set_plugin_features,
    )
    from debputy.lsp.lsp_dispatch import DEBPUTY_LANGUAGE_SERVER

    lsp_set_plugin_features(feature_set)
    ensure_lsp_features_are_loaded()
    debputy_language_server = DEBPUTY_LANGUAGE_SERVER

    if parsed_args.tcp:
        debputy_language_server.start_tcp(parsed_args.host, parsed_args.port)
    elif parsed_args.ws:
        debputy_language_server.start_ws(parsed_args.host, parsed_args.port)
    else:
        debputy_language_server.start_io()


@lsp_command.register_subcommand(
    "editor-config",
    help_description="Provide editor configuration snippets",
    argparser=[
        add_arg(
            "editor_name",
            metavar="editor",
            choices=_EDITOR_SNIPPETS,
            default=None,
            nargs="?",
            help="The editor to provide a snippet for",
        ),
    ],
)
def lsp_editor_glue(context: CommandContext) -> None:
    editor_name = context.parsed_args.editor_name

    if editor_name is None:
        content = []
        for editor_name, payload in _EDITOR_SNIPPETS.items():
            alias_of = ""
            if payload in _EDITOR_SNIPPETS:
                alias_of = f" (short for: {payload})"
            content.append((editor_name, alias_of))
        max_name = max(len(c[0]) for c in content)
        print("This version of debputy has editor snippets for the following editors: ")
        for editor_name, alias_of in content:
            print(f" * {editor_name:<{max_name}}{alias_of}")
        return
    result = _EDITOR_SNIPPETS[editor_name]
    while result in _EDITOR_SNIPPETS:
        result = _EDITOR_SNIPPETS[result]
    print(result)


@lsp_command.register_subcommand(
    "features",
    help_description="Describe language ids and features",
)
def lsp_editor_glue(_context: CommandContext) -> None:
    try:
        import lsprotocol
        import pygls
    except ImportError:
        _error(
            "This feature requires lsprotocol and pygls (apt-get install python3-lsprotocol python3-pygls)"
        )

    from debputy.lsp.lsp_features import describe_lsp_features

    describe_lsp_features()


@ROOT_COMMAND.register_subcommand(
    "lint",
    log_only_to_stderr=True,
    argparser=[
        add_arg(
            "--spellcheck",
            dest="spellcheck",
            action="store_true",
            shared=True,
            help="Enable spellchecking",
        ),
        add_arg(
            "--auto-fix",
            dest="auto_fix",
            action="store_true",
            shared=True,
            help="Automatically fix problems with trivial or obvious corrections.",
        ),
        add_arg(
            "--linter-exit-code",
            dest="linter_exit_code",
            default=True,
            action=BooleanOptionalAction,
            help='Enable or disable the "linter" convention of exiting with an error if severe issues were found',
        ),
    ],
)
def lint_cmd(context: CommandContext) -> None:
    try:
        import lsprotocol
    except ImportError:
        _error("This feature requires lsprotocol (apt-get install python3-lsprotocol)")

    from debputy.linting.lint_impl import perform_linting

    context.must_be_called_in_source_root()
    feature_set = context.load_plugins()

    from debputy.lsp.lsp_features import lsp_set_plugin_features

    lsp_set_plugin_features(feature_set)

    perform_linting(context)


def ensure_lint_and_lsp_commands_are_loaded():
    # Loading the module does the heavy lifting
    # However, having this function means that we do not have an "unused" import that some tool
    # gets tempted to remove
    assert ROOT_COMMAND.has_command("lsp")
    assert ROOT_COMMAND.has_command("lint")