Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@
<OutputPath>bin/$(Configuration)/</OutputPath>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<DeployExtension>True</DeployExtension>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CodingWithCalvin.Otel4Vsix" Version="0.2.2" />
<PackageReference Include="Community.VisualStudio.Toolkit.17" Version="17.0.507" />
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.14.40265" ExcludeAssets="runtime" />
<PackageReference Include="LibGit2Sharp" Version="0.30.0" />
Expand Down
24 changes: 23 additions & 1 deletion src/CodingWithCalvin.GitRanger/Commands/BlameCommands.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Threading.Tasks;
using CodingWithCalvin.GitRanger.Options;
using CodingWithCalvin.Otel4Vsix;
using Community.VisualStudio.Toolkit;
using Microsoft.VisualStudio.Shell;
using Task = System.Threading.Tasks.Task;
Expand Down Expand Up @@ -60,13 +62,17 @@ private static void OnToggleInlineBlame(object sender, EventArgs e)
{
ThreadHelper.ThrowIfNotOnUIThread();

using var activity = VsixTelemetry.StartCommandActivity("GitRanger.ToggleInlineBlame");

var options = GeneralOptions.Instance;
if (options != null)
{
options.EnableInlineBlame = !options.EnableInlineBlame;
options.Save();

var status = options.EnableInlineBlame ? "enabled" : "disabled";
activity?.SetTag("inline_blame.enabled", options.EnableInlineBlame);
VsixTelemetry.LogInformation("Inline blame {Status}", status);
VS.StatusBar.ShowMessageAsync($"Git Ranger: Inline blame {status}").FireAndForget();
}
}
Expand All @@ -91,13 +97,17 @@ private static void OnToggleBlameGutter(object sender, EventArgs e)
{
ThreadHelper.ThrowIfNotOnUIThread();

using var activity = VsixTelemetry.StartCommandActivity("GitRanger.ToggleBlameGutter");

var options = GeneralOptions.Instance;
if (options != null)
{
options.EnableBlameGutter = !options.EnableBlameGutter;
options.Save();

var status = options.EnableBlameGutter ? "enabled" : "disabled";
activity?.SetTag("blame_gutter.enabled", options.EnableBlameGutter);
VsixTelemetry.LogInformation("Blame gutter {Status}", status);
VS.StatusBar.ShowMessageAsync($"Git Ranger: Blame gutter {status}").FireAndForget();
}
}
Expand All @@ -119,6 +129,8 @@ private static async void OnCopyCommitSha(object sender, EventArgs e)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

using var activity = VsixTelemetry.StartCommandActivity("GitRanger.CopyCommitSha");

try
{
var docView = await VS.Documents.GetActiveDocumentViewAsync();
Expand All @@ -129,24 +141,34 @@ private static async void OnCopyCommitSha(object sender, EventArgs e)
if (string.IsNullOrEmpty(filePath))
return;

// Get the current line
// Get the current line
var caretPosition = docView.TextView.Caret.Position.BufferPosition;
var lineNumber = docView.TextView.TextSnapshot.GetLineNumberFromPosition(caretPosition.Position) + 1;

activity?.SetTag("line.number", lineNumber);

// Get blame for this line
var blameInfo = GitRangerPackage.BlameService?.GetBlameForLine(filePath, lineNumber);
if (blameInfo != null)
{
System.Windows.Clipboard.SetText(blameInfo.CommitSha);
activity?.SetTag("commit.sha", blameInfo.ShortSha);
VsixTelemetry.LogInformation("Copied commit SHA {CommitSha} to clipboard", blameInfo.ShortSha);
await VS.StatusBar.ShowMessageAsync($"Git Ranger: Copied commit SHA {blameInfo.ShortSha} to clipboard");
}
else
{
VsixTelemetry.LogInformation("No blame information available for line {LineNumber}", lineNumber);
await VS.StatusBar.ShowMessageAsync("Git Ranger: No blame information available for this line");
}
}
catch (Exception ex)
{
activity?.RecordError(ex);
VsixTelemetry.TrackException(ex, new Dictionary<string, object>
{
{ "operation.name", "CopyCommitSha" }
});
await VS.StatusBar.ShowMessageAsync($"Git Ranger: Error copying commit SHA - {ex.Message}");
}
}
Expand Down
33 changes: 30 additions & 3 deletions src/CodingWithCalvin.GitRanger/GitRangerPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using CodingWithCalvin.GitRanger.Commands;
using CodingWithCalvin.GitRanger.Options;
using CodingWithCalvin.GitRanger.Services;
using CodingWithCalvin.Otel4Vsix;
using Community.VisualStudio.Toolkit;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
Expand Down Expand Up @@ -85,16 +86,32 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
{
await base.InitializeAsync(cancellationToken, progress);

// Switch to the main thread for telemetry initialization
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

// Initialize telemetry
var builder = VsixTelemetry.Configure()
.WithServiceName(VsixInfo.DisplayName)
.WithServiceVersion(VsixInfo.Version)
.WithVisualStudioAttributes(this)
.WithEnvironmentAttributes();

#if !DEBUG
builder
.WithOtlpHttp("https://api.honeycomb.io")
.WithHeader("x-honeycomb-team", HoneycombConfig.ApiKey);
#endif

builder.Initialize();

// Initialize services
await InitializeServicesAsync();

// Switch to the main thread for command registration
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

// Register commands
await RegisterCommandsAsync();

// Log successful initialization
VsixTelemetry.LogInformation("Git Ranger initialized successfully");
await VS.StatusBar.ShowMessageAsync("Git Ranger initialized successfully");
}

Expand Down Expand Up @@ -133,6 +150,16 @@ private async Task RegisterCommandsAsync()
await BlameCommands.InitializeAsync(this);
// Note: HistoryCommands and GraphCommands are not registered yet (coming soon)
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
VsixTelemetry.Shutdown();
}

base.Dispose(disposing);
}
}

/// <summary>
Expand Down
7 changes: 7 additions & 0 deletions src/CodingWithCalvin.GitRanger/HoneycombConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace CodingWithCalvin.GitRanger
{
internal static class HoneycombConfig
{
public const string ApiKey = "PLACEHOLDER";
}
}
28 changes: 18 additions & 10 deletions src/CodingWithCalvin.GitRanger/Services/BlameService.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CodingWithCalvin.GitRanger.Core.Models;
using CodingWithCalvin.Otel4Vsix;

namespace CodingWithCalvin.GitRanger.Services
{
Expand Down Expand Up @@ -38,21 +38,28 @@ public BlameService(GitService gitService, ThemeService themeService)
/// <returns>Blame line information, or empty if not available.</returns>
public IReadOnlyList<BlameLineInfo> GetBlame(string filePath)
{
if (string.IsNullOrEmpty(filePath))
using var activity = VsixTelemetry.StartCommandActivity("BlameService.GetBlame");

if (string.IsNullOrEmpty(filePath))
return Array.Empty<BlameLineInfo>();

// Check cache
if (_cache.TryGetValue(filePath, out var cached) && !cached.IsExpired)
{
activity?.SetTag("cache.hit", true);
return cached.Lines;
}

activity?.SetTag("cache.hit", false);

// Load synchronously
var lines = _gitService.GetBlame(filePath).ToList();

// Update cache
_cache[filePath] = new BlameCache(lines);

activity?.SetTag("lines.count", lines.Count);

return lines;
}

Expand All @@ -73,28 +80,29 @@ public Task<IReadOnlyList<BlameLineInfo>> GetBlameAsync(string filePath, Cancell
/// <param name="filePath">The file path.</param>
public void LoadBlameInBackground(string filePath)
{
Debug.WriteLine($"[GitRanger] BlameService.LoadBlameInBackground - FilePath: {filePath}");
using var activity = VsixTelemetry.StartCommandActivity("BlameService.LoadBlameInBackground");

if (string.IsNullOrEmpty(filePath))
if (string.IsNullOrEmpty(filePath))
{
Debug.WriteLine("[GitRanger] BlameService.LoadBlameInBackground - FilePath is empty");
VsixTelemetry.LogInformation("LoadBlameInBackground - FilePath is empty");
return;
}

Task.Run(() =>
{
try
{
Debug.WriteLine($"[GitRanger] BlameService.LoadBlameInBackground - Starting GetBlame for {filePath}");
VsixTelemetry.LogInformation("Loading blame for file");
var lines = GetBlame(filePath);
Debug.WriteLine($"[GitRanger] BlameService.LoadBlameInBackground - Got {lines.Count} lines");
VsixTelemetry.LogInformation("Loaded {LineCount} blame lines", lines.Count);
BlameLoaded?.Invoke(this, new BlameLoadedEventArgs(filePath, lines));
Debug.WriteLine("[GitRanger] BlameService.LoadBlameInBackground - BlameLoaded event fired");
}
catch (Exception ex)
{
Debug.WriteLine($"[GitRanger] BlameService.LoadBlameInBackground - ERROR: {ex.Message}");
Debug.WriteLine($"[GitRanger] BlameService.LoadBlameInBackground - StackTrace: {ex.StackTrace}");
VsixTelemetry.TrackException(ex, new Dictionary<string, object>
{
{ "operation.name", "LoadBlameInBackground" },
});
}
});
}
Expand Down