From 6fdecab7155acaedb8ced4557df17748ac494726 Mon Sep 17 00:00:00 2001 From: filipstrand Date: Tue, 13 Jan 2026 16:42:25 +0100 Subject: [PATCH 1/3] More icons and thrid-party-notices - More icons and thrid-party-notices (06f21092) - fix (41b3081a) --- THIRD_PARTY_NOTICES.md | 11 +++++ media/icons/CMake.svg | 19 ++++++++ media/icons/actionScript.svg | 5 +++ media/icons/angularJS.svg | 1 + media/icons/anyType.svg | 5 +++ media/icons/application.svg | 6 +++ media/icons/archive.svg | 8 ++++ media/icons/beam.svg | 1 + media/icons/cargo.svg | 7 +++ media/icons/cargoLock.svg | 12 +++++ media/icons/cassandra.svg | 10 +++++ media/icons/clojure.svg | 1 + media/icons/config.svg | 7 +++ media/icons/cs.svg | 10 +++++ media/icons/cshtml.svg | 11 +++++ media/icons/csproj.svg | 18 ++++++++ media/icons/csv.svg | 7 +++ media/icons/dart.svg | 8 ++++ media/icons/docker.svg | 2 +- media/icons/dockerCompose.svg | 7 +++ media/icons/dune.svg | 1 + media/icons/editorConfig.svg | 4 ++ media/icons/eex.svg | 6 +++ media/icons/elixir.svg | 4 ++ media/icons/erbFile.svg | 5 +++ media/icons/erlang.svg | 1 + media/icons/eslint.svg | 1 + media/icons/folder.svg | 2 +- media/icons/font.svg | 5 +++ media/icons/gitignore.svg | 1 + media/icons/gleam.svg | 15 +++++++ media/icons/gomodsum.svg | 1 + media/icons/gradle.svg | 1 + media/icons/graphql.svg | 1 + media/icons/h.svg | 4 ++ media/icons/haskell.svg | 1 + media/icons/hcl.svg | 5 +++ media/icons/html.svg | 4 +- media/icons/http.svg | 12 +++++ media/icons/ignored.svg | 5 +++ media/icons/image.svg | 6 +++ media/icons/java.svg | 6 +++ media/icons/javaScript.svg | 6 +-- media/icons/json.svg | 4 +- media/icons/jupyter.svg | 1 + media/icons/kotlin.svg | 5 +++ media/icons/less.svg | 5 +++ media/icons/lua.svg | 1 + media/icons/makefile.svg | 3 ++ media/icons/markdown.svg | 4 +- media/icons/mdx.svg | 1 + media/icons/ml.svg | 1 + media/icons/mli.svg | 1 + media/icons/opam.svg | 1 + media/icons/php.svg | 6 +++ media/icons/pnpm.svg | 10 +++++ media/icons/postcss.svg | 1 + media/icons/projectProperties.svg | 6 +++ media/icons/properties.svg | 4 ++ media/icons/protobuf.svg | 1 + media/icons/rakeTask.svg | 6 +++ media/icons/react.svg | 8 ++-- media/icons/rego.svg | 1 + media/icons/ruby.svg | 4 ++ media/icons/rubyGems.svg | 5 +++ media/icons/rustFile.svg | 3 ++ media/icons/scala.svg | 6 +++ media/icons/scss.svg | 1 + media/icons/shell.svg | 6 +++ media/icons/slim.svg | 4 ++ media/icons/solution.svg | 6 +++ media/icons/sql.svg | 10 +++++ media/icons/svelte.svg | 1 + media/icons/swift.svg | 1 + media/icons/tailwind.svg | 1 + media/icons/terraform.svg | 7 +++ media/icons/text.svg | 8 ++-- media/icons/toml.svg | 5 +++ media/icons/typeScript.svg | 2 +- media/icons/vite.svg | 15 +++++++ media/icons/vueJs.svg | 1 + media/icons/xml.svg | 4 ++ media/icons/yaml.svg | 4 +- media/icons/yarn.svg | 1 + src/webview/components/FileTree.tsx | 62 +++++++++----------------- src/webview/state/iconTheme.ts | 68 +++++++++++++++++++++++++++++ 86 files changed, 485 insertions(+), 63 deletions(-) create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 media/icons/CMake.svg create mode 100644 media/icons/actionScript.svg create mode 100644 media/icons/angularJS.svg create mode 100644 media/icons/anyType.svg create mode 100644 media/icons/application.svg create mode 100644 media/icons/archive.svg create mode 100644 media/icons/beam.svg create mode 100644 media/icons/cargo.svg create mode 100644 media/icons/cargoLock.svg create mode 100644 media/icons/cassandra.svg create mode 100644 media/icons/clojure.svg create mode 100644 media/icons/config.svg create mode 100644 media/icons/cs.svg create mode 100644 media/icons/cshtml.svg create mode 100644 media/icons/csproj.svg create mode 100644 media/icons/csv.svg create mode 100644 media/icons/dart.svg create mode 100644 media/icons/dockerCompose.svg create mode 100644 media/icons/dune.svg create mode 100644 media/icons/editorConfig.svg create mode 100644 media/icons/eex.svg create mode 100644 media/icons/elixir.svg create mode 100644 media/icons/erbFile.svg create mode 100644 media/icons/erlang.svg create mode 100644 media/icons/eslint.svg create mode 100644 media/icons/font.svg create mode 100644 media/icons/gitignore.svg create mode 100644 media/icons/gleam.svg create mode 100644 media/icons/gomodsum.svg create mode 100644 media/icons/gradle.svg create mode 100644 media/icons/graphql.svg create mode 100644 media/icons/h.svg create mode 100644 media/icons/haskell.svg create mode 100644 media/icons/hcl.svg create mode 100644 media/icons/http.svg create mode 100644 media/icons/ignored.svg create mode 100644 media/icons/image.svg create mode 100644 media/icons/java.svg create mode 100644 media/icons/jupyter.svg create mode 100644 media/icons/kotlin.svg create mode 100644 media/icons/less.svg create mode 100644 media/icons/lua.svg create mode 100644 media/icons/makefile.svg create mode 100644 media/icons/mdx.svg create mode 100644 media/icons/ml.svg create mode 100644 media/icons/mli.svg create mode 100644 media/icons/opam.svg create mode 100644 media/icons/php.svg create mode 100644 media/icons/pnpm.svg create mode 100644 media/icons/postcss.svg create mode 100644 media/icons/projectProperties.svg create mode 100644 media/icons/properties.svg create mode 100644 media/icons/protobuf.svg create mode 100644 media/icons/rakeTask.svg create mode 100644 media/icons/rego.svg create mode 100644 media/icons/ruby.svg create mode 100644 media/icons/rubyGems.svg create mode 100644 media/icons/rustFile.svg create mode 100644 media/icons/scala.svg create mode 100644 media/icons/scss.svg create mode 100644 media/icons/shell.svg create mode 100644 media/icons/slim.svg create mode 100644 media/icons/solution.svg create mode 100644 media/icons/sql.svg create mode 100644 media/icons/svelte.svg create mode 100644 media/icons/swift.svg create mode 100644 media/icons/tailwind.svg create mode 100644 media/icons/terraform.svg create mode 100644 media/icons/toml.svg create mode 100644 media/icons/vite.svg create mode 100644 media/icons/vueJs.svg create mode 100644 media/icons/xml.svg create mode 100644 media/icons/yarn.svg create mode 100644 src/webview/state/iconTheme.ts diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..26c8aae --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,11 @@ +# Third-Party Notices + +This project bundles third-party assets under their respective licenses. + +## JetBrains IntelliJ Platform Icons (2023+ UI, Auto) + +- **Source**: `ardonplay.vscode-jetbrains-icon-theme` (VS Code extension) +- **License text**: see `media/icons/LICENSE-2.0.txt` + +If you believe any bundled icon has different licensing requirements, please open an issue with the icon filename and its upstream source. + diff --git a/media/icons/CMake.svg b/media/icons/CMake.svg new file mode 100644 index 0000000..4972017 --- /dev/null +++ b/media/icons/CMake.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/media/icons/actionScript.svg b/media/icons/actionScript.svg new file mode 100644 index 0000000..de57c7a --- /dev/null +++ b/media/icons/actionScript.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/media/icons/angularJS.svg b/media/icons/angularJS.svg new file mode 100644 index 0000000..0f1b14e --- /dev/null +++ b/media/icons/angularJS.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/anyType.svg b/media/icons/anyType.svg new file mode 100644 index 0000000..eee0e6b --- /dev/null +++ b/media/icons/anyType.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/media/icons/application.svg b/media/icons/application.svg new file mode 100644 index 0000000..71f3a85 --- /dev/null +++ b/media/icons/application.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/media/icons/archive.svg b/media/icons/archive.svg new file mode 100644 index 0000000..ab3cae2 --- /dev/null +++ b/media/icons/archive.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/media/icons/beam.svg b/media/icons/beam.svg new file mode 100644 index 0000000..c6cc27c --- /dev/null +++ b/media/icons/beam.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/cargo.svg b/media/icons/cargo.svg new file mode 100644 index 0000000..9139e71 --- /dev/null +++ b/media/icons/cargo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/media/icons/cargoLock.svg b/media/icons/cargoLock.svg new file mode 100644 index 0000000..ab5c308 --- /dev/null +++ b/media/icons/cargoLock.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/media/icons/cassandra.svg b/media/icons/cassandra.svg new file mode 100644 index 0000000..d16da70 --- /dev/null +++ b/media/icons/cassandra.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/media/icons/clojure.svg b/media/icons/clojure.svg new file mode 100644 index 0000000..75befe9 --- /dev/null +++ b/media/icons/clojure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/config.svg b/media/icons/config.svg new file mode 100644 index 0000000..7e652e5 --- /dev/null +++ b/media/icons/config.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/media/icons/cs.svg b/media/icons/cs.svg new file mode 100644 index 0000000..0249b5a --- /dev/null +++ b/media/icons/cs.svg @@ -0,0 +1,10 @@ + + Csharp(GrayDark) + + + + + + + + diff --git a/media/icons/cshtml.svg b/media/icons/cshtml.svg new file mode 100644 index 0000000..bce3db6 --- /dev/null +++ b/media/icons/cshtml.svg @@ -0,0 +1,11 @@ + + Razor(GrayDark) + + + + + + + + + diff --git a/media/icons/csproj.svg b/media/icons/csproj.svg new file mode 100644 index 0000000..809d086 --- /dev/null +++ b/media/icons/csproj.svg @@ -0,0 +1,18 @@ + + CsharpProject(GrayDark) + + + + + + + + + + + + + + + + diff --git a/media/icons/csv.svg b/media/icons/csv.svg new file mode 100644 index 0000000..d038177 --- /dev/null +++ b/media/icons/csv.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/media/icons/dart.svg b/media/icons/dart.svg new file mode 100644 index 0000000..2d0e595 --- /dev/null +++ b/media/icons/dart.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/media/icons/docker.svg b/media/icons/docker.svg index 76c5f02..f8a6d4c 100644 --- a/media/icons/docker.svg +++ b/media/icons/docker.svg @@ -1,4 +1,4 @@ - + diff --git a/media/icons/dockerCompose.svg b/media/icons/dockerCompose.svg new file mode 100644 index 0000000..7ebb58b --- /dev/null +++ b/media/icons/dockerCompose.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/media/icons/dune.svg b/media/icons/dune.svg new file mode 100644 index 0000000..ee6e036 --- /dev/null +++ b/media/icons/dune.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/editorConfig.svg b/media/icons/editorConfig.svg new file mode 100644 index 0000000..528d02e --- /dev/null +++ b/media/icons/editorConfig.svg @@ -0,0 +1,4 @@ + + + + diff --git a/media/icons/eex.svg b/media/icons/eex.svg new file mode 100644 index 0000000..ddcaff5 --- /dev/null +++ b/media/icons/eex.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/media/icons/elixir.svg b/media/icons/elixir.svg new file mode 100644 index 0000000..7c8ce9c --- /dev/null +++ b/media/icons/elixir.svg @@ -0,0 +1,4 @@ + + + + diff --git a/media/icons/erbFile.svg b/media/icons/erbFile.svg new file mode 100644 index 0000000..b3e727d --- /dev/null +++ b/media/icons/erbFile.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/media/icons/erlang.svg b/media/icons/erlang.svg new file mode 100644 index 0000000..375a926 --- /dev/null +++ b/media/icons/erlang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/eslint.svg b/media/icons/eslint.svg new file mode 100644 index 0000000..ac72f26 --- /dev/null +++ b/media/icons/eslint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/folder.svg b/media/icons/folder.svg index 7ece6b7..0af492b 100644 --- a/media/icons/folder.svg +++ b/media/icons/folder.svg @@ -1,4 +1,4 @@ - + diff --git a/media/icons/font.svg b/media/icons/font.svg new file mode 100644 index 0000000..94fbd32 --- /dev/null +++ b/media/icons/font.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/media/icons/gitignore.svg b/media/icons/gitignore.svg new file mode 100644 index 0000000..1527df8 --- /dev/null +++ b/media/icons/gitignore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/gleam.svg b/media/icons/gleam.svg new file mode 100644 index 0000000..5175ff6 --- /dev/null +++ b/media/icons/gleam.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/media/icons/gomodsum.svg b/media/icons/gomodsum.svg new file mode 100644 index 0000000..18b9289 --- /dev/null +++ b/media/icons/gomodsum.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/gradle.svg b/media/icons/gradle.svg new file mode 100644 index 0000000..f169520 --- /dev/null +++ b/media/icons/gradle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/graphql.svg b/media/icons/graphql.svg new file mode 100644 index 0000000..2fa86b6 --- /dev/null +++ b/media/icons/graphql.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/h.svg b/media/icons/h.svg new file mode 100644 index 0000000..62d4619 --- /dev/null +++ b/media/icons/h.svg @@ -0,0 +1,4 @@ + + + + diff --git a/media/icons/haskell.svg b/media/icons/haskell.svg new file mode 100644 index 0000000..64f0389 --- /dev/null +++ b/media/icons/haskell.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/hcl.svg b/media/icons/hcl.svg new file mode 100644 index 0000000..b1740f1 --- /dev/null +++ b/media/icons/hcl.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/media/icons/html.svg b/media/icons/html.svg index c446cf3..9e10d57 100644 --- a/media/icons/html.svg +++ b/media/icons/html.svg @@ -1,5 +1,5 @@ - - + + diff --git a/media/icons/http.svg b/media/icons/http.svg new file mode 100644 index 0000000..e89963e --- /dev/null +++ b/media/icons/http.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/media/icons/ignored.svg b/media/icons/ignored.svg new file mode 100644 index 0000000..1cbad7a --- /dev/null +++ b/media/icons/ignored.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/media/icons/image.svg b/media/icons/image.svg new file mode 100644 index 0000000..4f189a0 --- /dev/null +++ b/media/icons/image.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/media/icons/java.svg b/media/icons/java.svg new file mode 100644 index 0000000..155295f --- /dev/null +++ b/media/icons/java.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/media/icons/javaScript.svg b/media/icons/javaScript.svg index d8bff96..043bb73 100644 --- a/media/icons/javaScript.svg +++ b/media/icons/javaScript.svg @@ -1,6 +1,6 @@ - - - + + + diff --git a/media/icons/json.svg b/media/icons/json.svg index d8f707d..29371ab 100644 --- a/media/icons/json.svg +++ b/media/icons/json.svg @@ -1,5 +1,5 @@ - - + + diff --git a/media/icons/jupyter.svg b/media/icons/jupyter.svg new file mode 100644 index 0000000..d1b8fec --- /dev/null +++ b/media/icons/jupyter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/kotlin.svg b/media/icons/kotlin.svg new file mode 100644 index 0000000..10edacb --- /dev/null +++ b/media/icons/kotlin.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/media/icons/less.svg b/media/icons/less.svg new file mode 100644 index 0000000..09d461d --- /dev/null +++ b/media/icons/less.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/media/icons/lua.svg b/media/icons/lua.svg new file mode 100644 index 0000000..6dd7e80 --- /dev/null +++ b/media/icons/lua.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/makefile.svg b/media/icons/makefile.svg new file mode 100644 index 0000000..e1ca58b --- /dev/null +++ b/media/icons/makefile.svg @@ -0,0 +1,3 @@ + + + diff --git a/media/icons/markdown.svg b/media/icons/markdown.svg index c603fd2..2d86cd3 100644 --- a/media/icons/markdown.svg +++ b/media/icons/markdown.svg @@ -1,5 +1,5 @@ - - + + diff --git a/media/icons/mdx.svg b/media/icons/mdx.svg new file mode 100644 index 0000000..6e7e5d3 --- /dev/null +++ b/media/icons/mdx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/ml.svg b/media/icons/ml.svg new file mode 100644 index 0000000..0f0c222 --- /dev/null +++ b/media/icons/ml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/mli.svg b/media/icons/mli.svg new file mode 100644 index 0000000..32309da --- /dev/null +++ b/media/icons/mli.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/opam.svg b/media/icons/opam.svg new file mode 100644 index 0000000..b4711f5 --- /dev/null +++ b/media/icons/opam.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/php.svg b/media/icons/php.svg new file mode 100644 index 0000000..a96a947 --- /dev/null +++ b/media/icons/php.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/media/icons/pnpm.svg b/media/icons/pnpm.svg new file mode 100644 index 0000000..0d32042 --- /dev/null +++ b/media/icons/pnpm.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/media/icons/postcss.svg b/media/icons/postcss.svg new file mode 100644 index 0000000..3b55428 --- /dev/null +++ b/media/icons/postcss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/projectProperties.svg b/media/icons/projectProperties.svg new file mode 100644 index 0000000..e4e1b03 --- /dev/null +++ b/media/icons/projectProperties.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/media/icons/properties.svg b/media/icons/properties.svg new file mode 100644 index 0000000..528d02e --- /dev/null +++ b/media/icons/properties.svg @@ -0,0 +1,4 @@ + + + + diff --git a/media/icons/protobuf.svg b/media/icons/protobuf.svg new file mode 100644 index 0000000..ff37164 --- /dev/null +++ b/media/icons/protobuf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/rakeTask.svg b/media/icons/rakeTask.svg new file mode 100644 index 0000000..d7aeb48 --- /dev/null +++ b/media/icons/rakeTask.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/media/icons/react.svg b/media/icons/react.svg index 7059146..46c0ac8 100644 --- a/media/icons/react.svg +++ b/media/icons/react.svg @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/media/icons/rego.svg b/media/icons/rego.svg new file mode 100644 index 0000000..e45ca5c --- /dev/null +++ b/media/icons/rego.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/ruby.svg b/media/icons/ruby.svg new file mode 100644 index 0000000..acb164c --- /dev/null +++ b/media/icons/ruby.svg @@ -0,0 +1,4 @@ + + + + diff --git a/media/icons/rubyGems.svg b/media/icons/rubyGems.svg new file mode 100644 index 0000000..312dd5c --- /dev/null +++ b/media/icons/rubyGems.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/media/icons/rustFile.svg b/media/icons/rustFile.svg new file mode 100644 index 0000000..86a824f --- /dev/null +++ b/media/icons/rustFile.svg @@ -0,0 +1,3 @@ + + + diff --git a/media/icons/scala.svg b/media/icons/scala.svg new file mode 100644 index 0000000..3fd07cc --- /dev/null +++ b/media/icons/scala.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/media/icons/scss.svg b/media/icons/scss.svg new file mode 100644 index 0000000..715212f --- /dev/null +++ b/media/icons/scss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/shell.svg b/media/icons/shell.svg new file mode 100644 index 0000000..7022d0c --- /dev/null +++ b/media/icons/shell.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/media/icons/slim.svg b/media/icons/slim.svg new file mode 100644 index 0000000..9fa4f38 --- /dev/null +++ b/media/icons/slim.svg @@ -0,0 +1,4 @@ + + + + diff --git a/media/icons/solution.svg b/media/icons/solution.svg new file mode 100644 index 0000000..aa8acc9 --- /dev/null +++ b/media/icons/solution.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/media/icons/sql.svg b/media/icons/sql.svg new file mode 100644 index 0000000..85d9593 --- /dev/null +++ b/media/icons/sql.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/media/icons/svelte.svg b/media/icons/svelte.svg new file mode 100644 index 0000000..4c6bc79 --- /dev/null +++ b/media/icons/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/swift.svg b/media/icons/swift.svg new file mode 100644 index 0000000..69d7c07 --- /dev/null +++ b/media/icons/swift.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/tailwind.svg b/media/icons/tailwind.svg new file mode 100644 index 0000000..96ccfa5 --- /dev/null +++ b/media/icons/tailwind.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/terraform.svg b/media/icons/terraform.svg new file mode 100644 index 0000000..c953fbb --- /dev/null +++ b/media/icons/terraform.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/media/icons/text.svg b/media/icons/text.svg index 110139c..8646827 100644 --- a/media/icons/text.svg +++ b/media/icons/text.svg @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/media/icons/toml.svg b/media/icons/toml.svg new file mode 100644 index 0000000..b9e6aea --- /dev/null +++ b/media/icons/toml.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/media/icons/typeScript.svg b/media/icons/typeScript.svg index 957790f..bcac440 100644 --- a/media/icons/typeScript.svg +++ b/media/icons/typeScript.svg @@ -1,5 +1,5 @@ - + diff --git a/media/icons/vite.svg b/media/icons/vite.svg new file mode 100644 index 0000000..6ceec21 --- /dev/null +++ b/media/icons/vite.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/media/icons/vueJs.svg b/media/icons/vueJs.svg new file mode 100644 index 0000000..bbd74f1 --- /dev/null +++ b/media/icons/vueJs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/icons/xml.svg b/media/icons/xml.svg new file mode 100644 index 0000000..8a2eb77 --- /dev/null +++ b/media/icons/xml.svg @@ -0,0 +1,4 @@ + + + + diff --git a/media/icons/yaml.svg b/media/icons/yaml.svg index 8bffda4..31e1126 100644 --- a/media/icons/yaml.svg +++ b/media/icons/yaml.svg @@ -1,5 +1,5 @@ - - + + diff --git a/media/icons/yarn.svg b/media/icons/yarn.svg new file mode 100644 index 0000000..1e7a312 --- /dev/null +++ b/media/icons/yarn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/webview/components/FileTree.tsx b/src/webview/components/FileTree.tsx index 5e76a89..b42b54e 100644 --- a/src/webview/components/FileTree.tsx +++ b/src/webview/components/FileTree.tsx @@ -1,5 +1,6 @@ import React, { useState, useMemo, useEffect } from 'react'; import { Change } from '../../extension/protocol/types'; +import { iconTheme } from '../state/iconTheme'; declare global { interface Window { @@ -37,6 +38,12 @@ export const FileTree: React.FC = ({ selectedPaths, onToggleSelect }) => { + const getFolderIcon = (isExpanded: boolean) => { + const base = window.iconsUri; + const p = (isExpanded ? iconTheme.folderExpanded : iconTheme.folder) || iconTheme.folder; + return `${base}/${p}`; + }; + const changeByPath = useMemo(() => { const map = new Map(); for (const c of changes) map.set(c.path, c); @@ -92,47 +99,20 @@ export const FileTree: React.FC = ({ }; const getFileIcon = (fileName: string) => { - const ext = fileName.split('.').pop()?.toLowerCase(); + const lower = fileName.toLowerCase(); + const ext = (lower.includes('.') ? lower.split('.').pop() : '') ?? ''; const base = window.iconsUri; - - switch (ext) { - case 'py': - return `${base}/python.svg`; - case 'ts': - return `${base}/typeScript.svg`; - case 'tsx': - return `${base}/react.svg`; - case 'js': - case 'jsx': - return `${base}/javaScript.svg`; - case 'json': - return `${base}/json.svg`; - case 'md': - return `${base}/markdown.svg`; - case 'yaml': - case 'yml': - return `${base}/yaml.svg`; - case 'html': - return `${base}/html.svg`; - case 'css': - return `${base}/css.svg`; - case 'rs': - return `${base}/rust.svg`; - case 'go': - return `${base}/go.svg`; - case 'c': - return `${base}/c.svg`; - case 'cpp': - case 'cc': - case 'cxx': - return `${base}/cpp.svg`; - case 'dockerfile': - return `${base}/docker.svg`; - default: - if (fileName.startsWith('.git')) return `${base}/git.svg`; - if (fileName.toLowerCase().includes('docker')) return `${base}/docker.svg`; - return `${base}/file.svg`; - } + + // 1) Exact filename mapping + const byName = iconTheme.fileNames[lower]; + if (byName) return `${base}/${byName}`; + + // 2) Extension mapping + const byExt = iconTheme.fileExtensions[ext]; + if (byExt) return `${base}/${byExt}`; + + // 3) Fallback + return `${base}/${iconTheme.file || 'text.svg'}`; }; const renderNode = (node: TreeNode, depth: number) => { @@ -215,7 +195,7 @@ export const FileTree: React.FC = ({ <> folder diff --git a/src/webview/state/iconTheme.ts b/src/webview/state/iconTheme.ts new file mode 100644 index 0000000..2845ccf --- /dev/null +++ b/src/webview/state/iconTheme.ts @@ -0,0 +1,68 @@ +export type SimpleIconTheme = { + /** + * Lowercased exact file names (no path) -> icon filename in `media/icons/` + * Examples: "dockerfile", "yarn.lock" + */ + fileNames: Record; + /** + * Lowercased file extensions (without leading dot) -> icon filename in `media/icons/` + * Examples: "ts", "png" + */ + fileExtensions: Record; + folder: string; + folderExpanded?: string; + file: string; +}; + +// Lightweight, curated mapping for the webview file tree icons. +// Keep this small on purpose—add only what we actually care about. +export const iconTheme: SimpleIconTheme = { + folder: 'folder.svg', + folderExpanded: 'folder.svg', + file: 'text.svg', + fileNames: { + 'dockerfile': 'docker.svg', + '.dockerignore': 'ignored.svg', + '.gitignore': 'gitignore.svg', + '.gitattributes': 'gitignore.svg', + '.gitmodules': 'gitignore.svg', + '.editorconfig': 'editorConfig.svg', + 'readme': 'markdown.svg', + 'readme.md': 'markdown.svg', + 'license': 'text.svg', + 'package.json': 'json.svg', + 'package-lock.json': 'json.svg', + 'tsconfig.json': 'json.svg', + 'yarn.lock': 'yarn.svg', + 'pnpm-lock.yaml': 'pnpm.svg', + 'docker-compose.yml': 'dockerCompose.svg', + 'docker-compose.yaml': 'dockerCompose.svg', + 'makefile': 'makefile.svg', + 'cmakelists.txt': 'CMake.svg', + }, + fileExtensions: { + // code + 'ts': 'typeScript.svg', + 'tsx': 'react.svg', + 'js': 'javaScript.svg', + 'jsx': 'react.svg', + 'json': 'json.svg', + 'md': 'markdown.svg', + 'mdx': 'mdx.svg', + 'yml': 'yaml.svg', + 'yaml': 'yaml.svg', + 'toml': 'toml.svg', + 'html': 'html.svg', + 'css': 'css.svg', + 'scss': 'scss.svg', + 'less': 'less.svg', + // images / assets + 'png': 'image.svg', + 'jpg': 'image.svg', + 'jpeg': 'image.svg', + 'gif': 'image.svg', + 'webp': 'image.svg', + 'svg': 'image.svg', + }, +}; + From 8c9b8c7b02cdb9f0af691678a43339e7d65e7a35 Mon Sep 17 00:00:00 2001 From: filipstrand Date: Tue, 13 Jan 2026 16:32:38 +0100 Subject: [PATCH 2/3] Various small improvements --- src/extension/GitGraphViewProvider.ts | 281 ++++++++++++++++++++++- src/webview/components/ContextMenu.tsx | 18 +- src/webview/components/DetailsPane.tsx | 72 +++++- src/webview/components/FileTree.tsx | 12 + src/webview/components/SquashPreview.tsx | 13 ++ src/webview/index.tsx | 97 ++++++-- src/webview/styles/main.css | 49 ++++ 7 files changed, 511 insertions(+), 31 deletions(-) diff --git a/src/extension/GitGraphViewProvider.ts b/src/extension/GitGraphViewProvider.ts index 8e1d73e..f11aa51 100644 --- a/src/extension/GitGraphViewProvider.ts +++ b/src/extension/GitGraphViewProvider.ts @@ -20,6 +20,9 @@ export class GitGraphViewProvider implements vscode.WebviewViewProvider { private _disposables: vscode.Disposable[] = []; private _startTime: string = new Date().toISOString(); private _repoChangedTimer: NodeJS.Timeout | undefined; + private _ephemeralDiffKeys = new Set(); + private _ephemeralDiffCloser?: vscode.Disposable; + private _moveModeActive = false; constructor(private readonly _extensionUri: vscode.Uri) { this._outputChannel = vscode.window.createOutputChannel('GitBit'); @@ -58,6 +61,37 @@ export class GitGraphViewProvider implements vscode.WebviewViewProvider { }); } + private _ensureEphemeralDiffCloser() { + if (this._ephemeralDiffCloser) return; + this._ephemeralDiffCloser = vscode.window.tabGroups.onDidChangeTabs(() => { + void this._closeEphemeralDiffTabs(); + }); + this._disposables.push(this._ephemeralDiffCloser); + } + + private async _closeEphemeralDiffTabs() { + if (this._ephemeralDiffKeys.size === 0) return; + + for (const group of vscode.window.tabGroups.all) { + for (const tab of group.tabs) { + const input = tab.input; + if (input instanceof vscode.TabInputTextDiff) { + const key = `${input.original.toString()}@@${input.modified.toString()}`; + if (this._ephemeralDiffKeys.has(key)) { + try { + // Close the tab without stealing focus. + await vscode.window.tabGroups.close(tab, true); + } catch (err: any) { + this._outputChannel.appendLine(`Failed to auto-close returned diff tab: ${err?.message ?? String(err)}`); + } finally { + this._ephemeralDiffKeys.delete(key); + } + } + } + } + } + } + private async _discoverRepos(): Promise { if (this._reposCache) return this._reposCache; @@ -178,6 +212,11 @@ export class GitGraphViewProvider implements vscode.WebviewViewProvider { this._outputChannel.appendLine(`Received message: ${message.type} (${message.requestId})`); try { switch (message.type) { + case 'ui/moveMode': { + this._moveModeActive = !!message.payload?.active; + this._sendResponse(message.requestId, 'ok'); + break; + } case 'repos/list': { const base = await this._discoverRepos(); const enriched = await Promise.all( @@ -459,6 +498,7 @@ export class GitGraphViewProvider implements vscode.WebviewViewProvider { } this._outputChannel.appendLine(`Opening diff: ${leftUri.toString()} <-> ${rightUri.toString()}`); + const diffKey = `${leftUri.toString()}@@${rightUri.toString()}`; // Find first diff line to jump to let diffSelection: vscode.Range | undefined; @@ -492,11 +532,80 @@ export class GitGraphViewProvider implements vscode.WebviewViewProvider { // Attempt to move the diff to a new floating window try { await vscode.commands.executeCommand('workbench.action.moveEditorToNewWindow'); + // VS Code will "return" moved editors back into the original window when the floating window closes. + // We track this diff and auto-close it if it reappears as a tab. + this._ensureEphemeralDiffCloser(); + this._ephemeralDiffKeys.add(diffKey); + setTimeout(() => this._ephemeralDiffKeys.delete(diffKey), 5 * 60 * 1000).unref?.(); } catch (err) { this._outputChannel.appendLine(`Failed to move to new window: ${err}`); // Fallback: stay in the current column if moving fails } break; + case 'file/revealInOS': { + if (!this._gitRunner) return; + const relPathRaw: unknown = message.payload?.path; + const oldPathRaw: unknown = message.payload?.oldPath; + const relPath = typeof relPathRaw === 'string' ? relPathRaw : ''; + const oldRelPath = typeof oldPathRaw === 'string' ? oldPathRaw : ''; + + const repoRoot = this._gitRunner!.cwd; + + const exists = async (fsPath: string) => { + try { + await fs.promises.stat(fsPath); + return true; + } catch { + return false; + } + }; + + const revealPathOrParent = async (repoRelativePath: string) => { + const full = path.join(repoRoot, repoRelativePath); + if (await exists(full)) { + await vscode.commands.executeCommand('revealFileInOS', vscode.Uri.file(full)); + return true; + } + + // If the file doesn't exist in the working tree (common when viewing older commits), + // fall back to revealing the nearest existing parent folder. + let dir = path.dirname(full); + while (dir && dir !== repoRoot && dir.startsWith(repoRoot + path.sep)) { + if (await exists(dir)) { + await vscode.commands.executeCommand('revealFileInOS', vscode.Uri.file(dir)); + return true; + } + const next = path.dirname(dir); + if (next === dir) break; + dir = next; + } + + // Final fallback: reveal repo root. + if (await exists(repoRoot)) { + await vscode.commands.executeCommand('revealFileInOS', vscode.Uri.file(repoRoot)); + return true; + } + + return false; + }; + + if (!relPath) { + vscode.window.showWarningMessage('Reveal in Finder: missing file path.'); + break; + } + + // Prefer current path, fall back to old path (renames). + const ok = await revealPathOrParent(relPath); + if (!ok && oldRelPath) { + const okOld = await revealPathOrParent(oldRelPath); + if (okOld) break; + } + + if (!ok) { + vscode.window.showWarningMessage('Cannot reveal: failed to resolve a path to reveal.'); + } + break; + } case 'git/reword': { if (!this._gitRunner) return; const rewordSha = message.payload.sha; @@ -1553,11 +1662,16 @@ export class GitGraphViewProvider implements vscode.WebviewViewProvider { // Restore previously staged changes (best effort). if (stagedPatchFile) { - const applyRes = await this._gitRunner.run(['apply', '--cached', '--whitespace=nowarn', stagedPatchFile]); + // First attempt: 3-way apply (more robust when history/index moved, e.g. after a soft reset). + let applyRes = await this._gitRunner.run(['apply', '--cached', '--3way', '--whitespace=nowarn', stagedPatchFile]); + if (applyRes.exitCode !== 0) { + // Fallback: plain apply (some patch types don't support 3-way). + applyRes = await this._gitRunner.run(['apply', '--cached', '--whitespace=nowarn', stagedPatchFile]); + } if (applyRes.exitCode !== 0) { this._outputChannel.appendLine(`Warning: failed to restore previously staged changes: ${applyRes.stderr}`); vscode.window.showWarningMessage( - 'Checkpoint commit succeeded, but restoring previously staged changes failed. You may need to re-stage them manually.' + 'Commit succeeded, but GitBit could not restore your previously staged changes. You may need to re-stage them manually.' ); } } @@ -1785,6 +1899,162 @@ export class GitGraphViewProvider implements vscode.WebviewViewProvider { } } break; + case 'git/drop': + if (!this._gitRunner) return; + { + const dropShas: string[] = Array.isArray(message.payload?.shas) + ? (message.payload.shas as any[]).map(s => String(s)).filter(s => s && s !== GitGraphViewProvider.UNCOMMITTED_SHA) + : []; + + + + if (dropShas.length < 1) { + this._sendError(message.requestId, 'Invalid selection for drop'); + break; + } + + if (!(await this._ensureClean('Dropping commits rewrites history. You have local changes. Continue?'))) { + this._sendError(message.requestId, 'Drop cancelled'); + break; + } + + const branchRes = await this._gitRunner.run(['symbolic-ref', '--quiet', '--short', 'HEAD']); + const originalBranch = branchRes.exitCode === 0 ? branchRes.stdout.trim() : null; + if (!originalBranch) { + this._sendError(message.requestId, 'Drop is not supported in detached HEAD state. Checkout a branch first.'); + break; + } + + const tipRes = await this._gitRunner.run(['rev-parse', 'HEAD']); + const originalTip = tipRes.exitCode === 0 ? tipRes.stdout.trim() : ''; + if (!originalTip) { + this._sendError(message.requestId, 'Failed to determine current HEAD'); + break; + } + + // Use first-parent log for consistent ordering with the UI. + const logRes = await this._gitRunner.run(['log', '--first-parent', '--format=%H']); + if (logRes.exitCode !== 0) { + this._sendError(message.requestId, 'Failed to fetch log for drop', logRes.stderr); + break; + } + const allNewestFirst = logRes.stdout.trim().split('\n').filter(Boolean); + const allOldestFirst = [...allNewestFirst].reverse(); + + const selectedPositions = dropShas.map(sha => allOldestFirst.indexOf(sha)); + if (selectedPositions.some(p => p === -1)) { + this._sendError(message.requestId, 'Drop failed: one or more selected commits are not on the current branch history.'); + break; + } + + const startPos = Math.min(...selectedPositions); + const rangeOldestFirst = allOldestFirst.slice(startPos); + const startCommit = rangeOldestFirst[0]; + + // Determine base (parent of range start). If range start is root, we currently don't support this. + const parentsRes = await this._gitRunner.run(['rev-list', '--parents', '-n', '1', startCommit]); + const parts = parentsRes.exitCode === 0 ? parentsRes.stdout.trim().split(' ') : []; + if (parts.length < 2) { + this._sendError(message.requestId, 'Cannot drop commits when the operation includes the root commit (not supported yet).'); + break; + } + const baseSha = parts[1]; + + // For now, only support linear history (no merge commits) in the rewritten range. + let hasMergeCommit = false; + for (const sha of rangeOldestFirst) { + const p = await this._gitRunner.run(['rev-list', '--parents', '-n', '1', sha]); + const toks = p.exitCode === 0 ? p.stdout.trim().split(' ').filter(Boolean) : []; + if (toks.length > 2) { + this._sendError(message.requestId, 'Drop is not supported for merge commits yet.'); + hasMergeCommit = true; + break; + } + } + if (hasMergeCommit) break; + + const selectedSet = new Set(dropShas); + const remainingSeq = rangeOldestFirst.filter(sha => !selectedSet.has(sha)); + const dropCount = rangeOldestFirst.length - remainingSeq.length; + + if (dropCount <= 0) { + this._sendResponse(message.requestId, { newHead: originalTip }); + break; + } + + const confirm = await vscode.window.showWarningMessage( + `Drop ${dropCount} commit(s) and rewrite history on ${originalBranch}? This will rewrite ${remainingSeq.length} commit(s) that come after the oldest dropped commit.`, + { modal: true }, + 'Drop' + ); + if (confirm !== 'Drop') { + this._sendError(message.requestId, 'Drop cancelled'); + break; + } + + const tmpBranch = `cgg-tmp-drop-${Date.now()}`; + await this._gitRunner.run(['branch', tmpBranch, originalTip]); + + const restoreOriginal = async () => { + await this._gitRunner!.run(['checkout', originalBranch]); + await this._gitRunner!.run(['reset', '--hard', originalTip]); + }; + + try { + const checkoutBase = await this._gitRunner.run(['checkout', '--detach', baseSha]); + if (checkoutBase.exitCode !== 0) { + this._sendError(message.requestId, 'Failed to checkout base for drop', checkoutBase.stderr); + await restoreOriginal(); + break; + } + + let cherryFailed = false; + for (const sha of remainingSeq) { + const cherryRes = await this._gitRunner.run(['cherry-pick', sha], 600000); + if (cherryRes.exitCode !== 0) { + await this._gitRunner.run(['cherry-pick', '--abort']); + await restoreOriginal(); + this._sendError( + message.requestId, + 'Drop failed due to conflicts while rewriting history. Your branch was restored.', + cherryRes.stderr + ); + cherryFailed = true; + break; + } + } + if (cherryFailed) break; + + const newTipRes = await this._gitRunner.run(['rev-parse', 'HEAD']); + const newTip = newTipRes.exitCode === 0 ? newTipRes.stdout.trim() : ''; + if (!newTip) { + await restoreOriginal(); + this._sendError(message.requestId, 'Drop failed: could not resolve new HEAD.'); + break; + } + + const checkoutBranch = await this._gitRunner.run(['checkout', originalBranch]); + if (checkoutBranch.exitCode !== 0) { + await restoreOriginal(); + this._sendError(message.requestId, 'Drop failed: could not return to branch.', checkoutBranch.stderr); + break; + } + + const resetBranch = await this._gitRunner.run(['reset', '--hard', newTip]); + if (resetBranch.exitCode !== 0) { + await restoreOriginal(); + this._sendError(message.requestId, 'Drop failed: could not move branch to new history.', resetBranch.stderr); + break; + } + + this._notifyRepoChanged('drop'); + this._sendResponse(message.requestId, { newHead: newTip }); + } finally { + // Best-effort cleanup of temp branch. + await this._gitRunner.run(['branch', '-D', tmpBranch]); + } + } + break; case 'git/moveCommits': if (!this._gitRunner) return; { @@ -2210,6 +2480,13 @@ export class GitGraphViewProvider implements vscode.WebviewViewProvider { if (e.focused) notify('focus'); })); + // If the user clicks in an editor while commits are in "move mode", treat it as Escape (cancel move mode). + this._disposables.push(vscode.window.onDidChangeTextEditorSelection(e => { + if (!this._moveModeActive) return; + if (e.kind !== vscode.TextEditorSelectionChangeKind.Mouse) return; + this._view?.webview.postMessage({ type: 'ui/escape' }); + })); + // 2. Watch .git changes (HEAD, refs, index) as a fallback / for non-working-tree events. // Note: we intentionally do NOT watch the entire workspace. Dev builds commonly write to `dist/` // on every save, which would otherwise cause a noisy refresh loop. diff --git a/src/webview/components/ContextMenu.tsx b/src/webview/components/ContextMenu.tsx index 5de6787..4357a8a 100644 --- a/src/webview/components/ContextMenu.tsx +++ b/src/webview/components/ContextMenu.tsx @@ -6,8 +6,9 @@ interface ContextMenuProps { onClose: () => void; actions: { label?: string; - onClick?: () => void; + onClick?: () => void | Promise; danger?: boolean; + tone?: 'warning' | 'success'; icon?: string; disabled?: boolean; primary?: boolean; @@ -51,6 +52,15 @@ export const ContextMenu: React.FC = ({ x, y, onClose, actions const disabled = !!action.disabled; const label = action.label || ''; + const baseColor = + action.danger + ? 'var(--vscode-errorForeground)' + : action.tone === 'warning' + ? 'var(--vscode-editorWarning-foreground, #d19a66)' + : action.tone === 'success' + ? 'var(--vscode-gitDecoration-addedResourceForeground, #73c991)' + : 'inherit'; + const isToned = !!action.danger || !!action.tone; return (
= ({ x, y, onClose, actions padding: '6px 12px', cursor: disabled ? 'default' : 'pointer', opacity: disabled ? 0.45 : 1, - color: action.danger ? 'var(--vscode-errorForeground)' : 'inherit', + color: baseColor, fontSize: '12px', display: 'flex', alignItems: 'center', @@ -75,11 +85,11 @@ export const ContextMenu: React.FC = ({ x, y, onClose, actions onMouseEnter={(e) => { if (disabled) return; e.currentTarget.style.backgroundColor = 'var(--vscode-menu-selectionBackground)'; - e.currentTarget.style.color = action.danger ? 'var(--vscode-errorForeground)' : 'var(--vscode-menu-selectionForeground)'; + e.currentTarget.style.color = isToned ? baseColor : 'var(--vscode-menu-selectionForeground)'; }} onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = 'transparent'; - e.currentTarget.style.color = action.danger ? 'var(--vscode-errorForeground)' : 'inherit'; + e.currentTarget.style.color = baseColor; }} > {action.icon ? ( diff --git a/src/webview/components/DetailsPane.tsx b/src/webview/components/DetailsPane.tsx index 1db2041..511de20 100644 --- a/src/webview/components/DetailsPane.tsx +++ b/src/webview/components/DetailsPane.tsx @@ -69,6 +69,18 @@ export const DetailsPane: React.FC = ({ sha }) => { }); }; + const handleRevealInOS = (change: Change) => { + vscode.postMessage({ + type: 'file/revealInOS', + requestId: `reveal-${Date.now()}`, + payload: { + path: change.path, + oldPath: change.oldPath, + status: change.status + } + }); + }; + const handleFileClick = (change: Change) => { if (!details) return; @@ -128,6 +140,16 @@ export const DetailsPane: React.FC = ({ sha }) => { } }; + const copySubject = () => { + if (!details || details.sha === 'UNCOMMITTED') return; + const text = String(details.subject || '').trim(); + if (!text) return; + vscode.postMessage({ + type: 'app/copyToClipboard', + payload: { text } + }); + }; + const isUncommitted = details?.sha === 'UNCOMMITTED'; const allFilePaths = isUncommitted ? changes.map(c => c.path) : []; const hasExtendedMessage = !!details && !isUncommitted && details.message.trim() !== details.subject.trim(); @@ -182,6 +204,28 @@ export const DetailsPane: React.FC = ({ sha }) => { return 'No files selected'; }, [selectedPaths.size]); + const handleCommitClick = () => { + // UX: if the user typed a message but forgot to select files, first click selects all, + // second click commits. + if (selectedPaths.size === 0) { + if (!commitMessage.trim()) return; + if (allFilePaths.length === 0) return; + selectAll(); + return; + } + performCommit(); + }; + + const handleAmendClick = () => { + // Same UX for amend (message optional): first click selects all if nothing is selected. + if (selectedPaths.size === 0) { + if (allFilePaths.length === 0) return; + selectAll(); + return; + } + performCommit({ amend: true }); + }; + const hasCommitBox = isUncommitted || (hasExtendedMessage && showFullMessage); const formatDateYYYYMMDD = (iso: string) => { @@ -220,6 +264,25 @@ export const DetailsPane: React.FC = ({ sha }) => { /> )} {details.subject} + {!isUncommitted && ( + { + e.stopPropagation(); + copySubject(); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + e.stopPropagation(); + copySubject(); + } + }} + /> + )}
{!isUncommitted && (
@@ -277,15 +340,15 @@ export const DetailsPane: React.FC = ({ sha }) => {