Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 54ed613

Browse files
committed
feat: add git blame and open on remote
- Git blame annotations in source file viewer with per-file toggle button in the file info bar (off by default, resets on file switch) - Resizable blame column: drag handle at gutter edge, clamped 80–400px - Right-click context menu on file lines to open file at that line on remote (current branch or main branch) - Open on Remote menu items on file tab context menu - remote-url.ts utility for GitHub/GitLab/Bitbucket URL construction - git_blame Tauri command in Rust backend (porcelain parser) - Fix default branch returning 'origin/main' instead of 'main' in codelane-git repository.rs
1 parent ae25d5c commit 54ed613

11 files changed

Lines changed: 785 additions & 4 deletions

File tree

crates/codelane-git/src/repository.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,16 @@ impl Repository {
126126
// Try origin/HEAD first
127127
if let Ok(origin_head) = self.repo.find_reference("origin/HEAD") {
128128
if let Some(target) = origin_head.target().try_name() {
129-
return Ok(target.shorten().to_string());
129+
let shortened = target.shorten().to_string();
130+
// shorten() yields "origin/main" for refs/remotes/origin/main;
131+
// strip the remote prefix to get just the branch name.
132+
let branch_name = shortened
133+
.find('/')
134+
.map(|pos| shortened[pos + 1..].to_string())
135+
.unwrap_or(shortened);
136+
if !branch_name.is_empty() {
137+
return Ok(branch_name);
138+
}
130139
}
131140
}
132141

frontend/src/components/editor/EditorPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ export function EditorPanel(props: EditorPanelProps) {
248248
{/* Show DiffViewer for diff mode, otherwise show regular FileViewer */}
249249
<Show
250250
when={file()!.isDiffView && file()!.diffContent !== undefined}
251-
fallback={<FileViewer file={file()!} laneId={props.laneId} />}
251+
fallback={<FileViewer file={file()!} laneId={props.laneId} workingDir={props.basePath} />}
252252
>
253253
<DiffViewer
254254
diff={file()!.diffContent!}

frontend/src/components/editor/EditorTabs.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import {
1111
} from '@thisbeyond/solid-dnd';
1212
import { FileIcon } from './FileIcon';
1313
import type { EditorTab } from './types';
14+
import { getRemoteUrl, getGitBranch, getDefaultBranch } from '../../lib/git-api';
15+
import { buildRemoteFileUrl } from '../../utils/remote-url';
16+
import { open as shellOpen } from '@tauri-apps/plugin-shell';
1417

1518
interface ContextMenuState {
1619
x: number;
@@ -162,6 +165,34 @@ export function EditorTabs(props: EditorTabsProps) {
162165
closeContextMenu();
163166
};
164167

168+
// Open file on a remote git provider
169+
const openTabOnRemote = async (useDefaultBranch: boolean) => {
170+
const menu = contextMenu();
171+
if (!menu || !props.basePath) return;
172+
closeContextMenu();
173+
174+
try {
175+
const [remoteUrl, branchInfo, defaultBranchName] = await Promise.all([
176+
getRemoteUrl(props.basePath, 'origin').catch(() => null),
177+
getGitBranch(props.basePath).catch(() => null),
178+
useDefaultBranch ? getDefaultBranch(props.basePath).catch(() => null) : Promise.resolve(null),
179+
]);
180+
181+
if (!remoteUrl) return;
182+
183+
const branch = useDefaultBranch
184+
? (defaultBranchName ?? branchInfo?.current ?? null)
185+
: (branchInfo?.current ?? null);
186+
if (!branch) return;
187+
188+
const relPath = getRelativePath(menu.tab.path);
189+
const url = buildRemoteFileUrl(remoteUrl, relPath, branch);
190+
if (url) await shellOpen(url);
191+
} catch (err) {
192+
console.error('[EditorTabs] Failed to open on remote:', err);
193+
}
194+
};
195+
165196
// Close context menu on click outside
166197
const handleClickOutside = (e: MouseEvent) => {
167198
if (contextMenuRef && !contextMenuRef.contains(e.target as Node)) {
@@ -286,6 +317,27 @@ export function EditorTabs(props: EditorTabsProps) {
286317
</svg>
287318
Copy Absolute Path
288319
</button>
320+
<Show when={props.basePath}>
321+
<div class="border-t border-zed-border-subtle my-1" />
322+
<button
323+
class="w-full px-3 py-1.5 text-left text-sm text-zed-text-primary hover:bg-zed-bg-hover flex items-center gap-2"
324+
onClick={() => openTabOnRemote(false)}
325+
>
326+
<svg class="w-4 h-4 text-zed-text-tertiary flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
327+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
328+
</svg>
329+
Open on Remote (Current Branch)
330+
</button>
331+
<button
332+
class="w-full px-3 py-1.5 text-left text-sm text-zed-text-primary hover:bg-zed-bg-hover flex items-center gap-2"
333+
onClick={() => openTabOnRemote(true)}
334+
>
335+
<svg class="w-4 h-4 text-zed-text-tertiary flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
336+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
337+
</svg>
338+
Open on Remote (Main Branch)
339+
</button>
340+
</Show>
289341
<div class="border-t border-zed-border-subtle my-1" />
290342
<button
291343
class="w-full px-3 py-1.5 text-left text-sm text-zed-text-primary hover:bg-zed-bg-hover flex items-center gap-2"

0 commit comments

Comments
 (0)