summaryrefslogtreecommitdiffstats
path: root/src/debputy/commands/debputy_cmd/lint_and_lsp_cmds.py
blob: b30b98dd642c4b7825cb3582968a019fe15e7d37 (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
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
        ;; 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")))
            ;; The debian/rules file uses the qmake mode.
            (add-to-list 'eglot-server-programs
                    '(makefile-gmake-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)
        (add-hook 'makefile-gmake-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
        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)"
        )

    from debputy.lsp.lsp_features import ensure_lsp_features_are_loaded
    from debputy.lsp.lsp_dispatch import DEBPUTY_LANGUAGE_SERVER

    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,
            help="The editor to provide a snippet for",
        ),
    ],
)
def lsp_editor_glue(context: CommandContext) -> None:
    editor_name = context.parsed_args.editor_name
    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

    # For the side effect of validating that we are run from a debian directory.
    context.binary_packages()
    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")