mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 02:14:49 +00:00
Merge remote-tracking branch 'origin/main' into features/move_and_safe_delete
This commit is contained in:
commit
fac745fc7c
74 files changed
+1656
-140
No files matched your search
@@ -566,7 +566,7 @@ jobs:
|
||||
df -h
|
||||
- name: Test with pytest
|
||||
shell: bash
|
||||
run: uv run poe test
|
||||
run: uv run poe test -q --tb=short
|
||||
- name: Type-checking with mypy
|
||||
shell: bash
|
||||
run: uv run poe type-check
|
||||
@@ -2,38 +2,80 @@
|
||||
|
||||
Serena comes with built-in tools for monitoring and managing the current session:
|
||||
|
||||
* the **web-based dashboard** (enabled by default)
|
||||
* the **Serena Dashboard** (enabled by default)
|
||||
|
||||
The dashboard provides detailed information on your Serena session, the current configuration and provides access to logs.
|
||||
Some settings (e.g. the current set of active programming languages) can also be directly modified through the dashboard.
|
||||
|
||||
The dashboard is supported on all platforms.
|
||||
|
||||
By default, it will be accessible at `http://localhost:24282/dashboard/index.html`,
|
||||
but a higher port may be used if the default port is unavailable/multiple instances are running.
|
||||
|
||||
**We recommend always enabling the dashboard**. If you don't want the browser to open automatically,
|
||||
you can disable it while still keeping the dashboard running in the background (see below).
|
||||
|
||||
* the **GUI tool** (disabled by default)
|
||||
* the **GUI Log Viewer** (disabled by default)
|
||||
|
||||
The GUI tool is a native application window which displays logs.
|
||||
It furthermore allows you to shut down the agent and to access the dashboard's URL (if it is running).
|
||||
The GUI tool is a legacy native application which displays Serena's live logs.
|
||||
|
||||
This is mainly supported on Windows, but it may also work on Linux; macOS is unsupported.
|
||||
|
||||
Both can be configured in Serena's [configuration](050_configuration) file (`serena_config.yml`).
|
||||
If enabled, they will automatically be opened as soon as the Serena agent/MCP server is started.
|
||||
For the dashboard, this can be disabled if desired (see below).
|
||||
|
||||
## Disabling Automatic Browser Opening
|
||||
## The Serena Dashboard
|
||||
|
||||
If you prefer not to have the dashboard open automatically (e.g., to avoid focus stealing), you can disable it
|
||||
by setting `web_dashboard_open_on_launch: False` in your `serena_config.yml` or by passing `--open-web-dashboard False`
|
||||
to `start-mcp-server` CLI command.
|
||||
The dashboard is a web application, which is opened via a native application wrapper on Windows and macOS,
|
||||
where it will appear in the system tray. **Click on the tray icon** to open the application window.
|
||||
|
||||
When automatic opening is disabled, you can still access the dashboard by:
|
||||
On Linux, native app support is difficult to provide, so the dashboard will be opened in your default web browser instead.
|
||||
|
||||
By default, the dashboard can be accessed at `http://localhost:24282/dashboard/index.html`,
|
||||
but a higher port may be used if the default port is unavailable/multiple instances are running.
|
||||
|
||||
### Features
|
||||
|
||||
The dashboard provides ...
|
||||
* a detailed overview of
|
||||
* the current Serena status and configuration (e.g. active tools, active programming languages, enabled modes and contexts, etc.)
|
||||
* ongoing and past tool calls (and statistics on tool usage)
|
||||
* access to Serena's live logs
|
||||
* the ability to modify settings (e.g. the set of active programming languages) on the fly
|
||||
* the ability to edit
|
||||
* configuration files (global `serena_config.yml` and project-specific `project.yml`)
|
||||
* memories of the current project
|
||||
* the ability to shut down the Serena MCP server.
|
||||
|
||||
### Configuring the Dashboard
|
||||
|
||||
You can configure settings for the dashboard in the [global configuration file](global-config).
|
||||
|
||||
In particular, you can configure
|
||||
* whether the dashboard is enabled at all.
|
||||
We highly recommend keeping it enabled, as it provides valuable insights into Serena's operation and allows you to adjust configuration conveniently.
|
||||
* whether the dashboard window is opened automatically when Serena is started (see below).
|
||||
* network settings (for cases where you might want to access the Dashboard non-locally).
|
||||
|
||||
#### Dashboard Opening Behaviour
|
||||
|
||||
When Serena is started, the Dashboard window is opened by default in order to make users aware of its existence.
|
||||
|
||||
If you prefer not to have this happen (e.g., to avoid focus stealing), you can disable prevent it from opening
|
||||
by setting `web_dashboard_open_on_launch: False` in the [global configuration file](global-config) or by passing `--open-web-dashboard False`
|
||||
to the `start-mcp-server` CLI command.
|
||||
|
||||
On Windows and macOS, you can conveniently open the dashboard at any time by clicking on the tray icon, so automatic
|
||||
opening is not a requirement to be able to access the dashboard on these platforms.
|
||||
|
||||
On Linux, access to the dashboard is more cumbersome without native app support.
|
||||
Yet you may still access it by
|
||||
* asking the LLM to "open the Serena dashboard", which will open the dashboard in your default browser
|
||||
(the tool `open_dashboard` is enabled for this purpose, provided that the dashboard is active,
|
||||
not opened by default and the GUI tool, which can provide the URL, is not enabled)
|
||||
* navigating directly to the URL (see above)
|
||||
* navigating directly to the URL (see above).
|
||||
|
||||
## The GUI Log Viewer
|
||||
|
||||
The Serena GUI Log Viewer is a legacy application which provides access to Serena's live logs.
|
||||
|
||||
Via its menu, it furthermore allows you to
|
||||
* shut down the agent
|
||||
* access the dashboard's URL (if it is running).
|
||||
|
||||
The tool is mainly supported on Windows, but it may work on some Linux systems as well (depending on your desktop environment).
|
||||
|
||||
To enable the tool, set `gui_log_window` to `true` in Serena's [global configuration file](global-config).
|
||||
@@ -57,6 +57,17 @@
|
||||
final.setuptools
|
||||
];
|
||||
});
|
||||
proxy-tools = prev.proxy-tools.overrideAttrs (old: {
|
||||
nativeBuildInputs = (old.nativeBuildInputs or []) ++ [
|
||||
final.setuptools
|
||||
];
|
||||
});
|
||||
pywebview = prev.pywebview.overrideAttrs (old: {
|
||||
nativeBuildInputs = (old.nativeBuildInputs or []) ++ [
|
||||
final.setuptools
|
||||
final.setuptools-scm
|
||||
];
|
||||
});
|
||||
};
|
||||
|
||||
python = pkgs.python311;
|
||||
|
||||
+7
-1
@@ -36,6 +36,8 @@ dependencies = [
|
||||
"tiktoken==0.12.0",
|
||||
"anthropic==0.59.0",
|
||||
"beautifulsoup4==4.14.2",
|
||||
"pywebview",
|
||||
"pystray==0.19.5",
|
||||
# Transitive deps pinned for security (dependabot alerts).
|
||||
# Exact pins because uvx installs from git, ignoring the lock file.
|
||||
"urllib3==2.6.3",
|
||||
@@ -45,8 +47,12 @@ dependencies = [
|
||||
"filelock==3.25.2",
|
||||
"cryptography==46.0.6",
|
||||
"regex==2026.2.28",
|
||||
"pythonnet==3.1.0-rc0 ; sys_platform == 'win32'",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
pywebview = { git = "https://github.com/r0x0r/pywebview", rev = "345596c1f4fd59fd26c7aa02d4176c5362bcbabe" }
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "testpypi"
|
||||
url = "https://test.pypi.org/simple/"
|
||||
@@ -88,7 +94,7 @@ dev = [
|
||||
"nbconvert==7.17.0",
|
||||
"wheel==0.46.3",
|
||||
"pyasn1==0.6.3",
|
||||
"PyJWT==2.10.1",
|
||||
"PyJWT==2.12.0",
|
||||
]
|
||||
agno = ["agno==2.5.10", "sqlalchemy==2.0.41"] # agno bumped for session state overwrite CVE
|
||||
google = ["google-genai==1.27.0"]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+47
-7
@@ -2,6 +2,7 @@
|
||||
The Serena Model Context Protocol (MCP) Server
|
||||
"""
|
||||
|
||||
import multiprocessing
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
@@ -11,6 +12,7 @@ from contextlib import contextmanager
|
||||
from logging import Logger
|
||||
from typing import TYPE_CHECKING, Optional, TypeVar
|
||||
|
||||
import webview
|
||||
from sensai.util import logging
|
||||
from sensai.util.logging import LogTime
|
||||
from sensai.util.string import dict_string
|
||||
@@ -28,7 +30,7 @@ from serena.config.serena_config import (
|
||||
SerenaPaths,
|
||||
ToolInclusionDefinition,
|
||||
)
|
||||
from serena.dashboard import SerenaDashboardAPI
|
||||
from serena.dashboard import SerenaDashboardAPI, SerenaDashboardViewer
|
||||
from serena.ls_manager import LanguageServerManager
|
||||
from serena.project import MemoriesManager, Project
|
||||
from serena.prompt_factory import SerenaPromptFactory
|
||||
@@ -375,8 +377,7 @@ class SerenaAgent:
|
||||
dashboard_url = f"http://{dashboard_host}:{port}/dashboard/index.html"
|
||||
self._dashboard_url = dashboard_url
|
||||
log.info("Serena web dashboard started at %s", dashboard_url)
|
||||
if self.serena_config.web_dashboard_open_on_launch:
|
||||
self.open_dashboard()
|
||||
self._start_dashboard_viewer(minimized=not self.serena_config.web_dashboard_open_on_launch)
|
||||
# inform the GUI window (if any)
|
||||
if self._gui_log_viewer is not None:
|
||||
self._gui_log_viewer.set_dashboard_url(dashboard_url)
|
||||
@@ -519,11 +520,46 @@ class SerenaAgent:
|
||||
"""
|
||||
return self._dashboard_url
|
||||
|
||||
@staticmethod
|
||||
def _start_dashboard_viewer_process_function(url: str, minimized: bool) -> None:
|
||||
"""
|
||||
Main function of the subprocess for starting the dashboard viewer
|
||||
"""
|
||||
try:
|
||||
SerenaDashboardViewer(url, start_minimized=minimized).run()
|
||||
except webview.errors.WebViewException as e:
|
||||
log.warning(f"Could not open Serena Dashboard viewer. Cause:\n{e}")
|
||||
# Fall back to opening the browser window if the window was supposed to be shown directly
|
||||
if not minimized:
|
||||
SerenaAgent._open_dashboard_in_browser(url)
|
||||
|
||||
def _start_dashboard_viewer(self, minimized: bool) -> None:
|
||||
"""
|
||||
Starts the dashboard viewer (in a separate process) or, if the current platform does not support it,
|
||||
opens the dashboard in the default web browser.
|
||||
|
||||
:param minimized: whether the dashboard viewer should be started minimized (if supported on the current platform).
|
||||
If the viewer is not supported on the current platform, then this controls whether to open the browser window.
|
||||
"""
|
||||
if not system_has_usable_display():
|
||||
log.info("Not starting the Serena dashboard viewer because no usable display was detected.")
|
||||
return
|
||||
|
||||
url = self.get_dashboard_url()
|
||||
assert url is not None
|
||||
if SerenaDashboardViewer.is_current_platform_supported():
|
||||
process = multiprocessing.Process(target=self._start_dashboard_viewer_process_function, args=(url, minimized), daemon=True)
|
||||
process.start()
|
||||
else:
|
||||
log.info("Not starting Serena dashboard viewer because the current platform does not support it; using browser-based fallback")
|
||||
if not minimized:
|
||||
self._open_dashboard_in_browser(url)
|
||||
|
||||
def open_dashboard(self) -> bool:
|
||||
"""
|
||||
Opens the Serena web dashboard in the default web browser.
|
||||
Opens the Serena dashboard (for on-demand usage as triggered by the user, e.g. via a tool)
|
||||
|
||||
:return: a message indicating success or failure
|
||||
:return: True if the dashboard was opened, False if it could not be opened
|
||||
"""
|
||||
if self._dashboard_url is None:
|
||||
raise Exception("Dashboard is not running.")
|
||||
@@ -532,15 +568,19 @@ class SerenaAgent:
|
||||
log.warning("Not opening the Serena web dashboard because no usable display was detected.")
|
||||
return False
|
||||
|
||||
self._open_dashboard_in_browser(self._dashboard_url)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _open_dashboard_in_browser(url: str) -> None:
|
||||
# Use a subprocess to avoid any output from webbrowser.open being written to stdout
|
||||
subprocess.Popen(
|
||||
[sys.executable, "-c", f"import webbrowser; webbrowser.open({self._dashboard_url!r})"],
|
||||
[sys.executable, "-c", f"import webbrowser; webbrowser.open({url!r})"],
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=True, # Detach from parent process
|
||||
)
|
||||
return True
|
||||
|
||||
def get_exposed_tool_instances(self) -> list["Tool"]:
|
||||
"""
|
||||
|
||||
@@ -406,6 +406,22 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
run_kwargs["port"] = port
|
||||
server.run(**run_kwargs)
|
||||
|
||||
@staticmethod
|
||||
@click.command(
|
||||
"dashboard-viewer",
|
||||
help="Open the Serena dashboard viewer for a given URL.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("url", type=str)
|
||||
@click.option("--width", type=int, default=1400, show_default=True, help="Window width.")
|
||||
@click.option("--height", type=int, default=900, show_default=True, help="Window height.")
|
||||
@click.option("--minimized", is_flag=True, default=False, help="Whether to start minimized/in tray.")
|
||||
def dashboard_viewer(url: str, width: int, height: int, minimized: bool) -> None:
|
||||
from serena.dashboard import SerenaDashboardViewer
|
||||
|
||||
viewer = SerenaDashboardViewer(url, start_minimized=minimized, width=width, height=height)
|
||||
viewer.run()
|
||||
|
||||
|
||||
class ModeCommands(AutoRegisteringGroup):
|
||||
"""Group for 'mode' subcommands."""
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Self
|
||||
|
||||
import webview
|
||||
from flask import Flask, Response, redirect, request, send_from_directory
|
||||
from PIL import Image
|
||||
from pydantic import BaseModel
|
||||
from sensai.util import logging
|
||||
|
||||
@@ -676,3 +679,199 @@ class SerenaDashboardAPI:
|
||||
thread = threading.Thread(target=lambda: self.run(host=host, port=port), daemon=True)
|
||||
thread.start()
|
||||
return thread, port
|
||||
|
||||
|
||||
class SerenaDashboardViewer:
|
||||
"""
|
||||
Minimal pywebview wrapper with optional system tray.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url: str,
|
||||
*,
|
||||
start_minimized: bool = False,
|
||||
width: int = 1400,
|
||||
height: int = 900,
|
||||
):
|
||||
self.url = url
|
||||
# Use system tray to allow hiding to tray and intercepting close to hide instead of quit
|
||||
self.tray = True
|
||||
self.title = "Serena Dashboard"
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.start_minimized = start_minimized
|
||||
|
||||
self.window: webview.Window
|
||||
self._tray_icon: Any
|
||||
self._quitting = False
|
||||
self._app_icon_path: str | None = None
|
||||
|
||||
@staticmethod
|
||||
def is_current_platform_supported() -> bool:
|
||||
"""
|
||||
:return: whether the current platform supports the dashboard viewer
|
||||
"""
|
||||
# The dashboard viewer (with system tray) is technically supported only on Windows and macOS.
|
||||
# Linux support is problematic; see https://github.com/oraios/serena/pull/1117#issuecomment-4128753943
|
||||
supported_platforms = [
|
||||
"win32",
|
||||
# NOTE: Disabling macOS support for now, because the tray behaviour is suboptimal (too many icons when
|
||||
# subagents are spawned, etc.)
|
||||
# "darwin"
|
||||
]
|
||||
return sys.platform in supported_platforms
|
||||
|
||||
def run(self) -> None:
|
||||
# set app id (avoid app being lumped together with other Python-based apps in Windows taskbar)
|
||||
if sys.platform == "win32":
|
||||
import ctypes
|
||||
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("oraios.serena")
|
||||
|
||||
dashboard_path = Path(SERENA_DASHBOARD_DIR)
|
||||
# .ico is Windows-only; macOS expects a PNG for the window/dock icon.
|
||||
icon_filename = "serena.ico" if sys.platform == "win32" else "serena-icon-1024-mac.png"
|
||||
icon_path = str(dashboard_path / icon_filename)
|
||||
self._app_icon_path = icon_path
|
||||
|
||||
# Create hidden to avoid flash; show/restore/minimize in start callback.
|
||||
window = webview.create_window(
|
||||
self.title,
|
||||
self.url,
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
hidden=self.start_minimized,
|
||||
)
|
||||
assert window is not None
|
||||
self.window = window
|
||||
|
||||
if self.tray:
|
||||
self.window.events.closing += self._on_closing
|
||||
self._start_tray()
|
||||
|
||||
def _start_callback() -> None:
|
||||
if self.start_minimized:
|
||||
self._hide_window()
|
||||
else:
|
||||
self._show_window()
|
||||
|
||||
webview.start(_start_callback, icon=icon_path)
|
||||
|
||||
def _show_window(self) -> None:
|
||||
if not self.window:
|
||||
return
|
||||
|
||||
if sys.platform == "darwin":
|
||||
from PyObjCTools.AppHelper import callAfter
|
||||
|
||||
callAfter(self._show_window_on_macos)
|
||||
else:
|
||||
self.window.show()
|
||||
self.window.restore()
|
||||
|
||||
def _hide_window(self) -> None:
|
||||
if not self.window:
|
||||
return
|
||||
|
||||
if sys.platform == "darwin":
|
||||
from PyObjCTools.AppHelper import callAfter
|
||||
|
||||
callAfter(self._hide_window_on_macos)
|
||||
else:
|
||||
self.window.hide()
|
||||
|
||||
def _show_window_on_macos(self) -> None:
|
||||
from AppKit import (
|
||||
NSApplication,
|
||||
NSApplicationActivationPolicyRegular,
|
||||
)
|
||||
from PyObjCTools.AppHelper import callLater
|
||||
|
||||
ns_app = NSApplication.sharedApplication()
|
||||
ns_app.setActivationPolicy_(NSApplicationActivationPolicyRegular)
|
||||
self._set_macos_app_icon(ns_app)
|
||||
ns_app.unhide_(None)
|
||||
ns_app.activateIgnoringOtherApps_(True)
|
||||
# Give the status item menu a beat to close before restoring the window.
|
||||
callLater(0.1, self._restore_window_on_macos)
|
||||
|
||||
def _restore_window_on_macos(self) -> None:
|
||||
self.window.show()
|
||||
self.window.restore()
|
||||
|
||||
def _hide_window_on_macos(self) -> None:
|
||||
from AppKit import (
|
||||
NSApplication,
|
||||
NSApplicationActivationPolicyAccessory,
|
||||
)
|
||||
|
||||
self.window.hide()
|
||||
NSApplication.sharedApplication().setActivationPolicy_(NSApplicationActivationPolicyAccessory)
|
||||
|
||||
def _set_macos_app_icon(self, ns_app: Any) -> None:
|
||||
if not self._app_icon_path:
|
||||
return
|
||||
|
||||
from AppKit import NSImage
|
||||
|
||||
ns_image = NSImage.alloc().initByReferencingFile_(self._app_icon_path)
|
||||
if ns_image is not None:
|
||||
ns_app.setApplicationIconImage_(ns_image)
|
||||
|
||||
def _on_closing(self) -> bool:
|
||||
"""Intercept window close: hide window instead of quitting (macOS standard behavior)."""
|
||||
if self._quitting:
|
||||
return True
|
||||
self._hide_window()
|
||||
return False # prevent the window from actually closing
|
||||
|
||||
def _start_tray(self) -> None:
|
||||
# import pystray locally, because the import fails when there is no display!
|
||||
import pystray
|
||||
from pystray import MenuItem as Item
|
||||
from pystray._base import Icon as TrayIcon
|
||||
|
||||
dashboard_path = Path(SERENA_DASHBOARD_DIR)
|
||||
|
||||
# macOS menu bar icons are displayed at 16pt; 32px covers Retina (@2x).
|
||||
# Windows/Linux tray icons are larger, so 48px is the better fit there.
|
||||
icon_filename = "serena-icon-tray-mac.png" if sys.platform == "darwin" else "serena-icon-48.png"
|
||||
icon_img = Image.open(dashboard_path / icon_filename)
|
||||
|
||||
def show(_icon: TrayIcon, _item: Item) -> None:
|
||||
self._show_window()
|
||||
|
||||
def hide(_icon: TrayIcon, _item: Item) -> None:
|
||||
self._hide_window()
|
||||
|
||||
def quit_app(_icon: TrayIcon, _item: Item) -> None:
|
||||
self._quitting = True
|
||||
try:
|
||||
_icon.stop()
|
||||
finally:
|
||||
if self.window:
|
||||
self.window.destroy()
|
||||
|
||||
menu = pystray.Menu(
|
||||
Item("Open", show, default=True),
|
||||
Item("Hide", hide),
|
||||
Item("Quit", quit_app),
|
||||
)
|
||||
|
||||
kwargs: dict[str, Any] = {}
|
||||
if sys.platform == "darwin":
|
||||
# Passing darwin_nsapplication integrates pystray with the NSApplication
|
||||
# run loop that webview.start() is about to enter. sharedApplication()
|
||||
# is idempotent; pywebview will reuse the same singleton.
|
||||
from AppKit import NSApplication
|
||||
|
||||
kwargs["darwin_nsapplication"] = NSApplication.sharedApplication()
|
||||
|
||||
self._tray_icon = pystray.Icon("dashboard_viewer", icon_img, self.title, menu, **kwargs)
|
||||
|
||||
# On Windows/Linux, run_detached spawns pystray's own internal thread and
|
||||
# returns immediately. On macOS it hooks into the NSApplication run loop
|
||||
# that webview.start() is about to enter (run_detached is always called
|
||||
# before webview.start() on macOS — see run()).
|
||||
self._tray_icon.run_detached()
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
@@ -29,28 +29,26 @@ line_ending: native
|
||||
# we have to live with this limitation for now.
|
||||
gui_log_window: False
|
||||
|
||||
# whether to start the Serena web dashboard, which provides access to Serena's configuration
|
||||
# and state as well as the current session logs.
|
||||
# The web dashboard is supported on all platforms.
|
||||
# We strongly recommend to always enable this option, since the dashboard provides important information
|
||||
# about the current state of Serena.
|
||||
# If you don't want the browser window to pop up automatically, set `web_dashboard_open_on_launch` to False
|
||||
# (see option below).
|
||||
# whether to start the Serena Dashboard, which provides detailed information on your Serena session,
|
||||
# the current configuration and furthermore allows some settings to be conveniently modified on the fly.
|
||||
# We strongly recommend to always enable this option!
|
||||
# If you want to prevent the Dashboard window from being opened on launch,
|
||||
# set `web_dashboard_open_on_launch` to false (see below).
|
||||
# Further information: https://oraios.github.io/serena/02-usage/060_dashboard.html
|
||||
web_dashboard: True
|
||||
|
||||
# the address the web dashboard will listen on (bind address).
|
||||
web_dashboard_listen_address: 127.0.0.1
|
||||
|
||||
# whether to open a browser window with the web dashboard when Serena starts (provided that web_dashboard
|
||||
# is enabled). See also the web_dashboard option.
|
||||
# If set to false, you can still open the dashboard manually by
|
||||
# whether to open the Dashboard window/browser tab when Serena starts (provided that web_dashboard is enabled).
|
||||
# If set to false, you can still open the dashboard manually by clicking on the Serena icon in your system
|
||||
# tray on Windows and macOS. On Linux, there is no system tray support, so you can only open the dashboard by
|
||||
# a) telling the LLM to "open the dashboard" (provided that the open_dashboard tool is enabled) or by
|
||||
# b) manually navigating to http://localhost:24282/dashboard/ in your web browser (actual port
|
||||
# may be higher if you have multiple instances running; try ports 24283, 24284, etc.)
|
||||
# See also: https://oraios.github.io/serena/02-usage/060_dashboard.html
|
||||
web_dashboard_open_on_launch: True
|
||||
|
||||
# the address the web dashboard will listen on (bind address).
|
||||
web_dashboard_listen_address: 127.0.0.1
|
||||
|
||||
# address where JetBrains plugin servers are running (only relevant when using the JetBrains language backend)
|
||||
jetbrains_plugin_server_address: 127.0.0.1
|
||||
|
||||
|
||||
@@ -1013,9 +1013,18 @@ class ALLanguageServer(SolidLanguageServer):
|
||||
original_name = symbol["name"]
|
||||
normalized_name = self._extract_al_display_name(original_name)
|
||||
|
||||
# Store original name if it was normalized (for hover injection)
|
||||
if symbol.get("kind") in (SymbolKind.Function, SymbolKind.Method) and "(" in normalized_name:
|
||||
normalized_name = normalized_name.split("(", 1)[0].strip()
|
||||
|
||||
if symbol.get("kind") == SymbolKind.Method and normalized_name.lower().startswith("action "):
|
||||
normalized_name = normalized_name.split(None, 1)[-1].strip()
|
||||
|
||||
if symbol.get("kind") == SymbolKind.Field and ":" in normalized_name:
|
||||
normalized_name = normalized_name.split(":", 1)[0].strip()
|
||||
|
||||
# Store original name if it was normalized for an AL object declaration
|
||||
# Only store if we have valid position data to avoid false matches at (0, 0)
|
||||
if original_name != normalized_name:
|
||||
if original_name != normalized_name and self._AL_OBJECT_NAME_PATTERN.match(original_name):
|
||||
sel_range = symbol.get("selectionRange")
|
||||
if sel_range:
|
||||
start = sel_range.get("start") # type: ignore
|
||||
@@ -1026,6 +1035,11 @@ class ALLanguageServer(SolidLanguageServer):
|
||||
|
||||
return normalized_name
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> int:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def request_hover(
|
||||
self, relative_file_path: str, line: int, column: int, file_buffer: LSPFileBuffer | None = None
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
from collections.abc import Hashable
|
||||
from typing import cast
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
@@ -34,6 +37,15 @@ class DartLanguageServer(SolidLanguageServer):
|
||||
config, repository_root_path, ProcessLaunchInfo(cmd=executable_path, cwd=repository_root_path), "dart", solidlsp_settings
|
||||
)
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
return symbol["name"].rsplit(".", 1)[-1]
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str:
|
||||
dart_settings = solidlsp_settings.get_ls_specific_settings(Language.DART)
|
||||
|
||||
@@ -4,11 +4,12 @@ import pathlib
|
||||
import stat
|
||||
import subprocess
|
||||
import threading
|
||||
from collections.abc import Hashable
|
||||
from typing import Any, cast
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_utils import FileUtils, PlatformId, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
@@ -215,6 +216,15 @@ class ElixirTools(SolidLanguageServer):
|
||||
# Set generous timeout for Expert which can be slow to initialize and respond
|
||||
self.set_request_timeout(180.0)
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
return symbol["name"].removeprefix("defp ").removeprefix("def ").split("(", 1)[0].strip()
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
from collections.abc import Hashable
|
||||
from typing import Any, cast
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
@@ -165,15 +168,13 @@ class Gopls(SolidLanguageServer):
|
||||
_CACHE_CONTEXT_ENV_KEYS = ("GOFLAGS", "GOOS", "GOARCH", "CGO_ENABLED")
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> str | None:
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
"""
|
||||
Compute a deterministic fingerprint of the Go build context.
|
||||
|
||||
The fingerprint includes gopls_settings and selected env vars that affect symbol discovery.
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
normalize_symbol_name_version = 1
|
||||
gopls_settings_raw = self._custom_settings.settings.get("gopls_settings")
|
||||
|
||||
gopls_settings: dict | None
|
||||
@@ -190,11 +191,14 @@ class Gopls(SolidLanguageServer):
|
||||
if value:
|
||||
env_subset[key] = value
|
||||
|
||||
# Return None only when BOTH settings and env subset are effectively empty.
|
||||
# Version processed symbols even when the build context itself is empty.
|
||||
if gopls_settings is None and not env_subset:
|
||||
return None
|
||||
return normalize_symbol_name_version
|
||||
|
||||
fingerprint_data: dict[str, object] = {"env": env_subset}
|
||||
fingerprint_data: dict[str, object] = {
|
||||
"env": env_subset,
|
||||
"normalize_symbol_name_version": normalize_symbol_name_version,
|
||||
}
|
||||
if gopls_settings is not None:
|
||||
fingerprint_data["gopls_settings"] = gopls_settings
|
||||
|
||||
@@ -202,6 +206,10 @@ class Gopls(SolidLanguageServer):
|
||||
|
||||
return hashlib.sha256(canonical_json.encode("utf-8")).hexdigest()[:16]
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
return symbol["name"].rsplit(".", 1)[-1]
|
||||
|
||||
def _start_server(self) -> None:
|
||||
"""Start gopls server process"""
|
||||
|
||||
|
||||
@@ -11,12 +11,14 @@ import os
|
||||
import pathlib
|
||||
import platform
|
||||
import shutil
|
||||
from collections.abc import Hashable
|
||||
from pathlib import Path
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import FileUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
@@ -169,6 +171,26 @@ class LuaLanguageServer(SolidLanguageServer):
|
||||
)
|
||||
self.request_id = 0
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
original_name = symbol["name"]
|
||||
|
||||
if symbol.get("kind") not in (SymbolKind.Function, SymbolKind.Method):
|
||||
return original_name
|
||||
|
||||
if "." in original_name:
|
||||
return original_name.rsplit(".", 1)[-1]
|
||||
|
||||
if ":" in original_name:
|
||||
return original_name.rsplit(":", 1)[-1]
|
||||
|
||||
return original_name
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
|
||||
@@ -14,12 +14,14 @@ import platform
|
||||
import shutil
|
||||
import tempfile
|
||||
import threading
|
||||
from collections.abc import Hashable
|
||||
from pathlib import Path
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import FileUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
@@ -199,6 +201,30 @@ class PowerShellLanguageServer(SolidLanguageServer):
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
original_name = symbol["name"]
|
||||
symbol_kind = symbol.get("kind")
|
||||
|
||||
# normalize class declarations
|
||||
if symbol_kind == SymbolKind.Class:
|
||||
return original_name.removeprefix("class ").split("{", 1)[0].strip()
|
||||
|
||||
# normalize method signatures
|
||||
if symbol_kind == SymbolKind.Method:
|
||||
return original_name.split("(", 1)[0].rsplit(None, 1)[-1]
|
||||
|
||||
# normalize function declarations
|
||||
if symbol_kind == SymbolKind.Function:
|
||||
return original_name.removeprefix("function ").split("(", 1)[0].strip()
|
||||
|
||||
return original_name
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
|
||||
@@ -3,12 +3,14 @@ import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import time
|
||||
from collections.abc import Hashable
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp import ls_types
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
@@ -56,6 +58,23 @@ class SourceKitLSP(SolidLanguageServer):
|
||||
self._did_sleep_before_requesting_references = False
|
||||
self._initialization_timestamp: float | None = None
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
original_name = symbol["name"]
|
||||
|
||||
if symbol.get("kind") not in (SymbolKind.Function, SymbolKind.Method, SymbolKind.Constructor):
|
||||
return original_name
|
||||
|
||||
if "(" not in original_name:
|
||||
return original_name
|
||||
|
||||
return original_name.split("(", 1)[0].strip()
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
|
||||
@@ -42,6 +42,7 @@ _LANGUAGE_REPO_ALIASES: dict[Language, Language] = {
|
||||
Language.PHP_PHPACTOR: Language.PHP,
|
||||
Language.PYTHON_JEDI: Language.PYTHON,
|
||||
Language.RUBY_SOLARGRAPH: Language.RUBY,
|
||||
Language.PYTHON_TY: Language.PYTHON,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ type DemoStruct struct {
|
||||
Field int
|
||||
}
|
||||
|
||||
func (d *DemoStruct) Value() int {
|
||||
return d.Field
|
||||
}
|
||||
|
||||
func UsingHelper() {
|
||||
Helper()
|
||||
}
|
||||
@@ -105,5 +105,11 @@ function Main {
|
||||
Write-Output "Script completed successfully"
|
||||
}
|
||||
|
||||
class PersonFormatter {
|
||||
[string] FormatName([string]$Name) {
|
||||
return "Formatted: $Name"
|
||||
}
|
||||
}
|
||||
|
||||
# Run main function
|
||||
Main @args
|
||||
@@ -194,7 +194,7 @@ class TestSerenaAgent:
|
||||
pytest.param(Language.PHP, "helperFunction", "Function", "helper.php", marks=pytest.mark.php),
|
||||
pytest.param(Language.CLOJURE, "greet", "Function", clj.CORE_PATH, marks=pytest.mark.clojure),
|
||||
pytest.param(Language.CSHARP, "Calculator", "Class", "Program.cs", marks=pytest.mark.csharp),
|
||||
pytest.param(Language.POWERSHELL, "function Greet-User ()", "Function", "main.ps1", marks=pytest.mark.powershell),
|
||||
pytest.param(Language.POWERSHELL, "Greet-User", "Function", "main.ps1", marks=pytest.mark.powershell),
|
||||
pytest.param(Language.CPP_CCLS, "add", "Function", "b.cpp", marks=pytest.mark.cpp),
|
||||
pytest.param(Language.LEAN4, "add", "Method", "Helper.lean", marks=pytest.mark.lean4),
|
||||
],
|
||||
@@ -295,7 +295,7 @@ class TestSerenaAgent:
|
||||
marks=pytest.mark.clojure,
|
||||
),
|
||||
pytest.param(Language.CSHARP, "Calculator", "Program.cs", "Program.cs", marks=pytest.mark.csharp),
|
||||
pytest.param(Language.POWERSHELL, "function Greet-User ()", "main.ps1", "main.ps1", marks=pytest.mark.powershell),
|
||||
pytest.param(Language.POWERSHELL, "Greet-User", "main.ps1", "main.ps1", marks=pytest.mark.powershell),
|
||||
pytest.param(Language.CPP_CCLS, "add", "b.cpp", "a.cpp", marks=pytest.mark.cpp),
|
||||
pytest.param(Language.LEAN4, "add", "Helper.lean", "Main.lean", marks=pytest.mark.lean4),
|
||||
],
|
||||
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
from serena.jetbrains.jetbrains_types import SymbolDTO, SymbolDTOKey
|
||||
from serena.project import Project
|
||||
from serena.symbol import LanguageServerSymbol, LanguageServerSymbolRetriever, NamePathComponent, NamePathMatcher
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES
|
||||
|
||||
|
||||
class TestSymbolNameMatching:
|
||||
@@ -235,7 +235,7 @@ class TestSymbolNameMatching:
|
||||
|
||||
@pytest.mark.python
|
||||
class TestLanguageServerSymbolRetriever:
|
||||
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_info(self, project_with_ls: Project):
|
||||
symbol_retriever = LanguageServerSymbolRetriever(project_with_ls)
|
||||
create_user_method_symbol = symbol_retriever.find("UserService/create_user", within_relative_path="test_repo/services.py")[0]
|
||||
@@ -280,7 +280,7 @@ def _make_mock_symbols(count: int, *, relative_path: str = "test_repo/services.p
|
||||
class TestHoverBudget:
|
||||
"""Tests for symbol_info_budget time budget behavior."""
|
||||
|
||||
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_budget_not_exceeded_all_lookups_performed(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
|
||||
"""With a large budget, all hover lookups are performed."""
|
||||
# Create symbol retriever with a mock agent that has large budget
|
||||
@@ -309,7 +309,7 @@ class TestHoverBudget:
|
||||
assert all(info is not None for info in result.values())
|
||||
assert len(result) == 3
|
||||
|
||||
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_budget_exceeded_partial_info(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
|
||||
"""With a small budget, hover lookups stop and remaining symbols get None info."""
|
||||
project_with_ls.serena_config.symbol_info_budget = 0.1
|
||||
@@ -353,7 +353,7 @@ class TestHoverBudget:
|
||||
assert result_list[3] is None
|
||||
assert result_list[4] is None
|
||||
|
||||
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_budget_zero_means_unlimited(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
|
||||
"""With budget=0, all hover lookups proceed (no early stopping)."""
|
||||
project_with_ls.serena_config.symbol_info_budget = 0.0
|
||||
@@ -380,7 +380,7 @@ class TestHoverBudget:
|
||||
assert call_count == 5
|
||||
assert all(info is not None for info in result.values())
|
||||
|
||||
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_project_budget_overrides_global(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
|
||||
"""Project-level budget overrides global budget."""
|
||||
# Create symbol retriever with global budget 10.0 but project budget 0.05
|
||||
@@ -418,7 +418,7 @@ class TestHoverBudget:
|
||||
# So 2 calls succeed (proving project budget 0.05 overrode global 10.0)
|
||||
assert call_count == 2
|
||||
|
||||
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_project_null_inherits_global(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
|
||||
"""When project budget is None, global budget is used."""
|
||||
# Create symbol retriever with project budget=None (inherit global)
|
||||
|
||||
@@ -6,8 +6,10 @@ from serena.symbol import LanguageServerSymbol
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.language_servers.al_language_server import ALLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import language_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
pytestmark = [pytest.mark.al, pytest.mark.skipif(not language_tests_enabled(Language.AL), reason="AL tests are disabled")]
|
||||
|
||||
@@ -556,3 +558,38 @@ class TestALPathNormalization:
|
||||
f"Hover for {symbol_name} should have injection with mixed paths. Got: {value[:200]}"
|
||||
)
|
||||
break
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
is_al_display_symbol = (
|
||||
s["name"].endswith((".Codeunit", ".Page", ".Table", ".TableExt", ".Enum", ".Interface"))
|
||||
or (s["name"].startswith('"') and s["name"].endswith('"'))
|
||||
or s["name"].startswith(
|
||||
(
|
||||
"Enum Name ",
|
||||
"Area ",
|
||||
"Group ",
|
||||
"Field ",
|
||||
"Part ",
|
||||
"SystemPart ",
|
||||
"Repeater ",
|
||||
"ActionRef ",
|
||||
"Key ",
|
||||
"FieldGroup ",
|
||||
)
|
||||
)
|
||||
or ":" in s["name"]
|
||||
)
|
||||
if not is_al_display_symbol and has_malformed_name(
|
||||
s,
|
||||
whitespace_allowed=s["kind"] in {SymbolKind.Class, SymbolKind.Struct, SymbolKind.Interface, SymbolKind.Enum},
|
||||
):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -13,6 +13,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(platform.system() == "Windows", reason="ansible-language-server does not support native Windows operation")
|
||||
@@ -51,3 +52,17 @@ class TestAnsibleLanguageServerBasics:
|
||||
assert len(result) > 0, "Expected non-empty completion list"
|
||||
labels = [item["completionText"] for item in result if "completionText" in item]
|
||||
assert labels, f"Expected completions with completionText, got: {result[:3]}"
|
||||
|
||||
@pytest.mark.xfail(reason="Seems like ansible LS lacks basic functionality at the moment, textDocument/documentSymbol doesn't work")
|
||||
@pytest.mark.parametrize("language_server", [Language.ANSIBLE], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.bash
|
||||
@@ -119,3 +120,16 @@ class TestBashLanguageServerBasics:
|
||||
# Verify total counts match expectations
|
||||
assert len(main_functions) >= 3, f"Should find at least 3 functions in main.sh, found {len(main_functions)}"
|
||||
assert len(utils_functions) >= 8, f"Should find at least 8 functions in utils.sh, found {len(utils_functions)}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.BASH], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -3,8 +3,9 @@ import pytest
|
||||
from serena.project import Project
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import UnifiedSymbolInformation
|
||||
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
|
||||
from test.conftest import language_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
from . import CORE_PATH, UTILS_PATH
|
||||
|
||||
@@ -187,6 +188,8 @@ class TestLanguageServerBasics:
|
||||
assert found_relevant_references, f"Should have found calculate-area referencing multiply, but got: {result}"
|
||||
|
||||
|
||||
@pytest.mark.skipif(not language_tests_enabled(Language.CLOJURE), reason="Clojure tests are disabled")
|
||||
@pytest.mark.clojure
|
||||
class TestProjectBasics:
|
||||
@pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True)
|
||||
def test_retrieve_content_around_line(self, project: Project):
|
||||
@@ -221,3 +224,20 @@ class TestProjectBasics:
|
||||
assert result is not None, "Should find require statements"
|
||||
utils_matches = [match for match in result if match.source_file_path and "utils.clj" in match.source_file_path]
|
||||
assert len(utils_matches) > 0, "Should find require statement in utils.clj"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
# clojure-lsp exposes namespace and dependency/container entries in addition to real vars/functions.
|
||||
# Those qualified names are not the target of this regression check.
|
||||
if s["kind"] in {SymbolKind.Namespace, SymbolKind.Struct}:
|
||||
continue
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
|
||||
|
||||
PYTHON_BACKEND_LANGUAGES = [Language.PYTHON, Language.PYTHON_TY]
|
||||
|
||||
|
||||
def has_malformed_name(
|
||||
symbol: UnifiedSymbolInformation,
|
||||
whitespace_allowed: bool = False,
|
||||
period_allowed: bool = False,
|
||||
colon_allowed: bool = False,
|
||||
brace_allowed: bool = False,
|
||||
parenthesis_allowed: bool = False,
|
||||
comma_allowed: bool = False,
|
||||
) -> bool:
|
||||
forbidden_chars: list[str] = []
|
||||
|
||||
if not whitespace_allowed:
|
||||
forbidden_chars.append(" ")
|
||||
if not period_allowed:
|
||||
forbidden_chars.append(".")
|
||||
if not colon_allowed:
|
||||
forbidden_chars.append(":")
|
||||
if not brace_allowed:
|
||||
forbidden_chars.append("{")
|
||||
if not parenthesis_allowed:
|
||||
forbidden_chars.append("(")
|
||||
if not comma_allowed:
|
||||
forbidden_chars.append(",")
|
||||
|
||||
return any(separator in symbol["name"] for separator in forbidden_chars)
|
||||
|
||||
|
||||
def request_all_symbols(language_server: SolidLanguageServer) -> list[UnifiedSymbolInformation]:
|
||||
result: list[UnifiedSymbolInformation] = []
|
||||
|
||||
def visit(symbol: UnifiedSymbolInformation) -> None:
|
||||
result.append(symbol)
|
||||
for child in symbol.get("children", []):
|
||||
visit(child)
|
||||
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
for symbol in symbols:
|
||||
visit(symbol)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def format_symbol_for_assert(symbol: UnifiedSymbolInformation) -> str:
|
||||
relative_path = symbol.get("location", {}).get("relativePath", "<unknown>")
|
||||
try:
|
||||
kind = SymbolKind(symbol["kind"]).name
|
||||
except ValueError:
|
||||
kind = str(symbol["kind"])
|
||||
return f"{symbol['name']} [{kind}] ({relative_path})"
|
||||
@@ -15,6 +15,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
def _ccls_available() -> bool:
|
||||
@@ -142,3 +143,16 @@ int use_add() {
|
||||
# Clean up the new file
|
||||
if os.path.exists(new_file_abs_path):
|
||||
os.remove(new_file_abs_path)
|
||||
|
||||
@pytest.mark.parametrize("language_server", _cpp_servers, indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -15,8 +15,10 @@ from solidlsp.language_servers.csharp_language_server import (
|
||||
find_solution_or_project_file,
|
||||
)
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.csharp
|
||||
@@ -359,3 +361,16 @@ class TestCSharpSolutionProjectOpening:
|
||||
|
||||
# Verify the file actually exists
|
||||
assert os.path.exists(result)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s, period_allowed=s["kind"] == SymbolKind.Namespace):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -8,6 +8,7 @@ from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.dart
|
||||
@@ -361,3 +362,16 @@ class TestDartLanguageServer:
|
||||
if main_dart_refs:
|
||||
for ref in main_dart_refs:
|
||||
assert "range" in ref or "location" in ref
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -11,6 +11,8 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
from . import EXPERT_UNAVAILABLE, EXPERT_UNAVAILABLE_REASON
|
||||
|
||||
@@ -110,3 +112,29 @@ class TestElixirBasic:
|
||||
for _ in range(3):
|
||||
symbols = language_server.request_document_symbols("lib/services.ex").get_all_symbols_and_roots()
|
||||
assert symbols is not None
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if s["kind"] in {SymbolKind.Module, SymbolKind.Namespace, SymbolKind.Struct}:
|
||||
continue
|
||||
|
||||
allow_test_style_name = s["name"].startswith(('test "', 'describe "'))
|
||||
allow_struct_name = s["name"].startswith("%")
|
||||
allow_type_name = s["name"].startswith("@type ")
|
||||
if has_malformed_name(
|
||||
s,
|
||||
whitespace_allowed=allow_test_style_name or allow_type_name,
|
||||
period_allowed=allow_struct_name,
|
||||
colon_allowed=allow_test_style_name,
|
||||
brace_allowed=allow_test_style_name or allow_struct_name,
|
||||
comma_allowed=allow_test_style_name,
|
||||
):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -1,12 +1,16 @@
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("elm") is None and not is_ci, reason="Elm compiler is not available")
|
||||
@pytest.mark.elm
|
||||
class TestElmLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
|
||||
@@ -52,3 +56,16 @@ class TestElmLanguageServer:
|
||||
|
||||
# Verify that at least one reference is in Main.elm (where formatMessage is used)
|
||||
assert any("Main.elm" in ref.get("relativePath", "") for ref in refs), "Expected to find usage of formatMessage in Main.elm"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
from . import ERLANG_LS_UNAVAILABLE, ERLANG_LS_UNAVAILABLE_REASON
|
||||
|
||||
@@ -41,3 +42,16 @@ class TestErlangLanguageServerBasics:
|
||||
pytest.skip("Erlang language server not fully initialized")
|
||||
else:
|
||||
raise
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -11,6 +11,7 @@ from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
# Mark all tests in this module as fortran tests
|
||||
pytestmark = pytest.mark.fortran
|
||||
@@ -272,3 +273,16 @@ class TestFortranLanguageServer:
|
||||
_refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
# refs might be empty if Point3D isn't used elsewhere, but the call should not fail
|
||||
# The important thing is that it doesn't error due to wrong character position
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -8,6 +8,7 @@ from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
# Currently, most F# tests fail, there seems to be a regression or instability.
|
||||
@@ -183,3 +184,16 @@ class TestFSharpLanguageServer:
|
||||
|
||||
# This is a successful test - FsAutoComplete is working with F# files
|
||||
assert True, "F# language server can handle files successfully"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -3,9 +3,11 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from serena.symbol import LanguageServerSymbol
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.go
|
||||
@@ -17,6 +19,20 @@ class TestGoLanguageServer:
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Helper"), "Helper function not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "DemoStruct"), "DemoStruct not found in symbol tree"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
|
||||
def test_find_symbol_matches_go_method_by_bare_name(self, language_server: SolidLanguageServer) -> None:
|
||||
symbols = language_server.request_full_symbol_tree(within_relative_path="main.go")
|
||||
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Value"), "Expected Go method name to be normalized to bare name"
|
||||
assert not SymbolUtils.symbol_tree_contains_name(symbols, "(*DemoStruct).Value"), (
|
||||
"Expected receiver-qualified Go method name to be normalized away"
|
||||
)
|
||||
|
||||
bare_name_matches = [match for root in symbols for match in LanguageServerSymbol(root).find("Value")]
|
||||
|
||||
assert bare_name_matches, "Expected a Go method to match by bare name"
|
||||
assert all(match.name == "Value" for match in bare_name_matches)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
|
||||
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
file_path = os.path.join("main.go")
|
||||
@@ -192,3 +208,16 @@ class TestGoBuildTags:
|
||||
|
||||
# A cache miss should repopulate and mark caches modified.
|
||||
_assert_caches_modified(ls_foo)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -8,6 +8,7 @@ from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.groovy
|
||||
@@ -103,3 +104,16 @@ class TestGroovyLanguageServer:
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "ModelUser"), "ModelUser missing from overview"
|
||||
|
||||
def test_bare_symbol_names(self) -> None:
|
||||
assert self.language_server is not None
|
||||
all_symbols = request_all_symbols(self.language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -19,6 +19,8 @@ import pytest
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.haskell
|
||||
@@ -217,3 +219,24 @@ class TestHaskellLanguageServer:
|
||||
assert any("Main.hs" in path or "Calculator.hs" in path for path in calc_ref_paths), (
|
||||
f"Expected Calculator to be referenced in Main.hs or Calculator.hs, got: {calc_ref_paths}"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if s["kind"] == SymbolKind.Module:
|
||||
continue
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
diagnostics = [
|
||||
{
|
||||
"formatted": format_symbol_for_assert(sym),
|
||||
"name": sym["name"],
|
||||
"kind": SymbolKind(sym["kind"]).name,
|
||||
"detail": sym.get("detail"),
|
||||
}
|
||||
for sym in malformed_symbols
|
||||
]
|
||||
pytest.fail(f"Found malformed symbols: {diagnostics}", pytrace=False)
|
||||
@@ -12,7 +12,9 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
def _find_symbol_by_name(language_server: SolidLanguageServer, file_path: str, name: str) -> dict[str, Any] | None:
|
||||
@@ -158,3 +160,16 @@ class TestHlslHover:
|
||||
hover_info = language_server.request_hover("common.hlsl", 3, 7)
|
||||
assert hover_info is not None, "Hover should return information for VertexInput"
|
||||
assert "contents" in hover_info, "Hover should have contents"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s, period_allowed=s["kind"] == SymbolKind.File):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -6,6 +6,7 @@ from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import language_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
pytestmark = [pytest.mark.java, pytest.mark.skipif(not language_tests_enabled(Language.JAVA), reason="Java tests disabled")]
|
||||
|
||||
@@ -50,3 +51,16 @@ class TestJavaLanguageServer:
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -1,9 +1,14 @@
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("julia") is None and not is_ci, reason="Julia is not available")
|
||||
@pytest.mark.julia
|
||||
class TestJuliaLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True)
|
||||
@@ -48,3 +53,16 @@ class TestJuliaLanguageServer:
|
||||
reference_paths = [ref["relativePath"] for ref in references]
|
||||
# The reference might be in either file (definition or usage)
|
||||
assert "main.jl" in reference_paths or "lib/helper.jl" in reference_paths
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -4,8 +4,10 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
# Kotlin LSP (IntelliJ-based, pre-alpha v261) crashes on JVM restart under CI resource constraints
|
||||
@@ -55,3 +57,16 @@ class TestKotlinLanguageServer:
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.KOTLIN], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s, period_allowed=s["kind"] == SymbolKind.File):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -10,6 +10,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.lua
|
||||
@@ -253,3 +254,21 @@ class TestLuaLanguageServer:
|
||||
# The test file should have some content that references calculator
|
||||
symbol_list = test_symbols[0] if isinstance(test_symbols, tuple) else test_symbols
|
||||
assert len(symbol_list) > 0, "test_calculator.lua should have symbols"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(
|
||||
s,
|
||||
whitespace_allowed=s["name"] == " ",
|
||||
period_allowed="." in s["name"],
|
||||
colon_allowed=":" in s["name"],
|
||||
):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.luau
|
||||
@@ -151,3 +152,16 @@ class TestLuauLanguageServer:
|
||||
assert definition["uri"].endswith("init.luau"), f"Definition should be in init.luau, got: {definition['uri']}"
|
||||
# createConfig is defined at line 8 (0-indexed): `local function createConfig(...)`
|
||||
assert definition["range"]["start"]["line"] == 8, f"Definition should be at line 8, got line {definition['range']['start']['line']}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.LUAU], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -11,6 +11,7 @@ from serena.symbol import LanguageServerSymbol
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.markdown
|
||||
@@ -93,3 +94,16 @@ class TestMarkdownLanguageServerBasics:
|
||||
|
||||
for symbol in all_symbols:
|
||||
assert symbol["kind"] == SymbolKind.Namespace, f"Nested heading '{symbol['name']}' should be remapped to Namespace"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s, whitespace_allowed=True, parenthesis_allowed=True):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -16,6 +16,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
# Skip all tests if MATLAB is not available
|
||||
pytestmark = pytest.mark.matlab
|
||||
@@ -115,3 +116,16 @@ class TestMatlabLanguageServerReferences:
|
||||
|
||||
# Should find references in both main.m and Calculator.m
|
||||
assert references is not None
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -10,7 +10,9 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
# Skip all Nix tests on Windows as Nix doesn't support Windows
|
||||
pytestmark = pytest.mark.skipif(platform.system() == "Windows", reason="Nix and nil are not available on Windows")
|
||||
@@ -218,3 +220,33 @@ class TestNixLanguageServer:
|
||||
root = symbols[0]
|
||||
assert isinstance(root, dict), "Root should be a dict"
|
||||
assert "name" in root, "Root should have a name"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
# nixd can surface anonymous expression nodes using synthetic display labels
|
||||
# like "(anonymous lambda)", "(dynamic string)", "{anonymous}", and
|
||||
# "(dynamic attribute name)". It also emits literal value nodes such as
|
||||
# strings and arrays. These do not correspond to named declarations, so
|
||||
# there is no bare identifier we could or should normalize them to in the
|
||||
# Nix wrapper. This test is only meant to enforce bare names for symbols
|
||||
# that actually have declaration names.
|
||||
if s["kind"] in {SymbolKind.String, SymbolKind.Array}:
|
||||
continue
|
||||
if s["name"] in {"(anonymous lambda)", "(dynamic string)", "{anonymous}", "(dynamic attribute name)"}:
|
||||
continue
|
||||
if s["kind"] in {SymbolKind.Field, SymbolKind.Property} and "." in s["name"]:
|
||||
continue
|
||||
if has_malformed_name(
|
||||
s,
|
||||
whitespace_allowed=s["name"] == "(anonymous lambda)",
|
||||
parenthesis_allowed=s["name"] == "(anonymous lambda)",
|
||||
):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -1,12 +1,16 @@
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("opam") is None and not is_ci, reason="OPAM is not available")
|
||||
@pytest.mark.ocaml
|
||||
class TestOCamlLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
|
||||
@@ -49,8 +53,6 @@ class TestOCamlLanguageServer:
|
||||
|
||||
def test_reason_file_patterns(self) -> None:
|
||||
"""Test that OCaml language configuration recognizes Reason file extensions"""
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
ocaml_lang = Language.OCAML
|
||||
file_matcher = ocaml_lang.get_source_fn_matcher()
|
||||
|
||||
@@ -150,3 +152,19 @@ class TestOCamlLanguageServer:
|
||||
file_path = os.path.join("bin", "main.ml")
|
||||
symbols, _roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
assert len(symbols) > 0, "Should find symbols in main.ml that use opened modules"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
# happens for lambdas a la let () = ...
|
||||
if s["name"] == "()":
|
||||
continue
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -10,12 +10,15 @@ Uses genericptr/pascal-language-server which returns SymbolInformation[] format:
|
||||
- Method names don't include parent class prefix; uses containerName instead
|
||||
"""
|
||||
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.conftest import language_tests_enabled
|
||||
from test.conftest import is_ci, language_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.pascal,
|
||||
@@ -24,6 +27,7 @@ pytestmark = [
|
||||
|
||||
|
||||
@pytest.mark.pascal
|
||||
@pytest.mark.skipif(shutil.which("fpc") is None and not is_ci, reason="Free Pascal compiler is not available")
|
||||
class TestPascalLanguageServerBasics:
|
||||
"""Test basic functionality of the Pascal language server."""
|
||||
|
||||
@@ -193,3 +197,18 @@ class TestPascalLanguageServerBasics:
|
||||
|
||||
# Should contain the doc comment
|
||||
assert "Calculates the sum" in value, f"Hover should include doc comment. Got: {value[:500]}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PASCAL], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if s["kind"] == SymbolKind.Package:
|
||||
continue
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.perl
|
||||
@@ -74,3 +75,16 @@ class TestPerlLanguageServer:
|
||||
main_pl_lines = sorted([ref["range"]["start"]["line"] for ref in main_pl_refs])
|
||||
assert 17 in main_pl_lines, f"Expected reference at line 18 (0-indexed 17), found: {main_pl_lines}"
|
||||
assert 20 in main_pl_lines, f"Expected reference at line 21 (0-indexed 20), found: {main_pl_lines}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PERL], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -1,3 +1,4 @@
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -5,10 +6,18 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import is_ci, is_windows, language_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
def _php_supports_phpactor() -> bool:
|
||||
completed_process = subprocess.run(["php", "-m"], capture_output=True, text=True, check=False)
|
||||
installed_extensions = {line.strip().lower() for line in completed_process.stdout.splitlines()}
|
||||
return "openssl" in installed_extensions
|
||||
|
||||
|
||||
_php_servers: list[Language] = [Language.PHP]
|
||||
if language_tests_enabled(Language.PHP_PHPACTOR):
|
||||
if not (is_windows and is_ci): # TODO: Phpactor tests are flaky in Windows CI and can even cause hangs #1040
|
||||
if not (is_windows and is_ci) and _php_supports_phpactor():
|
||||
_php_servers.append(Language.PHP_PHPACTOR)
|
||||
|
||||
|
||||
@@ -256,3 +265,16 @@ class TestPhpLanguageServers:
|
||||
dog_root = next((s for s in symbols if s.get("name") == "Dog"), None)
|
||||
if dog_root is not None:
|
||||
assert SymbolUtils.symbol_tree_contains_name([dog_root], "greet"), "greet should be nested under Dog in symbol tree"
|
||||
|
||||
@pytest.mark.parametrize("language_server", _php_servers, indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -9,6 +9,8 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.powershell
|
||||
@@ -130,6 +132,14 @@ class TestPowerShellLanguageServerBasics:
|
||||
assert len(main_functions) >= 3, f"Should find at least 3 functions in main.ps1, found {len(main_functions)}"
|
||||
assert len(utils_functions) >= 8, f"Should find at least 8 functions in utils.ps1, found {len(utils_functions)}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
|
||||
def test_powershell_class_method_symbols_use_bare_method_name(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test whether PSES already reports class methods with bare names."""
|
||||
symbols = language_server.request_full_symbol_tree(within_relative_path="main.ps1")
|
||||
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "PersonFormatter"), "Should find PersonFormatter class in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "FormatName"), "Expected PowerShell method to be exposed with bare name"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
|
||||
def test_powershell_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding references to a function within the same file."""
|
||||
@@ -172,3 +182,16 @@ class TestPowerShellLanguageServerBasics:
|
||||
assert any("utils.ps1" in loc.get("uri", "") for loc in definition_locations), (
|
||||
f"Should find definition in utils.ps1, got {definition_locations}"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s, colon_allowed=s["name"].startswith("$")):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -12,14 +12,14 @@ import pytest
|
||||
from serena.project import Project
|
||||
from serena.util.text_utils import LineType
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES, format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.python
|
||||
class TestPythonLanguageServerBasics:
|
||||
"""Test basic functionality of the language server."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_references_user_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_references on the User class."""
|
||||
# Get references to the User class in models.py
|
||||
@@ -34,7 +34,7 @@ class TestPythonLanguageServerBasics:
|
||||
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
assert len(references) > 1, "User class should be referenced in multiple files (using selectionRange if present)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_references_item_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_references on the Item class."""
|
||||
# Get references to the Item class in models.py
|
||||
@@ -50,7 +50,7 @@ class TestPythonLanguageServerBasics:
|
||||
services_references = [ref for ref in references if "services.py" in ref["uri"]]
|
||||
assert len(services_references) > 0, "At least one reference should be in services.py (using selectionRange if present)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_references_function_parameter(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_references on a function parameter."""
|
||||
# Get references to the id parameter in get_user method
|
||||
@@ -65,7 +65,7 @@ class TestPythonLanguageServerBasics:
|
||||
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
assert len(references) > 0, "id parameter should be referenced within the method (using selectionRange if present)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_references_create_user_method(self, language_server: SolidLanguageServer) -> None:
|
||||
# Get references to the create_user method in UserService
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -81,7 +81,7 @@ class TestPythonLanguageServerBasics:
|
||||
|
||||
|
||||
class TestProjectBasics:
|
||||
@pytest.mark.parametrize("project", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_retrieve_content_around_line(self, project: Project) -> None:
|
||||
"""Test retrieve_content_around_line functionality with various scenarios."""
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
@@ -188,7 +188,7 @@ class TestProjectBasics:
|
||||
else:
|
||||
assert line.match_type == LineType.AFTER_MATCH
|
||||
|
||||
@pytest.mark.parametrize("project", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_search_files_for_pattern(self, project: Project) -> None:
|
||||
"""Test search_files_for_pattern with various patterns and glob filters."""
|
||||
# Test 1: Search for class definitions across all files
|
||||
@@ -233,3 +233,16 @@ class TestProjectBasics:
|
||||
no_match_pattern = r"def\s+this_method_does_not_exist\s*\([^)]*\):"
|
||||
matches = project.search_source_files_for_pattern(no_match_pattern)
|
||||
assert len(matches) == 0
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -6,6 +6,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import start_ls_context
|
||||
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES
|
||||
|
||||
# This mark will be applied to all tests in this module
|
||||
pytestmark = pytest.mark.python
|
||||
@@ -19,7 +20,7 @@ def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]:
|
||||
yield ls
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("ls_with_ignored_dirs", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
"""Tests that request_full_symbol_tree ignores the configured directory."""
|
||||
root = ls_with_ignored_dirs.request_full_symbol_tree()[0]
|
||||
@@ -28,7 +29,7 @@ def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
assert children_names == {"test_repo", "examples"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("ls_with_ignored_dirs", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
"""Tests that find_references ignores the configured directory."""
|
||||
# Location of Item, which is referenced in scripts
|
||||
@@ -42,7 +43,7 @@ def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
assert not any("scripts" in ref["relativePath"] for ref in references)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("repo_path", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
|
||||
"""Tests that refs and symbols with glob patterns are ignored."""
|
||||
ignored_paths = ["*ipts", "custom_t*"]
|
||||
|
||||
@@ -12,8 +12,8 @@ import pytest
|
||||
|
||||
from serena.symbol import LanguageServerSymbol
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES
|
||||
|
||||
pytestmark = pytest.mark.python
|
||||
|
||||
@@ -21,7 +21,7 @@ pytestmark = pytest.mark.python
|
||||
class TestLanguageServerSymbols:
|
||||
"""Test the language server's symbol-related functionality."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a function."""
|
||||
# Test for a position inside the create_user method
|
||||
@@ -36,7 +36,7 @@ class TestLanguageServerSymbols:
|
||||
if "body" in containing_symbol:
|
||||
assert containing_symbol["body"].get_text().strip().startswith("def create_user(self")
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_references_to_variables(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a variable."""
|
||||
file_path = os.path.join("test_repo", "variables.py")
|
||||
@@ -51,7 +51,7 @@ class TestLanguageServerSymbols:
|
||||
assert "dataclass_instance" in ref_names
|
||||
assert "second_dataclass" in ref_names
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a class."""
|
||||
# Test for a position inside the UserService class but outside any method
|
||||
@@ -64,7 +64,7 @@ class TestLanguageServerSymbols:
|
||||
assert containing_symbol["name"] == "UserService"
|
||||
assert containing_symbol["kind"] == SymbolKind.Class
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol with nested scopes."""
|
||||
# Test for a position inside a method which is inside a class
|
||||
@@ -90,7 +90,7 @@ class TestLanguageServerSymbols:
|
||||
assert parent_symbol["name"] == "UserService"
|
||||
assert parent_symbol["kind"] == SymbolKind.Class
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_none(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a position with no containing symbol."""
|
||||
# Test for a position outside any function/class (e.g., in imports)
|
||||
@@ -101,7 +101,7 @@ class TestLanguageServerSymbols:
|
||||
# Should return None or an empty dictionary
|
||||
assert containing_symbol is None or containing_symbol == {}
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_referencing_symbols_function(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a function."""
|
||||
# Test referencing symbols for create_user function
|
||||
@@ -125,7 +125,7 @@ class TestLanguageServerSymbols:
|
||||
assert "start" in symbol["location"]["range"]
|
||||
assert "end" in symbol["location"]["range"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_referencing_symbols_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a class."""
|
||||
# Test referencing symbols for User class
|
||||
@@ -146,7 +146,7 @@ class TestLanguageServerSymbols:
|
||||
]
|
||||
assert len(services_references) > 0, "No referencing symbols from services.py for User (selectionRange)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_referencing_symbols_parameter(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a function parameter."""
|
||||
# Test referencing symbols for id parameter in get_user
|
||||
@@ -167,7 +167,7 @@ class TestLanguageServerSymbols:
|
||||
]
|
||||
assert len(method_refs) > 0, "No referencing symbols within method body for get_user (selectionRange)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_referencing_symbols_none(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a position with no symbol."""
|
||||
# For positions with no symbol, the method might throw an error or return None/empty list
|
||||
@@ -185,7 +185,7 @@ class TestLanguageServerSymbols:
|
||||
pass
|
||||
|
||||
# Tests for request_defining_symbol
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_variable(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a variable usage."""
|
||||
# Test finding the definition of a symbol in the create_user method
|
||||
@@ -204,7 +204,7 @@ class TestLanguageServerSymbols:
|
||||
if "location" in defining_symbol and "uri" in defining_symbol["location"]:
|
||||
assert "services.py" in defining_symbol["location"]["uri"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_imported_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for an imported class."""
|
||||
# Test finding the definition of the 'User' class used in the UserService.create_user method
|
||||
@@ -216,7 +216,7 @@ class TestLanguageServerSymbols:
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "User"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_method_call(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a method call."""
|
||||
# Create an example file path for a file that calls UserService.create_user
|
||||
@@ -241,7 +241,7 @@ class TestLanguageServerSymbols:
|
||||
|
||||
warnings.warn("Could not verify method call definition - file structure may differ from expected")
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_none(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a position with no symbol."""
|
||||
# Test for a position with no symbol (e.g., whitespace or comment)
|
||||
@@ -252,7 +252,7 @@ class TestLanguageServerSymbols:
|
||||
# Should return None for positions with no symbol
|
||||
assert defining_symbol is None
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_variable(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol where the symbol is a variable."""
|
||||
# Test for a position inside a variable definition
|
||||
@@ -265,7 +265,7 @@ class TestLanguageServerSymbols:
|
||||
assert containing_symbol["name"] == "user_var_str"
|
||||
assert containing_symbol["kind"] == SymbolKind.Variable
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_nested_function(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a nested function or closure."""
|
||||
# Use the existing nested.py file which contains nested classes and methods
|
||||
@@ -290,15 +290,8 @@ class TestLanguageServerSymbols:
|
||||
# Test 3: Find definition of a method-local function
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 9, 15) # Position inside func_within_func
|
||||
|
||||
# This is challenging for many language servers and may fail
|
||||
try:
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "func_within_func"
|
||||
except (AssertionError, TypeError, KeyError):
|
||||
# This is expected to potentially fail in many implementations
|
||||
import warnings
|
||||
|
||||
warnings.warn("Could not resolve nested class method definition - implementation limitation")
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "func_within_func"
|
||||
|
||||
# Test 2: Find definition of the nested class
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 15, 18) # Position of NestedClass
|
||||
@@ -314,9 +307,9 @@ class TestLanguageServerSymbols:
|
||||
# This is challenging for many language servers and may fail
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "func_within_func"
|
||||
assert defining_symbol.get("kind") == SymbolKind.Function.value
|
||||
assert defining_symbol.get("kind") in [SymbolKind.Function.value, SymbolKind.Method.value]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_symbol_methods_integration(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test the integration between different symbol-related methods."""
|
||||
# This test demonstrates using the various symbol methods together
|
||||
@@ -365,7 +358,7 @@ class TestLanguageServerSymbols:
|
||||
|
||||
warnings.warn("Could not verify container hierarchy - implementation detail")
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_symbol_tree_structure(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that the symbol tree structure is correctly built."""
|
||||
# Get all symbols in the test file
|
||||
@@ -395,7 +388,7 @@ class TestLanguageServerSymbols:
|
||||
).get_all_symbols_and_roots()
|
||||
assert user_management_roots == user_management_node["children"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_symbol_tree_structure_subdir(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that the symbol tree structure is correctly built."""
|
||||
# Get all symbols in the test file
|
||||
@@ -418,7 +411,7 @@ class TestLanguageServerSymbols:
|
||||
).get_all_symbols_and_roots()
|
||||
assert user_management_roots == user_management_node["children"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_dir_overview(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that request_dir_overview returns correct symbol information for files in a directory."""
|
||||
# Get overview of the examples directory
|
||||
@@ -442,7 +435,7 @@ class TestLanguageServerSymbols:
|
||||
retrieved_symbols = {symbol["name"] for symbol in services_symbols if "name" in symbol}
|
||||
assert expected_symbols.issubset(retrieved_symbols)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_document_overview(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that request_document_overview returns correct symbol information for a file."""
|
||||
# Get overview of the user_management.py file
|
||||
@@ -451,25 +444,3 @@ class TestLanguageServerSymbols:
|
||||
# Verify that we have entries for both files
|
||||
symbol_names = {LanguageServerSymbol(s_info).name for s_info in overview}
|
||||
assert {"UserStats", "UserManager", "process_user_data", "main"}.issubset(symbol_names)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_containing_symbol_of_var_is_file(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that the containing symbol of a variable is the file itself."""
|
||||
# Get the containing symbol of a variable in a file
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# import of typing
|
||||
references_to_typing = [
|
||||
ref.symbol
|
||||
for ref in language_server.request_referencing_symbols(file_path, 4, 6, include_imports=False, include_file_symbols=True)
|
||||
]
|
||||
assert {ref["kind"] for ref in references_to_typing} == {SymbolKind.File}
|
||||
|
||||
# now include bodies
|
||||
references_to_typing = [
|
||||
ref.symbol
|
||||
for ref in language_server.request_referencing_symbols(
|
||||
file_path, 4, 6, include_imports=False, include_file_symbols=True, include_body=True
|
||||
)
|
||||
]
|
||||
assert {ref["kind"] for ref in references_to_typing} == {SymbolKind.File}
|
||||
assert references_to_typing[0]["body"]
|
||||
@@ -3,14 +3,18 @@ Basic tests for R Language Server integration
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("R") is None and not is_ci, reason="R is not available")
|
||||
@pytest.mark.r
|
||||
class TestRLanguageServer:
|
||||
"""Test basic functionality of the R language server."""
|
||||
@@ -82,8 +86,6 @@ class TestRLanguageServer:
|
||||
|
||||
def test_file_matching(self):
|
||||
"""Test that R files are properly matched."""
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
matcher = Language.R.get_source_fn_matcher()
|
||||
|
||||
assert matcher.is_relevant_filename("script.R")
|
||||
@@ -95,3 +97,16 @@ class TestRLanguageServer:
|
||||
"""Test R language enum value."""
|
||||
assert Language.R == "r"
|
||||
assert str(Language.R) == "r"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.R], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -1,14 +1,19 @@
|
||||
"""Tests for Rego language server (Regal) functionality."""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("regal") is None and not is_ci, reason="Regal is not available")
|
||||
@pytest.mark.rego
|
||||
class TestRegoLanguageServer:
|
||||
"""Test Regal language server functionality for Rego."""
|
||||
@@ -132,3 +137,18 @@ class TestRegoLanguageServer:
|
||||
assert "validate_user_input" in symbol_names, "validate_user_input rule not found"
|
||||
assert "has_valid_credentials" in symbol_names, "has_valid_credentials function not found"
|
||||
assert "validate_request" in symbol_names, "validate_request rule not found"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.REGO], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if s["kind"] == SymbolKind.Package:
|
||||
continue
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -6,6 +6,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.ruby
|
||||
@@ -37,8 +38,36 @@ class TestRubyLanguageServer:
|
||||
str(repo_path / "main.rb"), 16, 17
|
||||
) # add method at line 17 (0-indexed 16), position 17
|
||||
|
||||
assert len(definition_location_list) == 1
|
||||
definition_location = definition_location_list[0]
|
||||
unique_locations = {
|
||||
(
|
||||
location["relativePath"],
|
||||
location["range"]["start"]["line"],
|
||||
location["range"]["start"]["character"],
|
||||
location["range"]["end"]["line"],
|
||||
location["range"]["end"]["character"],
|
||||
): location
|
||||
for location in definition_location_list
|
||||
}
|
||||
|
||||
assert len(unique_locations) == 1
|
||||
definition_location = next(iter(unique_locations.values()))
|
||||
print(f"Found definition: {definition_location}")
|
||||
assert definition_location["uri"].endswith("lib.rb")
|
||||
assert definition_location["range"]["start"]["line"] == 1 # add method on line 2 (0-indexed 1)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(
|
||||
s,
|
||||
whitespace_allowed=s["name"] == "<< self",
|
||||
period_allowed=s["name"].startswith("self."),
|
||||
):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.rust
|
||||
@@ -55,3 +56,16 @@ class TestRustLanguageServer:
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add missing from overview"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -13,6 +13,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
def _find_identifier_position(file_path: Path, symbol_name: str) -> Optional[tuple[int, int]]:
|
||||
@@ -163,3 +164,16 @@ class TestSolidityLanguageServerBasics:
|
||||
|
||||
ref_files = {ref.get("uri", "") for ref in references}
|
||||
assert any("Token.sol" in uri for uri in ref_files), "IERC20.transfer references should include Token.sol"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -15,6 +15,7 @@ from serena.util.text_utils import LineType
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
# Skip Swift tests on Windows due to complex GitHub Actions configuration
|
||||
WINDOWS_SKIP = platform.system() == "Windows"
|
||||
@@ -224,3 +225,16 @@ class TestSwiftProjectBasics:
|
||||
# Should find Status enum
|
||||
status_matches = [m for m in matches if "Status" in str(m)]
|
||||
assert len(status_matches) > 0, "Should find Status enum"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -5,6 +5,7 @@ This module tests Language.SYSTEMVERILOG using verible-verilog-ls.
|
||||
Tests are skipped if the language server is not available.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
@@ -12,6 +13,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
def _find_symbol_by_name(language_server: SolidLanguageServer, file_path: str, name: str) -> dict[str, Any] | None:
|
||||
@@ -262,3 +264,17 @@ class TestSystemVerilogRename:
|
||||
for uri, file_edits in changes.items():
|
||||
for edit in file_edits:
|
||||
assert edit["newText"] == "my_counter", f"Expected 'my_counter', got {edit['newText']}"
|
||||
|
||||
@pytest.mark.skipif(shutil.which("verible-verilog-ls") is None, reason="verible-verilog-ls is not available")
|
||||
@pytest.mark.parametrize("language_server", [Language.SYSTEMVERILOG], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -5,12 +5,18 @@ These tests validate the functionality of the language server APIs
|
||||
like request_references using the test repository.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("terraform") is None and not is_ci, reason="Terraform CLI is not available")
|
||||
@pytest.mark.terraform
|
||||
class TestLanguageServerBasics:
|
||||
"""Test basic functionality of the Terraform language server."""
|
||||
@@ -51,3 +57,18 @@ class TestLanguageServerBasics:
|
||||
sel_start = var_symbol["selectionRange"]["start"]
|
||||
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
assert len(references) >= 1, "variable should be referenced at least once"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.TERRAFORM], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if s["kind"] == SymbolKind.Class:
|
||||
continue
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -11,6 +11,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.toml
|
||||
@@ -233,3 +234,16 @@ class TestTomlLanguageServerBasics:
|
||||
strict_symbol = next((s for s in pyproject_symbols if s.get("name") == "strict"), None)
|
||||
if strict_symbol:
|
||||
assert strict_symbol.get("kind") == 17, "'strict' should have kind 17 (boolean)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.typescript
|
||||
@@ -31,3 +32,16 @@ class TestTypescriptLanguageServer:
|
||||
assert any("index.ts" in ref.get("relativePath", "") for ref in refs), (
|
||||
"index.ts should reference helperFunction (tried all positions in selectionRange)"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.TYPESCRIPT], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.vue
|
||||
@@ -360,3 +361,25 @@ class TestVueEdgeCases:
|
||||
f"Composable '{expected_composable}' should appear exactly once in directory overview. "
|
||||
f"Found {len(matching_files)} matches. All files: {list(composables_overview.keys())}"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
is_callback = s["name"].endswith(" callback")
|
||||
is_vue_selector = "." in s["name"] or ":" in s["name"]
|
||||
is_vue_block = s["name"] in {"script setup", "style scoped"}
|
||||
if has_malformed_name(
|
||||
s,
|
||||
whitespace_allowed=is_callback or is_vue_selector or is_vue_block,
|
||||
period_allowed=is_vue_selector,
|
||||
colon_allowed=is_vue_selector,
|
||||
parenthesis_allowed=is_callback or is_vue_selector,
|
||||
):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -11,6 +11,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.yaml
|
||||
@@ -175,3 +176,16 @@ class TestYAMLLanguageServerBasics:
|
||||
app_port = next((s for s in port_symbols if s["range"]["start"]["line"] == 4), None)
|
||||
assert app_port is not None, "Should find 'port' under 'app'"
|
||||
assert app_port["range"]["start"]["character"] == 2, "'port' should be indented 2 spaces"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.YAML], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -13,6 +13,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.zig
|
||||
@@ -341,3 +342,16 @@ class TestZigLanguageServer:
|
||||
root = symbols[0]
|
||||
assert isinstance(root, dict), "Root should be a dict"
|
||||
assert "name" in root, "Root should have a name"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ZIG], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s, whitespace_allowed=True):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -216,6 +216,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bottle"
|
||||
version = "0.13.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7a/71/cca6167c06d00c81375fd668719df245864076d284f7cb46a694cbeb5454/bottle-0.13.4.tar.gz", hash = "sha256:787e78327e12b227938de02248333d788cfe45987edca735f8f88e03472c3f47", size = 98717, upload-time = "2025-06-15T10:08:59.439Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/83/f6/b55ec74cfe68c6584163faa311503c20b0da4c09883a41e8e00d6726c954/bottle-0.13.4-py2.py3-none-any.whl", hash = "sha256:045684fbd2764eac9cdeb824861d1551d113e8b683d8d26e296898d3dd99a12e", size = 103807, upload-time = "2025-06-15T10:08:57.691Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cachecontrol"
|
||||
version = "0.14.4"
|
||||
@@ -414,6 +423,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clr-loader"
|
||||
version = "0.2.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/24/c12faf3f61614b3131b5c98d3bf0d376b49c7feaa73edca559aeb2aee080/clr_loader-0.2.10.tar.gz", hash = "sha256:81f114afbc5005bafc5efe5af1341d400e22137e275b042a8979f3feb9fc9446", size = 83605, upload-time = "2026-01-03T23:13:06.984Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/61/cf819f8e8bb4d4c74661acf2498ba8d4a296714be3478d21eaabf64f5b9b/clr_loader-0.2.10-py3-none-any.whl", hash = "sha256:ebbbf9d511a7fe95fa28a95a4e04cd195b097881dfe66158dc2c281d3536f282", size = 56483, upload-time = "2026-01-03T23:13:05.439Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
@@ -1730,6 +1751,93 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.9.4"
|
||||
@@ -1773,6 +1881,12 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proxy-tools"
|
||||
version = "0.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/cf/77d3e19b7fabd03895caca7857ef51e4c409e0ca6b37ee6e9f7daa50b642/proxy_tools-0.1.0.tar.gz", hash = "sha256:ccb3751f529c047e2d8a58440d86b205303cf0fe8146f784d1cbcd94f0a28010", size = 2978, upload-time = "2014-05-05T21:02:24.606Z" }
|
||||
|
||||
[[package]]
|
||||
name = "psutil"
|
||||
version = "7.0.0"
|
||||
@@ -2065,11 +2179,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.10.1"
|
||||
version = "2.12.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a8/10/e8192be5f38f3e8e7e046716de4cae33d56fd5ae08927a823bb916be36c1/pyjwt-2.12.0.tar.gz", hash = "sha256:2f62390b667cd8257de560b850bb5a883102a388829274147f1d724453f8fb02", size = 102511, upload-time = "2026-03-12T17:15:30.831Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/70/70f895f404d363d291dcf62c12c85fdd47619ad9674ac0f53364d035925a/pyjwt-2.12.0-py3-none-any.whl", hash = "sha256:9bb459d1bdd0387967d287f5656bf7ec2b9a26645d1961628cda1764e087fd6e", size = 29700, upload-time = "2026-03-12T17:15:29.257Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@@ -2077,6 +2191,104 @@ crypto = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-core"
|
||||
version = "12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/95/df/d2b290708e9da86d6e7a9a2a2022b91915cf2e712a5a82e306cb6ee99792/pyobjc_core-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c918ebca280925e7fcb14c5c43ce12dcb9574a33cccb889be7c8c17f3bcce8b6", size = 671263, upload-time = "2025-11-14T09:31:35.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/d2/29e5e536adc07bc3d33dd09f3f7cf844bf7b4981820dc2a91dd810f3c782/pyobjc_core-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:01c0cf500596f03e21c23aef9b5f326b9fb1f8f118cf0d8b66749b6cf4cbb37a", size = 677370, upload-time = "2025-11-14T09:33:05.273Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/f0/4b4ed8924cd04e425f2a07269943018d43949afad1c348c3ed4d9d032787/pyobjc_core-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:177aaca84bb369a483e4961186704f64b2697708046745f8167e818d968c88fc", size = 719586, upload-time = "2025-11-14T09:33:53.302Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/98/9f4ed07162de69603144ff480be35cd021808faa7f730d082b92f7ebf2b5/pyobjc_core-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:844515f5d86395b979d02152576e7dee9cc679acc0b32dc626ef5bda315eaa43", size = 670164, upload-time = "2025-11-14T09:34:37.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/50/dc076965c96c7f0de25c0a32b7f8aa98133ed244deaeeacfc758783f1f30/pyobjc_core-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:453b191df1a4b80e756445b935491b974714456ae2cbae816840bd96f86db882", size = 712204, upload-time = "2025-11-14T09:35:24.148Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-cocoa"
|
||||
version = "12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/07/5760735c0fffc65107e648eaf7e0991f46da442ac4493501be5380e6d9d4/pyobjc_framework_cocoa-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f52228bcf38da64b77328787967d464e28b981492b33a7675585141e1b0a01e6", size = 383812, upload-time = "2025-11-14T09:40:53.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/31/0c2e734165abb46215797bd830c4bdcb780b699854b15f2b6240515edcc6/pyobjc_framework_cocoa-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5a3dcd491cacc2f5a197142b3c556d8aafa3963011110102a093349017705118", size = 384689, upload-time = "2025-11-14T09:41:41.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/3b/b9f61be7b9f9b4e0a6db18b3c35c4c4d589f2d04e963e2174d38c6555a92/pyobjc_framework_cocoa-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:914b74328c22d8ca261d78c23ef2befc29776e0b85555973927b338c5734ca44", size = 388843, upload-time = "2025-11-14T09:42:05.719Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/bb/f777cc9e775fc7dae77b569254570fe46eb842516b3e4fe383ab49eab598/pyobjc_framework_cocoa-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:03342a60fc0015bcdf9b93ac0b4f457d3938e9ef761b28df9564c91a14f0129a", size = 384932, upload-time = "2025-11-14T09:42:29.771Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/27/b457b7b37089cad692c8aada90119162dfb4c4a16f513b79a8b2b022b33b/pyobjc_framework_cocoa-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6ba1dc1bfa4da42d04e93d2363491275fb2e2be5c20790e561c8a9e09b8cf2cc", size = 388970, upload-time = "2025-11-14T09:42:53.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-quartz"
|
||||
version = "12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-framework-cocoa" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ef/dcd22b743e38b3c430fce4788176c2c5afa8bfb01085b8143b02d1e75201/pyobjc_framework_quartz-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19f99ac49a0b15dd892e155644fe80242d741411a9ed9c119b18b7466048625a", size = 217795, upload-time = "2025-11-14T09:59:46.922Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/2d/e8f495328101898c16c32ac10e7b14b08ff2c443a756a76fd1271915f097/pyobjc_framework_quartz-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:629b7971b1b43a11617f1460cd218bd308dfea247cd4ee3842eb40ca6f588860", size = 219206, upload-time = "2025-11-14T10:00:15.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/43/b1f0ad3b842ab150a7e6b7d97f6257eab6af241b4c7d14cb8e7fde9214b8/pyobjc_framework_quartz-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:53b84e880c358ba1ddcd7e8d5ea0407d760eca58b96f0d344829162cda5f37b3", size = 224317, upload-time = "2025-11-14T10:00:30.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/00/96249c5c7e5aaca5f688ca18b8d8ad05cd7886ebd639b3c71a6a4cadbe75/pyobjc_framework_quartz-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:42d306b07f05ae7d155984503e0fb1b701fecd31dcc5c79fe8ab9790ff7e0de0", size = 219558, upload-time = "2025-11-14T10:00:45.476Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/a6/708a55f3ff7a18c403b30a29a11dccfed0410485a7548c60a4b6d4cc0676/pyobjc_framework_quartz-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0cc08fddb339b2760df60dea1057453557588908e42bdc62184b6396ce2d6e9a", size = 224580, upload-time = "2025-11-14T10:01:00.091Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-security"
|
||||
version = "12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-framework-cocoa" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/3d/8d3a39cd292d7c76ab76233498189bc7170fc80f573b415308464f68c7ee/pyobjc_framework_security-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b2d8819f0fb7b619ec7627a0d8c1cac1a57c5143579ce8ac21548165680684b", size = 41287, upload-time = "2025-11-14T10:02:54.491Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/66/5160c0f938fc0515fe8d9af146aac1b093f7ef285ce797fedae161b6c0e8/pyobjc_framework_security-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab42e55f5b782332be5442750fcd9637ee33247d57c7b1d5801bc0e24ee13278", size = 41280, upload-time = "2025-11-14T10:02:58.097Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/48/b294ed75247c5cfa00d51925a10237337d24f54961d49a179b20a4307642/pyobjc_framework_security-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:afc36661cc6eb98cd794bed1d6668791e96557d6f72d9ac70aa49022d26af1d4", size = 41284, upload-time = "2025-11-14T10:03:01.722Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/57/0d3ef78779cf5c3bba878b2f824137e50978ad4a21dabe65d8b5ae0fc0d1/pyobjc_framework_security-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9510c98ab56921d1d416437372605cc1c1f6c1ad8d3061ee56b17bf423dd5427", size = 42162, upload-time = "2025-11-14T10:03:05.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/4d/63c15f9449c191e7448a05ff8af4a82c39a51bb627bc96dc9697586c0f79/pyobjc_framework_security-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6319a34508fd87ab6ca3cda6f54e707196197a65b792b292705af967e225438a", size = 41348, upload-time = "2025-11-14T10:03:08.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/d8/5aaa2a8124ed04a9d6ca7053dc0fa64e42be51497ed8263a24b744a95598/pyobjc_framework_security-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:03d166371cefdef24908825148eb848f99ee2c0b865870a09dcbb94334dd3e0a", size = 42908, upload-time = "2025-11-14T10:03:13.01Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-uniformtypeidentifiers"
|
||||
version = "12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-framework-cocoa" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/65/b8/dd9d2a94509a6c16d965a7b0155e78edf520056313a80f0cd352413f0d0b/pyobjc_framework_uniformtypeidentifiers-12.1.tar.gz", hash = "sha256:64510a6df78336579e9c39b873cfcd03371c4b4be2cec8af75a8a3d07dff607d", size = 17030, upload-time = "2025-11-14T10:23:02.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/5f/1f10f5275b06d213c9897850f1fca9c881c741c1f9190cea6db982b71824/pyobjc_framework_uniformtypeidentifiers-12.1-py2.py3-none-any.whl", hash = "sha256:ec5411e39152304d2a7e0e426c3058fa37a00860af64e164794e0bcffee813f2", size = 4901, upload-time = "2025-11-14T10:05:51.532Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-webkit"
|
||||
version = "12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-framework-cocoa" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/37/5082a0bbe12e48d4ffa53b0c0f09c77a4a6ffcfa119e26fa8dd77c08dc1c/pyobjc_framework_webkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3db734877025614eaef4504fadc0fbbe1279f68686a6f106f2e614e89e0d1a9d", size = 49970, upload-time = "2025-11-14T10:07:01.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/3d/80d36280164c69220ce99372f7736a028617c207e42cb587716009eecb88/pyobjc_framework_webkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1da0c428c9d9891c93e0de51c9f272bfeb96d34356cdf3136cb4ad56ce32ec2d", size = 50096, upload-time = "2025-11-14T10:07:10.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/7a/03c29c46866e266b0c705811c55c22625c349b0a80f5cf4776454b13dc4c/pyobjc_framework_webkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1a29e334d5a7dd4a4f0b5647481b6ccf8a107b92e67b2b3c6b368c899f571965", size = 50572, upload-time = "2025-11-14T10:07:14.232Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/ac/924878f239c167ffe3bfc643aee4d6dd5b357e25f6b28db227e40e9e6df3/pyobjc_framework_webkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:99d0d28542a266a95ee2585f51765c0331794bca461aaf4d1f5091489d475179", size = 50210, upload-time = "2025-11-14T10:07:18.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/86/637cda4983dc0936b73a385f3906256953ac434537b812814cb0b6d231a2/pyobjc_framework_webkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1aaa3bf12c7b68e1a36c0b294d2728e06f2cc220775e6dc4541d5046290e4dc8", size = 50680, upload-time = "2025-11-14T10:07:23.331Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyright"
|
||||
version = "1.1.403"
|
||||
@@ -2090,6 +2302,20 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/b6/b04e5c2f41a5ccad74a1a4759da41adb20b4bc9d59a5e08d29ba60084d07/pyright-1.1.403-py3-none-any.whl", hash = "sha256:c0eeca5aa76cbef3fcc271259bbd785753c7ad7bcac99a9162b4c4c7daed23b3", size = 5684504, upload-time = "2025-07-09T07:15:50.958Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pystray"
|
||||
version = "0.19.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pillow" },
|
||||
{ name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "python-xlib", marker = "sys_platform == 'linux'" },
|
||||
{ name = "six" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/64/927a4b9024196a4799eba0180e0ca31568426f258a4a5c90f87a97f51d28/pystray-0.19.5-py2.py3-none-any.whl", hash = "sha256:a0c2229d02cf87207297c22d86ffc57c86c227517b038c0d3c59df79295ac617", size = 49068, upload-time = "2023-09-17T13:44:26.872Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.4.1"
|
||||
@@ -2161,6 +2387,48 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-xlib"
|
||||
version = "0.33"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398", size = 182185, upload-time = "2022-12-25T18:52:58.662Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pythonnet"
|
||||
version = "3.1.0rc0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "clr-loader" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/59/4295652a9a93a2bd6755ecce2dc32e3d3c704e7b8ef301946ee65c818a59/pythonnet-3.1.0rc0.tar.gz", hash = "sha256:5f0f505e100233d70864b00dc5f5dd760603cfd94ac64da5c8b0e15ed95af939", size = 241025, upload-time = "2026-02-25T10:46:53.632Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/3d/a378aa74085fc6f72923d5e39fc30efbc97541cfd49b3fe0a35f20d09ff7/pythonnet-3.1.0rc0-py3-none-any.whl", hash = "sha256:286ccf23d08e1cc2b5200bb91d0ee649436d2e8386fec36a85193c66d0ff8b5b", size = 293150, upload-time = "2026-02-25T10:46:48.535Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywebview"
|
||||
version = "6.2.dev50+g345596c1f"
|
||||
source = { git = "https://github.com/r0x0r/pywebview?rev=345596c1f4fd59fd26c7aa02d4176c5362bcbabe#345596c1f4fd59fd26c7aa02d4176c5362bcbabe" }
|
||||
dependencies = [
|
||||
{ name = "bottle" },
|
||||
{ name = "proxy-tools" },
|
||||
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-uniformtypeidentifiers", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pythonnet", marker = "sys_platform == 'win32'" },
|
||||
{ name = "qtpy", marker = "sys_platform == 'openbsd6'" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32"
|
||||
version = "311"
|
||||
@@ -2273,6 +2541,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qtpy"
|
||||
version = "2.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "packaging" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982, upload-time = "2025-02-11T15:09:25.759Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/76/37c0ccd5ab968a6a438f9c623aeecc84c202ab2fabc6a8fd927580c15b5a/QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1", size = 95045, upload-time = "2025-02-11T15:09:24.162Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.37.0"
|
||||
@@ -2645,8 +2925,11 @@ dependencies = [
|
||||
{ name = "psutil" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyright" },
|
||||
{ name = "pystray" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "pythonnet", marker = "sys_platform == 'win32'" },
|
||||
{ name = "pywebview" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "regex" },
|
||||
{ name = "requests" },
|
||||
@@ -2720,13 +3003,16 @@ requires-dist = [
|
||||
{ name = "pyasn1", marker = "extra == 'dev'", specifier = "==0.6.3" },
|
||||
{ name = "pydantic", specifier = "==2.12.5" },
|
||||
{ name = "pyinstrument", marker = "extra == 'dev'", specifier = "==5.1.1" },
|
||||
{ name = "pyjwt", marker = "extra == 'dev'", specifier = "==2.10.1" },
|
||||
{ name = "pyjwt", marker = "extra == 'dev'", specifier = "==2.12.0" },
|
||||
{ name = "pyright", specifier = "==1.1.403" },
|
||||
{ name = "pystray", specifier = "==0.19.5" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = "==8.4.1" },
|
||||
{ name = "pytest-timeout", marker = "extra == 'dev'", specifier = "==2.4.0" },
|
||||
{ name = "pytest-xdist", marker = "extra == 'dev'", specifier = "==3.8.0" },
|
||||
{ name = "python-dotenv", specifier = "==1.2.1" },
|
||||
{ name = "python-multipart", specifier = "==0.0.22" },
|
||||
{ name = "pythonnet", marker = "sys_platform == 'win32'", specifier = "==3.1.0rc0" },
|
||||
{ name = "pywebview", git = "https://github.com/r0x0r/pywebview?rev=345596c1f4fd59fd26c7aa02d4176c5362bcbabe" },
|
||||
{ name = "pyyaml", specifier = "==6.0.2" },
|
||||
{ name = "regex", specifier = "==2026.2.28" },
|
||||
{ name = "requests", specifier = "==2.33.0" },
|
||||
|
||||
Reference in new issue
Block a user