Open project in ide (#25)
* Task attempt a92c0626-bdf1-4325-9d85-fdec9dbb46c1 - Final changes * Task attempt a92c0626-bdf1-4325-9d85-fdec9dbb46c1 - Final changes * Prettier fix
This commit is contained in:
committed by
GitHub
parent
76277b279a
commit
97af4d3f30
@@ -1,4 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
@@ -8,6 +8,7 @@ use axum::{
|
||||
Json, Router,
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::models::{
|
||||
@@ -348,6 +349,89 @@ pub async fn delete_project(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct OpenEditorRequest {
|
||||
editor_type: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn open_project_in_editor(
|
||||
Path(id): Path<Uuid>,
|
||||
Extension(pool): Extension<SqlitePool>,
|
||||
Extension(config): Extension<Arc<RwLock<crate::models::config::Config>>>,
|
||||
Json(payload): Json<Option<OpenEditorRequest>>,
|
||||
) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
|
||||
// Get the project
|
||||
let project = match Project::find_by_id(&pool, id).await {
|
||||
Ok(Some(project)) => project,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to fetch project {}: {}", id, e);
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
// Get editor command from config or override
|
||||
let editor_command = {
|
||||
let config_guard = config.read().await;
|
||||
if let Some(ref request) = payload {
|
||||
if let Some(ref editor_type) = request.editor_type {
|
||||
// Create a temporary editor config with the override
|
||||
use crate::models::config::{EditorConfig, EditorType};
|
||||
let override_editor_type = match editor_type.as_str() {
|
||||
"vscode" => EditorType::VSCode,
|
||||
"cursor" => EditorType::Cursor,
|
||||
"windsurf" => EditorType::Windsurf,
|
||||
"intellij" => EditorType::IntelliJ,
|
||||
"zed" => EditorType::Zed,
|
||||
"custom" => EditorType::Custom,
|
||||
_ => config_guard.editor.editor_type.clone(),
|
||||
};
|
||||
let temp_config = EditorConfig {
|
||||
editor_type: override_editor_type,
|
||||
custom_command: config_guard.editor.custom_command.clone(),
|
||||
};
|
||||
temp_config.get_command()
|
||||
} else {
|
||||
config_guard.editor.get_command()
|
||||
}
|
||||
} else {
|
||||
config_guard.editor.get_command()
|
||||
}
|
||||
};
|
||||
|
||||
// Open editor in the project directory
|
||||
let mut cmd = std::process::Command::new(&editor_command[0]);
|
||||
for arg in &editor_command[1..] {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
cmd.arg(&project.git_repo_path);
|
||||
|
||||
match cmd.spawn() {
|
||||
Ok(_) => {
|
||||
tracing::info!(
|
||||
"Opened editor ({}) for project {} at path: {}",
|
||||
editor_command.join(" "),
|
||||
id,
|
||||
project.git_repo_path
|
||||
);
|
||||
Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: None,
|
||||
message: Some("Editor opened successfully".to_string()),
|
||||
}))
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to open editor ({}) for project {}: {}",
|
||||
editor_command.join(" "),
|
||||
id,
|
||||
e
|
||||
);
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn search_project_files(
|
||||
Path(id): Path<Uuid>,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
@@ -488,6 +572,8 @@ async fn search_files_in_repo(
|
||||
}
|
||||
|
||||
pub fn projects_router() -> Router {
|
||||
use axum::routing::post;
|
||||
|
||||
Router::new()
|
||||
.route("/projects", get(get_projects).post(create_project))
|
||||
.route(
|
||||
@@ -500,4 +586,5 @@ pub fn projects_router() -> Router {
|
||||
get(get_project_branches).post(create_project_branch),
|
||||
)
|
||||
.route("/projects/:id/search", get(search_project_files))
|
||||
.route("/projects/:id/open-editor", post(open_project_in_editor))
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
Loader2,
|
||||
MoreHorizontal,
|
||||
ExternalLink,
|
||||
FolderOpen,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -83,6 +84,28 @@ export function ProjectList() {
|
||||
setShowForm(true);
|
||||
};
|
||||
|
||||
const handleOpenInIDE = async (projectId: string) => {
|
||||
try {
|
||||
const response = await makeRequest(
|
||||
`/api/projects/${projectId}/open-editor`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(null),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to open project in IDE');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to open project in IDE:', error);
|
||||
setError('Failed to open project in IDE');
|
||||
}
|
||||
};
|
||||
|
||||
const handleFormSuccess = () => {
|
||||
setShowForm(false);
|
||||
setEditingProject(null);
|
||||
@@ -172,6 +195,15 @@ export function ProjectList() {
|
||||
<ExternalLink className="mr-2 h-4 w-4" />
|
||||
View Project
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOpenInIDE(project.id);
|
||||
}}
|
||||
>
|
||||
<FolderOpen className="mr-2 h-4 w-4" />
|
||||
Open in IDE
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Plus, Settings } from 'lucide-react';
|
||||
import { Plus, Settings, FolderOpen } from 'lucide-react';
|
||||
import { makeRequest } from '@/lib/api';
|
||||
import { TaskFormDialog } from '@/components/tasks/TaskFormDialog';
|
||||
import { ProjectForm } from '@/components/projects/project-form';
|
||||
@@ -55,6 +55,30 @@ export function ProjectTasks() {
|
||||
setIsTaskDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleOpenInIDE = async () => {
|
||||
if (!projectId) return;
|
||||
|
||||
try {
|
||||
const response = await makeRequest(
|
||||
`/api/projects/${projectId}/open-editor`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(null),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to open project in IDE');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to open project in IDE:', error);
|
||||
setError('Failed to open project in IDE');
|
||||
}
|
||||
};
|
||||
|
||||
// Setup keyboard shortcuts
|
||||
useKeyboardShortcuts({
|
||||
navigate,
|
||||
@@ -359,11 +383,21 @@ export function ProjectTasks() {
|
||||
{project.current_branch}
|
||||
</span>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleOpenInIDE}
|
||||
className="h-8 w-8 p-0"
|
||||
title="Open in IDE"
|
||||
>
|
||||
<FolderOpen className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setIsProjectSettingsOpen(true)}
|
||||
className="h-8 w-8 p-0"
|
||||
title="Project Settings"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user