Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,13 @@ If you've made a bunch of changes to a file, and want to restore an older versio

## Status Bar

Whenever `GitDoc` is enabled, it will contribute a status bar item to your status bar. This simply indicates that it's enabled, and makes it easier for you to know which "versioning mode" you're in (auto-commit vs. manual commit). Additionally, if you enable [auto-pushing](#auto-pushing), then the status bar will indicate when it's syncing your commits with your repo. If you click the `GitDoc` status bar item, this will disable `GitDoc`. This allows you to easily enable GitDoc for a period of time, and then quickly turn it off.
Whenever `GitDoc` is installed, it will contribute a status bar item to your status bar. The status bar icon changes based on the current state:

- When disabled: Shows a "sync-ignored" icon ($(sync-ignored))
- When enabled and viewing a matching file: Shows a "mirror" icon ($(mirror))
- When enabled but viewing a non-matching file: Shows a "mirror" icon with a circle-slash next to it ($(mirror) $(circle-slash))

Additionally, if you enable [auto-pushing](#auto-pushing), then the status bar will indicate when it's syncing your commits with your repo. If you click the `GitDoc` status bar item, this will toggle between enabled and disabled states. This allows you to easily enable GitDoc for a period of time, and then quickly turn it off.

## Contributed Commands

Expand Down Expand Up @@ -129,4 +135,4 @@ The following settings enable you to customize the default behavior of `GitDoc`:

- `GitDoc > AI: Custom Instructions` - Specifies custom instructions to use when generating commit messages (e.g. use conventional commit syntax, use emojis). This setting only applies when `GitDoc > AI: Enabled` is set to `true`."

- `GitDoc > AI: Use Emojis` - Specifies whether to prepend AI-generated commit messages with an emoji. This setting only applies when `GitDoc > AI: Enabled` is set to `true`. Defaults to `false`.
- `GitDoc > AI: Use Emojis` - Specifies whether to prepend AI-generated commit messages with an emoji. This setting only applies when `GitDoc > AI: Enabled` is set to `true`. Defaults to `false`.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 59 additions & 6 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,41 @@ import { registerCommands } from "./commands";
import config from "./config";
import { getGitApi, GitAPI, RefType } from "./git";
import { store } from "./store";
import { commit, watchForChanges } from "./watcher";
import { commit, watchForChanges, ensureStatusBarItem, updateStatusBarItem } from "./watcher";
import { updateContext } from "./utils";
import * as minimatch from "minimatch";

// Helper function for file pattern matching
function matches(uri: vscode.Uri) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of duplicating this function, we should define it as a reusable utility that can be imported in this file and in the watcher.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just export the function from the watcher module and then reuse it here

return minimatch(uri.path, config.filePattern, { dot: true });
}

export async function activate(context: vscode.ExtensionContext) {
// Wait for Git extension to be ready
const git = await getGitApi();
if (!git) {
return;
}

// Initialize the store based on the
// user/workspace configuration.
store.enabled = config.enabled;
// Wait for initial repository to be available
if (git.repositories.length === 0) {
await new Promise<void>((resolve) => {
const disposable = git.onDidOpenRepository(() => {
disposable.dispose();
resolve();
});
});
}

// Initialize the store and context based on the configuration
const initialEnabled = vscode.workspace.getConfiguration('gitdoc').get('enabled', false);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous code was handling this logic already, since "config.enabled" pulls its value from the workspace configuration. Is there a reason we need to change it to this more verbose implementation?

store.enabled = initialEnabled;
updateContext(initialEnabled, false); // Set initial context to match config
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateContext will already be run in response to setting store.enabled, so we don't need this. And then related to my previous comment, I think we can just revert this entire hunk.


// Create status bar item and show it immediately
const statusBar = ensureStatusBarItem();
statusBar.show();
context.subscriptions.push(statusBar);

registerCommands(context);

Expand All @@ -25,9 +48,38 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(git.onDidOpenRepository(() => checkEnabled(git)));
context.subscriptions.push(git.onDidCloseRepository(() => checkEnabled(git)));

// Create a debounced version of updateStatusBarItem
let updateTimeout: NodeJS.Timeout | null = null;
const debouncedUpdateStatusBar = (editor: vscode.TextEditor | undefined) => {
Comment thread
tomglynch marked this conversation as resolved.
Outdated
if (updateTimeout) {
clearTimeout(updateTimeout);
}
updateTimeout = setTimeout(() => {
updateStatusBarItem(editor);
}, 50); // 50ms debounce
};

// Watch for active editor changes to update icon state
context.subscriptions.push(
vscode.window.onDidChangeActiveTextEditor((editor) => {
debouncedUpdateStatusBar(editor);
})
);

// Set initial icon state
updateStatusBarItem(vscode.window.activeTextEditor);

// Initial check of enabled state
await checkEnabled(git);

// Watch for store changes
reaction(
() => [store.enabled],
() => checkEnabled(git)
() => store.enabled,
(enabled) => {
updateContext(enabled, true);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need this since checkEnabled will already update the context as needed.

checkEnabled(git);
debouncedUpdateStatusBar(vscode.window.activeTextEditor);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be handled by the checkEnabled function, and effectively revert to the previous code. That way this reaction is simply watching for enabled changes and then updating the UX based on that (via checkEnabled)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, you already have a reaction in the watcher module that will update the status bar when the store.enabled changes. So I think we can just remove this?

}
);

context.subscriptions.push(
Expand All @@ -37,6 +89,7 @@ export async function activate(context: vscode.ExtensionContext) {
e.affectsConfiguration("gitdoc.autoCommitDelay") ||
e.affectsConfiguration("gitdoc.filePattern")) {
checkEnabled(git);
debouncedUpdateStatusBar(vscode.window.activeTextEditor);
Comment thread
tomglynch marked this conversation as resolved.
Outdated
}
})
);
Expand Down
90 changes: 41 additions & 49 deletions src/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,49 +232,57 @@ export function ensureStatusBarItem() {
statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Left
);

statusBarItem.text = "$(mirror)";
statusBarItem.tooltip = "GitDoc: Auto-commiting files on save";
statusBarItem.command = "gitdoc.disable";
updateStatusBarItem(vscode.window.activeTextEditor);
statusBarItem.show();
}

return statusBarItem;
}

export function updateStatusBarItem(editor: vscode.TextEditor | undefined) {
if (!statusBarItem) return;

const enabled = store.enabled;
const isMatchingFile = editor && matches(editor.document.uri);

// Set the base state first (before any async operations might trigger)
if (!enabled) {
statusBarItem.text = "$(sync-ignored)";
statusBarItem.tooltip = "GitDoc: Click to enable auto-commit";
statusBarItem.command = "gitdoc.enable";
return;
}

// Handle enabled state
const suffix = store.isPushing
? " (Pushing...)"
: store.isPulling
? " (Pulling...)"
: "";

// Icon states:
// - Enabled + Matching file: Show mirror
// - Enabled + Non-matching file: Show mirror with circle-slash overlay using ~
// var icon = "$(mirror)";
const icon = isMatchingFile ? "$(mirror)" : "$(mirror) $(circle-slash)";

statusBarItem.text = `${icon}${suffix}`;
statusBarItem.tooltip = `GitDoc: ${isMatchingFile ? "Auto-committing this file on save" : "This file is not auto-committed"}`;
statusBarItem.command = "gitdoc.disable";
}

// Add reaction to store changes
reaction(
() => [store.enabled, store.isPushing, store.isPulling],
() => {
updateStatusBarItem(vscode.window.activeTextEditor);
}
);

let disposables: vscode.Disposable[] = [];
export function watchForChanges(git: GitAPI): vscode.Disposable {
const commitAfterDelay = debouncedCommit(git.repositories[0]);
disposables.push(git.repositories[0].state.onDidChange(commitAfterDelay));

ensureStatusBarItem();

disposables.push(
vscode.window.onDidChangeActiveTextEditor((editor) => {
if (editor && matches(editor.document.uri)) {
statusBarItem?.show();
} else {
statusBarItem?.hide();
}
})
);

if (
vscode.window.activeTextEditor &&
matches(vscode.window.activeTextEditor.document.uri)
) {
statusBarItem?.show();
} else {
statusBarItem?.hide();
}

disposables.push({
dispose: () => {
statusBarItem?.dispose();
statusBarItem = null;
},
});

if (config.autoPush === "afterDelay") {
const interval = setInterval(async () => {
pushRepository(git.repositories[0]);
Expand All @@ -298,22 +306,6 @@ export function watchForChanges(git: GitAPI): vscode.Disposable {
});
}

const reactionDisposable = reaction(
() => [store.isPushing, store.isPulling],
() => {
const suffix = store.isPushing
? " (Pushing...)"
: store.isPulling
? " (Pulling...)"
: "";
statusBarItem!.text = `$(mirror)${suffix}`;
}
);

disposables.push({
dispose: reactionDisposable,
});

if (config.pullOnOpen) {
pullRepository(git.repositories[0]);
}
Expand Down