Skip to content

Commit a292111

Browse files
authored
Merge pull request #117 from ls1intum/fix/clone-open-folder-116
fix: clone 'Open Folder' opens local path instead of browser
2 parents 55091f0 + 7be8eda commit a292111

7 files changed

Lines changed: 57 additions & 11 deletions

File tree

extension/src/extension/controller/commands/repositoryCommands.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export class RepositoryCommandModule {
126126
[WebviewCmd.StartPractice]: this.handleStartPractice,
127127
[WebviewCmd.StartExercise]: this.handleStartExercise,
128128
[WebviewCmd.OpenRepository]: this.handleOpenRepository,
129+
[WebviewCmd.OpenClonedRepository]: this.handleOpenClonedRepository,
129130
[WebviewCmd.ViewBuildLog]: this.handleViewBuildLog,
130131
[WebviewCmd.GoToSource]: this.handleGoToSource,
131132
};
@@ -338,7 +339,8 @@ export class RepositoryCommandModule {
338339

339340
this.context.sendMessage({
340341
type: ExtensionMsg.ShowClonedRepoNotice,
341-
exerciseTitle: exerciseTitle
342+
exerciseTitle: exerciseTitle,
343+
participationId,
342344
});
343345

344346
const openAction = await vscode.window.showInformationMessage(
@@ -752,6 +754,44 @@ export class RepositoryCommandModule {
752754
}
753755
};
754756

757+
private handleOpenClonedRepository = async (message: WebviewToExtensionMessage): Promise<void> => {
758+
try {
759+
const { participationId } = getPayload<WebCmd<'openClonedRepository'>>(message);
760+
const repoInfo = this.clonedRepositories.get(participationId);
761+
762+
if (!repoInfo) {
763+
vscode.window.showWarningMessage('Cloned repository not found. It may have been moved or deleted.');
764+
return;
765+
}
766+
767+
try {
768+
const stats = fs.statSync(repoInfo.path);
769+
if (!stats.isDirectory()) {
770+
this.clonedRepositories.delete(participationId);
771+
vscode.window.showWarningMessage('Cloned repository path is not a directory.');
772+
return;
773+
}
774+
} catch {
775+
this.clonedRepositories.delete(participationId);
776+
vscode.window.showWarningMessage('Cloned repository not found. It may have been moved or deleted.');
777+
return;
778+
}
779+
780+
const repoUri = vscode.Uri.file(repoInfo.path);
781+
782+
const currentFolder = vscode.workspace.workspaceFolders?.[0];
783+
if (currentFolder && currentFolder.uri.fsPath === repoInfo.path) {
784+
await vscode.commands.executeCommand('workbench.view.explorer');
785+
return;
786+
}
787+
788+
await vscode.commands.executeCommand('vscode.openFolder', repoUri, true);
789+
} catch (error: unknown) {
790+
logger.error('Open cloned repository error:', LogCategory.SUBMISSION, error);
791+
vscode.window.showErrorMessage('Failed to open cloned repository.');
792+
}
793+
};
794+
755795
private handleOpenRepository = async (message: WebviewToExtensionMessage): Promise<void> => {
756796
try {
757797
const repositoryUri = getOptionalPayload<WebCmd<'openRepository'>>(message)?.repositoryUri;

extension/src/shared/messageContracts/extensionMessages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ interface ExtensionMsgPayloads {
269269
dirtyFileCount: number;
270270
autoSaveEnabled: boolean;
271271
};
272-
showClonedRepoNotice: { exerciseTitle: string };
272+
showClonedRepoNotice: { exerciseTitle: string; participationId: number };
273273
gitCredentialsResult: {
274274
status: 'success' | 'error' | 'warning' | 'info';
275275
message: string;

extension/src/shared/messageContracts/webviewCommands.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const WebviewCmd = {
4444
CloneRepository: 'cloneRepository',
4545
CopyAuthenticatedCloneUrl: 'copyAuthenticatedCloneUrl',
4646
OpenRepository: 'openRepository',
47+
OpenClonedRepository: 'openClonedRepository',
4748
SubmitExercise: 'submitExercise',
4849
StartExercise: 'startExercise',
4950
StartPractice: 'startPractice',
@@ -137,6 +138,7 @@ interface WebviewCmdPayloads {
137138
cloneRepository: { participationId: number; repositoryUri: string; exerciseTitle: string };
138139
copyAuthenticatedCloneUrl: { participationId: number; repositoryUri: string };
139140
openRepository: { repositoryUri?: string };
141+
openClonedRepository: { participationId: number };
140142
submitExercise: { participationId: number; exerciseId?: number; exerciseTitle?: string; commitMessage?: string };
141143
startExercise: { exerciseId: number };
142144
startPractice: { exerciseId: number; exerciseTitle?: string };
@@ -230,6 +232,7 @@ export const COMMANDS_REQUIRING_PAYLOAD = new Set<string>([
230232
WebviewCmd.RenderPlantUmlInline,
231233
WebviewCmd.ViewBuildLog,
232234
WebviewCmd.GoToSource,
235+
WebviewCmd.OpenClonedRepository,
233236
]);
234237

235238
/** Auto-generated command messages */

extension/src/webview/hooks/useExerciseStatusMessages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function useExerciseStatusMessages(vscodeApi: VsCodeApi): void {
1717
setRepoStatus({ isConnected: msg.isConnected, hasChanges: msg.hasChanges, isPracticeRepo: msg.isPracticeRepo });
1818
break;
1919
case ExtensionMsg.ShowClonedRepoNotice:
20-
setClonedNotice(msg.exerciseTitle);
20+
setClonedNotice(msg.exerciseTitle, msg.participationId);
2121
break;
2222
case ExtensionMsg.UpdateDirtyPagesStatus:
2323
setDirtyPagesStatus({ hasDirtyPages: msg.hasDirtyPages, dirtyFileCount: msg.dirtyFileCount, autoSaveEnabled: msg.autoSaveEnabled });

extension/src/webview/stores/useExerciseDetailStore.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ interface ExerciseDetailState {
4040

4141
// Extension→Webview response state
4242
repoStatus: RepoStatus | null;
43-
clonedNotice: string | null;
43+
clonedNotice: { exerciseTitle: string; participationId: number } | null;
4444
dirtyPagesStatus: DirtyPagesStatus | null;
4545

4646
// Actions
@@ -52,7 +52,7 @@ interface ExerciseDetailState {
5252
updateSubmission: (payload: SubmissionSummary) => void;
5353
updateSubmissionProcessing: (payload: PendingSubmissionInfo) => void;
5454
setRepoStatus: (status: RepoStatus) => void;
55-
setClonedNotice: (exerciseTitle: string) => void;
55+
setClonedNotice: (exerciseTitle: string, participationId: number) => void;
5656
setDirtyPagesStatus: (status: DirtyPagesStatus) => void;
5757
clearClonedNotice: () => void;
5858
clearPendingSubmission: () => void;
@@ -224,8 +224,8 @@ export const useExerciseDetailStore = create<ExerciseDetailState>()(
224224
set({ repoStatus: status }, false, 'setRepoStatus');
225225
},
226226

227-
setClonedNotice: (exerciseTitle) => {
228-
set({ clonedNotice: exerciseTitle }, false, 'setClonedNotice');
227+
setClonedNotice: (exerciseTitle, participationId) => {
228+
set({ clonedNotice: { exerciseTitle, participationId } }, false, 'setClonedNotice');
229229
},
230230

231231
setDirtyPagesStatus: (status) => {

extension/src/webview/views/ExerciseDetail/ExerciseDetailView.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export function ExerciseDetailView({ vscodeApi }: ExerciseDetailViewProps) {
250250
{/* Cloned repo notice */}
251251
{clonedNotice && (
252252
<div className={styles.banner} data-variant="info">
253-
<span>Repository cloned for "{clonedNotice}"</span>
253+
<span>Repository cloned for "{clonedNotice.exerciseTitle}"</span>
254254
<button className={styles.bannerDismiss} onClick={clearClonedNotice}>×</button>
255255
</div>
256256
)}
@@ -369,7 +369,10 @@ export function ExerciseDetailView({ vscodeApi }: ExerciseDetailViewProps) {
369369
postCommand(vscodeApi, 'openRepository', { repositoryUri });
370370
}}
371371
onOpenClonedRepository={() => {
372-
postCommand(vscodeApi, 'openRepository', { repositoryUri });
372+
if (clonedNotice) {
373+
postCommand(vscodeApi, 'openClonedRepository', { participationId: clonedNotice.participationId });
374+
clearClonedNotice();
375+
}
373376
}}
374377
onCheckWorkspace={() => {
375378
postCommand(vscodeApi, 'checkRepositoryStatus');

extension/test/react/stores/useExerciseDetailStore.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,11 @@ describe('useExerciseDetailStore', () => {
287287

288288
// Simulate receiving clonedNotice and dirtyPagesStatus from a previous exercise
289289
act(() => {
290-
result.current.setClonedNotice('Old Exercise');
290+
result.current.setClonedNotice('Old Exercise', 42);
291291
result.current.setDirtyPagesStatus({ hasDirtyPages: true, dirtyFileCount: 3, autoSaveEnabled: false });
292292
});
293293

294-
expect(result.current.clonedNotice).toBe('Old Exercise');
294+
expect(result.current.clonedNotice).toEqual({ exerciseTitle: 'Old Exercise', participationId: 42 });
295295
expect(result.current.dirtyPagesStatus?.hasDirtyPages).toBe(true);
296296

297297
// Switch to a new exercise

0 commit comments

Comments
 (0)