Visual Studio bookmarks are useful for marking a line and returning to it later. The problem is that a line number alone does not say why the location matters, how it relates to other locations, or whether the rest of the team should know about it.
Bookmark Studio treats a bookmark as a small piece of project knowledge. A bookmark can have a name, note, color, folder, and one of nine direct-navigation shortcuts. It can stay private in the .vs folder, live at the workspace level in source control, or be global across solutions.
That sounds like a straightforward feature, but it touches several distinct parts of Visual Studio extensibility: editor tags and glyphs, text-buffer tracking, commands, a searchable tool window, options, solution lifecycle events, and persistent storage. The interesting part is making those pieces behave like one feature rather than a collection of extension points.

Keep the editor layer fast
The visible bookmark in the editor margin starts with a MEF-exported IViewTaggerProvider. It creates an ITagger<BookmarkGlyphTag> for primary text document views, while an IGlyphFactory turns each tag into the colored square shown in the glyph margin. If a bookmark has a shortcut from 1 through 9, the number is drawn inside the glyph.
The tag also carries the bookmark label, note, color, and shortcut number. That gives the glyph factory enough information to build a useful tooltip and context menu without reading the bookmark file. A second tagger produces OverviewMarkTag instances so the same bookmarks appear as colored markers in the editor scrollbar.
A useful rule for editor extensions is to keep GetTags cheap. Visual Studio can ask for tags often, including while scrolling and editing. Bookmark Studio does not load JSON or filter the complete bookmark collection on every request. The shared session owns the current bookmark state, and each tagger caches only the bookmarks for its document. When the session reports a change, the tagger replaces that small cache and raises TagsChanged.
That separation is broadly useful: do I/O and normalization in a service, then give the editor a read-optimized snapshot.
A persisted line number is not an editor position
A bookmark must be serializable, so its stored location includes a document path and line number. An open text buffer is more dynamic. Insert three lines above a bookmark and the persisted number is immediately stale.
Bookmark Studio handles that mismatch with ITrackingPoint. While a document is open, each bookmark gets a tracking point anchored inside the line content. The editor moves that point as the buffer changes, which keeps the glyph attached to the intended code instead of the original numeric line. The tagger can still fall back to the stored line number when no tracking point exists.
There is an additional wrinkle: users can explicitly drag a glyph to another line. The tagger therefore distinguishes an editor-tracked move from an explicit bookmark move before deciding whether to preserve or rebuild the tracking point. The custom MouseProcessorBase implementation calculates the target editor line, displays a drop indicator, updates the bookmark, and refreshes the manager if it is open.

The lesson is that storage coordinates and live editor coordinates are different concepts. Persist a stable representation, but use the text model's tracking primitives while a buffer is active.
Reuse Visual Studio instead of recreating it
The Bookmark Manager is a WPF control hosted in a BaseToolWindow. Its surrounding experience comes from Visual Studio rather than custom imitations.
The package registers the window with ProvideToolWindow, docks it alongside Solution Explorer, and assigns a toolbar declared through VSCT to ToolWindowPane.ToolBar. Search uses the tool window's native search box by enabling SearchEnabled and implementing IVsSearchTask. Commands and keyboard bindings are also declared through VSCT, including direct navigation with Alt+Shift+1 through Alt+Shift+9.
This approach matters for more than appearance. Native command placement participates in Visual Studio's command routing, toolbar customization, keyboard system, and accessibility behavior. The same principle applies to the search box: using the host's search contract gives the tool window familiar behavior without building another search control.
Bookmark Studio also offers an opt-in bridge from Visual Studio's built-in bookmark gestures. VS.Commands.InterceptAsync handles commands such as toggle, next, previous, and clear-in-document. The first toggle can prompt the user, and the setting determines whether later commands stop in Bookmark Studio or continue to Visual Studio's native implementation. The extension adds its own direct gestures as well, so interception is a choice rather than a requirement.

Make sharing explicit in the storage model
Bookmarks are stored with System.Text.Json in .bookmarks.json. The file is a folder tree containing bookmark IDs, relative document paths, line numbers, labels, notes, colors, shortcut slots, and manual sort indexes.
There are three storage scopes:
- Personal bookmarks live under the solution's
.vsfolder. - Workspace bookmarks live beside the solution or at the Git repository root and can be committed.
- Global bookmarks live in
%USERPROFILE%\.bookmarks.jsonand use absolute paths because they are machine-specific.
Workspace discovery walks upward from the solution directory and reuses the first bookmarks file it finds. This is important for repositories where the solution sits below the root. New workspace files record "documentPathRoot": "bookmarksFile", which tells the loader to resolve relative document paths from the bookmarks file itself. Existing files without that marker retain the older solution-relative behavior.
That small format marker avoids a common portability bug. A relative path is only meaningful when both writer and reader agree on its base directory. If a team-shared file may move higher in a repository, the base must travel with the format or be defined by it.
Naming can use the editor's understanding of code
When name prompting is enabled, Bookmark Studio does not have to settle for the current line as the label. A MEF service queries Visual Studio's classification system and ranks identifiers such as methods, properties, fields, and types. It supports view classifiers first, then projection buffers and tag aggregators as fallbacks.
The complete fallback order is pragmatic: selected text, classified identifier, word under the caret, file name, trimmed line text, and finally Bookmark. Duplicate labels receive a numeric suffix.
This is a good middle ground for editor tooling. The extension gets language-aware suggestions from classifications that Visual Studio already produces, without taking a dependency on a specific language compiler or syntax tree.
The extension points form one state machine
The package loads in the background for solutions, Open Folder workspaces, and the no-solution context because global bookmarks remain useful without a solution. Solution and folder events invalidate the cached path and refresh the shared session. Editor taggers, commands, and the tool window all observe or update that same session.
That shared state is the architectural center of Bookmark Studio. The editor should not own persistence, the tool window should not be the only source of truth, and commands should not need to know where JSON lives. Each surface delegates bookmark operations to services, then reacts to the resulting state change.
For extension authors, the reusable ideas are straightforward:
- Keep taggers read-only and inexpensive.
- Use tracking points for live buffers instead of treating line numbers as stable positions.
- Prefer native tool window search, VSCT commands, and host chrome over look-alike WPF controls.
- Define the base of relative paths as part of a shareable file format.
- Put cross-surface state behind a service so commands, editors, and tool windows stay synchronized.
Bookmark Studio is available on the Visual Studio Marketplace. The complete source, including the editor providers, tool window, command table, storage format, and tests, is on GitHub.










