Skip to content

Commit 0c2ec54

Browse files
authored
Merge pull request #235 from swyddfa/develop
New Release
2 parents a22568f + a1d7e52 commit 0c2ec54

17 files changed

Lines changed: 350 additions & 74 deletions

File tree

.github/workflows/lsp-devtools-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
hatch build
4545
4646
- name: 'Upload Artifact'
47-
uses: actions/upload-artifact@v5
47+
uses: actions/upload-artifact@v7
4848
with:
4949
name: 'dist'
5050
path: lib/lsp-devtools/dist

.github/workflows/pytest-lsp-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
hatch build
4545
4646
- name: 'Upload Artifact'
47-
uses: actions/upload-artifact@v5
47+
uses: actions/upload-artifact@v7
4848
with:
4949
name: 'dist'
5050
path: lib/pytest-lsp/dist

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ repos:
1212
- id: trailing-whitespace
1313

1414
- repo: https://github.com/astral-sh/ruff-pre-commit
15-
rev: v0.14.7
15+
rev: v0.14.10
1616
hooks:
1717
- id: ruff
1818
name: ruff (lsp-devtools)
@@ -33,7 +33,7 @@ repos:
3333
files: 'lib/pytest-lsp/.*\.py'
3434

3535
- repo: https://github.com/pre-commit/mirrors-mypy
36-
rev: 'v1.19.0'
36+
rev: 'v1.19.1'
3737
hooks:
3838
- id: mypy
3939
name: mypy (pytest-lsp)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix issue where `lsp-devtools inspect` would not exit cleanly
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix crash caused by trying to dismiss the message filter dialog twice.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Bump textual to `>=8.1.0`
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The server command used with the `lsp-devtools client` command can now be configured at runtime.
2+
3+
Server process lifecycle information and errors are now also logged to the `Server` panel.

lib/lsp-devtools/lsp_devtools/cli/client.py renamed to lib/lsp-devtools/lsp_devtools/cli/client/__init__.py

Lines changed: 99 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import argparse
44
import importlib.metadata
5+
import logging
56
import os
67
import typing
78

@@ -10,28 +11,28 @@
1011
from textual import events
1112
from textual import on
1213
from textual.app import App
14+
from textual.containers import Vertical
1315
from textual.message import Message
16+
from textual.widgets import Button
1417
from textual.widgets import Footer
1518

1619
from lsp_devtools.cli.utils import LiveSqlHandler
17-
from lsp_devtools.client import LanguageClient
1820
from lsp_devtools.editor import Explorer
1921
from lsp_devtools.editor import OutputWindow
2022
from lsp_devtools.editor import Panel
2123
from lsp_devtools.editor import TextEditorView
22-
from lsp_devtools.inspector.message_browser import MessageBrowser
24+
from lsp_devtools.inspector import MessageBrowser
25+
26+
from .client import ClientState
27+
from .client import LanguageClient
28+
from .config import AppConfig
29+
from .config import ConfigurationScreen
2330

2431
if typing.TYPE_CHECKING:
2532
from textual.app import ComposeResult
2633
from textual.widgets import DirectoryTree
2734

2835

29-
class StderrReceived(Message):
30-
def __init__(self, data: bytes):
31-
super().__init__()
32-
self.data = data
33-
34-
3536
@typing.final
3637
class LSPClient(App[None]):
3738
"""A simple LSP client."""
@@ -41,11 +42,16 @@ class LSPClient(App[None]):
4142
display: none;
4243
}
4344
44-
Explorer {
45+
#left-sidebar {
4546
dock: left;
4647
width: 15%;
4748
}
4849
50+
#open-settings-btn {
51+
margin: 1;
52+
width: 100%;
53+
}
54+
4955
MessageBrowser {
5056
dock: right;
5157
width: 30%;
@@ -64,14 +70,15 @@ class LSPClient(App[None]):
6470
BINDINGS = [
6571
("ctrl+c", "quit"),
6672
("f2", "toggle_explorer", "Explorer"),
73+
("f5", "run_server", "Run Server"),
6774
("f8", "toggle_panel", "Panel"),
6875
("f12", "toggle_devtools", "Devtools"),
6976
]
7077

71-
def __init__(self, *args, server_command: list[str], **kwargs):
78+
def __init__(self, *args, config: AppConfig, **kwargs):
7279
super().__init__(*args, **kwargs)
7380

74-
self.server_command = server_command
81+
self.config: AppConfig = config
7582
self.client: LanguageClient | None = None
7683

7784
self.db = LiveSqlHandler()
@@ -84,78 +91,119 @@ def compose(self) -> ComposeResult:
8491

8592
# Sidebars
8693
yield MessageBrowser()
87-
yield Explorer()
94+
95+
with Vertical(id="left-sidebar"):
96+
yield Explorer()
97+
yield Button("Settings", id="open-settings-btn")
8898

8999
# Footer
90100
yield Footer()
91101

102+
def action_open_settings(self):
103+
def maybe_update_config(new_config: AppConfig | None):
104+
if new_config is not None:
105+
# TODO: Restart server as required.
106+
self.config = new_config
107+
108+
_ = self.push_screen(
109+
ConfigurationScreen(config=self.config), maybe_update_config
110+
)
111+
92112
def action_toggle_explorer(self) -> None:
93-
explorer = self.query_one(Explorer)
113+
explorer = self.query_one("#left-sidebar")
94114
is_visible = not explorer.has_class("hidden")
95115

96116
if is_visible:
97-
explorer.add_class("hidden")
117+
_ = explorer.add_class("hidden")
98118

99119
else:
100-
explorer.remove_class("hidden")
120+
_ = explorer.remove_class("hidden")
101121
self.screen.set_focus(explorer)
102122

103123
def action_toggle_devtools(self) -> None:
104124
devtools = self.query_one(MessageBrowser)
105125
is_visible = not devtools.has_class("hidden")
106126

107127
if is_visible:
108-
devtools.add_class("hidden")
128+
_ = devtools.add_class("hidden")
109129

110130
else:
111-
devtools.remove_class("hidden")
131+
_ = devtools.remove_class("hidden")
112132
self.screen.set_focus(devtools)
113133

114134
def action_toggle_panel(self) -> None:
115135
panel = self.query_one(Panel)
116136
is_visible = not panel.has_class("hidden")
117137

118138
if is_visible:
119-
panel.add_class("hidden")
139+
_ = panel.add_class("hidden")
120140

121141
else:
122-
panel.remove_class("hidden")
142+
_ = panel.remove_class("hidden")
123143
self.screen.set_focus(panel)
124144

125-
def on_ready(self, event: events.Ready):
126-
self.run_worker(self.start_server(), name="lsp-connection")
145+
async def action_run_server(self):
146+
_ = self.run_worker(self.run_server())
147+
148+
async def on_ready(self, event: events.Ready):
149+
# Auto start server if possible.
150+
if len(self.config.server.command) > 0:
151+
await self.action_run_server()
127152

128153
@on(LiveSqlHandler.MessageReceived)
129154
def on_message_received(self, event: LiveSqlHandler.MessageReceived):
130155
browser = self.query_one(MessageBrowser)
131156
browser.reload(follow=True)
132157

133-
@on(StderrReceived)
134-
def on_stderr_received(self, event: StderrReceived):
135-
panel = self.query_one(Panel)
136-
log = panel.query_one("#stderr-window", OutputWindow)
137-
log.write(event.data)
158+
def on_button_pressed(self, event: Button.Pressed):
159+
if event.button.id == "open-settings-btn":
160+
self.action_open_settings()
138161

139162
def on_directory_tree_file_selected(self, event: DirectoryTree.FileSelected):
140163
"""Handle file-open."""
141164
editor = self.query_one(TextEditorView)
142165
editor.open_text_document(event.path)
143166

144-
async def start_server(self):
167+
async def run_server(self):
168+
"""Start, or restart the server."""
169+
if self.client is None:
170+
self.client = await self.start_server()
171+
return
172+
173+
# Don't interfere with a client that is starting up.
174+
if self.client.state in {ClientState.Starting}:
175+
return
176+
177+
if self.client.state in {ClientState.Running}:
178+
await self.stop_server()
179+
180+
self.client = await self.start_server()
181+
182+
async def start_server(self) -> LanguageClient | None:
145183
"""Start the server and connect to it."""
146184

147-
def stderr_handler(data: bytes):
148-
self.app.post_message(StderrReceived(data))
185+
server_config = self.config.server
186+
if len(server_config.command) == 0:
187+
# TODO: Prompt user to set a command.
188+
return
149189

150-
self.client = LanguageClient(
190+
output_window = self.query_one("#stderr-window", OutputWindow)
191+
output_window.clear()
192+
193+
server_logger = logging.getLogger("server")
194+
server_logger.setLevel(logging.DEBUG)
195+
server_logger.addHandler(output_window.log_handler)
196+
197+
client = LanguageClient(
151198
self.db,
152-
stderr_handler=stderr_handler,
199+
logger=server_logger,
153200
name="lsp-devtools",
154201
version=importlib.metadata.version("lsp-devtools"),
155202
)
156-
await self.client.start_io(*self.server_command)
157203

158-
result = await self.client.initialize_async(
204+
await client.start_io(*server_config.command)
205+
206+
result = await client.initialize_async(
159207
types.InitializeParams(
160208
capabilities=types.ClientCapabilities(),
161209
process_id=os.getpid(),
@@ -167,16 +215,28 @@ def stderr_handler(data: bytes):
167215
],
168216
)
169217
)
170-
self.client.initialized(types.InitializedParams())
218+
client.initialized(types.InitializedParams())
219+
return client
220+
221+
async def stop_server(self):
222+
if self.client is None or self.client.state not in {ClientState.Running}:
223+
return
224+
225+
await self.client.shutdown_async(None)
226+
self.client.exit(None)
227+
228+
await self.client.stop()
171229

172230

173231
def client(args, extra: list[str]):
174-
if len(extra) == 0:
175-
raise ValueError(
176-
"Missing server command. (e.g. lsp-devtools client -- server-cmd --stdio)"
177-
)
232+
# TODO: Read configs from file.
233+
config = AppConfig()
234+
235+
# Allow for a server command to be passed on the cli.
236+
if len(extra) > 0:
237+
config.server.command = extra
178238

179-
app = LSPClient(server_command=extra)
239+
app = LSPClient(config=config)
180240
app.run()
181241

182242

0 commit comments

Comments
 (0)