feat: Second pass aligning interactions with IntelliJ IDEA (#23)

* feat: Second pass aligning interactions with IntelliJ IDEA

Result tool windows
- Next/Previous Occurrence (⌥⌘↓/↑) across Find, Usages, Problems, TODO,
  Inspection and Hierarchy, with an "N of M" status hint
- Find: reuse the unpinned tab, Pin Tab, source preview, group by file or
  directory, exclude moves to the next result, Rerun
- Usages: tabs with pinning, exclude and preview
- Problems, TODO, Inspection: no autoscroll by default, severity-first sorting,
  context menu with quick fixes and copy description
- Trees: F4/⌘↓ jump to source; speed search Home/End and arrow handling

Find in Files, bookmarks, navigation
- Replace mode: Enter replaces the selection, ⇧⌥Enter replaces all, multi-select
- Pin Window toggle, highlighted read-only preview, search/replace history (⌥↓),
  Alt mnemonics, search context filter (comments / string literals)
- Mnemonic bookmarks (⌥F3, ⌃⇧digit, ⌃digit), ⌘F3 bookmarks popup, descriptions,
  ⌘-click gutter toggles a bookmark
- Select In popup (⌥F1); Recent Locations double-click and Delete;
  Search Everywhere remembers the query per tab

Code insight and refactoring
- Extract/Introduce continue with in-place rename and name suggestions;
  expression and occurrence choosers with live highlighting
- Safe Delete confirms first; numbered Refactor This
- Quick Documentation history, in-popup links and a Documentation tool window;
  Quick Definition switches between targets and shows the whole declaration
- Goto chooser lists symbol names and can pin to the Find window
- Parameter Info Tab/⇧Tab, intention preview, Reformat dialog and summary,
  inlay hint ⌘-click and context menu
- Live templates: Enter moves to the next field, two-step Esc, ⌘J as a lookup,
  language surrounders with mnemonics
- File Structure popup as a filterable tree; Show Usages filter and preview;
  call hierarchy jumps to call sites

Editor
- Paste re-indents each line by default; Move Statement moves whole statements
  in and out of blocks and re-indents
- Type over closing quotes of existing strings; ⌘D/⌘⌫ cover folded regions;
  ⌥⌫ deletes the preceding word
- ⌃G / ⌃⌘G whole-word rules and wrap hint; double-modifier caret cloning
- Visual-column block selection, nested Collapse All, recursive folding,
  fold selection toggle, Go to Matching Brace, gutter drag selects lines,
  sticky line and error stripe clicks, Insert toggles overwrite mode

Run, VCS, workbench
- Gutter icons show the last test result; Modify Run Configuration; test tree
  hides passed tests, context menu and assertion diff
- Stack frames resolve by fully qualified class name; context run chooser
- Inline Smart Step Into
- Git Log multi-select filters, "me" and date range; favourite branches,
  Fetch and tags in the branch popup; stash Keep index and Unstash options
- Terminal tab titles follow the shell; Distraction Free persists; gear menu;
  sticky balloons move to the notifications tool window after a while

* fix(vcs): Label restored local changes as yours after Smart Checkout; name the branch checked out

Conflicts from restoring stashed changes show the user's uncommitted
changes on the left like other operations, and the checkout notification
names the new branch instead of the previous one.

* fix: Close the merge editor on Apply; confirm before terminating a running Run tab

Like IDEA, applying a merge result closes its tab, and closing a Run tab
whose process still runs asks before terminating it (with a Settings
toggle and "Do not ask again").

* fix(terminal): Don't start a new session in the background after closing the last tab

The hidden Terminal window stays mounted, so it reopened a session as soon
as the last tab closed. It now opens the Local tab only once the window is
shown again, as IDEA does.

* fix(vcs): Start the merge result from a three-way merge; close the editor on Apply

The result pane now begins with non-conflicting changes merged and each
conflict showing the base text, resolved from the side panes, instead of
raw conflict markers. Apply now reports success, so the tab closes.

fix(search): Match File Structure filter on word starts

* fix: Limit Smart Step Into to the paused line; drop usages hints on callbacks; show full intention titles

Adapters that list every remaining call in the function (debugpy) made Smart
Step Into fall back to a chooser that offered calls from later lines. Targets
are now narrowed to the calls on the paused line, so they are picked in place.

Anonymous functions and callbacks no longer get a 'no usages' hint, and
intention titles are no longer cut short when there is room for them.

* fix: Undo / Redo from the Edit menu and Find Action use the editor's history

They ran the webview's native undo, which misses programmatic edits such as
templates, completion and refactorings, so nothing happened in the editor.
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-10-25 09:44:46 +08:00
1 джерело 81e0e8e2d0
коміт 82b6980a59
262 files changed
+13085 -1596

No files matched your search

+6 -2
Переглянути файл
@@ -23,6 +23,8 @@ export interface ListProps<T> {
activeIndex?: number;
defaultActiveIndex?: number;
onActiveChange?: (index: number) => void;
/** Rows selected besides the active one (multi-selection); drawn like the active row. */
isSelected?: (item: T, index: number) => boolean;
onActivate?: (item: T, index: number, e: KeyboardEvent | MouseEvent) => void;
onContextMenu?: (item: T, index: number, e: MouseEvent) => void;
rowHeight?: number;
@@ -102,6 +104,7 @@ export function List<T>({
activeIndex,
defaultActiveIndex,
onActiveChange,
isSelected,
onActivate,
onContextMenu,
rowHeight = 24,
@@ -187,15 +190,16 @@ export function List<T>({
);
}
const isActive = v.index === active;
const selected = isActive || (isSelected?.(item, v.index) ?? false);
return (
<div
key={v.key}
id={`${baseId}-${v.index}`}
role="option"
aria-selected={isActive}
aria-selected={selected}
aria-posinset={optionIndex.idx[v.index]}
aria-setsize={optionIndex.count}
className={clsx(styles.row, isActive && styles.active)}
className={clsx(styles.row, selected && styles.active)}
style={style}
onPointerMove={hoverSelects && !isActive ? () => setActive(v.index) : undefined}
onMouseDown={(e) => {
+55
Переглянути файл
@@ -0,0 +1,55 @@
.preview {
/* offsetParent for the scroll-to-line math. */
position: relative;
flex: 1;
min-height: 0;
height: 100%;
overflow: auto;
background: var(--bg-app);
font-family: var(--font-mono);
font-size: var(--fs-ui);
line-height: 19px;
color: var(--fg);
}
.empty {
display: flex;
align-items: center;
justify-content: center;
color: var(--fg-faint);
font-family: var(--font-ui);
}
.pre {
margin: 0;
padding: 4px 0;
font: inherit;
min-width: max-content;
}
.codeLine {
display: flex;
white-space: pre;
}
.currentLine {
background: var(--bg-current-line);
}
.gutter {
flex: none;
position: sticky;
left: 0;
width: 48px;
padding-right: 12px;
text-align: right;
color: var(--fg-gutter);
background-color: var(--bg-app);
}
.currentLine .gutter {
color: var(--fg-muted);
background-color: var(--bg-current-line);
}
.code {
padding-right: 16px;
}
.match {
background: var(--accent-soft);
color: var(--fg-strong);
border-radius: 2px;
}
+111
Переглянути файл
@@ -0,0 +1,111 @@
/**
* Read-only source view around a line, with line numbers and highlighted
* ranges: the preview pane beside result trees (TODO, Problems, Inspection
* Results…). The caller supplies the text loader (open buffer or disk).
*/
import { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from "react";
import clsx from "clsx";
import { T } from "./T";
import styles from "./SourcePreview.module.css";
export interface SourceHighlight {
line: number;
/** UTF-16 column ranges in the line. */
ranges: ReadonlyArray<readonly [number, number]>;
}
/** Lines rendered on each side of the current one. */
const WINDOW = 300;
export interface SourcePreviewProps {
path: string | null;
/** 0-based line to centre and mark as current. */
line: number;
highlights?: readonly SourceHighlight[];
/** Stable (module-level) loader; a new function reloads. */
load: (path: string) => Promise<string>;
/** Bump to reload the same path. */
version?: number;
className?: string;
}
export function SourcePreview({ path, line, highlights = [], load, version = 0, className }: SourcePreviewProps) {
const [text, setText] = useState<{ path: string; lines: string[] } | null>(null);
const [error, setError] = useState<string | null>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const current = text && text.path === path ? text : null;
useEffect(() => {
if (!path) return;
let alive = true;
// Debounced so arrowing through results doesn't load every file.
const t = setTimeout(() => {
load(path).then(
(content) => {
if (!alive) return;
setText({ path, lines: content.split(/\r\n|\r|\n/) });
setError(null);
},
(e) => alive && setError(String((e as Error)?.message ?? e)),
);
}, 80);
return () => {
alive = false;
clearTimeout(t);
};
}, [path, version, load]);
useLayoutEffect(() => {
const box = scrollRef.current;
const el = box?.querySelector<HTMLElement>(`[data-line="${line}"]`);
if (box && el) box.scrollTop = el.offsetTop - box.clientHeight / 2 + el.offsetHeight / 2;
}, [current, line]);
if (!path)
return (
<div className={clsx(styles.preview, styles.empty, className)}>
<T>Select an item to preview</T>
</div>
);
if (error && !current) return <div className={clsx(styles.preview, styles.empty, className)}>{error}</div>;
if (!current) return <div className={clsx(styles.preview, className)} />;
const byLine = new Map(highlights.map((h) => [h.line, h.ranges]));
const start = Math.max(0, line - WINDOW);
const end = Math.min(current.lines.length, line + WINDOW);
const rows: ReactNode[] = [];
for (let n = start; n < end; n++) {
const content = current.lines[n] ?? "";
const ranges = byLine.get(n);
rows.push(
<div key={n} data-line={n} className={clsx(styles.codeLine, n === line && styles.currentLine)}>
<span className={styles.gutter}>{n + 1}</span>
<span className={styles.code}>{ranges ? highlight(content, ranges) : content || " "}</span>
</div>,
);
}
return (
<div ref={scrollRef} className={clsx(styles.preview, className)} role="region" aria-label="Preview">
<pre className={styles.pre}>{rows}</pre>
</div>
);
}
function highlight(text: string, ranges: ReadonlyArray<readonly [number, number]>): ReactNode[] {
const parts: ReactNode[] = [];
let pos = 0;
[...ranges]
.sort((a, b) => a[0] - b[0])
.forEach(([s, e], i) => {
if (s < pos || e <= s) return;
if (s > pos) parts.push(text.slice(pos, s));
parts.push(
<mark key={i} className={styles.match}>
{text.slice(s, e)}
</mark>,
);
pos = e;
});
if (pos < text.length) parts.push(text.slice(pos));
return parts;
}
+75 -8
Переглянути файл
@@ -1,6 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import { fireEvent, render, screen, within } from "@testing-library/react";
import { Tree } from "./Tree";
import { createRef } from "react";
import { act, fireEvent, render, screen, within } from "@testing-library/react";
import { Tree, type TreeHandle } from "./Tree";
interface N {
id: string;
@@ -78,6 +79,16 @@ describe("Tree keyboard navigation", () => {
expect(onActivate).toHaveBeenCalledWith(expect.objectContaining({ id: "README.md" }), expect.anything());
});
it("F4 and ⌘↓ activate like Enter (Jump to Source)", () => {
const { key, onActivate } = setup();
key("End");
key("F4");
expect(onActivate).toHaveBeenCalledTimes(1);
key("ArrowDown", { metaKey: true });
expect(onActivate).toHaveBeenCalledTimes(2);
expect(onActivate).toHaveBeenLastCalledWith(expect.objectContaining({ id: "README.md" }), expect.anything());
});
it("extends the selection with Shift+Arrow", () => {
const onSelectionChange = vi.fn();
const { key } = setup({ defaultExpanded: ["src"], onSelectionChange });
@@ -121,6 +132,22 @@ describe("Tree speed search", () => {
expect(tree.querySelectorAll("mark")).toHaveLength(0);
});
it("Home / End jump to the first / last match; Left closes the search and collapses", () => {
const { tree, lead, key } = setup({ defaultExpanded: ["src", "main"] });
key("ArrowDown");
key("t");
key("s");
// "ts" matches App.tsx, index.ts and util.ts.
key("End");
expect(lead()).toBe("util.ts");
key("Home");
expect(lead()).toBe("App.tsx");
key("ArrowLeft");
expect(screen.queryByRole("status")).toBeNull();
expect(lead()).toBe("main");
expect(labels(tree)).toContain("App.tsx");
});
it("filter mode hides non-matching rows but keeps ancestors", () => {
const { tree, key } = setup({ defaultExpanded: ["src", "main"], speedSearchMode: "filter" });
key("ArrowDown");
@@ -129,16 +156,56 @@ describe("Tree speed search", () => {
});
});
describe("Tree occurrences", () => {
it("selects the next / previous leaf, expanding its ancestors", () => {
const handle = createRef<TreeHandle>();
const onSelectionChange = vi.fn();
const { tree, lead } = setup({ handleRef: handle, onSelectionChange });
let step: ReturnType<TreeHandle["selectOccurrence"]> = null;
act(() => void (step = handle.current!.selectOccurrence(1)));
expect(step).toMatchObject({ key: "App.tsx", index: 1, total: 4 });
expect(labels(tree)).toEqual(["src", "main", "App.tsx", "index.ts", "util.ts", "README.md"]);
expect(lead()).toBe("App.tsx");
expect(onSelectionChange).toHaveBeenLastCalledWith(
new Set(["App.tsx"]),
expect.objectContaining({ id: "App.tsx" }),
);
act(() => void (step = handle.current!.selectOccurrence(1)));
expect(step).toMatchObject({ key: "index.ts", index: 2 });
act(() => void (step = handle.current!.selectOccurrence(-1)));
expect(step).toMatchObject({ key: "App.tsx", index: 1 });
act(() => void (step = handle.current!.selectOccurrence(-1)));
expect(step).toBeNull();
expect(lead()).toBe("App.tsx");
});
it("starts from the selected group and honours isOccurrence", () => {
const handle = createRef<TreeHandle>();
const { key } = setup({ handleRef: handle, isOccurrence: (n) => n.id !== "src" });
key("ArrowDown"); // src
let step: ReturnType<TreeHandle["selectOccurrence"]> = null;
act(() => void (step = handle.current!.selectOccurrence(1)));
expect(step).toMatchObject({ key: "main", index: 1, total: 5 });
act(() => void (step = handle.current!.selectOccurrence(-1)));
expect(step).toBeNull();
});
it("skips the lead's subtree on request", () => {
const handle = createRef<TreeHandle>();
const { key } = setup({ handleRef: handle, defaultExpanded: ["src"] });
key("ArrowDown");
key("ArrowDown"); // main
let step: ReturnType<TreeHandle["selectOccurrence"]> = null;
act(() => void (step = handle.current!.selectOccurrence(1, { skipSubtree: true })));
expect(step).toMatchObject({ key: "util.ts", index: 3 });
});
});
describe("siblingPositions", () => {
it("numbers rows among siblings", async () => {
const { siblingPositions } = await import("./Tree");
expect(
siblingPositions([
{ parentKey: null },
{ parentKey: "a" },
{ parentKey: "a" },
{ parentKey: null },
]),
siblingPositions([{ parentKey: null }, { parentKey: "a" }, { parentKey: "a" }, { parentKey: null }]),
).toEqual([
[1, 2],
[1, 2],
+107 -8
Переглянути файл
@@ -1,9 +1,10 @@
/**
* Virtualized tree with lazy children, multi-selection, IDEA keyboard model
* (↑↓←→, Enter, Home/End, PageUp/PageDown, Shift-extend) and speed search:
* typing while the tree is focused highlights matches and jumps to them
* (↑/↓ cycle matches, Esc clears); `speedSearchMode="filter"` hides rows
* that neither match nor contain a match.
* (↑↓←→, Enter / F4 / ⌘↓, Home/End, PageUp/PageDown, Shift-extend) and speed
* search: typing while the tree is focused highlights matches and jumps to them
* (↑/↓ cycle matches, Home/End first / last match, Esc clears; ←/→, Enter and
* PageUp/PageDown close it and act as usual); `speedSearchMode="filter"` hides
* rows that neither match nor contain a match.
*/
import {
useCallback,
@@ -39,6 +40,14 @@ export interface TreeRowState {
matches: number[] | null;
}
/** Where Next / Previous Occurrence landed: the selected node and its 1-based position among all occurrences. */
export interface TreeOccurrence {
key: string;
node: unknown;
index: number;
total: number;
}
export interface TreeHandle {
focus(): void;
/** Selects `key` (must be visible, i.e. ancestors expanded) and scrolls to it. */
@@ -46,6 +55,13 @@ export interface TreeHandle {
leadKey(): string | null;
/** Selects the next (1) / previous (-1) row, for a filter field that keeps the focus (Settings search). */
moveSelection(dir: 1 | -1): void;
/**
* Next / Previous Occurrence: selects the next (1) / previous (-1) node that
* `isOccurrence` accepts (default: leaves) after the lead, in tree order over
* the loaded nodes, expanding its collapsed ancestors. `null` when there is none.
* `skipSubtree`: forward, ignore the lead's descendants (after excluding a group).
*/
selectOccurrence(dir: 1 | -1, options?: { skipSubtree?: boolean }): TreeOccurrence | null;
}
export interface TreeProps<T> {
@@ -77,6 +93,8 @@ export interface TreeProps<T> {
/** Enter / double-click on a leaf (or on nodes `activateExpandable` accepts). */
onActivate?: (node: T, e: KeyboardEvent | MouseEvent) => void;
activateExpandable?: boolean | ((node: T) => boolean);
/** Nodes Next / Previous Occurrence stops at (`TreeHandle.selectOccurrence`); default: leaves. */
isOccurrence?: (node: T) => boolean;
onContextMenu?: (node: T | null, selection: Set<string>, e: MouseEvent) => void;
speedSearch?: boolean;
@@ -273,6 +291,32 @@ export function Tree<T>(props: TreeProps<T>) {
selectIndex(i);
virtualizer.scrollToIndex(i);
},
selectOccurrence: (dir: 1 | -1, options?: { skipSubtree?: boolean }) => {
const found = findOccurrence(
roots,
lead,
dir,
{ getKey, getChildren, isExpandable, isOccurrence: props.isOccurrence },
options?.skipSubtree,
);
if (!found) return null;
const missing = found.ancestors.filter((a) => !expanded.has(getKey(a)));
if (missing.length) {
const next = new Set(expanded);
for (const a of missing) {
next.add(getKey(a));
props.onExpandedChange?.(new Set(next), a, true);
}
if (!expandedControlled) setExpandedInner(next);
}
const key = getKey(found.node);
const sel = new Set([key]);
setAnchor(key);
if (!selectedControlled) setSelectedInner(sel);
setLead(key);
props.onSelectionChange?.(sel, found.node);
return { key, node: found.node, index: found.index, total: found.total };
},
}));
const findMatch = (from: number, dir: 1 | -1, inclusive: boolean, q = query, wrap = true): number => {
@@ -318,6 +362,15 @@ export function Tree<T>(props: TreeProps<T>) {
return;
}
const range = e.shiftKey && multiSelect;
// Jump to Source (F4 / ⌘↓) activates like Enter.
if (e.key === "F4" || (e.key === "ArrowDown" && e.metaKey && !e.shiftKey && !e.altKey)) {
if (!row) return;
setQuery("");
activate(row, e);
e.preventDefault();
e.stopPropagation();
return;
}
switch (e.key) {
case "Escape":
if (!query) return;
@@ -341,12 +394,14 @@ export function Tree<T>(props: TreeProps<T>) {
break;
}
case "ArrowRight":
setQuery("");
if (!row) return;
if (row.expandable && !row.expanded) setExpanded(row, true);
else if (row.expanded && rows[leadIndex + 1]?.parentKey === row.key && !rows[leadIndex + 1]!.loading)
selectIndex(leadIndex + 1);
break;
case "ArrowLeft":
setQuery("");
if (!row) return;
if (row.expanded) setExpanded(row, false);
else if (row.parentKey != null) {
@@ -355,15 +410,20 @@ export function Tree<T>(props: TreeProps<T>) {
}
break;
case "Home":
selectIndex(step(-1, 1), range ? "range" : "single");
break;
case "End":
selectIndex(step(rows.length, -1), range ? "range" : "single");
case "End": {
const first = e.key === "Home";
if (query) {
const hit = findMatch(-1, first ? 1 : -1, true, query, false);
if (hit >= 0) selectIndex(hit);
} else selectIndex(first ? step(-1, 1) : step(rows.length, -1), range ? "range" : "single");
break;
}
case "PageDown":
setQuery("");
selectIndex(Math.min(rows.length - 1, Math.max(0, leadIndex) + pageSize), range ? "range" : "single");
break;
case "PageUp":
setQuery("");
selectIndex(Math.max(0, leadIndex - pageSize), range ? "range" : "single");
break;
case "Enter":
@@ -542,3 +602,42 @@ export function siblingPositions(
rows.forEach((r, i) => (out[i]![1] = counts.get(r.parentKey)!));
return out;
}
/** The occurrence after (`dir` 1) / before (-1) `lead` in tree order over loaded nodes, with its ancestors. */
export function findOccurrence<T>(
roots: readonly T[],
lead: string | null,
dir: 1 | -1,
model: {
getKey: (node: T) => string;
getChildren: (node: T) => readonly T[] | undefined;
isExpandable: (node: T) => boolean;
isOccurrence?: (node: T) => boolean;
},
skipSubtree = false,
): { node: T; ancestors: T[]; index: number; total: number } | null {
const occurrences: Array<{ node: T; ancestors: T[]; at: number }> = [];
let leadAt = -1;
let leadEnd = -1;
let at = 0;
const walk = (nodes: readonly T[], ancestors: T[]) => {
for (const node of nodes) {
const here = at++;
const isLead = model.getKey(node) === lead;
if (isLead) leadAt = here;
const expandable = model.isExpandable(node);
if (model.isOccurrence ? model.isOccurrence(node) : !expandable) occurrences.push({ node, ancestors, at: here });
const children = expandable ? model.getChildren(node) : undefined;
if (children?.length) walk(children, [...ancestors, node]);
if (isLead) leadEnd = at - 1;
}
};
walk(roots, []);
const after = skipSubtree ? leadEnd : leadAt;
let i: number;
if (leadAt < 0) i = dir === 1 ? 0 : occurrences.length - 1;
else if (dir === 1) i = occurrences.findIndex((o) => o.at > after);
else i = occurrences.findLastIndex((o) => o.at < leadAt);
const hit = occurrences[i];
return hit ? { node: hit.node, ancestors: hit.ancestors, index: i + 1, total: occurrences.length } : null;
}
+5
Переглянути файл
@@ -0,0 +1,5 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 5.5C14 5.77614 13.7761 6 13.5 6C13.2239 6 13 5.77614 13 5.5V3L3 3L3 5.5C3 5.77614 2.77614 6 2.5 6C2.22386 6 2 5.77614 2 5.5V2.5C2 2.22386 2.22386 2 2.5 2H13.5C13.7761 2 14 2.22386 14 2.5V5.5Z" fill="#6C707E"/>
<path d="M5.14633 11.3537C4.95107 11.1584 4.95106 10.8418 5.14632 10.6465C5.34157 10.4513 5.65816 10.4513 5.85342 10.6465L7.50016 12.2932L7.50016 5.49996C7.50016 5.22381 7.72401 4.99996 8.00016 4.99996C8.2763 4.99996 8.50016 5.22381 8.50016 5.49996L8.50016 12.2927L10.1464 10.6464C10.3417 10.4511 10.6583 10.4511 10.8536 10.6464C11.0488 10.8417 11.0488 11.1583 10.8535 11.3535L8.35346 13.8536C8.1582 14.0488 7.84163 14.0488 7.64637 13.8536L5.14633 11.3537Z" fill="#6C707E"/>
</svg>

Після

Ширина:  |  Висота:  |  Розмір: 928 B

+5
Переглянути файл
@@ -0,0 +1,5 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 5.5C14 5.77614 13.7761 6 13.5 6C13.2239 6 13 5.77614 13 5.5V3L3 3L3 5.5C3 5.77614 2.77614 6 2.5 6C2.22386 6 2 5.77614 2 5.5V2.5C2 2.22386 2.22386 2 2.5 2H13.5C13.7761 2 14 2.22386 14 2.5V5.5Z" fill="#CED0D6"/>
<path d="M5.14633 11.3537C4.95107 11.1584 4.95106 10.8418 5.14632 10.6465C5.34157 10.4513 5.65816 10.4513 5.85342 10.6465L7.50016 12.2932L7.50016 5.49996C7.50016 5.22381 7.72401 4.99996 8.00016 4.99996C8.2763 4.99996 8.50016 5.22381 8.50016 5.49996L8.50016 12.2927L10.1464 10.6464C10.3417 10.4511 10.6583 10.4511 10.8536 10.6464C11.0488 10.8417 11.0488 11.1583 10.8535 11.3535L8.35346 13.8536C8.1582 14.0488 7.84163 14.0488 7.64637 13.8536L5.14633 11.3537Z" fill="#CED0D6"/>
</svg>

Після

Ширина:  |  Висота:  |  Розмір: 928 B

+5
Переглянути файл
@@ -0,0 +1,5 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5 6C13.7761 6 14 5.77614 14 5.5V2.5C14 2.22386 13.7761 2 13.5 2H2.5C2.22386 2 2 2.22386 2 2.5V5.5C2 5.77614 2.22386 6 2.5 6C2.77614 6 3 5.77614 3 5.5L3 3L13 3V5.5C13 5.77614 13.2239 6 13.5 6Z" fill="#6C707E"/>
<path d="M5.14644 8.35345C4.95118 8.15819 4.95119 7.8416 5.14646 7.64635L7.64649 5.14644C7.84175 4.95119 8.15833 4.95119 8.35358 5.14644L10.8537 7.64649C11.0489 7.84175 11.0489 8.15833 10.8537 8.35359C10.6584 8.54886 10.3418 8.54886 10.1466 8.3536L8.50028 6.70734L8.50028 13.5C8.50028 13.7762 8.27642 14 8.00028 14C7.72414 14 7.50028 13.7762 7.50028 13.5L7.50028 6.70682L5.85354 8.35347C5.65828 8.54873 5.3417 8.54872 5.14644 8.35345Z" fill="#6C707E"/>
</svg>

Після

Ширина:  |  Висота:  |  Розмір: 906 B

+5
Переглянути файл
@@ -0,0 +1,5 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5 6C13.7761 6 14 5.77614 14 5.5V2.5C14 2.22386 13.7761 2 13.5 2H2.5C2.22386 2 2 2.22386 2 2.5V5.5C2 5.77614 2.22386 6 2.5 6C2.77614 6 3 5.77614 3 5.5L3 3L13 3V5.5C13 5.77614 13.2239 6 13.5 6Z" fill="#CED0D6"/>
<path d="M5.14644 8.35345C4.95118 8.15819 4.95119 7.8416 5.14646 7.64635L7.64649 5.14644C7.84175 4.95119 8.15833 4.95119 8.35358 5.14644L10.8537 7.64649C11.0489 7.84175 11.0489 8.15833 10.8537 8.35359C10.6584 8.54886 10.3418 8.54886 10.1466 8.3536L8.50028 6.70734L8.50028 13.5C8.50028 13.7762 8.27642 14 8.00028 14C7.72414 14 7.50028 13.7762 7.50028 13.5L7.50028 6.70682L5.85354 8.35347C5.65828 8.54873 5.3417 8.54872 5.14644 8.35345Z" fill="#CED0D6"/>
</svg>

Після

Ширина:  |  Висота:  |  Розмір: 906 B

+8
Переглянути файл
@@ -0,0 +1,8 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.9681 6.09565C10.9892 5.89999 11 5.70126 11 5.5C11 2.46243 8.53757 0 5.5 0C2.46243 0 0 2.46243 0 5.5C0 8.53757 2.46243 11 5.5 11C5.66854 11 5.83532 10.9924 6 10.9776V7.50216C6 5.53926 8.15852 4.34204 9.8237 5.38136L10.9681 6.09565Z" fill="#E55765"/>
<path d="M5.5 2.5L5.5 6" stroke="white" stroke-width="1.2" stroke-linecap="round"/>
<path d="M6.25 8.25C6.25 8.66421 5.91421 9 5.5 9C5.08579 9 4.75 8.66421 4.75 8.25C4.75 7.83579 5.08579 7.5 5.5 7.5C5.91421 7.5 6.25 7.83579 6.25 8.25Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 7.50215C7 6.32441 8.29512 5.60608 9.29422 6.22967L13.2962 8.7275C14.2372 9.31485 14.2372 10.6851 13.2962 11.2725L9.29422 13.7703C8.29512 14.3939 7 13.6756 7 12.4978V7.50215ZM8.76474 7.07799C8.43171 6.87013 8 7.10957 8 7.50215V12.4978C8 12.8904 8.43171 13.1298 8.76474 12.922L12.7667 10.4241C13.0804 10.2284 13.0804 9.77161 12.7667 9.57582L8.76474 7.07799Z" fill="#208A3C"/>
<path d="M8 7.50217C8 7.10959 8.43171 6.87015 8.76474 7.07801L12.7667 9.57584C13.0804 9.77163 13.0804 10.2284 12.7667 10.4242L8.76474 12.922C8.43171 13.1299 8 12.8904 8 12.4978V7.50217Z" fill="#F2FCF3"/>
</svg>

Після

Ширина:  |  Висота:  |  Розмір: 1.3 KiB

+8
Переглянути файл
@@ -0,0 +1,8 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.9681 6.09565C10.9892 5.89999 11 5.70126 11 5.5C11 2.46243 8.53757 0 5.5 0C2.46243 0 0 2.46243 0 5.5C0 8.53757 2.46243 11 5.5 11C5.66854 11 5.83532 10.9924 6 10.9776V7.50216C6 5.53926 8.15852 4.34204 9.8237 5.38136L10.9681 6.09565Z" fill="#E55765"/>
<path d="M5.5 2.5L5.5 6" stroke="white" stroke-width="1.2" stroke-linecap="round"/>
<path d="M6.25 8.25C6.25 8.66421 5.91421 9 5.5 9C5.08579 9 4.75 8.66421 4.75 8.25C4.75 7.83579 5.08579 7.5 5.5 7.5C5.91421 7.5 6.25 7.83579 6.25 8.25Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 7.50215C7 6.32441 8.29512 5.60608 9.29422 6.22967L13.2962 8.7275C14.2372 9.31485 14.2372 10.6851 13.2962 11.2725L9.29422 13.7703C8.29512 14.3939 7 13.6756 7 12.4978V7.50215ZM8.76474 7.07799C8.43171 6.87013 8 7.10957 8 7.50215V12.4978C8 12.8904 8.43171 13.1298 8.76474 12.922L12.7667 10.4241C13.0804 10.2284 13.0804 9.77161 12.7667 9.57582L8.76474 7.07799Z" fill="#57965C"/>
<path d="M8 7.50217C8 7.10959 8.43171 6.87015 8.76474 7.07801L12.7667 9.57584C13.0804 9.77163 13.0804 10.2284 12.7667 10.4242L8.76474 12.922C8.43171 13.1299 8 12.8904 8 12.4978V7.50217Z" fill="#253627"/>
</svg>

Після

Ширина:  |  Висота:  |  Розмір: 1.3 KiB

+7
Переглянути файл
@@ -0,0 +1,7 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.9681 6.09565C10.9892 5.89999 11 5.70126 11 5.5C11 2.46243 8.53757 0 5.5 0C2.46243 0 0 2.46243 0 5.5C0 8.53757 2.46243 11 5.5 11C5.66854 11 5.83532 10.9924 6 10.9776V7.50216C6 5.53926 8.15852 4.34204 9.8237 5.38136L10.9681 6.09565Z" fill="#55A76A"/>
<path d="M2.5 5.5L4.5 7.5L8.5 3.5" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 7.50215C7 6.32441 8.29512 5.60608 9.29422 6.22967L13.2962 8.7275C14.2372 9.31485 14.2372 10.6851 13.2962 11.2725L9.29422 13.7703C8.29512 14.3939 7 13.6756 7 12.4978V7.50215ZM8.76474 7.07799C8.43171 6.87013 8 7.10957 8 7.50215V12.4978C8 12.8904 8.43171 13.1298 8.76474 12.922L12.7667 10.4241C13.0804 10.2284 13.0804 9.77161 12.7667 9.57582L8.76474 7.07799Z" fill="#208A3C"/>
<path d="M8 7.50217C8 7.10959 8.43171 6.87015 8.76474 7.07801L12.7667 9.57584C13.0804 9.77163 13.0804 10.2284 12.7667 10.4242L8.76474 12.922C8.43171 13.1299 8 12.8904 8 12.4978V7.50217Z" fill="#F2FCF3"/>
</svg>

Після

Ширина:  |  Висота:  |  Розмір: 1.2 KiB

+7
Переглянути файл
@@ -0,0 +1,7 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.9681 6.09565C10.9892 5.89999 11 5.70126 11 5.5C11 2.46243 8.53757 0 5.5 0C2.46243 0 0 2.46243 0 5.5C0 8.53757 2.46243 11 5.5 11C5.66854 11 5.83532 10.9924 6 10.9776V7.50216C6 5.53926 8.15852 4.34204 9.8237 5.38136L10.9681 6.09565Z" fill="#57965C"/>
<path d="M2.5 5.5L4.5 7.5L8.5 3.5" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 7.50215C7 6.32441 8.29512 5.60608 9.29422 6.22967L13.2962 8.7275C14.2372 9.31485 14.2372 10.6851 13.2962 11.2725L9.29422 13.7703C8.29512 14.3939 7 13.6756 7 12.4978V7.50215ZM8.76474 7.07799C8.43171 6.87013 8 7.10957 8 7.50215V12.4978C8 12.8904 8.43171 13.1298 8.76474 12.922L12.7667 10.4241C13.0804 10.2284 13.0804 9.77161 12.7667 9.57582L8.76474 7.07799Z" fill="#57965C"/>
<path d="M8 7.50217C8 7.10959 8.43171 6.87015 8.76474 7.07801L12.7667 9.57584C13.0804 9.77163 13.0804 10.2284 12.7667 10.4242L8.76474 12.922C8.43171 13.1299 8 12.8904 8 12.4978V7.50217Z" fill="#253627"/>
</svg>

Після

Ширина:  |  Висота:  |  Розмір: 1.2 KiB

+6
Переглянути файл
@@ -209,6 +209,8 @@ const ICONS = {
"collapse-all": "general/collapseAll",
"expand-all": "general/expandAll",
locate: "general/locate",
"autoscroll-to-source": "general/autoscrollToSource",
"autoscroll-from-source": "general/autoscrollFromSource",
sort: "objectBrowser/sortAlphabetically",
copy: "general/copy",
paste: "general/paste",
@@ -280,6 +282,8 @@ const ICONS = {
"toolwindow-profiler": "toolwindows/profiler",
"gutter-run": "gutter/run",
"gutter-run-all": "actions/runAll",
"gutter-run-passed": "gutter/runSuccess",
"gutter-run-failed": "gutter/runError",
stop: "run/stop",
rerun: "run/rerun",
"rerun-filled": "run/rerun_stroke",
@@ -343,6 +347,8 @@ const ICONS = {
changelist: "vcs/changelist",
stash: "openidea/vcs/stash",
star: "openidea/vcs/favorite",
"star-outline": "openidea/vcs/notFavorite",
tag: "openidea/vcs/tag",
"current-branch": "openidea/vcs/currentBranch",
// Symbols (also used by `symbol-<kind>`)
class: "nodes/class",
+1
Переглянути файл
@@ -12,6 +12,7 @@ export * from "./Popup";
export * from "./Menu";
export * from "./Dialog";
export * from "./Splitter";
export * from "./SourcePreview";
export * from "./Feedback";
export * from "./HighlightedText";
export { fuzzyMatch, indexRuns, wordMatch } from "./match";
+3
Переглянути файл
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 1.75L9.93 5.66L14.25 6.29L11.12 9.33L11.86 13.63L8 11.6L4.14 13.63L4.88 9.33L1.75 6.29L6.07 5.66L8 1.75Z" stroke="#6C707E" stroke-linejoin="round"/>
</svg>

Після

Ширина:  |  Висота:  |  Розмір: 264 B

+3
Переглянути файл
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 1.75L9.93 5.66L14.25 6.29L11.12 9.33L11.86 13.63L8 11.6L4.14 13.63L4.88 9.33L1.75 6.29L6.07 5.66L8 1.75Z" stroke="#CED0D6" stroke-linejoin="round"/>
</svg>

Після

Ширина:  |  Висота:  |  Розмір: 264 B

+4
Переглянути файл
@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 2.5H7.59L13.5 8.41L8.41 13.5L2.5 7.59V2.5Z" stroke="#6C707E" stroke-linejoin="round"/>
<circle cx="5.5" cy="5.5" r="1" fill="#6C707E"/>
</svg>

Після

Ширина:  |  Висота:  |  Розмір: 253 B

+4
Переглянути файл
@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 2.5H7.59L13.5 8.41L8.41 13.5L2.5 7.59V2.5Z" stroke="#CED0D6" stroke-linejoin="round"/>
<circle cx="5.5" cy="5.5" r="1" fill="#CED0D6"/>
</svg>

Після

Ширина:  |  Висота:  |  Розмір: 253 B