Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/utils/hyperlink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export function renderOsc8Link(url: string, text: string): string {
return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
}

/**
* Converts a git remote URL to a GitHub HTTPS base URL.
* Handles both SSH (git@github.com:owner/repo.git) and HTTPS formats.
* Returns null if the remote is not a GitHub URL.
*/
export function parseGitHubBaseUrl(remoteUrl: string): string | null {
const trimmed = remoteUrl.trim();

// SSH format: git@github.com:owner/repo.git
const sshMatch = /^git@github\.com:([^/]+\/[^/]+?)(?:\.git)?$/.exec(trimmed);
if (sshMatch?.[1]) {
return `https://github.com/${sshMatch[1]}`;
}

// HTTPS format: https://github.com/owner/repo.git
const httpsMatch = /^https?:\/\/github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/.exec(trimmed);
if (httpsMatch?.[1]) {
return `https://github.com/${httpsMatch[1]}`;
}

return null;
}
51 changes: 45 additions & 6 deletions src/widgets/GitBranch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,54 +10,93 @@ import {
isInsideGitWorkTree,
runGit
} from '../utils/git';
import {
parseGitHubBaseUrl,
renderOsc8Link
} from '../utils/hyperlink';

import { makeModifierText } from './shared/editor-display';
import {
getHideNoGitKeybinds,
getHideNoGitModifierText,
handleToggleNoGitAction,
isHideNoGitEnabled
} from './shared/git-no-git';
import {
isMetadataFlagEnabled,
toggleMetadataFlag
} from './shared/metadata';

const LINK_KEY = 'linkToGitHub';
const TOGGLE_LINK_ACTION = 'toggle-link';

export class GitBranchWidget implements Widget {
getDefaultColor(): string { return 'magenta'; }
getDescription(): string { return 'Shows the current git branch name'; }
getDisplayName(): string { return 'Git Branch'; }
getCategory(): string { return 'Git'; }
getEditorDisplay(item: WidgetItem): WidgetEditorDisplay {
const isLink = isMetadataFlagEnabled(item, LINK_KEY);
const modifiers: string[] = [];
const noGitText = getHideNoGitModifierText(item);
if (noGitText)
modifiers.push('hide \'no git\'');
if (isLink)
modifiers.push('GitHub link');
return {
displayText: this.getDisplayName(),
modifierText: getHideNoGitModifierText(item)
modifierText: makeModifierText(modifiers)
};
}

handleEditorAction(action: string, item: WidgetItem): WidgetItem | null {
if (action === TOGGLE_LINK_ACTION) {
return toggleMetadataFlag(item, LINK_KEY);
}
return handleToggleNoGitAction(action, item);
}

render(item: WidgetItem, context: RenderContext, settings: Settings): string | null {
void settings;
const hideNoGit = isHideNoGitEnabled(item);
const isLink = isMetadataFlagEnabled(item, LINK_KEY);

if (context.isPreview) {
return item.rawValue ? 'main' : '⎇ main';
const text = item.rawValue ? 'main' : '⎇ main';
return isLink ? renderOsc8Link('https://github.com/owner/repo/tree/main', text) : text;
}

if (!isInsideGitWorkTree(context)) {
return hideNoGit ? null : '⎇ no git';
}

const branch = this.getGitBranch(context);
if (branch)
return item.rawValue ? branch : `⎇ ${branch}`;
if (!branch) {
return hideNoGit ? null : '⎇ no git';
}

const displayText = item.rawValue ? branch : `⎇ ${branch}`;

if (isLink) {
const remoteUrl = runGit('remote get-url origin', context);
const baseUrl = remoteUrl ? parseGitHubBaseUrl(remoteUrl) : null;
if (baseUrl) {
return renderOsc8Link(`${baseUrl}/tree/${branch}`, displayText);
}
}

return hideNoGit ? null : '⎇ no git';
return displayText;
}

private getGitBranch(context: RenderContext): string | null {
return runGit('branch --show-current', context);
}

getCustomKeybinds(): CustomKeybind[] {
return getHideNoGitKeybinds();
return [
...getHideNoGitKeybinds(),
{ key: 'l', label: '(l)ink to GitHub', action: TOGGLE_LINK_ACTION }
];
}

supportsRawValue(): boolean { return true; }
Expand Down
42 changes: 36 additions & 6 deletions src/widgets/GitRootDir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,74 @@ import {
isInsideGitWorkTree,
runGit
} from '../utils/git';
import { renderOsc8Link } from '../utils/hyperlink';

import { makeModifierText } from './shared/editor-display';
import {
getHideNoGitKeybinds,
getHideNoGitModifierText,
handleToggleNoGitAction,
isHideNoGitEnabled
} from './shared/git-no-git';
import {
isMetadataFlagEnabled,
toggleMetadataFlag
} from './shared/metadata';

const LINK_KEY = 'linkToCursor';
const TOGGLE_LINK_ACTION = 'toggle-link';

export class GitRootDirWidget implements Widget {
getDefaultColor(): string { return 'cyan'; }
getDescription(): string { return 'Shows the git repository root directory name'; }
getDisplayName(): string { return 'Git Root Dir'; }
getCategory(): string { return 'Git'; }
getEditorDisplay(item: WidgetItem): WidgetEditorDisplay {
const isLink = isMetadataFlagEnabled(item, LINK_KEY);
const modifiers: string[] = [];
const noGitText = getHideNoGitModifierText(item);
if (noGitText)
modifiers.push('hide \'no git\'');
if (isLink)
modifiers.push('Cursor link');
return {
displayText: this.getDisplayName(),
modifierText: getHideNoGitModifierText(item)
modifierText: makeModifierText(modifiers)
};
}

handleEditorAction(action: string, item: WidgetItem): WidgetItem | null {
if (action === TOGGLE_LINK_ACTION) {
return toggleMetadataFlag(item, LINK_KEY);
}
return handleToggleNoGitAction(action, item);
}

render(item: WidgetItem, context: RenderContext, _settings: Settings): string | null {
const hideNoGit = isHideNoGitEnabled(item);
const isLink = isMetadataFlagEnabled(item, LINK_KEY);

if (context.isPreview) {
return 'my-repo';
const name = 'my-repo';
return isLink ? renderOsc8Link('cursor://file/Users/example/my-repo', name) : name;
}

if (!isInsideGitWorkTree(context)) {
return hideNoGit ? null : 'no git';
}

const rootDir = this.getGitRootDir(context);
if (rootDir) {
return this.getRootDirName(rootDir);
if (!rootDir) {
return hideNoGit ? null : 'no git';
}

const name = this.getRootDirName(rootDir);

if (isLink) {
return renderOsc8Link(`cursor://file${rootDir}`, name);
}

return hideNoGit ? null : 'no git';
return name;
}

private getGitRootDir(context: RenderContext): string | null {
Expand All @@ -66,7 +93,10 @@ export class GitRootDirWidget implements Widget {
}

getCustomKeybinds(): CustomKeybind[] {
return getHideNoGitKeybinds();
return [
...getHideNoGitKeybinds(),
{ key: 'l', label: '(l)ink to Cursor', action: TOGGLE_LINK_ACTION }
];
}

supportsRawValue(): boolean { return false; }
Expand Down