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
1 change: 1 addition & 0 deletions src/adapters/interfaces/ICodeRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export interface CodeDiscussion extends Snippet {

export interface ICodeRepository {
getDiscussionsByFile(file_path: string, remote_url: string, team_id: string): Promise<CodeDiscussion[]>;
subscribeToCodeSnippets(teamId: string, userId: string, callback: (snippet: CodeDiscussion) => void): Promise<{ unsubscribe: () => void }>;
}
53 changes: 51 additions & 2 deletions src/adapters/supabase/SupabaseCodeRepository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SupabaseClient } from "./SupabaseClient";
import { logger } from "../../core/utils/logger";
import { ICodeRepository, CodeDiscussion} from "../interfaces/ICodeRepository";
import { ICodeRepository, CodeDiscussion } from "../interfaces/ICodeRepository";

export class SupabaseCodeRepository implements ICodeRepository {
async getDiscussionsByFile(file_path: string, remote_url: string, teamId: string): Promise<CodeDiscussion[]> {
Expand All @@ -24,10 +24,59 @@ export class SupabaseCodeRepository implements ICodeRepository {
}

if (response.status === 'success') {
logger.info("SupabaseCodeRepository", `Discussions retrieved successfully: ${JSON.stringify(response.discussions)}`);
logger.info("SupabaseCodeRepository", "Code discussions retrieved successfully");
return response.discussions;
}

throw new Error(`Unexpected response status: ${response.status}`);
}

async subscribeToCodeSnippets(teamId: string, userId: string, callback: (snippet: CodeDiscussion) => void): Promise<{ unsubscribe: () => void }> {
logger.info("SupabaseCodeRepository", `Subscribing to code snippets for team: ${teamId}`);
const supabaseClient = SupabaseClient.getInstance();
await supabaseClient.syncRealtimeAuth();
const supabase = supabaseClient.client;

const channel = supabase
.channel(`code_snippets-team-${teamId}`)
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'code_snippets',
filter: `team_id=eq.${teamId}`
},
async (payload) => {
logger.info("SupabaseCodeRepository", 'Realtime snippet payload received', payload);

if (!payload.new || !payload.new.message_id) {
logger.warn("SupabaseCodeRepository", "Payload missing message_id");
return;
}

try {
const discussions = await this.getDiscussionsByFile(payload.new.file_path, payload.new.remote_url, teamId);
const newDiscussion = discussions.find(d => d.id === payload.new.id);
if (newDiscussion) {
callback(newDiscussion);
} else {
logger.warn("SupabaseCodeRepository", `New snippet discussion not found after fetch: ${payload.new.id}`);
}
} catch (error) {
logger.error("SupabaseCodeRepository", "Failed to fetch discussion details for snippet", error);
}
}
)
.subscribe((status) => {
logger.info("SupabaseCodeRepository", `Subscription status for team ${teamId} snippets: ${status}`);
});

return {
unsubscribe: () => {
logger.info("SupabaseCodeRepository", `Unsubscribing from code snippets for team: ${teamId}`);
channel.unsubscribe();
}
};
}
}
1 change: 1 addition & 0 deletions src/adapters/supabase/SupabaseMessageRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class SupabaseMessageRepository implements IMessageRepository {
p_team_id: teamId,
p_content: message.content,
p_attachments: message.attachments,
p_quoted_id: message.quoted_id,
p_parent_id: null,
});
if (error) {
Expand Down
15 changes: 13 additions & 2 deletions src/core/commands/CLensCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,23 @@ const applyPatch = async (content: string, patch: string, filePath: string): Pro
}
};

export const showDiffCommand = async (args: any) => {
export const showDiffCommand = async (diffReference: any) => {
try {
let diffArgs;

if (diffReference?.filePath && diffReference?.discussion_id) {
diffArgs = Container.get('ContextLensService').getDiffArgs(diffReference.filePath, diffReference.discussion_id);
}

if (!diffArgs) {
logger.error("CLensCommand", "Diff arguments not found");
return;
}

const { originalContent, currentFileUri,
startLine, endLine,
commit_sha, filePath, patch, remoteUrl,
liveStartLine, liveEndLine } = args;
liveStartLine, liveEndLine } = diffArgs;

if (!originalContent || !currentFileUri || startLine === undefined || endLine === undefined) {
logger.error("CLensCommand", "Missing arguments");
Expand Down
92 changes: 78 additions & 14 deletions src/core/services/ContextLensService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class ContextLensService {
allowStale: false
});
private relocator = new RelocatorEngine();
private snippetSubscription?: { unsubscribe: () => void };

constructor(private codeRepo: ICodeRepository, context: vscode.ExtensionContext) {
this.buzzDecorationType = vscode.window.createTextEditorDecorationType({
Expand All @@ -47,6 +48,9 @@ export class ContextLensService {
context.subscriptions.push(
vscode.workspace.onDidChangeTextDocument(e => this.updateLiveRanges(e))
);
if (this._isCLensActive) {
this.startSnippetSubscription();
}
}

public toggleCodeLens(value: boolean) {
Expand All @@ -55,6 +59,9 @@ export class ContextLensService {
vscode.window.visibleTextEditors.forEach(editor => {
editor.setDecorations(this.buzzDecorationType, []);
});
this.stopSnippetSubscription();
} else {
this.startSnippetSubscription();
}
this._isCLensActive = value;
Storage.setGlobal("clens.active", value);
Expand Down Expand Up @@ -131,6 +138,26 @@ export class ContextLensService {
editor.setDecorations(this.buzzDecorationType, decorations);
}

public getDiffArgs(uriStr: string, discussionId: string): any | undefined {
const trackedDiscussions = this.cache.get(uriStr);
const td = trackedDiscussions?.find(t => t.discussion.id === discussionId);
if (!td) return undefined;

return {
originalContent: td.discussion.content,
currentFileUri: uriStr,
startLine: td.discussion.start_line,
endLine: td.discussion.end_line,
liveStartLine: td.liveRange.start.line,
liveEndLine: td.liveRange.end.line,
ref: td.discussion.ref,
commit_sha: td.discussion.commit_sha,
patch: td.discussion.patch,
filePath: td.discussion.file_path,
remoteUrl: td.discussion.remote_url
};
}

private createMarkdownPopup(discussionList: TrackedDiscussion[], uri: vscode.Uri): vscode.MarkdownString {
const md = new vscode.MarkdownString('', true);
md.isTrusted = true;
Expand Down Expand Up @@ -168,20 +195,11 @@ export class ContextLensService {
md.appendMarkdown(`$(search-fuzzy) **Partial match:** Review the diff to see the changes.\n\n`);
}

const diffArgs = encodeURIComponent(JSON.stringify({
originalContent: d.discussion.content,
currentFileUri: uri.toString(),
startLine: d.discussion.start_line,
endLine: d.discussion.end_line,
liveStartLine: d.liveRange.start.line,
liveEndLine: d.liveRange.end.line,
ref: d.discussion.ref,
commit_sha: d.discussion.commit_sha,
patch: d.discussion.patch,
filePath: d.discussion.file_path,
remoteUrl: d.discussion.remote_url
}));
md.appendMarkdown(`[$(git-compare)](command:clens.showDiff?${diffArgs} "View Diff")`);
const diffReference = {
filePath: uri.toString(),
discussion_id: d.discussion.id
};
md.appendMarkdown(`[$(git-compare)](command:clens.showDiff?${encodeURIComponent(JSON.stringify(diffReference))} "View Diff")`);
md.appendMarkdown(`&nbsp;&nbsp;|&nbsp;&nbsp;`);
md.appendMarkdown(`[$(comment-discussion) Jump to Chat](command:linebuzz.jumpToMessage?${encodeURIComponent(JSON.stringify(d.discussion.message.message_id))} "View Discussion")`);

Expand Down Expand Up @@ -377,8 +395,54 @@ export class ContextLensService {
};
}

private async startSnippetSubscription() {
if (this.snippetSubscription) return;

const teamService = Container.get("TeamService");
const authService = Container.get("AuthService");
const team = teamService.getTeam();
const session = await authService.getSession();

if (!team || !session) return;

this.snippetSubscription = await this.codeRepo.subscribeToCodeSnippets(team.id, session.user_id, (discussion) => {
this.handleNewSnippet(discussion);
});
}

private stopSnippetSubscription() {
if (this.snippetSubscription) {
this.snippetSubscription.unsubscribe();
this.snippetSubscription = undefined;
}
}

private handleNewSnippet(discussion: CodeDiscussion) {
for (const editor of vscode.window.visibleTextEditors) {
const uriStr = editor.document.uri.toString();
if (this.cache.has(uriStr)) {
this.getFileContext(editor.document).then(context => {
if (context && context.file_path === discussion.file_path && context.remote_url === discussion.remote_url) {
const trackedDiscussions = this.cache.get(uriStr) || [];
if (trackedDiscussions.some(td => td.discussion.id === discussion.id)) {
return;
}

const [newTrackedDiscussion] = this.alignDiscussions(editor.document, [discussion]);
if (newTrackedDiscussion) {
trackedDiscussions.push(newTrackedDiscussion);
this.cache.set(uriStr, trackedDiscussions);
vscode.commands.executeCommand('linebuzz.refreshCLens');
}
}
});
}
}
}

public dispose() {
this.cache.clear();
this.buzzDecorationType.dispose();
this.stopSnippetSubscription();
}
}