Skip to content

Commit 96e18ea

Browse files
feat: add basedpyright (#5712)
## 📝 Summary Closes #1163 // cc: @nojaf ## 📋 Checklist - [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md). - [x] For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on [Discord](https://marimo.io/discord?ref=pr), or the community [discussions](https://github.com/marimo-team/marimo/discussions) (Please provide a link if applicable). - [ ] I have added tests for the changes made. - [ ] I have run the code and verified that it works as expected. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent e084501 commit 96e18ea

File tree

12 files changed

+173
-6
lines changed

12 files changed

+173
-6
lines changed

frontend/src/components/app-config/user-config-form.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,49 @@ export const UserConfigForm: React.FC = () => {
484484
</div>
485485
)}
486486
/>
487+
<FormField
488+
control={form.control}
489+
name="language_servers.basedpyright.enabled"
490+
render={({ field }) => (
491+
<div className="flex flex-col gap-1">
492+
<FormItem className={formItemClasses}>
493+
<FormLabel>
494+
<Badge variant="defaultOutline" className="mr-2">
495+
Beta
496+
</Badge>
497+
basedpyright (
498+
<ExternalLink href="https://github.com/DetachHead/basedpyright">
499+
docs
500+
</ExternalLink>
501+
)
502+
</FormLabel>
503+
<FormControl>
504+
<Checkbox
505+
data-testid="basedpyright-checkbox"
506+
checked={field.value}
507+
disabled={field.disabled}
508+
onCheckedChange={(checked) => {
509+
field.onChange(Boolean(checked));
510+
}}
511+
/>
512+
</FormControl>
513+
<FormMessage />
514+
<IsOverridden
515+
userConfig={config}
516+
name="language_servers.basedpyright.enabled"
517+
/>
518+
</FormItem>
519+
{field.value && !capabilities.basedpyright && (
520+
<Banner kind="danger">
521+
basedpyright is not available in your current
522+
environment. Please install{" "}
523+
<Kbd className="inline">basedpyright</Kbd> in your
524+
environment.
525+
</Banner>
526+
)}
527+
</div>
528+
)}
529+
/>
487530
<FormField
488531
control={form.control}
489532
name="language_servers.ty.enabled"

frontend/src/core/codemirror/language/languages/python.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,24 @@ const tyLspClient = once((_: LSPConfig) => {
153153
);
154154
});
155155

156+
const pyrightClient = once((_: LSPConfig) => {
157+
const lspClientOpts = {
158+
transport: createTransport("basedpyright"),
159+
rootUri: getLSPDocumentRootUri(),
160+
workspaceFolders: [],
161+
};
162+
163+
// We wrap the client in a NotebookLanguageServerClient to add some
164+
// additional functionality to handle multiple cells
165+
return new NotebookLanguageServerClient(
166+
new LanguageServerClient({
167+
...lspClientOpts,
168+
autoClose: false,
169+
}),
170+
{},
171+
);
172+
});
173+
156174
/**
157175
* Language adapter for Python.
158176
*/
@@ -209,6 +227,9 @@ export class PythonLanguageAdapter implements LanguageAdapter<{}> {
209227
if (lspConfig?.ty?.enabled && hasCapability("ty")) {
210228
clients.push(tyLspClient(lspConfig));
211229
}
230+
if (lspConfig?.basedpyright?.enabled && hasCapability("basedpyright")) {
231+
clients.push(pyrightClient(lspConfig));
232+
}
212233

213234
if (clients.length > 0) {
214235
const client =

frontend/src/core/codemirror/lsp/transports.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import { getRuntimeManager } from "../../runtime/config";
1212
* @param serverName - The name of the LSP server.
1313
* @returns The transport.
1414
*/
15-
export function createTransport(serverName: "pylsp" | "copilot" | "ty") {
15+
export function createTransport(
16+
serverName: "pylsp" | "basedpyright" | "copilot" | "ty",
17+
) {
1618
const runtimeManager = getRuntimeManager();
1719
const transport = new WebSocketTransport(
1820
runtimeManager.getLSPURL(serverName).toString(),

frontend/src/core/config/capabilities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { store } from "../state/jotai";
66
export const capabilitiesAtom = atom<Capabilities>({
77
terminal: false,
88
pylsp: false,
9+
basedpyright: false,
910
ty: false,
1011
});
1112

frontend/src/core/runtime/runtime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export class RuntimeManager {
135135
/**
136136
* The URL of the copilot server.
137137
*/
138-
getLSPURL(lsp: "pylsp" | "copilot" | "ty"): URL {
138+
getLSPURL(lsp: "pylsp" | "basedpyright" | "copilot" | "ty"): URL {
139139
if (lsp === "copilot") {
140140
// For copilot, don't include any query parameters
141141
const url = this.formatWsURL(`/lsp/${lsp}`);

marimo/_config/config.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,18 @@ class PythonLanguageServerConfig(TypedDict, total=False):
339339
enable_pyflakes: bool
340340

341341

342+
@dataclass
343+
class BasedpyrightServerConfig(TypedDict, total=False):
344+
"""
345+
Configuration options for basedpyright Language Server.
346+
347+
basedpyright handles completion, hover, go-to-definition, and diagnostics,
348+
but we only use it for diagnostics.
349+
"""
350+
351+
enabled: bool
352+
353+
342354
@dataclass
343355
class TyLanguageServerConfig(TypedDict, total=False):
344356
"""
@@ -361,6 +373,7 @@ class LanguageServersConfig(TypedDict, total=False):
361373
"""
362374

363375
pylsp: PythonLanguageServerConfig
376+
basedpyright: BasedpyrightServerConfig
364377
ty: TyLanguageServerConfig
365378

366379

marimo/_dependencies/dependencies.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ class DependencyManager:
222222
panel = Dependency("panel")
223223
sqlalchemy = Dependency("sqlalchemy")
224224
pylsp = Dependency("pylsp")
225+
basedpyright = Dependency("basedpyright")
225226
ty = Dependency("ty")
226227
pytest = Dependency("pytest")
227228
vegafusion = Dependency("vegafusion")

marimo/_messaging/ops.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,11 +419,13 @@ class KernelCapabilities:
419419
terminal: bool = False
420420
pylsp: bool = False
421421
ty: bool = False
422+
basedpyright: bool = False
422423

423424
def __post_init__(self) -> None:
424425
# Only available in mac/linux
425426
self.terminal = not is_windows() and not is_pyodide()
426427
self.pylsp = DependencyManager.pylsp.has()
428+
self.basedpyright = DependencyManager.basedpyright.has()
427429
self.ty = DependencyManager.ty.has()
428430

429431

marimo/_server/lsp.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,48 @@ def missing_binary_alert(self) -> Alert:
226226
)
227227

228228

229+
class BasedpyrightServer(BaseLspServer):
230+
id = "basedpyright"
231+
232+
def start(self) -> Optional[Alert]:
233+
# basedpyright is not required, so we don't want to alert or fail if it is not installed
234+
if not DependencyManager.basedpyright.has():
235+
LOGGER.debug("basedpyright is not installed. Skipping LSP server.")
236+
return None
237+
return super().start()
238+
239+
def validate_requirements(self) -> Union[str, Literal[True]]:
240+
if not DependencyManager.basedpyright.has():
241+
return "basedpyright is missing. Install it with `pip install basedpyright`."
242+
243+
if not DependencyManager.which("node"):
244+
return "node.js binary is missing. Install node at https://nodejs.org/."
245+
246+
return True
247+
248+
def get_command(self) -> list[str]:
249+
lsp_bin = marimo_package_path() / "_lsp" / "index.cjs"
250+
log_file = _loggers.get_log_directory() / "basedpyright-lsp.log"
251+
252+
return [
253+
"node",
254+
str(lsp_bin),
255+
"--port",
256+
str(self.port),
257+
"--lsp",
258+
"basedpyright-langserver --stdio",
259+
"--log-file",
260+
str(log_file),
261+
]
262+
263+
def missing_binary_alert(self) -> Alert:
264+
return Alert(
265+
title="basedpyright: Connection Error",
266+
description="<span><a class='hyperlink' href='https://docs.basedpyright.com'>Install basedpyright</a> for type checking support.</span>",
267+
variant="danger",
268+
)
269+
270+
229271
class TyServer(BaseLspServer):
230272
id = "ty"
231273

@@ -284,6 +326,7 @@ def is_running(self) -> bool:
284326
class CompositeLspServer(LspServer):
285327
LANGUAGE_SERVERS = {
286328
"pylsp": PyLspServer,
329+
"basedpyright": BasedpyrightServer,
287330
"ty": TyServer,
288331
"copilot": CopilotLspServer,
289332
}

packages/openapi/api.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1280,6 +1280,8 @@ components:
12801280
type: object
12811281
capabilities:
12821282
properties:
1283+
basedpyright:
1284+
type: boolean
12831285
pylsp:
12841286
type: boolean
12851287
terminal:
@@ -1290,6 +1292,7 @@ components:
12901292
- terminal
12911293
- pylsp
12921294
- ty
1295+
- basedpyright
12931296
type: object
12941297
cell_ids:
12951298
items:
@@ -1638,6 +1641,11 @@ components:
16381641
type: object
16391642
language_servers:
16401643
properties:
1644+
basedpyright:
1645+
properties:
1646+
enabled:
1647+
type: boolean
1648+
type: object
16411649
pylsp:
16421650
properties:
16431651
enable_flake8:
@@ -2741,7 +2749,7 @@ components:
27412749
type: object
27422750
info:
27432751
title: marimo API
2744-
version: 0.14.13
2752+
version: 0.14.15
27452753
openapi: 3.1.0
27462754
paths:
27472755
/@file/{filename_and_length}:

0 commit comments

Comments
 (0)