mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 18:16:10 +00:00
Merge branch 'main' into multi-ls
This commit is contained in:
3 files changed
+145
-19
No files matched your search
+12
-1
@@ -49,6 +49,7 @@ class ResponseConfigOverview(BaseModel):
|
||||
available_tools: list[dict[str, str | bool]]
|
||||
available_modes: list[dict[str, str | bool]]
|
||||
available_contexts: list[dict[str, str | bool]]
|
||||
available_memories: list[str] | None
|
||||
|
||||
|
||||
class SerenaDashboardAPI:
|
||||
@@ -155,6 +156,7 @@ class SerenaDashboardAPI:
|
||||
project_info = {
|
||||
"name": active_project_name,
|
||||
"language": ", ".join([l.value for l in project.project_config.languages]) if project else None,
|
||||
"path": str(project.project_root) if project else None,
|
||||
}
|
||||
|
||||
# Get context info
|
||||
@@ -162,11 +164,12 @@ class SerenaDashboardAPI:
|
||||
context_info = {
|
||||
"name": context.name,
|
||||
"description": context.description,
|
||||
"path": SerenaAgentContext.get_path(context.name),
|
||||
}
|
||||
|
||||
# Get active modes
|
||||
modes = self._agent.get_active_modes()
|
||||
modes_info = [{"name": mode.name, "description": mode.description} for mode in modes]
|
||||
modes_info = [{"name": mode.name, "description": mode.description, "path": SerenaAgentMode.get_path(mode.name)} for mode in modes]
|
||||
active_mode_names = [mode.name for mode in modes]
|
||||
|
||||
# Get active tools
|
||||
@@ -203,6 +206,7 @@ class SerenaDashboardAPI:
|
||||
{
|
||||
"name": mode_name,
|
||||
"is_active": mode_name in active_mode_names,
|
||||
"path": SerenaAgentMode.get_path(mode_name),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -214,6 +218,7 @@ class SerenaDashboardAPI:
|
||||
{
|
||||
"name": context_name,
|
||||
"is_active": context_name == context.name,
|
||||
"path": SerenaAgentContext.get_path(context_name),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -223,6 +228,11 @@ class SerenaDashboardAPI:
|
||||
full_stats = self._tool_usage_stats.get_tool_stats_dict()
|
||||
tool_stats_summary = {name: {"num_calls": stats["num_times_called"]} for name, stats in full_stats.items()}
|
||||
|
||||
# Get available memories if ReadMemoryTool is active
|
||||
available_memories = None
|
||||
if self._agent.tool_is_active("read_memory") and project is not None:
|
||||
available_memories = project.memories_manager.list_memories()
|
||||
|
||||
return ResponseConfigOverview(
|
||||
active_project=project_info,
|
||||
context=context_info,
|
||||
@@ -233,6 +243,7 @@ class SerenaDashboardAPI:
|
||||
available_tools=available_tools,
|
||||
available_modes=available_modes,
|
||||
available_contexts=available_contexts,
|
||||
available_memories=available_memories,
|
||||
)
|
||||
|
||||
def _shutdown(self) -> None:
|
||||
|
||||
@@ -89,7 +89,10 @@ class Dashboard {
|
||||
|
||||
// Register event handlers
|
||||
this.$loadButton.click(this.loadLogs.bind(this));
|
||||
this.$menuShutdown.click(this.shutdown.bind(this));
|
||||
this.$menuShutdown.click(function(e) {
|
||||
e.preventDefault();
|
||||
self.shutdown();
|
||||
});
|
||||
this.$menuToggle.click(this.toggleMenu.bind(this));
|
||||
this.$themeToggle.click(this.toggleTheme.bind(this));
|
||||
this.$refreshStats.click(this.loadStats.bind(this));
|
||||
@@ -184,6 +187,7 @@ class Dashboard {
|
||||
url: '/get_config_overview',
|
||||
type: 'GET',
|
||||
success: function(response) {
|
||||
self.failureCount = 0;
|
||||
self.configData = response;
|
||||
self.displayConfig(response);
|
||||
self.displayBasicStats(response.tool_stats_summary);
|
||||
@@ -194,6 +198,11 @@ class Dashboard {
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error loading config overview:', error);
|
||||
self.failureCount++;
|
||||
if (self.failureCount >= 3) {
|
||||
console.log('Server appears to be down, closing tab');
|
||||
window.close();
|
||||
}
|
||||
self.$configDisplay.html('<div class="error-message">Error loading configuration</div>');
|
||||
self.$basicStatsDisplay.html('<div class="error-message">Error loading stats</div>');
|
||||
self.$projectsDisplay.html('<div class="error-message">Error loading projects</div>');
|
||||
@@ -210,32 +219,38 @@ class Dashboard {
|
||||
}
|
||||
|
||||
displayConfig(config) {
|
||||
// Check if tools section is currently expanded
|
||||
const $existingContent = $('#tools-content');
|
||||
const wasExpanded = $existingContent.is(':visible');
|
||||
// Check if tools and memories sections are currently expanded
|
||||
const $existingToolsContent = $('#tools-content');
|
||||
const $existingMemoriesContent = $('#memories-content');
|
||||
const wasToolsExpanded = $existingToolsContent.is(':visible');
|
||||
const wasMemoriesExpanded = $existingMemoriesContent.is(':visible');
|
||||
|
||||
let html = '<div class="config-grid">';
|
||||
|
||||
// Project info
|
||||
html += '<div class="config-label">Active Project:</div>';
|
||||
html += '<div class="config-value">' + (config.active_project.name || 'None') + '</div>';
|
||||
if (config.active_project.name && config.active_project.path) {
|
||||
const configPath = config.active_project.path + '/.serena/project.yml';
|
||||
html += '<div class="config-value"><span title="Project configuration in ' + configPath + '">' + config.active_project.name + '</span></div>';
|
||||
} else {
|
||||
html += '<div class="config-value">' + (config.active_project.name || 'None') + '</div>';
|
||||
}
|
||||
|
||||
html += '<div class="config-label">Language:</div>';
|
||||
html += '<div class="config-value">' + (config.active_project.language || 'N/A') + '</div>';
|
||||
|
||||
// Context info
|
||||
html += '<div class="config-label">Context:</div>';
|
||||
html += '<div class="config-value">' + config.context.name + '</div>';
|
||||
html += '<div class="config-value"><span title="' + config.context.path + '">' + config.context.name + '</span></div>';
|
||||
|
||||
// Modes info
|
||||
html += '<div class="config-label">Active Modes:</div>';
|
||||
html += '<div class="config-value">';
|
||||
if (config.modes.length > 0) {
|
||||
html += '<ul class="config-list">';
|
||||
config.modes.forEach(function(mode) {
|
||||
html += '<li>' + mode.name + '</li>';
|
||||
const modeSpans = config.modes.map(function(mode) {
|
||||
return '<span title="' + mode.path + '">' + mode.name + '</span>';
|
||||
});
|
||||
html += '</ul>';
|
||||
html += modeSpans.join(', ');
|
||||
} else {
|
||||
html += 'None';
|
||||
}
|
||||
@@ -243,19 +258,40 @@ class Dashboard {
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Configuration help link
|
||||
html += '<div style="margin-top: 15px; padding: 10px; background: var(--bg-secondary); border-radius: 4px; font-size: 13px; border: 1px solid var(--border-color);">';
|
||||
html += '<span style="color: var(--text-muted);">📖</span>';
|
||||
html += '<a href="https://github.com/oraios/serena#configuration" target="_blank" rel="noopener noreferrer" style="color: var(--btn-primary); text-decoration: none; font-weight: 500;">View Configuration Guide</a>';
|
||||
html += '</div>';
|
||||
|
||||
// Active tools - collapsible
|
||||
html += '<div style="margin-top: 20px;">';
|
||||
html += '<h3 class="collapsible-header" id="tools-header" style="font-size: 16px; margin: 0;">';
|
||||
html += '<span>Active Tools (' + config.active_tools.length + ')</span>';
|
||||
html += '<span class="toggle-icon' + (wasExpanded ? ' expanded' : '') + '">▼</span>';
|
||||
html += '<span class="toggle-icon' + (wasToolsExpanded ? ' expanded' : '') + '">▼</span>';
|
||||
html += '</h3>';
|
||||
html += '<div class="collapsible-content tools-grid" id="tools-content" style="' + (wasExpanded ? '' : 'display:none;') + ' margin-top: 10px;">';
|
||||
html += '<div class="collapsible-content tools-grid" id="tools-content" style="' + (wasToolsExpanded ? '' : 'display:none;') + ' margin-top: 10px;">';
|
||||
config.active_tools.forEach(function(tool) {
|
||||
html += '<div class="tool-item" title="' + tool + '">' + tool + '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
// Available memories - collapsible (only show if memories exist)
|
||||
if (config.available_memories && config.available_memories.length > 0) {
|
||||
html += '<div style="margin-top: 20px;">';
|
||||
html += '<h3 class="collapsible-header" id="memories-header" style="font-size: 16px; margin: 0;">';
|
||||
html += '<span>Available Memories (' + config.available_memories.length + ')</span>';
|
||||
html += '<span class="toggle-icon' + (wasMemoriesExpanded ? ' expanded' : '') + '">▼</span>';
|
||||
html += '</h3>';
|
||||
html += '<div class="collapsible-content tools-grid" id="memories-content" style="' + (wasMemoriesExpanded ? '' : 'display:none;') + ' margin-top: 10px;">';
|
||||
config.available_memories.forEach(function(memory) {
|
||||
html += '<div class="tool-item" title="' + memory + '">' + memory + '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
this.$configDisplay.html(html);
|
||||
|
||||
// Re-attach collapsible handler for the newly created tools header
|
||||
@@ -267,6 +303,16 @@ class Dashboard {
|
||||
$content.slideToggle(300);
|
||||
$icon.toggleClass('expanded');
|
||||
});
|
||||
|
||||
// Re-attach collapsible handler for the newly created memories header
|
||||
$('#memories-header').click(function() {
|
||||
const $header = $(this);
|
||||
const $content = $('#memories-content');
|
||||
const $icon = $header.find('.toggle-icon');
|
||||
|
||||
$content.slideToggle(300);
|
||||
$icon.toggleClass('expanded');
|
||||
});
|
||||
}
|
||||
|
||||
displayBasicStats(stats) {
|
||||
@@ -340,7 +386,7 @@ class Dashboard {
|
||||
let html = '';
|
||||
modes.forEach(function(mode) {
|
||||
const activeClass = mode.is_active ? ' active' : '';
|
||||
html += '<div class="info-item' + activeClass + '" title="' + mode.name + '">' + mode.name + '</div>';
|
||||
html += '<div class="info-item' + activeClass + '" title="' + mode.path + '">' + mode.name + '</div>';
|
||||
});
|
||||
|
||||
this.$availableModesDisplay.html(html);
|
||||
@@ -355,7 +401,7 @@ class Dashboard {
|
||||
let html = '';
|
||||
contexts.forEach(function(context) {
|
||||
const activeClass = context.is_active ? ' active' : '';
|
||||
html += '<div class="info-item' + activeClass + '" title="' + context.name + '">' + context.name + '</div>';
|
||||
html += '<div class="info-item' + activeClass + '" title="' + context.path + '">' + context.name + '</div>';
|
||||
});
|
||||
|
||||
this.$availableContextsDisplay.html(html);
|
||||
|
||||
@@ -39,8 +39,24 @@ class RuntimeDependencyPaths:
|
||||
|
||||
|
||||
class EclipseJDTLS(SolidLanguageServer):
|
||||
"""
|
||||
r"""
|
||||
The EclipseJDTLS class provides a Java specific implementation of the LanguageServer class
|
||||
|
||||
You can configure the following options in ls_specific_settings (in serena_config.yml):
|
||||
- maven_user_settings: Path to Maven settings.xml file (default: ~/.m2/settings.xml)
|
||||
- gradle_user_home: Path to Gradle user home directory (default: ~/.gradle)
|
||||
|
||||
Note: Gradle wrapper is disabled by default. Projects will use the bundled Gradle distribution.
|
||||
|
||||
Example configuration in ~/.serena/serena_config.yml:
|
||||
```yaml
|
||||
ls_specific_settings:
|
||||
java:
|
||||
maven_user_settings: "/home/user/.m2/settings.xml" # Unix/Linux/Mac
|
||||
# maven_user_settings: 'C:\\Users\\YourName\\.m2\\settings.xml' # Windows (use single quotes!)
|
||||
gradle_user_home: "/home/user/.gradle" # Unix/Linux/Mac
|
||||
# gradle_user_home: 'C:\\Users\\YourName\\.gradle' # Windows (use single quotes!)
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -326,6 +342,57 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
repository_absolute_path = os.path.abspath(repository_absolute_path)
|
||||
repo_uri = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
# Load user's Maven and Gradle configuration paths from ls_specific_settings["java"]
|
||||
custom_java_settings = self._solidlsp_settings.ls_specific_settings.get(self.get_language_enum_instance(), {})
|
||||
|
||||
# Maven settings: default to ~/.m2/settings.xml
|
||||
default_maven_settings = os.path.join(os.path.expanduser("~"), ".m2", "settings.xml")
|
||||
maven_user_settings = None
|
||||
if "maven_user_settings" in custom_java_settings:
|
||||
# User explicitly provided a path
|
||||
maven_settings_path = custom_java_settings["maven_user_settings"]
|
||||
if not os.path.exists(maven_settings_path):
|
||||
error_msg = (
|
||||
f"Maven settings file not found: {maven_settings_path}. "
|
||||
f"Fix: create the file, update path in ~/.serena/serena_config.yml (ls_specific_settings -> java -> maven_user_settings), "
|
||||
f"or remove the setting to use default (~/.m2/settings.xml)"
|
||||
)
|
||||
self.logger.log(error_msg, logging.ERROR)
|
||||
raise FileNotFoundError(error_msg)
|
||||
maven_user_settings = maven_settings_path
|
||||
self.logger.log(f"Using Maven settings from custom location: {maven_user_settings}", logging.INFO)
|
||||
elif os.path.exists(default_maven_settings):
|
||||
maven_user_settings = default_maven_settings
|
||||
self.logger.log(f"Using Maven settings from default location: {maven_user_settings}", logging.INFO)
|
||||
else:
|
||||
self.logger.log(
|
||||
f"Maven settings not found at default location ({default_maven_settings}), will use JDTLS defaults", logging.INFO
|
||||
)
|
||||
|
||||
# Gradle user home: default to ~/.gradle
|
||||
default_gradle_home = os.path.join(os.path.expanduser("~"), ".gradle")
|
||||
gradle_user_home = None
|
||||
if "gradle_user_home" in custom_java_settings:
|
||||
# User explicitly provided a path
|
||||
gradle_user_home_path = custom_java_settings["gradle_user_home"]
|
||||
if not os.path.exists(gradle_user_home_path):
|
||||
error_msg = (
|
||||
f"Gradle user home directory not found: {gradle_user_home_path}. "
|
||||
f"Fix: create the directory, update path in ~/.serena/serena_config.yml (ls_specific_settings -> java -> gradle_user_home), "
|
||||
f"or remove the setting to use default (~/.gradle)"
|
||||
)
|
||||
self.logger.log(error_msg, logging.ERROR)
|
||||
raise FileNotFoundError(error_msg)
|
||||
gradle_user_home = gradle_user_home_path
|
||||
self.logger.log(f"Using Gradle user home from custom location: {gradle_user_home}", logging.INFO)
|
||||
elif os.path.exists(default_gradle_home):
|
||||
gradle_user_home = default_gradle_home
|
||||
self.logger.log(f"Using Gradle user home from default location: {gradle_user_home}", logging.INFO)
|
||||
else:
|
||||
self.logger.log(
|
||||
f"Gradle user home not found at default location ({default_gradle_home}), will use JDTLS defaults", logging.INFO
|
||||
)
|
||||
|
||||
initialize_params = {
|
||||
"locale": "en",
|
||||
"rootPath": repository_absolute_path,
|
||||
@@ -512,7 +579,7 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
"checkProjectSettingsExclusions": False,
|
||||
"updateBuildConfiguration": "interactive",
|
||||
"maven": {
|
||||
"userSettings": None,
|
||||
"userSettings": maven_user_settings,
|
||||
"globalSettings": None,
|
||||
"notCoveredPluginExecutionSeverity": "warning",
|
||||
"defaultMojoExecutionAction": "ignore",
|
||||
@@ -538,7 +605,7 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
"offline": {"enabled": False},
|
||||
"arguments": None,
|
||||
"jvmArguments": None,
|
||||
"user": {"home": None},
|
||||
"user": {"home": gradle_user_home},
|
||||
"annotationProcessing": {"enabled": True},
|
||||
},
|
||||
"exclusions": [
|
||||
@@ -549,7 +616,9 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
],
|
||||
"generatesMetadataFilesAtProjectRoot": False,
|
||||
},
|
||||
"maven": {"downloadSources": True, "updateSnapshots": True},
|
||||
# Set updateSnapshots to False to improve performance and avoid unnecessary network calls
|
||||
# Snapshots will only be updated when explicitly requested by the user
|
||||
"maven": {"downloadSources": True, "updateSnapshots": False},
|
||||
"eclipse": {"downloadSources": True},
|
||||
"signatureHelp": {"enabled": True, "description": {"enabled": True}},
|
||||
"implementationsCodeLens": {"enabled": True},
|
||||
|
||||
Reference in new issue
Block a user