Порівняти коміти

..
Автор SHA1 Повідомлення Дата
l0ng-ai c5bccf020e fix(vcs): don't commit a file that was added to the index and then deleted
A file added with git add and then deleted from disk (AD) is no change
against HEAD, so the Commit view lists it nowhere. The commit plan skipped
it, but it stayed in the index copy the commit is made from, so committing
other files also committed the deleted file. Such staged entries are now
excluded like unchecked staged files.
2026-11-12 19:15:10 +08:00
49 змінених файлів з 49 додано та 575 видалено

No files matched your search

Створено
-11
Переглянути файл
@@ -4601,7 +4601,6 @@ dependencies = [
"tree-sitter-nix",
"tree-sitter-ocaml",
"tree-sitter-php",
"tree-sitter-prisma-io",
"tree-sitter-proto",
"tree-sitter-python",
"tree-sitter-ruby",
@@ -10438,16 +10437,6 @@ dependencies = [
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-prisma-io"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c35912d65a6adb07f781db46d843bf0464935e267da6ed192705516adafc7c24"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-proto"
version = "0.6.0"
+3 -38
Переглянути файл
@@ -283,16 +283,6 @@ fn mssql_pool_err(e: bb8::RunError<tiberius::error::Error>) -> IdeError {
}
}
/// Tiberius requires TLS when a TLS backend is enabled. `trust_cert` only skips
/// certificate checks, so Disable has to turn encryption off.
fn apply_sqlserver_ssl(config: &mut tiberius::Config, ssl: DbSslMode) {
match ssl {
DbSslMode::Disable => config.encryption(tiberius::EncryptionLevel::NotSupported),
DbSslMode::Prefer => config.trust_cert(),
DbSslMode::Require | DbSslMode::VerifyCa | DbSslMode::VerifyFull => {}
}
}
const DAYS_1900_01_01: i64 = 693_595;
/// Formats a proleptic Gregorian date given days since `0001-01-01`.
@@ -886,7 +876,9 @@ impl Pool {
if !source.database.is_empty() {
config.database(&source.database);
}
apply_sqlserver_ssl(&mut config, source.ssl);
if matches!(source.ssl, DbSslMode::Disable | DbSslMode::Prefer) {
config.trust_cert();
}
let manager = MssqlConnectionManager(config);
let pool = bb8::Pool::builder()
.max_size(MAX)
@@ -1339,33 +1331,6 @@ mod tests {
use super::*;
use crate::source::{DbOptions, DbProperty};
fn sqlserver_debug(ssl: DbSslMode) -> String {
let mut config = tiberius::Config::new();
apply_sqlserver_ssl(&mut config, ssl);
format!("{config:?}")
}
#[test]
fn sqlserver_disable_does_not_require_tls() {
let debug = sqlserver_debug(DbSslMode::Disable);
assert!(debug.contains("encryption: NotSupported"), "{debug}");
assert!(debug.contains("bypass: false"), "{debug}");
}
#[test]
fn sqlserver_prefer_trusts_the_server_certificate() {
let debug = sqlserver_debug(DbSslMode::Prefer);
assert!(debug.contains("encryption: Required"), "{debug}");
assert!(debug.contains("bypass: true"), "{debug}");
}
#[test]
fn sqlserver_require_keeps_validated_tls() {
let debug = sqlserver_debug(DbSslMode::Require);
assert!(debug.contains("encryption: Required"), "{debug}");
assert!(debug.contains("bypass: false"), "{debug}");
}
#[test]
fn discarded_sqlserver_connections_are_broken() {
let manager = MssqlConnectionManager(tiberius::Config::new());
-1
Переглянути файл
@@ -55,7 +55,6 @@ tree-sitter-xml = "0.7.0"
tree-sitter-haskell = "0.24.1"
tree-sitter-dart = "0.2.0"
tree-sitter-graphql = "0.3.0"
tree-sitter-prisma-io = "1.6.0"
tree-sitter-gdscript = "6.1.0"
[dev-dependencies]
-40
Переглянути файл
@@ -1,40 +0,0 @@
; Prisma schema highlighting (our captures, not the grammar crate's). Later patterns win.
(identifier) @variable
(developer_comment) @comment
(comment) @comment.doc
(number) @number
(string) @string
[(true) (false) (null)] @keyword
(variable) @property
(column_declaration . (identifier) @property)
(type_expression . (identifier) @variable.parameter)
(enumeral (identifier) @constant)
(column_type (identifier) @type)
(column_type (call_expression (identifier) @type))
(call_expression (identifier) @function)
(model_declaration (identifier) @type)
(view_declaration (identifier) @type)
(type_declaration (identifier) @type)
(type_declaration (type_declaration_type) @type)
(enum_declaration (identifier) @type)
(datasource_declaration (identifier) @type)
(generator_declaration (identifier) @type)
(attribute (identifier) @attribute)
(attribute (call_expression (identifier) @attribute))
(attribute (member_expression (identifier) @attribute (property_identifier) @attribute))
(attribute (call_expression (member_expression (identifier) @attribute (property_identifier) @attribute)))
(block_attribute_declaration (identifier) @attribute)
(block_attribute_declaration (call_expression (identifier) @attribute))
["@" "@@"] @attribute
["datasource" "enum" "generator" "model" "type" "view"] @keyword
["(" ")" "[" "]" "{" "}"] @punctuation.bracket
["," ":" "."] @punctuation.delimiter
(maybe) @operator
"=" @operator
-8
Переглянути файл
@@ -1,8 +0,0 @@
(model_declaration (identifier) @name) @def.class
(view_declaration (identifier) @name) @def.class
(type_declaration . (identifier) @name (statement_block)) @def.struct
(enum_declaration (identifier) @name) @def.enum
(enumeral (identifier) @name) @def.enum_member
(datasource_declaration (identifier) @name) @outline.module
(generator_declaration (identifier) @name) @outline.module
(column_declaration . (identifier) @name) @def.field
-2
Переглянути файл
@@ -67,7 +67,6 @@ const BUILTIN: &[(&str, &str)] = &[
("php", include_str!("../../../languages/php.toml")),
("plaintext", include_str!("../../../languages/plaintext.toml")),
("powershell", include_str!("../../../languages/powershell.toml")),
("prisma", include_str!("../../../languages/prisma.toml")),
("properties", include_str!("../../../languages/properties.toml")),
("proto", include_str!("../../../languages/proto.toml")),
("python", include_str!("../../../languages/python.toml")),
@@ -1027,7 +1026,6 @@ mod tests {
("CMakeLists.txt", "cmake"),
("api.proto", "proto"),
("schema.graphql", "graphql"),
("schema.prisma", "prisma"),
("main.tf", "terraform"),
("prod.tfvars", "terraform-vars"),
("job.nomad", "hcl"),
-10
Переглянути файл
@@ -430,16 +430,6 @@ pub(crate) static SPECS: &[Spec] = &[
imports: NONE,
constructors: NONE,
},
Spec {
name: "prisma",
language: tree_sitter_prisma_io::LANGUAGE,
outline: outline!("prisma"),
highlights: Some(Highlights { query: highlights!("prisma"), first_wins: false }),
fold_nodes: NONE,
fold_bodies: NONE,
imports: NONE,
constructors: NONE,
},
Spec {
name: "make",
language: tree_sitter_make::LANGUAGE,
-22
Переглянути файл
@@ -501,28 +501,6 @@ mod tests {
("NEWHOPE", K::Constant),
],
),
(
"prisma",
"// hi\n/// Doc\nmodel User {\n id Int @id @default(autoincrement())\n email String? @db.VarChar(255)\n role Role\n @@index([email])\n}\nenum Role { ADMIN }\ndatasource db {\n provider = \"postgresql\"\n}\n",
&[
("// hi", K::Comment),
("/// Doc", K::DocComment),
("model", K::Keyword),
("User", K::Type),
("id", K::Property),
("Int", K::Type),
("@id", K::Attribute),
("@default", K::Attribute),
("autoincrement", K::FunctionCall),
("VarChar", K::Attribute),
("255", K::Number),
("Role", K::Type),
("@@index", K::Attribute),
("ADMIN", K::Constant),
("provider", K::Property),
("\"postgresql\"", K::String),
],
),
(
"hcl",
"# hi\nresource \"aws_instance\" \"web\" {\n ami = var.ami\n count = 2\n tags = { Name = \"web-${var.env}\" }\n name = upper(\"x\")\n}\n",
-11
Переглянути файл
@@ -886,17 +886,6 @@ fn outline_more_languages() {
Function GetUser
",
);
check_outline(
"prisma",
"datasource db {\n provider = \"sqlite\"\n}\nmodel User {\n id Int @id\n @@map(\"users\")\n}\nenum Role {\n ADMIN\n}\n",
"
Module db
Class User
Field id
Enum Role
EnumMember ADMIN
",
);
check_outline(
"nix",
"{ pkgs, ... }:\nlet\n foo = 1;\n bar = x: x;\nin { inherit foo; }\n",
-11
Переглянути файл
@@ -407,17 +407,6 @@ pub static TOOLS: &[ToolSpec] = &[
homepage: "https://github.com/graphql/graphiql",
needs: &[],
},
ToolSpec {
name: "prisma-language-server",
display: "Prisma Language Server",
kind: LanguageServer,
commands: &["prisma-language-server"],
method: Method::Npm { packages: &["@prisma/language-server"] },
fallback: None,
version: V,
homepage: "https://github.com/prisma/language-tools",
needs: &[],
},
ToolSpec {
name: "intelephense",
display: "Intelephense (PHP)",
+1 -1
Переглянути файл
@@ -67,7 +67,7 @@ All keys are kebab-case. Only `id` and `name` are required; a definition with no
| `formatter` | table | External formatter; see below. |
| `scratch-template` | string | Content of a new scratch file of this language. `$END$` marks where the caret goes. |
Bundled tree-sitter grammars: `bash`, `c`, `cmake`, `cpp`, `csharp`, `css`, `dart`, `elixir`, `gdscript`, `go`, `graphql`, `haskell`, `hcl`, `html`, `java`, `javascript`, `json`, `kotlin`, `lua`, `make`, `markdown`, `nix`, `ocaml`, `ocaml_interface`, `php`, `prisma`, `proto`, `python`, `ruby`, `rust`, `scala`, `swift`, `toml`, `tsx`, `typescript`, `xml`, `yaml`, `zig`. A new grammar has to be compiled into the IDE; a plugin can provide highlighting and structure for a language without a grammar instead (see [Language features](/en/plugins/guides/language-features)).
Bundled tree-sitter grammars: `bash`, `c`, `cmake`, `cpp`, `csharp`, `css`, `dart`, `elixir`, `gdscript`, `go`, `graphql`, `haskell`, `hcl`, `html`, `java`, `javascript`, `json`, `kotlin`, `lua`, `make`, `markdown`, `nix`, `ocaml`, `ocaml_interface`, `php`, `proto`, `python`, `ruby`, `rust`, `scala`, `swift`, `toml`, `tsx`, `typescript`, `xml`, `yaml`, `zig`. A new grammar has to be compiled into the IDE; a plugin can provide highlighting and structure for a language without a grammar instead (see [Language features](/en/plugins/guides/language-features)).
### `[[framework-ui-modes]]`
-1
Переглянути файл
@@ -151,7 +151,6 @@ The table lists every built-in language definition. Server and debugger names in
| PHP | `php` | `.php`, `.phtml`, `.php3`, `.php4`, `.php5`, … | Yes | Yes | **intelephense** → phpactor + **laravel-lsp** | **php-debug** |
| Plain Text | `plaintext` | `.txt`, `.text` | — | — | — | — |
| PowerShell | `powershell` | `.ps1`, `.psm1`, `.psd1`, `.pssc`, `.psrc` | Yes | — | powershell-editor-services | — |
| Prisma | `prisma` | `.prisma` | Yes | Yes | **prisma-language-server** | — |
| Properties | `properties` | `.properties`, `.prefs` | Yes | — | — | — |
| Protocol Buffers | `proto` | `.proto` | Yes | Yes | **buf** | — |
| Python | `python` | `.py`, `.pyi`, `.pyw` | Yes | Yes | **basedpyright** → **pyright** → **ty** → **pyrefly** + **ruff** | **debugpy** |
+1 -2
Переглянути файл
@@ -238,7 +238,7 @@ The full table is `UI_KEYS` in
| `--syn-rainbow-1` … `--syn-rainbow-5` (Semantic highlighting) | `RAINBOW_COLOR1` … `RAINBOW_COLOR5` |
| `--wave-error`, `--wave-warning` | `ERRORS_ATTRIBUTES`, `WARNING_ATTRIBUTES` (effect colour) |
| `--bg-matched-brace`, `--bg-identifier-under-caret` | `MATCHED_BRACE_ATTRIBUTES`, `IDENTIFIER_UNDER_CARET_ATTRIBUTES` |
| `--bg-text-search-result`, `--bg-search-result` | `TEXT_SEARCH_RESULT_ATTRIBUTES`, `SEARCH_RESULT_ATTRIBUTES` |
| `--bg-text-search-result`, `--bg-search-match` | `TEXT_SEARCH_RESULT_ATTRIBUTES`, `SEARCH_RESULT_ATTRIBUTES` |
| `--syn-self`, `--syn-keyword-arg`, `--syn-docstring`, `--syn-predefined` | `PY.SELF_PARAMETER`, `PY.KEYWORD_ARGUMENT`, `PY.DOC_COMMENT`, `PY.BUILTIN_NAME` |
| `--ansi-*` | `CONSOLE_*_OUTPUT` (`CONSOLE_DARKGRAY_OUTPUT` is bright black, `CONSOLE_GRAY_OUTPUT` white) |
@@ -504,7 +504,6 @@ conventionally go in the theme file, groups marked _editor scheme_ in the scheme
| `--fg-editor-selection` | `inherit` | `inherit` |
| `--bg-text-search-result` | `#114957` | `#fcd47e` |
| `--bg-text-search-result-current` | `#165e70` | `#f2bf57` |
| `--bg-search-result` | `#2d543f` | `#ccccff` |
| `--bg-matched-brace` | `#43454a` | `#93d9d9` |
| `--matched-brace-weight` | `var(--fw-code-bold)` | `inherit` |
| `--bg-matched-tag` | `#3f4045` | `#c9ecec` |
+1 -1
Переглянути файл
@@ -67,7 +67,7 @@ install-hint = "Install CodeLLDB in Settings › Languages & Frameworks › Lang
| `formatter` | 表 | 外部格式化工具,见下文。 |
| `scratch-template` | 字符串 | 新建该语言草稿文件时的内容,`$END$` 表示光标位置。 |
内置的 tree-sitter 语法:`bash`、`c`、`cmake`、`cpp`、`csharp`、`css`、`dart`、`elixir`、`gdscript`、`go`、`graphql`、`haskell`、`hcl`、`html`、`java`、`javascript`、`json`、`kotlin`、`lua`、`make`、`markdown`、`nix`、`ocaml`、`ocaml_interface`、`php`、`prisma`、`proto`、`python`、`ruby`、`rust`、`scala`、`swift`、`toml`、`tsx`、`typescript`、`xml`、`yaml`、`zig`。新的语法需要编译进 IDE;没有语法的语言可以改由插件提供高亮和结构(见 [语言功能](/zh/plugins/guides/language-features))。
内置的 tree-sitter 语法:`bash`、`c`、`cmake`、`cpp`、`csharp`、`css`、`dart`、`elixir`、`gdscript`、`go`、`graphql`、`haskell`、`hcl`、`html`、`java`、`javascript`、`json`、`kotlin`、`lua`、`make`、`markdown`、`nix`、`ocaml`、`ocaml_interface`、`php`、`proto`、`python`、`ruby`、`rust`、`scala`、`swift`、`toml`、`tsx`、`typescript`、`xml`、`yaml`、`zig`。新的语法需要编译进 IDE;没有语法的语言可以改由插件提供高亮和结构(见 [语言功能](/zh/plugins/guides/language-features))。
### `[[framework-ui-modes]]`
-1
Переглянути файл
@@ -146,7 +146,6 @@ IDE 安装工具从不需要 `sudo`。工具装在本地数据目录下 IDE 自
| PHP | `php` | `.php`, `.phtml`, `.php3`, `.php4`, `.php5`, … | 是 | 是 | **intelephense** → phpactor + **laravel-lsp** | **php-debug** |
| Plain Text | `plaintext` | `.txt`, `.text` | — | — | — | — |
| PowerShell | `powershell` | `.ps1`, `.psm1`, `.psd1`, `.pssc`, `.psrc` | 是 | — | powershell-editor-services | — |
| Prisma | `prisma` | `.prisma` | 是 | 是 | **prisma-language-server** | — |
| Properties | `properties` | `.properties`, `.prefs` | 是 | — | — | — |
| Protocol Buffers | `proto` | `.proto` | 是 | 是 | **buf** | — |
| Python | `python` | `.py`, `.pyi`, `.pyw` | 是 | 是 | **basedpyright** → **pyright** → **ty** → **pyrefly** + **ruff** | **debugpy** |
+1 -2
Переглянути файл
@@ -210,7 +210,7 @@ pnpm theme:import-intellij path/to/MyTheme.theme.json path/to/MyTheme.icls --out
| `--syn-rainbow-1` … `--syn-rainbow-5`(语义高亮) | `RAINBOW_COLOR1` … `RAINBOW_COLOR5` |
| `--wave-error`, `--wave-warning` | `ERRORS_ATTRIBUTES`, `WARNING_ATTRIBUTES`(效果颜色) |
| `--bg-matched-brace`, `--bg-identifier-under-caret` | `MATCHED_BRACE_ATTRIBUTES`, `IDENTIFIER_UNDER_CARET_ATTRIBUTES` |
| `--bg-text-search-result`, `--bg-search-result` | `TEXT_SEARCH_RESULT_ATTRIBUTES`, `SEARCH_RESULT_ATTRIBUTES` |
| `--bg-text-search-result`, `--bg-search-match` | `TEXT_SEARCH_RESULT_ATTRIBUTES`, `SEARCH_RESULT_ATTRIBUTES` |
| `--syn-self`, `--syn-keyword-arg`, `--syn-docstring`, `--syn-predefined` | `PY.SELF_PARAMETER`, `PY.KEYWORD_ARGUMENT`, `PY.DOC_COMMENT`, `PY.BUILTIN_NAME` |
| `--ansi-*` | `CONSOLE_*_OUTPUT`(`CONSOLE_DARKGRAY_OUTPUT` 为亮黑,`CONSOLE_GRAY_OUTPUT` 为白色) |
@@ -469,7 +469,6 @@ VS Code 主题(扩展中的 `themes/*.json`)包含 `type`(`dark` / `light`
| `--fg-editor-selection` | `inherit` | `inherit` |
| `--bg-text-search-result` | `#114957` | `#fcd47e` |
| `--bg-text-search-result-current` | `#165e70` | `#f2bf57` |
| `--bg-search-result` | `#2d543f` | `#ccccff` |
| `--bg-matched-brace` | `#43454a` | `#93d9d9` |
| `--matched-brace-weight` | `var(--fw-code-bold)` | `inherit` |
| `--bg-matched-tag` | `#3f4045` | `#c9ecec` |
-47
Переглянути файл
@@ -4,7 +4,6 @@
* by `crates/ide-lsp/tests`.
*/
import type { Locator, Page } from "@playwright/test";
import type { CompletionItem } from "../src/bindings";
import { expect, ipcCalls, openFromTree, stubDesktop, test } from "./fixtures";
const ROOT = "/dev/acme-api";
@@ -79,52 +78,6 @@ test("completion lists the server's items and inserts the chosen one", async ({
expect((await ipcCalls(page, "lsp_completion")).length).toBeGreaterThan(0);
});
for (const key of ["Enter", "Tab"]) {
for (const prefix of ["", "v"]) {
test(`${key} leaves the caret after a field snippet at root.${prefix}`, async ({ workbench: page }, info) => {
const editor = await openSample(page);
await page.evaluate(() => {
const item = (label: string): CompletionItem => ({
label,
labelDetail: null,
labelDescription: "int",
detail: "int",
kind: "field",
documentation: null,
sortText: null,
filterText: null,
preselect: label === "val",
deprecated: false,
insertText: label,
isSnippet: true,
replaceRange: null,
replaceRangeTab: null,
additionalEdits: [],
commitCharacters: [],
command: null,
data: null,
server: 1,
});
window.__ipc.handlers.lsp_completion = () => ({ isIncomplete: false, items: [item("val"), item("value")] });
});
await editor.locator(".cm-line", { hasText: "return order.toDto()" }).click();
await page.keyboard.press("End");
await page.keyboard.press("Enter");
await page.keyboard.type(`int a = root.${prefix}`);
await page.keyboard.press("Control+Space");
const popup = page.locator(".cm-tooltip-autocomplete");
await expect(popup.getByText("val", { exact: true })).toBeVisible();
// CodeMirror ignores acceptance during its 75 ms lookup interaction delay.
await page.waitForTimeout(100);
await page.keyboard.press(key);
await expect(popup).toBeHidden();
await page.screenshot({ path: info.outputPath("accepted-completion.png"), animations: "disabled" });
await page.keyboard.type(";");
await expect(editor.locator(".cm-activeLine")).toHaveText(/^\s*int a = root\.val;$/);
});
}
}
test("⌘B goes to the declaration in another file", async ({ workbench: page }) => {
const editor = await openSample(page);
await page.evaluate(
-14
Переглянути файл
@@ -105,20 +105,6 @@ test("Find in Files streams matches by file and opens the chosen one at its line
});
const preview = dialog.getByRole("region", { name: "Preview", exact: true }).locator(".cm-content");
await expect(preview).toHaveAttribute("contenteditable", "true");
// Matches take the editor scheme's search result background (IDEA's SEARCH_RESULT_ATTRIBUTES) and keep the syntax colours.
const match = preview.locator(".cm-openidea-searchResult").first();
await expect(match).toHaveText("OrderService");
expect(
await match.evaluate((el) => {
const probe = document.createElement("span");
probe.style.backgroundColor = "var(--bg-search-result)";
el.parentElement!.append(probe);
const expected = getComputedStyle(probe).backgroundColor;
probe.remove();
const own = getComputedStyle(el);
return own.backgroundColor === expected && own.color === getComputedStyle(el.parentElement!).color;
}),
).toBe(true);
const originalPreview = await preview.textContent();
await preview.focus();
await page.keyboard.press("Meta+a");
-2
Переглянути файл
@@ -41,5 +41,3 @@ nodePath = ""
problems = { shortenToSingleLine = false }
workingDirectory = { mode = "auto" }
codeAction = { disableRuleComment = { enable = true, location = "separateLine" }, showDocumentation = { enable = true } }
# vscode-eslint reads it for every source.fixAll request (Actions on Save, Code Cleanup).
codeActionOnSave = { mode = "all", rules = [] }
-2
Переглянути файл
@@ -75,8 +75,6 @@ nodePath = ""
problems = { shortenToSingleLine = false }
workingDirectory = { mode = "auto" }
codeAction = { disableRuleComment = { enable = true, location = "separateLine" }, showDocumentation = { enable = true } }
# vscode-eslint reads it for every source.fixAll request (Actions on Save, Code Cleanup).
codeActionOnSave = { mode = "all", rules = [] }
[debugger]
adapter = "js-debug"
-2
Переглянути файл
@@ -74,8 +74,6 @@ nodePath = ""
problems = { shortenToSingleLine = false }
workingDirectory = { mode = "auto" }
codeAction = { disableRuleComment = { enable = true, location = "separateLine" }, showDocumentation = { enable = true } }
# vscode-eslint reads it for every source.fixAll request (Actions on Save, Code Cleanup).
codeActionOnSave = { mode = "all", rules = [] }
[debugger]
adapter = "js-debug"
-17
Переглянути файл
@@ -1,17 +0,0 @@
id = "prisma"
name = "Prisma"
extensions = ["prisma"]
line-comment = "//"
indent = { tab-size = 2, insert-spaces = true }
tree-sitter = "prisma"
[[language-servers]]
name = "prisma-language-server"
command = "prisma-language-server"
args = ["--stdio"]
root-markers = ["package.json", "schema.prisma"]
install-hint = "npm i -g @prisma/language-server"
# Asked for as section `prisma` before every validation; a missing section crashes the server.
[language-servers.settings.prisma]
enableDiagnostics = true
-2
Переглянути файл
@@ -43,5 +43,3 @@ nodePath = ""
problems = { shortenToSingleLine = false }
workingDirectory = { mode = "auto" }
codeAction = { disableRuleComment = { enable = true, location = "separateLine" }, showDocumentation = { enable = true } }
# vscode-eslint reads it for every source.fixAll request (Actions on Save, Code Cleanup).
codeActionOnSave = { mode = "all", rules = [] }
-2
Переглянути файл
@@ -89,8 +89,6 @@ nodePath = ""
problems = { shortenToSingleLine = false }
workingDirectory = { mode = "auto" }
codeAction = { disableRuleComment = { enable = true, location = "separateLine" }, showDocumentation = { enable = true } }
# vscode-eslint reads it for every source.fixAll request (Actions on Save, Code Cleanup).
codeActionOnSave = { mode = "all", rules = [] }
[debugger]
adapter = "js-debug"
-2
Переглянути файл
@@ -74,8 +74,6 @@ nodePath = ""
problems = { shortenToSingleLine = false }
workingDirectory = { mode = "auto" }
codeAction = { disableRuleComment = { enable = true, location = "separateLine" }, showDocumentation = { enable = true } }
# vscode-eslint reads it for every source.fixAll request (Actions on Save, Code Cleanup).
codeActionOnSave = { mode = "all", rules = [] }
[debugger]
adapter = "js-debug"
-2
Переглянути файл
@@ -68,5 +68,3 @@ nodePath = ""
problems = { shortenToSingleLine = false }
workingDirectory = { mode = "auto" }
codeAction = { disableRuleComment = { enable = true, location = "separateLine" }, showDocumentation = { enable = true } }
# vscode-eslint reads it for every source.fixAll request (Actions on Save, Code Cleanup).
codeActionOnSave = { mode = "all", rules = [] }
-1
Переглянути файл
@@ -89,7 +89,6 @@
"--fg-editor-selection": "inherit",
"--bg-text-search-result": "#114957",
"--bg-text-search-result-current": "#165e70",
"--bg-search-result": "#2d543f",
"--bg-matched-brace": "#38424f",
"--matched-brace-weight": "700",
"--bg-matched-tag": "#353e4a",
+2 -3
Переглянути файл
@@ -231,7 +231,7 @@ The full table is `UI_KEYS` in [`src/features/plugins/intellijTheme.ts`](../src/
| `--syn-rainbow-1` … `--syn-rainbow-5` (Semantic highlighting, rainbow indent guides) | `RAINBOW_COLOR1` … `RAINBOW_COLOR5` |
| `--wave-error`, `--wave-warning` | `ERRORS_ATTRIBUTES`, `WARNING_ATTRIBUTES` (effect colour) |
| `--bg-matched-brace`, `--bg-identifier-under-caret` | `MATCHED_BRACE_ATTRIBUTES`, `IDENTIFIER_UNDER_CARET_ATTRIBUTES` |
| `--bg-text-search-result`, `--bg-search-result` | `TEXT_SEARCH_RESULT_ATTRIBUTES`, `SEARCH_RESULT_ATTRIBUTES` |
| `--bg-text-search-result`, `--bg-search-match` | `TEXT_SEARCH_RESULT_ATTRIBUTES`, `SEARCH_RESULT_ATTRIBUTES` |
| `--syn-self`, `--syn-keyword-arg`, `--syn-docstring`, `--syn-predefined` | `PY.SELF_PARAMETER`, `PY.KEYWORD_ARGUMENT`, `PY.DOC_COMMENT`, `PY.BUILTIN_NAME` |
| `--ansi-*` | `CONSOLE_*_OUTPUT` (`CONSOLE_DARKGRAY_OUTPUT` is bright black, `CONSOLE_GRAY_OUTPUT` white) |
@@ -329,7 +329,7 @@ a token.
<!-- BEGIN GENERATED TOKEN REFERENCE: pnpm gen:theme-docs -->
264 themeable tokens: 125 in the UI theme file, 139 in the editor colour scheme file.
263 themeable tokens: 125 in the UI theme file, 138 in the editor colour scheme file.
### Surfaces (UI theme)
@@ -509,7 +509,6 @@ a token.
| `--fg-editor-selection` | `inherit` | `inherit` |
| `--bg-text-search-result` | `#114957` | `#fcd47e` |
| `--bg-text-search-result-current` | `#165e70` | `#f2bf57` |
| `--bg-search-result` | `#2d543f` | `#ccccff` |
| `--bg-matched-brace` | `#43454a` | `#93d9d9` |
| `--matched-brace-weight` | `var(--fw-code-bold)` | `inherit` |
| `--bg-matched-tag` | `#3f4045` | `#c9ecec` |
-10
Переглянути файл
@@ -219,16 +219,6 @@ describe("IntelliJ theme import", () => {
});
});
it("keeps the search result colour apart from the text search result and the lists' search match", () => {
const bg = (c: string) => `<value><option name="BACKGROUND" value="${c}"/></value>`;
const s = convertIcls(`<scheme name="Search" parent_scheme="Darcula"><attributes>
<option name="SEARCH_RESULT_ATTRIBUTES">${bg("2d543f")}</option>
<option name="TEXT_SEARCH_RESULT_ATTRIBUTES">${bg("114957")}</option>
</attributes></scheme>`);
expect(s.tokens).toMatchObject({ "--bg-search-result": "#2d543f", "--bg-text-search-result": "#114957" });
expect(s.tokens).not.toHaveProperty("--bg-search-match");
});
it("produces an installable package", () => {
const light = stripJsonComments(themeJson.replace('"dark": true,', ""));
const c = convertIntellijTheme(light, icls.replace("Darcula", "Default"), "sea");
-2
Переглянути файл
@@ -332,8 +332,6 @@ export const openideaTheme = EditorView.theme({
// Find in file (cm/find.ts)
...findStyles,
// Find in Files preview matches (search/find/parts.tsx)
".cm-openidea-searchResult": { backgroundColor: "var(--bg-search-result)" },
// Overlays
".cm-tooltip": {
-38
Переглянути файл
@@ -318,44 +318,6 @@ describe("applying completions", () => {
});
for (const mode of ["insert", "replace"] as const) {
for (const prefix of ["", "v"]) {
it(`${mode} leaves the caret after a field snippet accepted at root.${prefix}`, () => {
const { view, req, pos } = setup(`int a = root.${prefix}|Suffix`);
const start = "int a = root.".length;
const c = makeCompletion(
item({
label: "val",
kind: "field",
insertText: "val",
isSnippet: true,
replaceRange: { start: { line: 0, col: start }, end: { line: 0, col: pos } },
}),
req,
);
applyLspCompletion(view, c, start, pos, mode);
expect(view.state.doc.toString()).toBe(`int a = root.val${mode === "insert" ? "Suffix" : ""}`);
expect(view.state.selection.main.head).toBe(start + 3);
});
}
it(`${mode} places a fieldless snippet after imports and a cached UTF-16 prefix`, () => {
const { view, req, pos } = setup("\n/* 😀 */ root.|Suffix", "v");
const c = makeCompletion(
item({
label: "val",
kind: "field",
insertText: "val",
isSnippet: true,
replaceRange: { start: { line: 1, col: pos - 1 }, end: { line: 1, col: pos - 1 } },
additionalEdits: [{ range: { start: { line: 0, col: 0 }, end: { line: 0, col: 0 } }, new_text: "import X;" }],
}),
req,
);
applyLspCompletion(view, c, pos, pos + 1, mode);
expect(view.state.doc.toString()).toBe(`import X;\n/* 😀 */ root.val${mode === "insert" ? "Suffix" : ""}`);
expect(view.state.selection.main.head).toBe(pos + "import X;val".length);
});
for (const shape of ["textEdit", "insertReplace", "noEdit"] as const) {
for (const isSnippet of [false, true]) {
it(`${mode} replaces a mixed-case prefix typed after a ${shape} request (snippet: ${isSnippet})`, () => {
-57
Переглянути файл
@@ -146,63 +146,6 @@ describe("silent code actions", () => {
});
});
describe("fix-all from every server", () => {
const fixAll = (server: number, title: string): CodeAction => ({
...resolved,
title,
kind: "source.fixAll",
server,
edit: null,
data: "{}",
});
const editor = () => {
const view = new EditorView({
state: EditorState.create({
doc: "const a = 'x';",
extensions: [lspDoc.of({ doc: 1, path: "/p/a.ts", language: "typescript", uiMode: null, readOnly: false })],
}),
});
views.push(view);
return view;
};
it("applies each server's source.fixAll in turn, asking the next one again after an edit", async () => {
const view = editor();
const ts = fixAll(1, "Fix all fixable JS/TS issues");
const eslint = fixAll(2, "Fix all fixable ESLint issues");
const eslintAfter = { ...eslint, data: '{"version":2}' };
lspCodeActions
.mockResolvedValueOnce({ status: "ok", data: [ts, eslint] })
.mockResolvedValueOnce({ status: "ok", data: [{ ...ts, data: '{"again":true}' }, eslintAfter] });
lspApplyCodeAction.mockResolvedValue({ status: "ok", data: null });
expect(await applyKindSilently(view, "source.fixAll")).toBe(true);
expect(lspCodeActions).toHaveBeenCalledTimes(2);
expect(lspApplyCodeAction.mock.calls.map((c) => (c as unknown[])[1])).toEqual([ts, eslintAfter]);
});
it("goes on with the next server when one server's action fails", async () => {
const view = editor();
const ts = fixAll(1, "Fix all fixable JS/TS issues");
const eslint = fixAll(2, "Fix all fixable ESLint issues");
lspCodeActions.mockResolvedValue({ status: "ok", data: [ts, eslint] });
lspApplyCodeAction
.mockResolvedValueOnce({ status: "error", error: { kind: "internal", message: "boom" } })
.mockResolvedValueOnce({ status: "ok", data: null });
expect(await applyKindSilently(view, "source.fixAll")).toBe(true);
expect(lspApplyCodeAction.mock.calls.map((c) => ((c as unknown[])[1] as CodeAction).server)).toEqual([1, 2]);
});
it("applies a single organize-imports action", async () => {
const view = editor();
const a = { ...fixAll(1, "Organize Imports"), kind: "source.organizeImports" };
const b = { ...fixAll(2, "Organize Imports"), kind: "source.organizeImports" };
lspCodeActions.mockResolvedValueOnce({ status: "ok", data: [a, b] });
lspApplyCodeAction.mockResolvedValue({ status: "ok", data: null });
expect(await applyKindSilently(view, "source.organizeImports")).toBe(true);
expect(lspApplyCodeAction).toHaveBeenCalledTimes(1);
});
});
describe("intention preview", () => {
it("does not resolve lazy plugin actions while previewing", async () => {
const view = new EditorView({
+13 -47
Переглянути файл
@@ -299,61 +299,27 @@ export function changedLines(before: Text, after: Text): Array<{ from: number; t
* Applies the first enabled whole-file action of `kind` without UI (Actions on
* Save). Waits until the backend edit reached this editor; false when none.
*/
/**
* Applies the server's `kind` action to the whole document without UI (Actions on Save,
* Code Cleanup). Fix-all runs once per server that offers it, one after another, each
* asked again for the document as the previous one left it (VS Code's codeActionsOnSave):
* vtsls and ESLint both answer `source.fixAll` for a TypeScript file.
*/
export async function applyKindSilently(view: EditorView, kind: string): Promise<boolean> {
const doc = docOf(view);
if (!doc || doc.readOnly) return false;
const matching = (actions: CodeAction[], server?: CodeAction["server"]) =>
actions.filter(
(a) =>
a.kind?.startsWith(kind) &&
!a.disabledReason &&
!isLazyPluginAction(a) &&
(server === undefined || a.server === server),
);
const pick = (actions: CodeAction[]) => actions.find((a) => a.isPreferred) ?? actions[0];
let actions: CodeAction[];
try {
actions = matching(await fetchActions(view, [kind], true));
} catch (e) {
console.warn(`[lsp] ${kind} on ${doc.path} failed`, e);
actions = (await fetchActions(view, [kind], true)).filter(
(a) => a.kind?.startsWith(kind) && !a.disabledReason && !isLazyPluginAction(a),
);
} catch {
return false;
}
const first = pick(actions);
if (!first) return false;
const servers = kind.startsWith("source.fixAll")
? [first.server, ...new Set(actions.map((a) => a.server).filter((s) => s !== first.server))]
: [first.server];
let applied = false;
for (const [i, server] of servers.entries()) {
let action: CodeAction | undefined = first;
if (i > 0) {
// The previous server's edit changed the document: this one's answer must be fresh.
try {
action = pick(matching(await fetchActions(view, [kind], true), server));
} catch (e) {
console.warn(`[lsp] ${kind} on ${doc.path} failed`, e);
continue;
}
if (!action) continue;
}
const before = view.state.doc;
const chosen = action;
try {
await lspRequest(() => commands.lspApplyCodeAction(doc.doc, chosen));
} catch (e) {
console.warn(`[lsp] applying ${chosen.kind} on ${doc.path} failed`, e);
continue;
}
applied = true;
for (let i = 0; i < 25 && view.state.doc === before; i++) await new Promise((r) => setTimeout(r, 20));
const action = actions.find((a) => a.isPreferred) ?? actions[0];
if (!action) return false;
const before = view.state.doc;
try {
await lspRequest(() => commands.lspApplyCodeAction(doc.doc, action));
} catch {
return false;
}
return applied;
for (let i = 0; i < 25 && view.state.doc === before; i++) await new Promise((r) => setTimeout(r, 20));
return true;
}
/**
+4 -29
Переглянути файл
@@ -23,7 +23,7 @@ describe("lspSnippetToCm", () => {
});
it("handles LSP escapes", () => {
expect(lspSnippetToCm("cost \\$5 \\} \\\\")).toBe("cost $5 \\} \\" + "${0}");
expect(lspSnippetToCm("cost \\$5 \\} \\\\")).toBe("cost $5 \\} \\");
expect(expand(lspSnippetToCm("a \\${1}")).text).toBe("a ${1}");
});
@@ -37,8 +37,8 @@ describe("lspSnippetToCm", () => {
it("resolves variables, defaults and unknown names", () => {
const vars = snippetVariables("/p/src/Main.kt");
expect(lspSnippetToCm("class $TM_FILENAME_BASE", vars)).toBe("class Main${0}");
expect(lspSnippetToCm("${NOPE:dflt}", vars)).toBe("dflt${0}");
expect(lspSnippetToCm("class $TM_FILENAME_BASE", vars)).toBe("class Main");
expect(lspSnippetToCm("${NOPE:dflt}", vars)).toBe("dflt");
expect(lspSnippetToCm("$NOPE", vars)).toBe("${NOPE}");
});
@@ -51,7 +51,7 @@ describe("lspSnippetToCm", () => {
});
it("treats a lone dollar as text", () => {
expect(lspSnippetToCm("$ x $")).toBe("$ x $${0}");
expect(lspSnippetToCm("$ x $")).toBe("$ x $");
});
it("selects the first field when instantiated", () => {
@@ -60,31 +60,6 @@ describe("lspSnippetToCm", () => {
expect([sel.from, sel.to]).toEqual([5, 8]);
});
it.each([
["val", {}, "val"],
["$TM_FILENAME_BASE", { TM_FILENAME_BASE: "Main" }, "Main"],
["${MISSING:val}", {}, "val"],
["\\${1}", {}, "${1}"],
["cost \\$5", {}, "cost $5"],
["first\n\tlast", {}, "first\n last"],
["/* 😀 */ val", {}, "/* 😀 */ val"],
])("places the caret at the end of a fieldless snippet (%s)", (source, vars, expected) => {
const { text, sel } = expand(lspSnippetToCm(source, vars));
expect(text).toBe(expected);
expect([sel.from, sel.to]).toEqual([expected.length, expected.length]);
});
it.each([
["before$0after", "beforeafter", 6, 6],
["${1:arg}after", "argafter", 0, 3],
["${1|val,other|}after", "valafter", 0, 3],
["$UNKNOWN after", "UNKNOWN after", 0, 7],
])("preserves the selected field in %s", (source, expected, from, to) => {
const { text, sel } = expand(lspSnippetToCm(source));
expect(text).toBe(expected);
expect([sel.from, sel.to]).toEqual([from, to]);
});
it("produces plain text previews", () => {
expect(lspSnippetToPlain("foo(${1:a}, ${2|x,y|})$0")).toBe("foo(a, x)");
});
+2 -7
Переглянути файл
@@ -189,12 +189,8 @@ function field(n: number | string, text: string): string {
/** Converts an LSP snippet into a CodeMirror snippet template. */
export function lspSnippetToCm(snippet: string, vars: SnippetVariables = {}): string {
const nodes = parse(snippet);
const hasField = nodes.some(
(n) => n.t === "tab" || n.t === "choice" || (n.t === "var" && vars[n.name] === undefined && !n.children),
);
let out = "";
for (const n of nodes) {
for (const n of parse(snippet)) {
if (n.t === "text") out += escapeText(n.v);
else if (n.t === "tab") out += field(n.n, n.children ? flatten(n.children, vars) : "");
else if (n.t === "choice") out += field(n.n, n.options[0] ?? "");
@@ -206,8 +202,7 @@ export function lspSnippetToCm(snippet: string, vars: SnippetVariables = {}): st
else out += field(n.name, "");
}
}
// CodeMirror leaves the caret before an insertion without fields.
return hasField ? out : out + "${0}";
return out;
}
/** The snippet's text with every field replaced by its default (for previews / plain insertion). */
+1 -1
Переглянути файл
@@ -343,7 +343,7 @@ export function sqlCandidates({ model, analysis: a, driver, sourceName }: Comple
kind: "function",
tail: `(${f.params})`,
type: f.returns?.toUpperCase(),
snippet: f.params ? `${name}(\${})` : `${name}()\${0}`,
snippet: f.params ? `${name}(\${})` : `${name}()`,
boost: f.aggregate && clause !== "select" && clause !== "having" ? fnBoost - 4 : fnBoost,
});
}
-16
Переглянути файл
@@ -1,6 +1,4 @@
import { describe, expect, it } from "vitest";
import { EditorState } from "@codemirror/state";
import { snippet } from "@codemirror/autocomplete";
import type { DbCompletion, DbDriver } from "@/platform/ipc";
import { analyze, templateContexts, valuesSlot } from "./analyze";
import { aliasFor, quoteIdent, sqlCandidates, type SqlCandidate } from "./complete";
@@ -329,20 +327,6 @@ describe("completion candidates", () => {
expect(c.find((x) => x.label === "count")).toMatchObject({ snippet: "count(${})" });
});
it.each(["sqlite", "postgres", "mysql"] as const)(
"leaves the caret after a %s function without parameters",
(driver) => {
const candidates = complete("select |", driver).filter((c) => c.kind === "function" && c.tail === "()");
expect(candidates.length).toBeGreaterThan(0);
for (const candidate of candidates) {
let state = EditorState.create({ doc: "select ", selection: { anchor: 7 } });
snippet(candidate.snippet!)({ state, dispatch: (tr) => (state = tr.state) }, null, 7, 7);
expect(state.doc.toString()).toBe(`select ${candidate.label}()`);
expect(state.selection.main.head).toBe(state.doc.length);
}
},
);
it("quotes identifiers that need it", () => {
expect(quoteIdent("Users", "postgres")).toBe('"Users"');
expect(quoteIdent("Users", "mysql")).toBe("Users");
+1 -1
Переглянути файл
@@ -172,7 +172,7 @@ export const ICLS_ATTRIBUTES: Record<string, Array<[Part, string]>> = {
WARNING_ATTRIBUTES: [["EFFECT_COLOR", "--wave-warning"]],
NOT_USED_ELEMENT_ATTRIBUTES: [["FOREGROUND", "--fg-unused"]],
MATCHED_BRACE_ATTRIBUTES: [["BACKGROUND", "--bg-matched-brace"]],
SEARCH_RESULT_ATTRIBUTES: [["BACKGROUND", "--bg-search-result"]],
SEARCH_RESULT_ATTRIBUTES: [["BACKGROUND", "--bg-search-match"]],
TEXT_SEARCH_RESULT_ATTRIBUTES: [["BACKGROUND", "--bg-text-search-result"]],
IDENTIFIER_UNDER_CARET_ATTRIBUTES: [["BACKGROUND", "--bg-identifier-under-caret"]],
WRITE_IDENTIFIER_UNDER_CARET_ATTRIBUTES: [["BACKGROUND", "--bg-identifier-under-caret-write"]],
+8 -8
Переглянути файл
@@ -57,7 +57,7 @@ async function preview(container: HTMLElement): Promise<EditorView> {
const { CodePreview, previewDecorations } = await import("../find/parts");
describe("CodePreview", () => {
it("marks matches and the current line", () => {
it("marks matches (the current line's as current) and the current line", () => {
const doc = Text.of(["ab", "order order", "x"]);
const set = previewDecorations(doc, 1, [
{
@@ -76,9 +76,9 @@ describe("CodePreview", () => {
});
expect(out).toEqual([
"3-3:cm-activeLine",
"3-8:cm-openidea-searchResult",
"9-14:cm-openidea-searchResult",
"15-16:cm-openidea-searchResult",
"3-8:cm-openidea-findMatch cm-openidea-findMatch-current",
"9-14:cm-openidea-findMatch cm-openidea-findMatch-current",
"15-16:cm-openidea-findMatch",
]);
});
@@ -87,10 +87,10 @@ describe("CodePreview", () => {
await preview(container);
expect(container.querySelector(".cm-content")?.textContent).toContain("val order = 1");
expect(container.querySelector(".cm-content")?.getAttribute("contenteditable")).toBe("true");
expect(container.querySelector(".cm-openidea-searchResult")?.textContent).toBe("order");
expect(container.querySelector(".cm-openidea-findMatch")?.textContent).toBe("order");
});
it("nests matches inside syntax token spans, as find matches in an editor tab", async () => {
it("nests matches inside syntax token spans, so the search-match colour reaches the text", async () => {
// Like the syntax highlighter: a `Prec.high` mark with its own colour over "order".
const token = Prec.high(
EditorView.decorations.of(Decoration.set(Decoration.mark({ class: "token" }).range(15, 20))),
@@ -101,7 +101,7 @@ describe("CodePreview", () => {
]);
const { container } = render(<CodePreview path={path} line={1} highlights={[{ line: 1, ranges: [[4, 9]] }]} />);
await preview(container);
expect(container.querySelector(".token .cm-openidea-searchResult")?.textContent).toBe("order");
expect(container.querySelector(".token .cm-openidea-findMatch")?.textContent).toBe("order");
});
it.each([false, true])("shares edits, document undo and save with a tab (mounted: %s)", async (mounted) => {
@@ -115,7 +115,7 @@ describe("CodePreview", () => {
act(() => view.dispatch({ changes: { from: 0, insert: "// 😀\n" }, userEvent: "input.type" }));
expect(tab.currentState().doc.toString()).toBe("// 😀\n" + original);
expect(model.getMeta().dirty).toBe(true);
expect(view.state.doc.lineAt(view.posAtDOM(container.querySelector(".cm-openidea-searchResult")!)).number).toBe(3);
expect(view.state.doc.lineAt(view.posAtDOM(container.querySelector(".cm-openidea-findMatch")!)).number).toBe(3);
// The tab's Undo removes preview typing; the preview follows immediately.
act(() => {
undo({ state: tab.currentState(), dispatch: (tr) => tab.dispatchLocal(tr) });
+5 -5
Переглянути файл
@@ -79,12 +79,12 @@ const previewMarks = StateField.define<DecorationSet>({
for (const e of tr.effects) if (e.is(setPreviewMarks)) return e.value;
return value.map(tr.changes);
},
// Above the syntax highlighter (`Prec.high`), so a match nests inside its token span as find
// matches do in an editor tab.
// Above the syntax highlighter (`Prec.high`), so a match nests inside its token span and
// `--fg-search-match` colours the text, as find matches do in an editor tab (#1733).
provide: (f) => Prec.highest(EditorView.decorations.from(f)),
});
/** IDEA's SEARCH_RESULT_ATTRIBUTES: a background only, so the syntax colours stay (#2858). */
const matchMark = Decoration.mark({ class: "cm-openidea-searchResult" });
const matchMark = Decoration.mark({ class: "cm-openidea-findMatch" });
const currentMatchMark = Decoration.mark({ class: "cm-openidea-findMatch cm-openidea-findMatch-current" });
const currentLineMark = Decoration.line({ class: "cm-activeLine" });
const readOnly = [EditorState.readOnly.of(true), EditorView.editable.of(false)];
@@ -98,7 +98,7 @@ export function previewDecorations(doc: Text, line: number, highlights: readonly
for (const [a, b] of h.ranges) {
const from = l.from + Math.min(a, l.length);
const to = l.from + Math.min(b, l.length);
if (to > from) out.push(matchMark.range(from, to));
if (to > from) out.push((h.line === line ? currentMatchMark : matchMark).range(from, to));
}
}
return Decoration.set(out, true);
+1 -1
Переглянути файл
@@ -86,7 +86,7 @@ function CommitDialog({ onClose }: { onClose: () => void }) {
<SplitHandle />
<SplitPanel defaultSize="42%" minSize="120px">
<div className={viewStyles.commit}>
<CommitMessageField included={included} runner={runner} autoFocus />
<CommitMessageField included={included} runner={runner} />
</div>
</SplitPanel>
</SplitGroup>
+1 -12
Переглянути файл
@@ -727,7 +727,6 @@ export function ChangesTree({ mode }: { mode: "commit" | "local" | "dialog" }) {
{mode !== "dialog" && <OperationBanner />}
<div
className={styles.treeWrap}
data-changes-tree={mode}
onKeyDownCapture={(e) => {
if (checkboxes && e.key === " " && !e.metaKey && !e.ctrlKey && selected.size) {
e.preventDefault();
@@ -980,16 +979,7 @@ export function useCommitRunner(included: number, onCommitted?: () => void): Com
}
/** Amend, message history and the message field with its inspections and failed checks. */
export function CommitMessageField({
included,
runner,
autoFocus = false,
}: {
included: number;
runner: CommitRunner;
/** The message takes the initial focus of the dialog it is in (the Commit dialog). */
autoFocus?: boolean;
}) {
export function CommitMessageField({ included, runner }: { included: number; runner: CommitRunner }) {
const message = useCommit((s) => s.message);
const amend = useCommit((s) => s.amend);
const focusTick = useCommit((s) => s.focusTick);
@@ -1062,7 +1052,6 @@ export function CommitMessageField({
</div>
<textarea
ref={ref}
data-autofocus={autoFocus ? "" : undefined}
className={clsx(styles.message, !showMargin && styles.noMargin, emptyError && styles.messageError)}
aria-invalid={emptyError || undefined}
style={{ ["--guide" as string]: `${margin}ch` }}
+1 -41
Переглянути файл
@@ -24,7 +24,7 @@ import { useProject } from "@/platform/project";
import { commitChangelist, commitFiles } from "../changelistsUi";
import { initialChangelists, useChangelists } from "../changelists";
import { gitTabs, shownTab, stashTabOf, stashTabs } from "../gitTabs";
import { commitFromContext, pushDialogNeeded } from "../commitUi";
import { pushDialogNeeded } from "../commitUi";
import {
installPreparedMessage,
keepMessageAfterCommit,
@@ -188,46 +188,6 @@ describe("Commit File… and the modal commit interface", () => {
});
});
describe("Commit… from Local Changes with the modal interface", () => {
const status: FileStatus[] = [
{ path: "/p/src/a.ts", relPath: "src/a.ts", staged: null, unstaged: "modified", renamedFrom: null },
{ path: "/p/src/b.ts", relPath: "src/b.ts", staged: null, unstaged: "modified", renamedFrom: null },
{ path: "/p/new.txt", relPath: "new.txt", staged: null, unstaged: "untracked", renamedFrom: null },
];
const tree = document.createElement("div");
tree.dataset.changesTree = "local";
const row = document.createElement("button");
tree.append(row);
beforeEach(() => {
showCommitDialog.mockClear();
document.body.append(tree);
useProject.setState({ settings: { vcs: { commit: { nonModal: false } } } as Settings });
useVcs.setState({ available: true, root: "/p", repo: "/p", roots: ["/p"], status });
useCommit.setState({ checks: new Map(), selection: ["/p/src/b.ts", "/p/new.txt"] });
});
it("includes just the files selected there", async () => {
row.focus();
commitFromContext();
await vi.waitFor(() => expect(showCommitDialog).toHaveBeenCalled());
const checks = useCommit.getState().checks;
expect([checks.get("/p/src/a.ts"), checks.get("/p/src/b.ts"), checks.get("/p/new.txt")]).toEqual([
"none",
"all",
"all",
]);
});
it("falls back to the default inclusion when invoked elsewhere", async () => {
useCommit.setState({ checks: new Map([["/p/src/a.ts", "none"]]) });
row.blur();
commitFromContext();
await vi.waitFor(() => expect(showCommitDialog).toHaveBeenCalled());
expect(useCommit.getState().checks.size).toBe(0);
});
});
describe("log tabs of their own", () => {
beforeEach(() => {
vcsLog.mockReset().mockResolvedValue({ status: "ok", data: [] });
+3 -3
Переглянути файл
@@ -44,7 +44,7 @@ import { gotoChange, rollbackSelectedLines } from "./gutter";
import { openLocalHistory } from "./localHistoryKind";
import { showSelectionHistory } from "./selectionHistoryKind";
import { openLogTab, revealCommit, selectGitTab, useGitLog } from "./logStore";
import { commitFromContext, isModalCommit, pushAfterCommit } from "./commitUi";
import { isModalCommit, openCommit, pushAfterCommit } from "./commitUi";
import { allRepos, runRemote } from "./ops";
import { showPushDialog, showUpdateDialog, type PushTarget, type UpdateTarget } from "./remoteUi";
import { comparePathsWithRevision, showDirectoryWorkingDiff } from "./comparePaths";
@@ -212,7 +212,7 @@ export function registerVcsActions(): () => void {
category: "Git",
icon: "commit",
when: VCS,
run: commitFromContext,
run: openCommit,
},
{
id: "vcs.commitAndPush",
@@ -223,7 +223,7 @@ export function registerVcsActions(): () => void {
run: async () => {
const { message, amend } = useCommit.getState();
// Nothing to commit with yet (or the modal interface): IDEA shows where the message is typed.
if (isModalCommit() || (!message.trim() && !amend)) return commitFromContext();
if (isModalCommit() || (!message.trim() && !amend)) return openCommit();
const id = await commitChanges();
if (id !== undefined) await pushAfterCommit();
},
-27
Переглянути файл
@@ -6,8 +6,6 @@
import { workflowSettings, type GitSettingsValid } from "@/platform/workflowSettings";
import { toolWindowService } from "@/workbench/contributions";
import { useCommit, type CommitTab } from "./commitStore";
import type { FileStatus } from "@/bindings";
import { groupOf, isPartiallyStaged, type CheckState } from "./changes";
import { stashTabOf } from "./gitTabs";
import { selectGitTab } from "./logStore";
import { isProtectedBranch, pushWithAutoUpdate, showPushDialog } from "./remoteUi";
@@ -33,31 +31,6 @@ export function openCommit() {
void import("./CommitDialog").then((m) => m.showCommitDialog());
}
/**
* Commit… (⌘K) as an action: with the modal interface the dialog includes just the files
* selected in Local Changes when invoked from there, otherwise the default inclusion.
*/
export function commitFromContext() {
if (isModalCommit()) {
const fromLocalChanges = !!(document.activeElement as HTMLElement | null)?.closest?.('[data-changes-tree="local"]');
const selection = fromLocalChanges ? useCommit.getState().selection : [];
useCommit.setState({ checks: selection.length ? includeOnly(useVcs.getState().status, selection) : new Map() });
}
openCommit();
}
/** Inclusion that commits only `paths` of `status` (files elsewhere left out, conflicts as they are). */
export function includeOnly(status: readonly FileStatus[], paths: readonly string[]): Map<string, CheckState> {
const chosen = new Set(paths);
const checks = new Map<string, CheckState>();
for (const fs of status) {
const group = groupOf(fs);
if (!group || group === "conflicts") continue;
checks.set(fs.path, !chosen.has(fs.path) ? "none" : isPartiallyStaged(fs) ? "staged" : "all");
}
return checks;
}
/**
* Commit and Push…: whether the Push dialog comes up after the commit (Settings › Git ›
* "Show Push dialog for Commit and Push", optionally only for protected branches) or
-4
Переглянути файл
@@ -195,7 +195,6 @@ html {
--fg-editor-selection: inherit;
--bg-text-search-result: #114957;
--bg-text-search-result-current: #165e70;
--bg-search-result: #2d543f;
--bg-matched-brace: #43454a;
--matched-brace-weight: var(--fw-code-bold);
--bg-matched-tag: #3f4045;
@@ -622,7 +621,6 @@ html {
--fg-editor-selection: inherit;
--bg-text-search-result: #fcd47e;
--bg-text-search-result-current: #f2bf57;
--bg-search-result: #ccccff;
--bg-matched-brace: #93d9d9;
--matched-brace-weight: inherit;
--bg-matched-tag: #c9ecec;
@@ -915,7 +913,6 @@ html {
--bg-editor-selection-inactive: #1f3f7a;
--bg-text-search-result: #146a7d;
--bg-text-search-result-current: #1d8aa3;
--bg-search-result: #006112;
--bg-matched-brace: #55585f;
--bg-matched-tag: #45474d;
--bg-fold-placeholder: #33353b;
@@ -1181,7 +1178,6 @@ html {
--bg-text-search-result: color-mix(in srgb, var(--palette-yellow) 22%, var(--palette-bg));
--bg-search-match-current: color-mix(in srgb, var(--palette-yellow) 36%, var(--palette-bg));
--bg-text-search-result-current: color-mix(in srgb, var(--palette-yellow) 36%, var(--palette-bg));
--bg-search-result: color-mix(in srgb, var(--palette-green) 22%, var(--palette-bg));
--fg-search-match: var(--palette-fg);
--border-tab-selected: var(--palette-accent);
--border-indent-guide-active: var(--palette-accent);
-2
Переглянути файл
@@ -178,7 +178,6 @@ const ICONS = {
"file-gdshader": "openidea/fileTypes/gdshader",
"file-asciidoc": "openidea/fileTypes/asciidoc",
"file-d": "openidea/fileTypes/d",
"file-prisma": "openidea/fileTypes/prisma",
"file-dart": "openidea/fileTypes/dart",
"file-elixir": "openidea/fileTypes/elixir",
"file-erlang": "openidea/fileTypes/erlang",
@@ -1099,7 +1098,6 @@ const EXT_ICONS = table({
"file-gdshader": "gdshader gdshaderinc",
"file-asciidoc": "adoc asciidoc",
"file-d": "d di",
"file-prisma": "prisma",
"file-dart": "dart",
"file-elixir": "ex exs",
"file-erlang": "erl hrl escript app.src",
-3
Переглянути файл
@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g stroke-linejoin="round"><path d="M14 4C14 2.89543 13.1046 2 12 2H4C2.89543 2 2 2.89543 2 4V12C2 13.1046 2.89543 14 4 14H12C13.1046 14 14 13.1046 14 12V4Z" fill="#3A8A80"/><path transform="translate(5 3.5)" d="M0.5 8.5V0.5H3.2C4.9 0.5 5.9 1.5 5.9 2.9C5.9 4.3 4.9 5.3 3.2 5.3H0.5" stroke="#2B2D30"/></g>
</svg>

До

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

-3
Переглянути файл
@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g stroke-linejoin="round"><path d="M14 4C14 2.89543 13.1046 2 12 2H4C2.89543 2 2 2.89543 2 4V12C2 13.1046 2.89543 14 4 14H12C13.1046 14 14 13.1046 14 12V4Z" fill="#4FA397"/><path transform="translate(5 3.5)" d="M0.5 8.5V0.5H3.2C4.9 0.5 5.9 1.5 5.9 2.9C5.9 4.3 4.9 5.3 3.2 5.3H0.5" stroke="#2B2D30"/></g>
</svg>

До

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