vite-plugin-source-locator 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,11 +42,11 @@ No `main.tsx` wiring required. The plugin auto-injects the client overlay in dev
42
42
 
43
43
  ## Pick Mode
44
44
 
45
- 1. Click the badge (bottom-right): **Source Locator (cursor) — click to pick**
45
+ 1. Click the badge (bottom-right): **Source Locator (auto) — click to pick**
46
46
  2. Hover elements — blue highlight + file paths in tooltip
47
47
  3. Click to open source — cycles **TSX → CSS → TSX** when both exist
48
48
  4. **Esc** — cancel pick mode
49
- 5. **Shift+L** — cycle IDE: cursor vscode webstorm
49
+ 5. **Shift+L** — cycle IDE (session only, resets on refresh)
50
50
 
51
51
  ## Exports
52
52
 
@@ -63,7 +63,7 @@ sourceLocator({
63
63
  enabled: true,
64
64
  endpoint: '/__open-in-editor',
65
65
  attribute: 'data-source',
66
- ides: ['cursor', 'vscode', 'webstorm'],
66
+ ides: ['auto', 'cursor', 'vscode', 'webstorm'],
67
67
  theme: 'light',
68
68
  })
69
69
  ```
@@ -89,7 +89,7 @@ import { initSourceLocator } from 'vite-plugin-source-locator/client'
89
89
  initSourceLocator({
90
90
  endpoint: '/__open-in-editor',
91
91
  attribute: 'data-source',
92
- ides: ['cursor', 'vscode', 'webstorm'],
92
+ ides: ['auto', 'cursor', 'vscode', 'webstorm'],
93
93
  theme: {
94
94
  background: '#ffffff',
95
95
  text: '#000000',
@@ -132,7 +132,7 @@ import { initSourceLocator } from 'vite-plugin-source-locator/client'
132
132
  initSourceLocator({
133
133
  endpoint: '/__open-in-editor',
134
134
  attribute: 'data-source',
135
- ides: ['cursor', 'vscode', 'webstorm'],
135
+ ides: ['auto', 'cursor', 'vscode', 'webstorm'],
136
136
  theme: 'blue',
137
137
  })
138
138
  ```
@@ -141,7 +141,32 @@ initSourceLocator({
141
141
 
142
142
  IDE opening works on **macOS, Windows, and Linux** via [`launch-editor`](https://github.com/vitejs/launch-editor).
143
143
 
144
- The editor CLI must be available on your `PATH`:
144
+ ### Auto detection (default)
145
+
146
+ By default, `ides` includes `'auto'` as the first entry. In `auto` mode, the plugin detects your open IDE:
147
+
148
+ 1. `LAUNCH_EDITOR` / `REACT_EDITOR` environment variable
149
+ 2. Running editor process (VS Code, Cursor, WebStorm, etc.)
150
+ 3. `VISUAL` / `EDITOR` environment variable
151
+
152
+ No CLI on `PATH` is required if the editor is already running.
153
+
154
+ ```typescript
155
+ // auto-detect open IDE (default)
156
+ sourceLocator()
157
+
158
+ // VS Code only
159
+ sourceLocator({ ides: ['vscode'] })
160
+
161
+ // auto + manual override with Shift+L (session only)
162
+ sourceLocator({ ides: ['auto', 'vscode'] })
163
+ ```
164
+
165
+ The first entry in `ides` is the default. Shift+L cycles through the list in memory (resets on page refresh).
166
+
167
+ ### Explicit editor
168
+
169
+ When not using `auto`, the editor CLI should be on your `PATH`:
145
170
 
146
171
  | IDE | CLI command |
147
172
  |-----|-------------|
@@ -149,13 +174,11 @@ The editor CLI must be available on your `PATH`:
149
174
  | VS Code | `code` |
150
175
  | WebStorm | `webstorm` |
151
176
 
152
- You can override the default editor with environment variables:
177
+ Override with environment variables:
153
178
 
154
- - `LAUNCH_EDITOR=cursor`
179
+ - `LAUNCH_EDITOR=code`
155
180
  - `REACT_EDITOR=code`
156
181
 
157
- Shift+L cycles through the `ides` list configured in plugin options.
158
-
159
182
  ## Adding a New IDE
160
183
 
161
184
  1. Extend `LocatorIde` and `IDE_ORDER` in `src/shared/index.ts`
@@ -1,7 +1,6 @@
1
- import { nextClickTarget, parseSourceLocation, resolveTheme } from '../shared/index.js';
1
+ import { nextClickTarget, nextIde, parseSourceLocation, resolveTheme } from '../shared/index.js';
2
2
  import { findCssSource } from './css-source.js';
3
3
  import { createLocatorOverlayUi } from './overlay.js';
4
- import { cycleStoredIde, getStoredIde } from './preference.js';
5
4
  const EMPTY_SOURCES = Object.freeze({
6
5
  tsx: undefined,
7
6
  css: undefined,
@@ -13,13 +12,13 @@ function getSourceEl(target, attribute, host) {
13
12
  const el = target.closest(`[${attribute}]`);
14
13
  return el instanceof HTMLElement ? el : null;
15
14
  }
16
- async function openSourceInEditor(source, config) {
15
+ async function openSourceInEditor(source, config, activeIde) {
17
16
  const loc = parseSourceLocation(source);
18
17
  const params = new URLSearchParams({
19
18
  file: loc.file,
20
19
  line: loc.line,
21
20
  col: loc.col,
22
- ide: getStoredIde(),
21
+ ide: activeIde,
23
22
  });
24
23
  await fetch(`${config.endpoint}?${params.toString()}`);
25
24
  }
@@ -34,7 +33,8 @@ function resolveOpenSource(sources) {
34
33
  export function startPickController(root, host, config) {
35
34
  let pickMode = false;
36
35
  let sources = EMPTY_SOURCES;
37
- const ui = createLocatorOverlayUi(root, () => setPickMode(!pickMode), resolveTheme(config.theme));
36
+ let activeIde = config.ides[0] ?? 'auto';
37
+ const ui = createLocatorOverlayUi(root, () => setPickMode(!pickMode), resolveTheme(config.theme), () => activeIde);
38
38
  function setPickMode(active) {
39
39
  pickMode = active;
40
40
  ui.setPickActive(active);
@@ -68,10 +68,10 @@ export function startPickController(root, host, config) {
68
68
  }
69
69
  if (!e.shiftKey || e.key !== 'L')
70
70
  return;
71
- const next = cycleStoredIde(config.ides);
71
+ activeIde = nextIde(activeIde, config.ides);
72
72
  if (!pickMode)
73
73
  ui.refreshBadgeLabel();
74
- ui.flashMessage(`IDE: ${next}`);
74
+ ui.flashMessage(`IDE: ${activeIde}`);
75
75
  };
76
76
  const onClick = async (e) => {
77
77
  if (!pickMode || host.contains(e.target))
@@ -86,7 +86,7 @@ export function startPickController(root, host, config) {
86
86
  ui.flashMessage('No source for this element');
87
87
  return;
88
88
  }
89
- await openSourceInEditor(openSource, config);
89
+ await openSourceInEditor(openSource, config, activeIde);
90
90
  sources = { ...sources, clickTarget: nextClickTarget(sources.clickTarget, !!sources.css) };
91
91
  ui.showSourceTooltip(el, sources.tsx, sources.css, sources.clickTarget, e.clientX, e.clientY);
92
92
  };
@@ -1,5 +1,5 @@
1
- import type { ClickTarget, LocatorTheme } from '../shared/index.js';
2
- export declare function createLocatorOverlayUi(root: ShadowRoot, onTogglePick: () => void, theme: LocatorTheme): {
1
+ import type { ClickTarget, LocatorIde, LocatorTheme } from '../shared/index.js';
2
+ export declare function createLocatorOverlayUi(root: ShadowRoot, onTogglePick: () => void, theme: LocatorTheme, getActiveIde: () => LocatorIde): {
3
3
  mountBadge: () => void;
4
4
  setPickActive: (active: boolean) => void;
5
5
  refreshBadgeLabel: () => void;
@@ -6,7 +6,7 @@ const TOOLTIP_CURSOR_OFFSET = 16;
6
6
  const FLASH_DURATION_MS = 1500;
7
7
  const FLASH_HORIZONTAL_OFFSET = 80;
8
8
  const FLASH_BOTTOM_OFFSET = 80;
9
- export function createLocatorOverlayUi(root, onTogglePick, theme) {
9
+ export function createLocatorOverlayUi(root, onTogglePick, theme, getActiveIde) {
10
10
  let activeEl = null;
11
11
  let flashTimeout = null;
12
12
  let badgeEl = null;
@@ -70,20 +70,20 @@ export function createLocatorOverlayUi(root, onTogglePick, theme) {
70
70
  document.body.style.cursor = active ? 'crosshair' : '';
71
71
  if (!badgeEl)
72
72
  return;
73
- badgeEl.textContent = badgeLabel(active);
73
+ badgeEl.textContent = badgeLabel(active, getActiveIde());
74
74
  applyBadgeColors(active);
75
75
  if (!active)
76
76
  removeTooltip();
77
77
  };
78
78
  const refreshBadgeLabel = () => {
79
79
  if (badgeEl)
80
- badgeEl.textContent = badgeLabel(false);
80
+ badgeEl.textContent = badgeLabel(false, getActiveIde());
81
81
  };
82
82
  const mountBadge = () => {
83
83
  badgeEl = document.createElement('button');
84
84
  badgeEl.id = UI_IDS.badge;
85
85
  badgeEl.type = 'button';
86
- badgeEl.textContent = badgeLabel(false);
86
+ badgeEl.textContent = badgeLabel(false, getActiveIde());
87
87
  Object.assign(badgeEl.style, LAYOUT.badge, {
88
88
  background: theme.badgeBackground,
89
89
  color: theme.badgeText,
@@ -1,5 +1,2 @@
1
1
  import type { LocatorIde } from '../shared/index.js';
2
- export declare function getStoredIde(): LocatorIde;
3
- export declare function setStoredIde(ide: LocatorIde): void;
4
- export declare function cycleStoredIde(order: LocatorIde[]): LocatorIde;
5
- export declare function badgeLabel(picking: boolean): string;
2
+ export declare function badgeLabel(picking: boolean, activeIde: LocatorIde): string;
@@ -1,21 +1,7 @@
1
- import { DEFAULT_IDE, STORAGE_KEY, isLocatorIde, nextIde } from '../shared/index.js';
2
- export function getStoredIde() {
3
- const stored = localStorage.getItem(STORAGE_KEY) ?? DEFAULT_IDE;
4
- return isLocatorIde(stored) ? stored : DEFAULT_IDE;
5
- }
6
- export function setStoredIde(ide) {
7
- localStorage.setItem(STORAGE_KEY, ide);
8
- }
9
- export function cycleStoredIde(order) {
10
- const next = nextIde(getStoredIde(), order);
11
- setStoredIde(next);
12
- return next;
13
- }
14
1
  const BADGE_PICKING_HINT = 'Esc to cancel';
15
2
  const BADGE_IDLE_HINT = 'click to pick';
16
- export function badgeLabel(picking) {
17
- const ide = getStoredIde();
3
+ export function badgeLabel(picking, activeIde) {
18
4
  if (picking)
19
- return `Pick element (${ide}) — ${BADGE_PICKING_HINT}`;
20
- return `Source Locator (${ide}) — ${BADGE_IDLE_HINT}`;
5
+ return `Pick element (${activeIde}) — ${BADGE_PICKING_HINT}`;
6
+ return `Source Locator (${activeIde}) — ${BADGE_IDLE_HINT}`;
21
7
  }
@@ -1,6 +1,6 @@
1
1
  export type { LocatorTheme, LocatorThemeInput, LocatorThemeOverride, LocatorThemePreset, } from './theme.js';
2
2
  export { resolveTheme } from './theme.js';
3
- export type LocatorIde = 'cursor' | 'vscode' | 'webstorm';
3
+ export type LocatorIde = 'auto' | 'cursor' | 'vscode' | 'webstorm';
4
4
  export type SourceLocation = {
5
5
  file: string;
6
6
  line: string;
@@ -9,10 +9,10 @@ export type SourceLocation = {
9
9
  export type ClickTarget = 'tsx' | 'css';
10
10
  export declare const SOURCE_ATTR = "data-source";
11
11
  export declare const OPEN_ENDPOINT = "/__open-in-editor";
12
- export declare const STORAGE_KEY = "locator-ide";
13
12
  export declare const DEFAULT_IDE: LocatorIde;
14
13
  export declare const IDE_ORDER: LocatorIde[];
15
14
  export declare function isLocatorIde(value: string): value is LocatorIde;
15
+ export declare function resolveIde(value: string, allowed: LocatorIde[]): LocatorIde;
16
16
  export declare function nextIde(current: LocatorIde, order?: LocatorIde[]): LocatorIde;
17
17
  export declare function parseSourceLocation(raw: string): SourceLocation;
18
18
  export declare function formatSourceLocation(loc: SourceLocation): string;
@@ -1,12 +1,19 @@
1
1
  export { resolveTheme } from './theme.js';
2
2
  export const SOURCE_ATTR = 'data-source';
3
3
  export const OPEN_ENDPOINT = '/__open-in-editor';
4
- export const STORAGE_KEY = 'locator-ide';
5
- export const DEFAULT_IDE = 'cursor';
6
- export const IDE_ORDER = ['cursor', 'vscode', 'webstorm'];
4
+ export const DEFAULT_IDE = 'auto';
5
+ export const IDE_ORDER = ['auto', 'cursor', 'vscode', 'webstorm'];
7
6
  export function isLocatorIde(value) {
8
7
  return IDE_ORDER.includes(value);
9
8
  }
9
+ export function resolveIde(value, allowed) {
10
+ const fallback = allowed[0] ?? DEFAULT_IDE;
11
+ if (!isLocatorIde(value))
12
+ return fallback;
13
+ if (!allowed.includes(value))
14
+ return fallback;
15
+ return value;
16
+ }
10
17
  export function nextIde(current, order = IDE_ORDER) {
11
18
  const index = order.indexOf(current);
12
19
  const nextIndex = (index + 1) % order.length;
@@ -1,2 +1,2 @@
1
- import type { SourceLocation } from '../shared/index.js';
2
- export declare function openInEditor(loc: SourceLocation, ideParam: string): void;
1
+ import type { LocatorIde, SourceLocation } from '../shared/index.js';
2
+ export declare function openInEditor(loc: SourceLocation, ideParam: string, allowed: LocatorIde[]): void;
@@ -1,7 +1,11 @@
1
1
  import launch from 'launch-editor';
2
- import { DEFAULT_IDE, isLocatorIde } from '../shared/index.js';
3
- export function openInEditor(loc, ideParam) {
4
- const ide = isLocatorIde(ideParam) ? ideParam : DEFAULT_IDE;
2
+ import { resolveIde } from '../shared/index.js';
3
+ export function openInEditor(loc, ideParam, allowed) {
4
+ const ide = resolveIde(ideParam, allowed);
5
5
  const spec = `${loc.file}:${loc.line}:${loc.col}`;
6
+ if (ide === 'auto') {
7
+ launch(spec);
8
+ return;
9
+ }
6
10
  launch(spec, ide);
7
11
  }
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { isAbsolute, resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- import { DEFAULT_IDE, IDE_ORDER, OPEN_ENDPOINT, SOURCE_ATTR } from '../shared/index.js';
4
+ import { IDE_ORDER, OPEN_ENDPOINT, SOURCE_ATTR } from '../shared/index.js';
5
5
  import { babelPluginAddSourceAttr } from './babel-plugin.js';
6
6
  import { openInEditor } from './editors.js';
7
7
  const VIRTUAL_CLIENT_ID = 'virtual:source-locator-client';
@@ -16,13 +16,13 @@ function resolveOptions(options = {}) {
16
16
  theme: options.theme,
17
17
  };
18
18
  }
19
- function readQuery(url) {
19
+ function readQuery(url, defaultIde) {
20
20
  const parsed = new URL(url, 'http://localhost');
21
21
  return {
22
22
  file: parsed.searchParams.get('file'),
23
23
  line: parsed.searchParams.get('line') ?? '1',
24
24
  col: parsed.searchParams.get('col') ?? '1',
25
- ide: parsed.searchParams.get('ide') ?? DEFAULT_IDE,
25
+ ide: parsed.searchParams.get('ide') ?? defaultIde,
26
26
  };
27
27
  }
28
28
  function resolveFilePath(file, root) {
@@ -56,7 +56,7 @@ function sourceLocator(options = {}) {
56
56
  },
57
57
  configureServer(server) {
58
58
  server.middlewares.use(config.endpoint, (req, res) => {
59
- const { file, line, col, ide } = readQuery(req.url ?? '');
59
+ const { file, line, col, ide } = readQuery(req.url ?? '', config.ides[0] ?? 'auto');
60
60
  if (!file) {
61
61
  res.writeHead(400);
62
62
  res.end('missing file');
@@ -69,7 +69,7 @@ function sourceLocator(options = {}) {
69
69
  res.end('file not found');
70
70
  return;
71
71
  }
72
- openInEditor({ file: resolvedFile, line, col }, ide);
72
+ openInEditor({ file: resolvedFile, line, col }, ide, config.ides);
73
73
  res.writeHead(200, { 'Content-Type': 'text/plain' });
74
74
  res.end('ok');
75
75
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-source-locator",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Dev-only Vite plugin: click UI elements to jump to source in your IDE",
5
5
  "license": "MIT",
6
6
  "type": "module",