mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-12 18:36:53 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b89e2a603c | ||
|
|
7d7de0b966 | ||
|
|
830f4aa802 |
No files matched your search
@@ -108,7 +108,9 @@ fn discover_with_limit(
|
||||
if name.starts_with('.') || SKIP_DIRS.contains(&name.as_ref()) {
|
||||
continue;
|
||||
}
|
||||
if entry.file_type().is_ok_and(|kind| kind.is_dir()) {
|
||||
if entry.file_type().is_ok_and(|kind| kind.is_dir())
|
||||
&& crate::roots::virtual_env_marker(&entry.path()).is_none()
|
||||
{
|
||||
children.push(entry.path());
|
||||
}
|
||||
}
|
||||
@@ -170,6 +172,19 @@ mod tests {
|
||||
assert_eq!(result.direct[0], root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skips_virtual_environments_whatever_their_name() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
project(root, "code10", "pyvenv.cfg");
|
||||
project(root, "code10/Lib/site-packages/pkg", "pyproject.toml");
|
||||
fs::create_dir_all(root.join("conda/conda-meta")).unwrap();
|
||||
project(root, "conda/pkgs/dep", "setup.py");
|
||||
let app = project(root, "app", "pyproject.toml");
|
||||
let result = discover(vec![root.into()], &CancellationToken::new()).unwrap();
|
||||
assert_eq!(result.projects.iter().map(|p| p.path.to_path_buf()).collect::<Vec<_>>(), [app]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_depth_four_but_not_five_and_retains_empty_folders() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -17,6 +17,19 @@ const PYTHON_FILES: &[&str] = &["pyproject.toml", "setup.py", "setup.cfg", "Pipf
|
||||
const SKIP_DIRS: &[&str] =
|
||||
&["node_modules", "target", "build", "dist", "out", "venv", "__pycache__", "vendor", "bower_components"];
|
||||
|
||||
/// A Python virtual environment (`python -m venv`, virtualenv, uv) or conda environment,
|
||||
/// whatever it is named: its packages are dependencies, not modules of the project.
|
||||
/// Returns the file that marks it.
|
||||
pub(crate) fn virtual_env_marker(dir: &Path) -> Option<&'static str> {
|
||||
if dir.join("pyvenv.cfg").is_file() {
|
||||
Some("pyvenv.cfg")
|
||||
} else if dir.join("conda-meta").is_dir() {
|
||||
Some("conda-meta")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Build files whose changes can change the detected roots.
|
||||
pub(crate) const BUILD_FILES: &[&str] = &[
|
||||
"pom.xml",
|
||||
@@ -65,7 +78,10 @@ pub fn affects_roots(rel: &Path) -> bool {
|
||||
let Some(name) = names.next() else { return false };
|
||||
BUILD_FILES.contains(&name)
|
||||
|| ROOT_NAMES.contains(&name)
|
||||
|| matches!(name, "Assets" | "Packages" | "ProjectSettings" | "ProjectVersion.txt")
|
||||
|| matches!(
|
||||
name,
|
||||
"Assets" | "Packages" | "ProjectSettings" | "ProjectVersion.txt" | "pyvenv.cfg" | "conda-meta"
|
||||
)
|
||||
|| names.next() == Some("src")
|
||||
}
|
||||
|
||||
@@ -131,6 +147,13 @@ pub fn detect(root: &Path) -> Vec<SourceFolder> {
|
||||
if found.iter().any(|f| *f == child || f.is_empty() && name == "src") {
|
||||
continue;
|
||||
}
|
||||
if let Some(marker) = virtual_env_marker(&root.join(&child)) {
|
||||
if !out.iter().any(|r| r.path == child) {
|
||||
let from = join(&child, marker);
|
||||
out.push(SourceFolder { path: child, kind: RootKind::Excluded, detected_from: Some(from) });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
queue.push_back((child, depth + 1));
|
||||
}
|
||||
}
|
||||
@@ -505,6 +528,35 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn virtual_environments_are_excluded_whatever_their_name() {
|
||||
// A package in the environment's site-packages has build files of its own; as a
|
||||
// source root it would be put on PYTHONPATH and shadow the standard library.
|
||||
let dir = tree(&[
|
||||
"requirements.txt",
|
||||
"app.py",
|
||||
"code10/pyvenv.cfg",
|
||||
"code10/Lib/site-packages/pkg/pyproject.toml",
|
||||
"code10/Lib/site-packages/pkg/io/__init__.py",
|
||||
"envs/conda/conda-meta/",
|
||||
"envs/conda/lib/pkg/setup.py",
|
||||
"svc/pyproject.toml",
|
||||
"svc/env/pyvenv.cfg",
|
||||
"svc/env/lib/python3.12/site-packages/dep/setup.py",
|
||||
]);
|
||||
assert_eq!(
|
||||
summary(&detect(dir.path())),
|
||||
vec![
|
||||
"[email protected]",
|
||||
"code10=Excluded@code10/pyvenv.cfg",
|
||||
"envs/conda=Excluded@envs/conda/conda-meta",
|
||||
"svc=Source@svc/pyproject.toml",
|
||||
"svc/env=Excluded@svc/env/pyvenv.cfg",
|
||||
]
|
||||
);
|
||||
assert!(affects_roots(Path::new("code10/pyvenv.cfg")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_ecosystem_wins_for_a_shared_folder() {
|
||||
// A Gradle build with a frontend: `build` stays Gradle's, `src` is not a Node root.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! project-relative and `/`-separated; `""` is the project root itself.
|
||||
|
||||
mod detect;
|
||||
pub(crate) use detect::BUILD_FILES;
|
||||
pub(crate) use detect::{BUILD_FILES, virtual_env_marker};
|
||||
mod gradle;
|
||||
mod java;
|
||||
mod maven;
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
||||
import { EditorSelection, EditorState } from "@codemirror/state";
|
||||
import { EditorView } from "@codemirror/view";
|
||||
import { java } from "@codemirror/lang-java";
|
||||
import { history, undo } from "@codemirror/commands";
|
||||
import { renderDocComments, renderedDocsExtension, renderedRanges } from "./renderedDocs";
|
||||
|
||||
let view: EditorView | null = null;
|
||||
beforeEach(() => void vi.useFakeTimers());
|
||||
afterEach(() => {
|
||||
view?.destroy();
|
||||
view = null;
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
const DOC = ["class Rooms {", " int n;", "", " /** Books a room. */", " void book() {}", "}"].join("\n");
|
||||
|
||||
function open(doc = DOC, extra: ReturnType<typeof history>[] = []) {
|
||||
view = new EditorView({
|
||||
state: EditorState.create({
|
||||
doc,
|
||||
extensions: [java(), renderedDocsExtension, renderDocComments.of(true), ...extra],
|
||||
}),
|
||||
parent: document.body,
|
||||
});
|
||||
return view;
|
||||
}
|
||||
|
||||
/** Types `text` at `at` the way a keystroke does. */
|
||||
const type = (v: EditorView, at: number, text: string) =>
|
||||
v.dispatch({ changes: { from: at, insert: text }, userEvent: "input.type" });
|
||||
|
||||
/** What a full rebuild of the same text renders, in a fresh editor state. */
|
||||
function fullRebuild(doc: string): Array<[number, number]> {
|
||||
return renderedRanges(
|
||||
EditorState.create({ doc, extensions: [java(), renderedDocsExtension, renderDocComments.of(true)] }),
|
||||
);
|
||||
}
|
||||
|
||||
const pause = () => vi.advanceTimersByTime(300);
|
||||
|
||||
it("moves rendered comments along with text typed in code, and rebuilds once typing pauses", () => {
|
||||
const v = open();
|
||||
const [before] = renderedRanges(v.state);
|
||||
// `/*` in code makes everything after it a comment: a full rebuild would drop the doc
|
||||
// comment at once. Typing only moves it along until the pause.
|
||||
type(v, v.state.doc.line(2).from + 2, "/*");
|
||||
expect(renderedRanges(v.state)).toEqual([[before![0] + 2, before![1] + 2]]);
|
||||
pause();
|
||||
expect(renderedRanges(v.state)).toEqual(fullRebuild(v.state.doc.toString()));
|
||||
expect(renderedRanges(v.state)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("renders a pasted comment at once", () => {
|
||||
const v = open();
|
||||
v.dispatch({
|
||||
changes: { from: v.state.doc.line(5).to + 1, insert: " /** Pasted. */\n void paste() {}\n" },
|
||||
userEvent: "input.paste",
|
||||
});
|
||||
expect(renderedRanges(v.state)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("brings a deleted comment back rendered at once on undo", () => {
|
||||
const v = open(DOC, [history()]);
|
||||
const line = v.state.doc.line(4);
|
||||
v.dispatch({ changes: { from: line.from, to: line.to + 1 }, userEvent: "delete.selection" });
|
||||
expect(renderedRanges(v.state)).toHaveLength(0);
|
||||
undo(v);
|
||||
expect(renderedRanges(v.state)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("rebuilds at once for a keystroke on a comment's line", () => {
|
||||
const v = open();
|
||||
// Code after the closing marker: the comment no longer has its line to itself.
|
||||
type(v, v.state.doc.line(4).to, " int x;");
|
||||
expect(renderedRanges(v.state)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("still shows the source of a comment the caret lands in after fast typing", () => {
|
||||
const v = open();
|
||||
type(v, v.state.doc.line(2).from + 2, "xx");
|
||||
const at = v.state.doc.toString().indexOf("Books");
|
||||
v.dispatch({ selection: EditorSelection.cursor(at) });
|
||||
expect(renderedRanges(v.state)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("after the pause, matches a full rebuild of the same text, whatever was typed", () => {
|
||||
// A seeded generator, so a failure replays.
|
||||
let seed = 7;
|
||||
const next = () => (seed = (seed * 1103515245 + 12345) % 2147483648) / 2147483648;
|
||||
const lines = ["class A {"];
|
||||
for (let i = 0; i < 12; i++) lines.push(` /** Doc ${i}. */`, ` void m${i}() {}`, " /// line doc", "");
|
||||
lines.push("}");
|
||||
const v = open(lines.join("\n"));
|
||||
const keys = ["/", "*", "\n", "x", " ", "{", "}"];
|
||||
for (let i = 0; i < 400; i++) {
|
||||
const at = Math.floor(next() * (v.state.doc.length + 1));
|
||||
if (next() < 0.3 && at > 0) v.dispatch({ changes: { from: at - 1, to: at }, userEvent: "delete.backward" });
|
||||
else type(v, at, keys[Math.floor(next() * keys.length)]!);
|
||||
if (next() < 0.05) pause();
|
||||
}
|
||||
pause();
|
||||
expect(renderedRanges(v.state)).toEqual(fullRebuild(v.state.doc.toString()));
|
||||
});
|
||||
@@ -25,8 +25,13 @@
|
||||
* Off, which is the default, the icon still shows over a comment, so one comment can be
|
||||
* read rendered, as in IDEA. That costs typing nothing: while the file renders no
|
||||
* comments and none is flipped, its comments are found only once typing pauses
|
||||
* (`passiveScanner`), the Markdown parser is not loaded, and the gutter has a column
|
||||
* (`deferredScanner`), the Markdown parser is not loaded, and the gutter has a column
|
||||
* only if the file has doc comments.
|
||||
*
|
||||
* On, typing costs about as little: a keystroke away from every comment's lines only
|
||||
* moves the rendered comments along with the text, and the file is scanned again once
|
||||
* typing pauses. Anything else that changes the text (paste, undo, a reload) rebuilds at
|
||||
* once, so the comments it brings in render at once.
|
||||
*/
|
||||
import {
|
||||
EditorState,
|
||||
@@ -36,6 +41,7 @@ import {
|
||||
RangeSet,
|
||||
StateEffect,
|
||||
StateField,
|
||||
type ChangeSet,
|
||||
type Extension,
|
||||
type Transaction,
|
||||
} from "@codemirror/state";
|
||||
@@ -369,11 +375,55 @@ type PlacedComment = DocComment & { line: number; end: number; rendered: boolean
|
||||
|
||||
interface DocsView {
|
||||
decorations: DecorationSet;
|
||||
/** Every doc comment, rendered or not. */
|
||||
/** Every doc comment, rendered or not, in document order. */
|
||||
comments: ReadonlyArray<PlacedComment>;
|
||||
/**
|
||||
* Moved along with keystrokes since the last scan, instead of found again: what the
|
||||
* parser made of them may differ, until `deferredScanner` rebuilds.
|
||||
*/
|
||||
stale: boolean;
|
||||
}
|
||||
|
||||
const INACTIVE: DocsView = { decorations: Decoration.none, comments: [] };
|
||||
const INACTIVE: DocsView = { decorations: Decoration.none, comments: [], stale: false };
|
||||
|
||||
/** Asks for the scan a stale view is waiting for. */
|
||||
const rescanDocs = StateEffect.define<null>();
|
||||
|
||||
/** Comments moved along with a change, as an edit elsewhere moves them; their text is unchanged. */
|
||||
function mapComments(comments: ReadonlyArray<PlacedComment>, changes: ChangeSet): PlacedComment[] {
|
||||
return comments.map((c) => ({
|
||||
...c,
|
||||
from: changes.mapPos(c.from, 1),
|
||||
to: changes.mapPos(c.to, -1),
|
||||
line: changes.mapPos(c.line, 1),
|
||||
end: changes.mapPos(c.end, -1),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a change can skip the scan: a keystroke (typed or deleted characters) that
|
||||
* touches no comment's lines. Everything else (paste, undo, formatting, a reload) brings
|
||||
* comments in or out at once, so it rebuilds at once; a keystroke on a comment's line
|
||||
* moves its edges. A keystroke elsewhere can still change comments (`/*` makes the rest of
|
||||
* the file one), which the scan after the pause takes in.
|
||||
*/
|
||||
function typedOutsideComments(view: DocsView, tr: Transaction): boolean {
|
||||
if (!(tr.isUserEvent("input.type") || tr.isUserEvent("delete"))) return false;
|
||||
const { comments } = view;
|
||||
let touches = false;
|
||||
tr.changes.iterChangedRanges((fromA, toA) => {
|
||||
// The first comment that does not end before the change.
|
||||
let lo = 0;
|
||||
let hi = comments.length;
|
||||
while (lo < hi) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
if (comments[mid]!.end < fromA) lo = mid + 1;
|
||||
else hi = mid;
|
||||
}
|
||||
if (lo < comments.length && comments[lo]!.line <= toA) touches = true;
|
||||
});
|
||||
return !touches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendered comments by their source, shared by every editor, least recently used first.
|
||||
@@ -442,7 +492,7 @@ function build(state: EditorState): DocsView {
|
||||
),
|
||||
);
|
||||
}
|
||||
return { decorations: Decoration.set(decorations), comments };
|
||||
return { decorations: Decoration.set(decorations), comments, stale: false };
|
||||
}
|
||||
|
||||
/** Start of the comment the mouse is over, or null. Cleared by any edit, which moves the text under it. */
|
||||
@@ -476,7 +526,7 @@ const setPassive = StateEffect.define<readonly PlacedComment[]>();
|
||||
/**
|
||||
* The doc comments of a file that renders none, for their icons. Rescanning the file on
|
||||
* each keystroke would make typing in a large file several times slower for a feature
|
||||
* that is off, so `passiveScanner` rescans once typing pauses; meanwhile the comments
|
||||
* that is off, so `deferredScanner` rescans once typing pauses; meanwhile the comments
|
||||
* found are mapped through the edits. Empty while the file renders comments: `docsField`
|
||||
* has them then.
|
||||
*/
|
||||
@@ -487,32 +537,29 @@ const passiveDocs = StateField.define<readonly PlacedComment[]>({
|
||||
// Switched off: the comments rendered a moment ago are known only to the last scan.
|
||||
if (active(tr.startState)) return placeComments(tr.state);
|
||||
for (const e of tr.effects) if (e.is(setPassive)) return e.value;
|
||||
if (!tr.docChanged) return value;
|
||||
const map = (pos: number, assoc: number) => tr.changes.mapPos(pos, assoc);
|
||||
return value.map((c) => ({
|
||||
...c,
|
||||
from: map(c.from, 1),
|
||||
to: map(c.to, -1),
|
||||
line: map(c.line, 1),
|
||||
end: map(c.end, -1),
|
||||
}));
|
||||
return tr.docChanged ? mapComments(value, tr.changes) : value;
|
||||
},
|
||||
});
|
||||
|
||||
/** Scans a file that renders no comments once the edits and the parser have paused. */
|
||||
const passiveScanner = ViewPlugin.fromClass(
|
||||
/**
|
||||
* Scans the file once edits and the parser have paused: a file that renders no comments,
|
||||
* for their icons; one that does, when keystrokes have only moved its comments along.
|
||||
*/
|
||||
const deferredScanner = ViewPlugin.fromClass(
|
||||
class {
|
||||
timer: ReturnType<typeof setTimeout> | null = null;
|
||||
constructor(readonly view: EditorView) {}
|
||||
update(update: ViewUpdate) {
|
||||
if (active(update.state)) return this.cancel();
|
||||
if (update.docChanged || syntaxTree(update.startState) !== syntaxTree(update.state)) {
|
||||
this.cancel();
|
||||
this.timer = setTimeout(() => {
|
||||
this.timer = null;
|
||||
if (!active(this.view.state)) this.view.dispatch({ effects: setPassive.of(placeComments(this.view.state)) });
|
||||
}, PASSIVE_SCAN_DELAY);
|
||||
}
|
||||
if (!update.docChanged && syntaxTree(update.startState) === syntaxTree(update.state)) return;
|
||||
// Rendering and up to date: the view was just built, nothing is waiting.
|
||||
if (active(update.state) && !update.state.field(docsField).stale) return this.cancel();
|
||||
this.cancel();
|
||||
this.timer = setTimeout(() => {
|
||||
this.timer = null;
|
||||
const { state } = this.view;
|
||||
if (!active(state)) this.view.dispatch({ effects: setPassive.of(placeComments(state)) });
|
||||
else if (state.field(docsField).stale) this.view.dispatch({ effects: rescanDocs.of(null) });
|
||||
}, RESCAN_DELAY);
|
||||
}
|
||||
cancel() {
|
||||
if (this.timer !== null) clearTimeout(this.timer);
|
||||
@@ -524,8 +571,8 @@ const passiveScanner = ViewPlugin.fromClass(
|
||||
},
|
||||
);
|
||||
|
||||
/** How long edits and parsing must pause before a file that renders no comments is scanned. */
|
||||
const PASSIVE_SCAN_DELAY = 300;
|
||||
/** How long edits and parsing must pause before the file is scanned again. */
|
||||
const RESCAN_DELAY = 300;
|
||||
|
||||
/** Every doc comment of the file, as last found. */
|
||||
function placedComments(state: EditorState): ReadonlyArray<PlacedComment> {
|
||||
@@ -587,13 +634,26 @@ const docsField = StateField.define<DocsView>({
|
||||
create: build,
|
||||
update(value, tr) {
|
||||
if (tr.effects.some((e) => e.is(markdownLoaded))) renderCache.clear();
|
||||
const rebuild =
|
||||
tr.docChanged ||
|
||||
if (
|
||||
tr.reconfigured ||
|
||||
tr.effects.some((e) => e.is(toggleRenderedView) || e.is(markdownLoaded) || e.is(relabel)) ||
|
||||
// The parser finishing more of the file can bring comments it had not reached.
|
||||
syntaxTree(tr.startState) !== syntaxTree(tr.state);
|
||||
return rebuild ? build(tr.state) : value;
|
||||
tr.effects.some((e) => e.is(toggleRenderedView) || e.is(markdownLoaded) || e.is(relabel) || e.is(rescanDocs))
|
||||
)
|
||||
return build(tr.state);
|
||||
if (tr.docChanged) {
|
||||
// A keystroke away from every comment moves them along; the scan waits for a pause.
|
||||
if (active(tr.state) && typedOutsideComments(value, tr))
|
||||
return {
|
||||
decorations: value.decorations.map(tr.changes),
|
||||
comments: mapComments(value.comments, tr.changes),
|
||||
stale: true,
|
||||
};
|
||||
return build(tr.state);
|
||||
}
|
||||
// The parser finishing more of the file can bring comments it had not reached: built
|
||||
// at once, so a file opens with the comments on screen rendered, unless keystrokes
|
||||
// already have the scan waiting for a pause.
|
||||
if (syntaxTree(tr.startState) !== syntaxTree(tr.state)) return value.stale ? value : build(tr.state);
|
||||
return value;
|
||||
},
|
||||
provide: (f) => EditorView.decorations.from(f, (v) => v.decorations),
|
||||
});
|
||||
@@ -838,7 +898,7 @@ export const renderedDocsExtension: Extension = [
|
||||
flipped,
|
||||
docsField,
|
||||
passiveDocs,
|
||||
passiveScanner,
|
||||
deferredScanner,
|
||||
hovered,
|
||||
hoverTracker,
|
||||
showSourceAtCaret,
|
||||
|
||||
@@ -25,6 +25,7 @@ import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import type { Branch, ChangedFile, Commit } from "@/bindings";
|
||||
import { call, commands } from "@/platform/ipc";
|
||||
import { executeAction } from "@/platform/actions";
|
||||
import { setContext } from "@/platform/context";
|
||||
import { maybeEditorService, useGitLogChecks, type EditorInput } from "@/workbench/contributions";
|
||||
import {
|
||||
EmptyState,
|
||||
@@ -2627,6 +2628,7 @@ export function CommitDetails({ commits }: { commits: Commit[] }) {
|
||||
},
|
||||
[scope],
|
||||
);
|
||||
useEffect(() => () => setContext("vcsLogChangesFocus", false), []);
|
||||
|
||||
if (!commit)
|
||||
return (
|
||||
@@ -2667,7 +2669,13 @@ export function CommitDetails({ commits }: { commits: Commit[] }) {
|
||||
const details = (
|
||||
<SplitGroup orientation="vertical" id="git-log-details">
|
||||
<SplitPanel id="files" defaultSize="60%" minSize="60px">
|
||||
<div className={styles.filesPane}>
|
||||
<div
|
||||
className={styles.filesPane}
|
||||
onFocusCapture={() => setContext("vcsLogChangesFocus", true)}
|
||||
onBlurCapture={(e) => {
|
||||
if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setContext("vcsLogChangesFocus", false);
|
||||
}}
|
||||
>
|
||||
<div className={styles.detailsBar} role="toolbar" aria-label={tx("Changed files")}>
|
||||
<IconButton
|
||||
icon="diff"
|
||||
|
||||
@@ -373,7 +373,8 @@ export function registerVcsActions(): () => void {
|
||||
icon: "diff",
|
||||
when: VCS,
|
||||
// ⌘D duplicates the line inside a text editor; Git › Show Diff still works there.
|
||||
keyWhen: "!editorTextFocus",
|
||||
// In the Git Log's changed files it shows the commit's diff instead.
|
||||
keyWhen: "!editorTextFocus && !vcsLogChangesFocus",
|
||||
run: async ({ arg, ctx }) => {
|
||||
if (ctx.shelfFocus && arg === undefined) return showShelfDiff();
|
||||
const line =
|
||||
|
||||
@@ -152,6 +152,7 @@ export function registerLogPaneActions(): () => void {
|
||||
icon: "diff",
|
||||
when: LOG,
|
||||
hidden: true,
|
||||
keyWhen: "vcsLogChangesFocus",
|
||||
run: ({ arg }) => void showChangesDiff(changesArg(arg)),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ const shared = [
|
||||
["code.quickDocumentation", "db.quickDoc", "debug.setValue", "vcs.changelist.edit"],
|
||||
["code.reformat", "project.reformatCode", "db.reformatValue"],
|
||||
["code.optimizeImports", "project.optimizeImports"],
|
||||
["code.deleteLine", "db.duplicateSource", "project.compareWith", "vcs.showDiff"],
|
||||
["code.deleteLine", "db.duplicateSource", "project.compareWith", "vcs.showDiff", "vcs.log.changes.showDiff"],
|
||||
["code.moveStatementDown", "find.field.recentSearches", "find.popup.recentSearches"],
|
||||
["code.foldAll", "project.collapseAll"],
|
||||
["refactor.rename", "project.rename", "db.rename", "vcs.changelist.edit", "vcs.log.editMessage", "vcs.shelf.rename"],
|
||||
|
||||
@@ -303,6 +303,7 @@ export const intellijMacos: Record<string, string[]> = {
|
||||
"vcs.rollback": ["Meta+Alt+Z"],
|
||||
"vcs.addToVcs": ["Meta+Alt+A"],
|
||||
"vcs.showDiff": ["Meta+D"],
|
||||
"vcs.log.changes.showDiff": ["Meta+D"],
|
||||
"vcs.showDiffInNewWindow": [],
|
||||
"vcs.nextChange": ["Ctrl+Alt+Shift+Down"],
|
||||
"vcs.prevChange": ["Ctrl+Alt+Shift+Up"],
|
||||
|
||||
@@ -297,6 +297,7 @@ export const intellijWindows: Record<string, string[]> = {
|
||||
"vcs.rollback": ["Ctrl+Alt+Z"],
|
||||
"vcs.addToVcs": ["Ctrl+Alt+A"],
|
||||
"vcs.showDiff": ["Ctrl+D"],
|
||||
"vcs.log.changes.showDiff": ["Ctrl+D"],
|
||||
"vcs.showDiffInNewWindow": [],
|
||||
"vcs.nextChange": ["Ctrl+Alt+Shift+Down"],
|
||||
"vcs.prevChange": ["Ctrl+Alt+Shift+Up"],
|
||||
|
||||
Reference in new issue
Block a user